Wiki source code of Active Installs Script Service

Last modified by Vincent Massol on 2026/09/11 11:20

Show last authors
1 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.
2
3 == Methods ==
4
5 |=Method |=Returns |=What it does
6 |##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.
7 |##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>>||anchor="HRetrievingPings"]] below.
8 |{{version since="18.8.0RC1"}}##countDistinctInstalls(String jsonQuery)##{{/version}}|##long##|Counts the distinct **instances** that sent a matching ping.
9 |{{version since="18.8.0RC1"}}##countDistinctInstallsByExtension(String jsonQuery)##{{/version}}|##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.
10
11 [[Active Installs Ping Data>>doc:documentation.xs.admin.active-installs.ping-data.WebHome]] lists the fields a ping carries. Those field names are what a query is written against.
12
13 == Retrieving Pings ==
14
15 ##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.
16
17 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.
18
19 == Counting Instances ==
20
21 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.
22
23 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.
24
25 ##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.
26
27 The two limits below are those of the default Elasticsearch-backed implementation, not guarantees of the API:
28
29 * 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.
30 * ##countDistinctInstallsByExtension## raises ##TooManyExtensionsException## when it finds more than 10000 extensions, rather than returning a map that silently leaves some of them out.
31
32 == Query Examples ==
33
34 This example selects the pings received in the last day, and counts them:
35
36 {{code language="velocity"}}
37 {{velocity}}
38 #set ($lastDay = '{ "range": { "date.current": { "gte": "now-1d" } } }')
39 #set ($pingCount = $services.activeinstalls2.countInstalls($lastDay))
40 $pingCount pings arrived in the last 24 hours.
41 {{/velocity}}
42 {{/code}}
43
44 The same query, counting the instances that sent those pings rather than the pings themselves:
45
46 {{code language="velocity"}}
47 {{velocity}}
48 #set ($lastDay = '{ "range": { "date.current": { "gte": "now-1d" } } }')
49 #set ($installCount = $services.activeinstalls2.countDistinctInstalls($lastDay))
50 $installCount instances pinged in the last 24 hours.
51 {{/velocity}}
52 {{/code}}
53
54 And the same query again, broken down by extension:
55
56 {{code language="velocity"}}
57 {{velocity}}
58 #set ($lastDay = '{ "range": { "date.current": { "gte": "now-1d" } } }')
59 #set ($counts = $services.activeinstalls2.countDistinctInstallsByExtension($lastDay))
60 #foreach ($entry in $counts.entrySet())
61 * $entry.key: $entry.value instances
62 #end
63 {{/velocity}}
64 {{/code}}

Get Connected