Skip to content

Commit 56058ab

Browse files
authored
Support multiple metrics in top_metrics agg (elastic#52965)
This adds support for returning multiple metrics to the `top_metrics` agg. It looks like: ``` POST /test/_search?filter_path=aggregations { "aggs": { "tm": { "top_metrics": { "metrics": [ {"field": "v"}, {"field": "m"} ], "sort": {"s": "desc"} } } } } ```
1 parent 67acaf6 commit 56058ab

File tree

15 files changed

+477
-201
lines changed

15 files changed

+477
-201
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/analytics/TopMetricsAggregationBuilder.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import org.elasticsearch.search.sort.SortBuilder;
3333

3434
import java.io.IOException;
35+
import java.util.Arrays;
36+
import java.util.List;
3537
import java.util.Map;
3638

3739
/**
@@ -49,20 +51,20 @@ public class TopMetricsAggregationBuilder extends AbstractAggregationBuilder<Top
4951

5052
private final SortBuilder<?> sort;
5153
private final int size;
52-
private final String metric;
54+
private final List<String> metrics;
5355

5456
/**
5557
* Build the request.
5658
* @param name the name of the metric
5759
* @param sort the sort key used to select the top metrics
5860
* @param size number of results to return per bucket
59-
* @param metric the name of the field to select
61+
* @param metrics the names of the fields to select
6062
*/
61-
public TopMetricsAggregationBuilder(String name, SortBuilder<?> sort, int size, String metric) {
63+
public TopMetricsAggregationBuilder(String name, SortBuilder<?> sort, int size, String... metrics) {
6264
super(name);
6365
this.sort = sort;
6466
this.size = size;
65-
this.metric = metric;
67+
this.metrics = Arrays.asList(metrics);
6668
}
6769

6870
@Override
@@ -78,7 +80,11 @@ protected XContentBuilder internalXContent(XContentBuilder builder, Params param
7880
sort.toXContent(builder, params);
7981
builder.endArray();
8082
builder.field("size", size);
81-
builder.startObject("metric").field("field", metric).endObject();
83+
builder.startArray("metrics");
84+
for (String metric: metrics) {
85+
builder.startObject().field("field", metric).endObject();
86+
}
87+
builder.endArray();
8288
}
8389
return builder.endObject();
8490
}

client/rest-high-level/src/test/java/org/elasticsearch/client/analytics/AnalyticsAggsIT.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,20 @@ public void testTopMetricsSizeOne() throws IOException {
7474
assertThat(metric.getMetrics(), equalTo(singletonMap("v", 3.0)));
7575
}
7676

77+
public void testTopMetricsManyMetrics() throws IOException {
78+
indexTopMetricsData();
79+
SearchRequest search = new SearchRequest("test");
80+
search.source().aggregation(new TopMetricsAggregationBuilder(
81+
"test", new FieldSortBuilder("s").order(SortOrder.DESC), 1, "v", "m"));
82+
SearchResponse response = highLevelClient().search(search, RequestOptions.DEFAULT);
83+
ParsedTopMetrics top = response.getAggregations().get("test");
84+
assertThat(top.getTopMetrics(), hasSize(1));
85+
ParsedTopMetrics.TopMetrics metric = top.getTopMetrics().get(0);
86+
assertThat(metric.getSort(), equalTo(singletonList(2)));
87+
assertThat(metric.getMetrics(), hasEntry("v", 3.0));
88+
assertThat(metric.getMetrics(), hasEntry("m", 13.0));
89+
}
90+
7791
public void testTopMetricsSizeTwo() throws IOException {
7892
indexTopMetricsData();
7993
SearchRequest search = new SearchRequest("test");
@@ -92,8 +106,8 @@ public void testTopMetricsSizeTwo() throws IOException {
92106

93107
private void indexTopMetricsData() throws IOException {
94108
BulkRequest bulk = new BulkRequest("test").setRefreshPolicy(RefreshPolicy.IMMEDIATE);
95-
bulk.add(new IndexRequest().source(XContentType.JSON, "s", 1, "v", 2));
96-
bulk.add(new IndexRequest().source(XContentType.JSON, "s", 2, "v", 3));
109+
bulk.add(new IndexRequest().source(XContentType.JSON, "s", 1, "v", 2.0, "m", 12.0));
110+
bulk.add(new IndexRequest().source(XContentType.JSON, "s", 2, "v", 3.0, "m", 13.0));
97111
highLevelClient().bulk(bulk, RequestOptions.DEFAULT);
98112
}
99113
}

docs/reference/aggregations/metrics/top-metrics-aggregation.asciidoc

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ POST /test/_search?filter_path=aggregations
2222
"aggs": {
2323
"tm": {
2424
"top_metrics": {
25-
"metric": {"field": "v"},
25+
"metrics": {"field": "v"},
2626
"sort": {"s": "desc"}
2727
}
2828
}
@@ -68,11 +68,61 @@ request. So,
6868
NOTE: This aggregation doesn't support any sort of "tie breaking". If two documents have
6969
the same sort values then this aggregation could return either document's fields.
7070

71-
==== `metric`
71+
==== `metrics`
72+
73+
`metrics` selects the fields to of the "top" document to return. Like most other
74+
aggregations, `top_metrics` casts these values cast to `double` precision
75+
floating point numbers. So they have to be numeric. Dates *work*, but they
76+
come back as a `double` precision floating point containing milliseconds since
77+
epoch. `keyword` fields aren't allowed.
78+
79+
You can return multiple metrics by providing a list:
80+
81+
[source,console,id=search-aggregations-metrics-top-metrics-list-of-metrics]
82+
----
83+
POST /test/_bulk?refresh
84+
{"index": {}}
85+
{"s": 1, "v": 3.1415, "m": 1.9}
86+
{"index": {}}
87+
{"s": 2, "v": 1.0, "m": 6.7}
88+
{"index": {}}
89+
{"s": 3, "v": 2.71828, "m": -12.2}
90+
POST /test/_search?filter_path=aggregations
91+
{
92+
"aggs": {
93+
"tm": {
94+
"top_metrics": {
95+
"metrics": [
96+
{"field": "v"},
97+
{"field": "m"}
98+
],
99+
"sort": {"s": "desc"}
100+
}
101+
}
102+
}
103+
}
104+
----
105+
106+
Which returns:
107+
108+
[source,js]
109+
----
110+
{
111+
"aggregations": {
112+
"tm": {
113+
"top": [ {
114+
"sort": [3],
115+
"metrics": {
116+
"v": 2.718280076980591,
117+
"m": -12.199999809265137
118+
}
119+
} ]
120+
}
121+
}
122+
}
123+
----
124+
// TESTRESPONSE
72125

73-
At this point `metric` supports only `{"field": "field_name"}` and all metrics
74-
are returned as double precision floating point numbers. Expect more to
75-
come here.
76126

77127
==== `size`
78128

@@ -92,7 +142,7 @@ POST /test/_search?filter_path=aggregations
92142
"aggs": {
93143
"tm": {
94144
"top_metrics": {
95-
"metric": {"field": "v"},
145+
"metrics": {"field": "v"},
96146
"sort": {"s": "desc"},
97147
"size": 2
98148
}
@@ -174,7 +224,7 @@ POST /node/_search?filter_path=aggregations
174224
"aggs": {
175225
"tm": {
176226
"top_metrics": {
177-
"metric": {"field": "v"},
227+
"metrics": {"field": "v"},
178228
"sort": {"date": "desc"}
179229
}
180230
}
@@ -230,7 +280,7 @@ POST /node/_search?filter_path=aggregations
230280
"aggs": {
231281
"tm": {
232282
"top_metrics": {
233-
"metric": {"field": "v"},
283+
"metrics": {"field": "v"},
234284
"sort": {"date": "desc"}
235285
}
236286
}
@@ -292,7 +342,7 @@ POST /test*/_search?filter_path=aggregations
292342
"aggs": {
293343
"tm": {
294344
"top_metrics": {
295-
"metric": {"field": "v"},
345+
"metrics": {"field": "v"},
296346
"sort": {"s": "asc"}
297347
}
298348
}
@@ -325,7 +375,7 @@ POST /test*/_search?filter_path=aggregations
325375
"aggs": {
326376
"tm": {
327377
"top_metrics": {
328-
"metric": {"field": "v"},
378+
"metrics": {"field": "v"},
329379
"sort": {"s": {"order": "asc", "numeric_type": "double"}}
330380
}
331381
}

server/src/test/java/org/elasticsearch/search/aggregations/metrics/MinAggregatorTests.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.elasticsearch.common.CheckedConsumer;
5252
import org.elasticsearch.common.collect.Tuple;
5353
import org.elasticsearch.common.settings.Settings;
54+
import org.elasticsearch.common.util.BigArrays;
5455
import org.elasticsearch.index.IndexSettings;
5556
import org.elasticsearch.index.mapper.ContentPath;
5657
import org.elasticsearch.index.mapper.DateFieldMapper;
@@ -180,8 +181,9 @@ protected ScriptService getMockScriptService() {
180181

181182
@Override
182183
protected QueryShardContext queryShardContextMock(IndexSearcher searcher, MapperService mapperService,
183-
IndexSettings indexSettings, CircuitBreakerService circuitBreakerService) {
184-
this.queryShardContext = super.queryShardContextMock(searcher, mapperService, indexSettings, circuitBreakerService);
184+
IndexSettings indexSettings, CircuitBreakerService circuitBreakerService,
185+
BigArrays bigArrays) {
186+
this.queryShardContext = super.queryShardContextMock(searcher, mapperService, indexSettings, circuitBreakerService, bigArrays);
185187
return queryShardContext;
186188
}
187189

server/src/test/java/org/elasticsearch/search/aggregations/metrics/ScriptedMetricAggregatorTests.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -421,11 +421,12 @@ public void testSelfReferencingAggStateAfterCombine() throws IOException {
421421
protected QueryShardContext queryShardContextMock(IndexSearcher searcher,
422422
MapperService mapperService,
423423
IndexSettings indexSettings,
424-
CircuitBreakerService circuitBreakerService) {
424+
CircuitBreakerService circuitBreakerService,
425+
BigArrays bigArrays) {
425426
MockScriptEngine scriptEngine = new MockScriptEngine(MockScriptEngine.NAME, SCRIPTS, Collections.emptyMap());
426427
Map<String, ScriptEngine> engines = Collections.singletonMap(scriptEngine.getType(), scriptEngine);
427428
ScriptService scriptService = new ScriptService(Settings.EMPTY, engines, ScriptModule.CORE_CONTEXTS);
428-
return new QueryShardContext(0, indexSettings, BigArrays.NON_RECYCLING_INSTANCE, null, null, mapperService, null, scriptService,
429+
return new QueryShardContext(0, indexSettings, bigArrays, null, null, mapperService, null, scriptService,
429430
xContentRegistry(), writableRegistry(), null, null, System::currentTimeMillis, null, null, () -> true);
430431
}
431432
}

test/framework/src/main/java/org/elasticsearch/search/aggregations/AggregatorTestCase.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ public boolean shouldCache(Query query) {
285285
when(searchContext.lookup()).thenReturn(searchLookup);
286286

287287
QueryShardContext queryShardContext =
288-
queryShardContextMock(contextIndexSearcher, mapperService, indexSettings, circuitBreakerService);
288+
queryShardContextMock(contextIndexSearcher, mapperService, indexSettings, circuitBreakerService, bigArrays);
289289
when(searchContext.getQueryShardContext()).thenReturn(queryShardContext);
290290
when(queryShardContext.getObjectMapper(anyString())).thenAnswer(invocation -> {
291291
String fieldName = (String) invocation.getArguments()[0];
@@ -336,9 +336,10 @@ protected MapperService mapperServiceMock() {
336336
protected QueryShardContext queryShardContextMock(IndexSearcher searcher,
337337
MapperService mapperService,
338338
IndexSettings indexSettings,
339-
CircuitBreakerService circuitBreakerService) {
339+
CircuitBreakerService circuitBreakerService,
340+
BigArrays bigArrays) {
340341

341-
return new QueryShardContext(0, indexSettings, BigArrays.NON_RECYCLING_INSTANCE, null,
342+
return new QueryShardContext(0, indexSettings, bigArrays, null,
342343
getIndexFieldDataLookup(mapperService, circuitBreakerService),
343344
mapperService, null, getMockScriptService(), xContentRegistry(),
344345
writableRegistry(), null, searcher, System::currentTimeMillis, null, null, () -> true);

0 commit comments

Comments
 (0)