Active Installs Script Service
Reference
The Active Installs script service is bound to $services.activeinstalls2. Every one of its methods takes the same argument, jsonQuery: the Elasticsearch query clause that selects which pings to work on. Pass an empty string to select every ping.
Methods
| Method | Returns | What it does |
|---|---|---|
| countInstalls(String jsonQuery) | long | Counts the matching pings, not the instances that sent them. An instance pings once a day, and again on every restart, so one instance accounts for many pings. |
| searchInstalls(String jsonQuery) | List<Ping> | Returns some of the matching pings themselves, parsed into Ping objects. Only a bounded number of them come back, whatever the query matches — see Retrieving Pings below. |
| XWiki 18.8.0+ countDistinctInstalls(String jsonQuery) | long | Counts the distinct instances that sent a matching ping. |
| XWiki 18.8.0+ countDistinctInstallsByExtension(String jsonQuery) | SequencedMap<String, Long> | Counts, for each extension, the distinct instances that have it installed and sent a matching ping. A single query covers every extension, so this is far cheaper than calling countDistinctInstalls once per extension. The returned map is keyed by extension id and ordered by descending number of matching pings — which is close to, but not the same as, ordering by the instance counts it returns. |
Active Installs Ping Data lists the fields a ping carries. Those field names are what a query is written against.
Retrieving Pings
searchInstalls answers "what does a matching ping look like", not "give me every matching ping". The default Elasticsearch-backed implementation returns at most 10 pings, however many the query matches, and which 10 come back is not defined — the pings are not sorted, so the same query can return different ones from one call to the next. There is no way to page through the remaining matches.
That bound is deliberate: a ping carries the whole extension list of the instance that sent it, and an instance pings once a day as well as every time it is restarted, so the pings matching even a narrow query are far too many and too large to hold in memory. Answer questions about how many with the counting methods instead — they aggregate inside Elasticsearch and never retrieve a ping at all.
Counting Instances
An empty jsonQuery makes the difference between the two counting methods plain: countInstalls returns every ping ever received, while countDistinctInstalls returns every instance that ever pinged, including the ones that stopped years ago. Restricting a count to the instances that are actually active is a matter of querying on the ping date, as the examples below do.
Both distinct counts are marked @Unstable: they are still experimental, and their signatures may change. Like the rest of the service they declare throws Exception, so a wiki page calling one of them fails to render when the data cannot be reached.
countDistinctInstallsByExtension keys its counts by the id under which each extension is installed, and does not resolve them through the features an extension provides: an extension that has been renamed, and whose former id is a feature of its new id, is reported as two separate entries. A query on the extensions does not restrict which extensions are counted either — it selects the pings holding a matching extension, and every extension of those pings is then counted, so reading the figure for one extension means reading its entry from the returned map rather than querying for it.
The two limits below are those of the default Elasticsearch-backed implementation, not guarantees of the API:
- A count is exact up to 40000 distinct instances, and an estimate above that. The whole population of XWiki instances is well below that figure, so in practice the counts are exact.
- countDistinctInstallsByExtension raises TooManyExtensionsException when it finds more than 10000 extensions, rather than returning a map that silently leaves some of them out.
Query Examples
This example selects the pings received in the last day, and counts them:
{{velocity}}
#set ($lastDay = '{ "range": { "date.current": { "gte": "now-1d" } } }')
#set ($pingCount = $services.activeinstalls2.countInstalls($lastDay))
$pingCount pings arrived in the last 24 hours.
{{/velocity}}The same query, counting the instances that sent those pings rather than the pings themselves:
{{velocity}}
#set ($lastDay = '{ "range": { "date.current": { "gte": "now-1d" } } }')
#set ($installCount = $services.activeinstalls2.countDistinctInstalls($lastDay))
$installCount instances pinged in the last 24 hours.
{{/velocity}}And the same query again, broken down by extension:
{{velocity}}
#set ($lastDay = '{ "range": { "date.current": { "gte": "now-1d" } } }')
#set ($counts = $services.activeinstalls2.countDistinctInstallsByExtension($lastDay))
#foreach ($entry in $counts.entrySet())
* $entry.key: $entry.value instances
#end
{{/velocity}}