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"} } } } } ```
This commit is contained in:
parent
01504df876
commit
28df7ae5ed
|
@ -32,6 +32,8 @@ import org.elasticsearch.search.builder.SearchSourceBuilder;
|
||||||
import org.elasticsearch.search.sort.SortBuilder;
|
import org.elasticsearch.search.sort.SortBuilder;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,20 +51,20 @@ public class TopMetricsAggregationBuilder extends AbstractAggregationBuilder<Top
|
||||||
|
|
||||||
private final SortBuilder<?> sort;
|
private final SortBuilder<?> sort;
|
||||||
private final int size;
|
private final int size;
|
||||||
private final String metric;
|
private final List<String> metrics;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the request.
|
* Build the request.
|
||||||
* @param name the name of the metric
|
* @param name the name of the metric
|
||||||
* @param sort the sort key used to select the top metrics
|
* @param sort the sort key used to select the top metrics
|
||||||
* @param size number of results to return per bucket
|
* @param size number of results to return per bucket
|
||||||
* @param metric the name of the field to select
|
* @param metrics the names of the fields to select
|
||||||
*/
|
*/
|
||||||
public TopMetricsAggregationBuilder(String name, SortBuilder<?> sort, int size, String metric) {
|
public TopMetricsAggregationBuilder(String name, SortBuilder<?> sort, int size, String... metrics) {
|
||||||
super(name);
|
super(name);
|
||||||
this.sort = sort;
|
this.sort = sort;
|
||||||
this.size = size;
|
this.size = size;
|
||||||
this.metric = metric;
|
this.metrics = Arrays.asList(metrics);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -78,7 +80,11 @@ public class TopMetricsAggregationBuilder extends AbstractAggregationBuilder<Top
|
||||||
sort.toXContent(builder, params);
|
sort.toXContent(builder, params);
|
||||||
builder.endArray();
|
builder.endArray();
|
||||||
builder.field("size", size);
|
builder.field("size", size);
|
||||||
builder.startObject("metric").field("field", metric).endObject();
|
builder.startArray("metrics");
|
||||||
|
for (String metric: metrics) {
|
||||||
|
builder.startObject().field("field", metric).endObject();
|
||||||
|
}
|
||||||
|
builder.endArray();
|
||||||
}
|
}
|
||||||
return builder.endObject();
|
return builder.endObject();
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,20 @@ public class AnalyticsAggsIT extends ESRestHighLevelClientTestCase {
|
||||||
assertThat(metric.getMetrics(), equalTo(singletonMap("v", 3.0)));
|
assertThat(metric.getMetrics(), equalTo(singletonMap("v", 3.0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testTopMetricsManyMetrics() throws IOException {
|
||||||
|
indexTopMetricsData();
|
||||||
|
SearchRequest search = new SearchRequest("test");
|
||||||
|
search.source().aggregation(new TopMetricsAggregationBuilder(
|
||||||
|
"test", new FieldSortBuilder("s").order(SortOrder.DESC), 1, "v", "m"));
|
||||||
|
SearchResponse response = highLevelClient().search(search, RequestOptions.DEFAULT);
|
||||||
|
ParsedTopMetrics top = response.getAggregations().get("test");
|
||||||
|
assertThat(top.getTopMetrics(), hasSize(1));
|
||||||
|
ParsedTopMetrics.TopMetrics metric = top.getTopMetrics().get(0);
|
||||||
|
assertThat(metric.getSort(), equalTo(singletonList(2)));
|
||||||
|
assertThat(metric.getMetrics(), hasEntry("v", 3.0));
|
||||||
|
assertThat(metric.getMetrics(), hasEntry("m", 13.0));
|
||||||
|
}
|
||||||
|
|
||||||
public void testTopMetricsSizeTwo() throws IOException {
|
public void testTopMetricsSizeTwo() throws IOException {
|
||||||
indexTopMetricsData();
|
indexTopMetricsData();
|
||||||
SearchRequest search = new SearchRequest("test");
|
SearchRequest search = new SearchRequest("test");
|
||||||
|
@ -92,8 +106,8 @@ public class AnalyticsAggsIT extends ESRestHighLevelClientTestCase {
|
||||||
|
|
||||||
private void indexTopMetricsData() throws IOException {
|
private void indexTopMetricsData() throws IOException {
|
||||||
BulkRequest bulk = new BulkRequest("test").setRefreshPolicy(RefreshPolicy.IMMEDIATE);
|
BulkRequest bulk = new BulkRequest("test").setRefreshPolicy(RefreshPolicy.IMMEDIATE);
|
||||||
bulk.add(new IndexRequest().source(XContentType.JSON, "s", 1, "v", 2));
|
bulk.add(new IndexRequest().source(XContentType.JSON, "s", 1, "v", 2.0, "m", 12.0));
|
||||||
bulk.add(new IndexRequest().source(XContentType.JSON, "s", 2, "v", 3));
|
bulk.add(new IndexRequest().source(XContentType.JSON, "s", 2, "v", 3.0, "m", 13.0));
|
||||||
highLevelClient().bulk(bulk, RequestOptions.DEFAULT);
|
highLevelClient().bulk(bulk, RequestOptions.DEFAULT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ POST /test/_search?filter_path=aggregations
|
||||||
"aggs": {
|
"aggs": {
|
||||||
"tm": {
|
"tm": {
|
||||||
"top_metrics": {
|
"top_metrics": {
|
||||||
"metric": {"field": "v"},
|
"metrics": {"field": "v"},
|
||||||
"sort": {"s": "desc"}
|
"sort": {"s": "desc"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,11 +68,61 @@ request. So,
|
||||||
NOTE: This aggregation doesn't support any sort of "tie breaking". If two documents have
|
NOTE: This aggregation doesn't support any sort of "tie breaking". If two documents have
|
||||||
the same sort values then this aggregation could return either document's fields.
|
the same sort values then this aggregation could return either document's fields.
|
||||||
|
|
||||||
==== `metric`
|
==== `metrics`
|
||||||
|
|
||||||
|
`metrics` selects the fields to of the "top" document to return. Like most other
|
||||||
|
aggregations, `top_metrics` casts these values cast to `double` precision
|
||||||
|
floating point numbers. So they have to be numeric. Dates *work*, but they
|
||||||
|
come back as a `double` precision floating point containing milliseconds since
|
||||||
|
epoch. `keyword` fields aren't allowed.
|
||||||
|
|
||||||
|
You can return multiple metrics by providing a list:
|
||||||
|
|
||||||
|
[source,console,id=search-aggregations-metrics-top-metrics-list-of-metrics]
|
||||||
|
----
|
||||||
|
POST /test/_bulk?refresh
|
||||||
|
{"index": {}}
|
||||||
|
{"s": 1, "v": 3.1415, "m": 1.9}
|
||||||
|
{"index": {}}
|
||||||
|
{"s": 2, "v": 1.0, "m": 6.7}
|
||||||
|
{"index": {}}
|
||||||
|
{"s": 3, "v": 2.71828, "m": -12.2}
|
||||||
|
POST /test/_search?filter_path=aggregations
|
||||||
|
{
|
||||||
|
"aggs": {
|
||||||
|
"tm": {
|
||||||
|
"top_metrics": {
|
||||||
|
"metrics": [
|
||||||
|
{"field": "v"},
|
||||||
|
{"field": "m"}
|
||||||
|
],
|
||||||
|
"sort": {"s": "desc"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
Which returns:
|
||||||
|
|
||||||
|
[source,js]
|
||||||
|
----
|
||||||
|
{
|
||||||
|
"aggregations": {
|
||||||
|
"tm": {
|
||||||
|
"top": [ {
|
||||||
|
"sort": [3],
|
||||||
|
"metrics": {
|
||||||
|
"v": 2.718280076980591,
|
||||||
|
"m": -12.199999809265137
|
||||||
|
}
|
||||||
|
} ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
// TESTRESPONSE
|
||||||
|
|
||||||
At this point `metric` supports only `{"field": "field_name"}` and all metrics
|
|
||||||
are returned as double precision floating point numbers. Expect more to
|
|
||||||
come here.
|
|
||||||
|
|
||||||
==== `size`
|
==== `size`
|
||||||
|
|
||||||
|
@ -92,7 +142,7 @@ POST /test/_search?filter_path=aggregations
|
||||||
"aggs": {
|
"aggs": {
|
||||||
"tm": {
|
"tm": {
|
||||||
"top_metrics": {
|
"top_metrics": {
|
||||||
"metric": {"field": "v"},
|
"metrics": {"field": "v"},
|
||||||
"sort": {"s": "desc"},
|
"sort": {"s": "desc"},
|
||||||
"size": 2
|
"size": 2
|
||||||
}
|
}
|
||||||
|
@ -174,7 +224,7 @@ POST /node/_search?filter_path=aggregations
|
||||||
"aggs": {
|
"aggs": {
|
||||||
"tm": {
|
"tm": {
|
||||||
"top_metrics": {
|
"top_metrics": {
|
||||||
"metric": {"field": "v"},
|
"metrics": {"field": "v"},
|
||||||
"sort": {"date": "desc"}
|
"sort": {"date": "desc"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -230,7 +280,7 @@ POST /node/_search?filter_path=aggregations
|
||||||
"aggs": {
|
"aggs": {
|
||||||
"tm": {
|
"tm": {
|
||||||
"top_metrics": {
|
"top_metrics": {
|
||||||
"metric": {"field": "v"},
|
"metrics": {"field": "v"},
|
||||||
"sort": {"date": "desc"}
|
"sort": {"date": "desc"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -292,7 +342,7 @@ POST /test*/_search?filter_path=aggregations
|
||||||
"aggs": {
|
"aggs": {
|
||||||
"tm": {
|
"tm": {
|
||||||
"top_metrics": {
|
"top_metrics": {
|
||||||
"metric": {"field": "v"},
|
"metrics": {"field": "v"},
|
||||||
"sort": {"s": "asc"}
|
"sort": {"s": "asc"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -325,7 +375,7 @@ POST /test*/_search?filter_path=aggregations
|
||||||
"aggs": {
|
"aggs": {
|
||||||
"tm": {
|
"tm": {
|
||||||
"top_metrics": {
|
"top_metrics": {
|
||||||
"metric": {"field": "v"},
|
"metrics": {"field": "v"},
|
||||||
"sort": {"s": {"order": "asc", "numeric_type": "double"}}
|
"sort": {"s": {"order": "asc", "numeric_type": "double"}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,7 @@ import org.elasticsearch.cluster.metadata.IndexMetaData;
|
||||||
import org.elasticsearch.common.CheckedConsumer;
|
import org.elasticsearch.common.CheckedConsumer;
|
||||||
import org.elasticsearch.common.collect.Tuple;
|
import org.elasticsearch.common.collect.Tuple;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.common.util.BigArrays;
|
||||||
import org.elasticsearch.index.IndexSettings;
|
import org.elasticsearch.index.IndexSettings;
|
||||||
import org.elasticsearch.index.mapper.ContentPath;
|
import org.elasticsearch.index.mapper.ContentPath;
|
||||||
import org.elasticsearch.index.mapper.DateFieldMapper;
|
import org.elasticsearch.index.mapper.DateFieldMapper;
|
||||||
|
@ -180,8 +181,9 @@ public class MinAggregatorTests extends AggregatorTestCase {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected QueryShardContext queryShardContextMock(IndexSearcher searcher, MapperService mapperService,
|
protected QueryShardContext queryShardContextMock(IndexSearcher searcher, MapperService mapperService,
|
||||||
IndexSettings indexSettings, CircuitBreakerService circuitBreakerService) {
|
IndexSettings indexSettings, CircuitBreakerService circuitBreakerService,
|
||||||
this.queryShardContext = super.queryShardContextMock(searcher, mapperService, indexSettings, circuitBreakerService);
|
BigArrays bigArrays) {
|
||||||
|
this.queryShardContext = super.queryShardContextMock(searcher, mapperService, indexSettings, circuitBreakerService, bigArrays);
|
||||||
return queryShardContext;
|
return queryShardContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -421,11 +421,12 @@ public class ScriptedMetricAggregatorTests extends AggregatorTestCase {
|
||||||
protected QueryShardContext queryShardContextMock(IndexSearcher searcher,
|
protected QueryShardContext queryShardContextMock(IndexSearcher searcher,
|
||||||
MapperService mapperService,
|
MapperService mapperService,
|
||||||
IndexSettings indexSettings,
|
IndexSettings indexSettings,
|
||||||
CircuitBreakerService circuitBreakerService) {
|
CircuitBreakerService circuitBreakerService,
|
||||||
|
BigArrays bigArrays) {
|
||||||
MockScriptEngine scriptEngine = new MockScriptEngine(MockScriptEngine.NAME, SCRIPTS, Collections.emptyMap());
|
MockScriptEngine scriptEngine = new MockScriptEngine(MockScriptEngine.NAME, SCRIPTS, Collections.emptyMap());
|
||||||
Map<String, ScriptEngine> engines = Collections.singletonMap(scriptEngine.getType(), scriptEngine);
|
Map<String, ScriptEngine> engines = Collections.singletonMap(scriptEngine.getType(), scriptEngine);
|
||||||
ScriptService scriptService = new ScriptService(Settings.EMPTY, engines, ScriptModule.CORE_CONTEXTS);
|
ScriptService scriptService = new ScriptService(Settings.EMPTY, engines, ScriptModule.CORE_CONTEXTS);
|
||||||
return new QueryShardContext(0, indexSettings, BigArrays.NON_RECYCLING_INSTANCE, null, null, mapperService, null, scriptService,
|
return new QueryShardContext(0, indexSettings, bigArrays, null, null, mapperService, null, scriptService,
|
||||||
xContentRegistry(), writableRegistry(), null, null, System::currentTimeMillis, null, null, () -> true);
|
xContentRegistry(), writableRegistry(), null, null, System::currentTimeMillis, null, null, () -> true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -243,7 +243,7 @@ public abstract class AggregatorTestCase extends ESTestCase {
|
||||||
when(searchContext.lookup()).thenReturn(searchLookup);
|
when(searchContext.lookup()).thenReturn(searchLookup);
|
||||||
|
|
||||||
QueryShardContext queryShardContext =
|
QueryShardContext queryShardContext =
|
||||||
queryShardContextMock(contextIndexSearcher, mapperService, indexSettings, circuitBreakerService);
|
queryShardContextMock(contextIndexSearcher, mapperService, indexSettings, circuitBreakerService, bigArrays);
|
||||||
when(searchContext.getQueryShardContext()).thenReturn(queryShardContext);
|
when(searchContext.getQueryShardContext()).thenReturn(queryShardContext);
|
||||||
when(queryShardContext.getObjectMapper(anyString())).thenAnswer(invocation -> {
|
when(queryShardContext.getObjectMapper(anyString())).thenAnswer(invocation -> {
|
||||||
String fieldName = (String) invocation.getArguments()[0];
|
String fieldName = (String) invocation.getArguments()[0];
|
||||||
|
@ -293,9 +293,10 @@ public abstract class AggregatorTestCase extends ESTestCase {
|
||||||
protected QueryShardContext queryShardContextMock(IndexSearcher searcher,
|
protected QueryShardContext queryShardContextMock(IndexSearcher searcher,
|
||||||
MapperService mapperService,
|
MapperService mapperService,
|
||||||
IndexSettings indexSettings,
|
IndexSettings indexSettings,
|
||||||
CircuitBreakerService circuitBreakerService) {
|
CircuitBreakerService circuitBreakerService,
|
||||||
|
BigArrays bigArrays) {
|
||||||
|
|
||||||
return new QueryShardContext(0, indexSettings, BigArrays.NON_RECYCLING_INSTANCE, null,
|
return new QueryShardContext(0, indexSettings, bigArrays, null,
|
||||||
getIndexFieldDataLookup(mapperService, circuitBreakerService),
|
getIndexFieldDataLookup(mapperService, circuitBreakerService),
|
||||||
mapperService, null, getMockScriptService(), xContentRegistry(),
|
mapperService, null, getMockScriptService(), xContentRegistry(),
|
||||||
writableRegistry(), null, searcher, System::currentTimeMillis, null, null, () -> true);
|
writableRegistry(), null, searcher, System::currentTimeMillis, null, null, () -> true);
|
||||||
|
|
|
@ -20,23 +20,27 @@ import org.elasticsearch.search.sort.SortValue;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
|
import static org.elasticsearch.search.builder.SearchSourceBuilder.SORT_FIELD;
|
||||||
|
import static org.elasticsearch.xpack.analytics.topmetrics.TopMetricsAggregationBuilder.METRIC_FIELD;
|
||||||
|
|
||||||
|
|
||||||
public class InternalTopMetrics extends InternalNumericMetricsAggregation.MultiValue {
|
public class InternalTopMetrics extends InternalNumericMetricsAggregation.MultiValue {
|
||||||
private final SortOrder sortOrder;
|
private final SortOrder sortOrder;
|
||||||
private final int size;
|
private final int size;
|
||||||
private final String metricName;
|
private final List<String> metricNames;
|
||||||
private final List<TopMetric> topMetrics;
|
private final List<TopMetric> topMetrics;
|
||||||
|
|
||||||
public InternalTopMetrics(String name, @Nullable SortOrder sortOrder, String metricName,
|
public InternalTopMetrics(String name, @Nullable SortOrder sortOrder, List<String> metricNames,
|
||||||
int size, List<TopMetric> topMetrics, List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData) {
|
int size, List<TopMetric> topMetrics, List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData) {
|
||||||
super(name, pipelineAggregators, metaData);
|
super(name, pipelineAggregators, metaData);
|
||||||
this.sortOrder = sortOrder;
|
this.sortOrder = sortOrder;
|
||||||
this.metricName = metricName;
|
this.metricNames = metricNames;
|
||||||
/*
|
/*
|
||||||
* topMetrics.size won't be size when the bucket doesn't have size docs!
|
* topMetrics.size won't be size when the bucket doesn't have size docs!
|
||||||
*/
|
*/
|
||||||
|
@ -44,9 +48,9 @@ public class InternalTopMetrics extends InternalNumericMetricsAggregation.MultiV
|
||||||
this.topMetrics = topMetrics;
|
this.topMetrics = topMetrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
static InternalTopMetrics buildEmptyAggregation(String name, String metricField,
|
static InternalTopMetrics buildEmptyAggregation(String name, List<String> metricNames,
|
||||||
List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData) {
|
List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData) {
|
||||||
return new InternalTopMetrics(name, SortOrder.ASC, metricField, 0, emptyList(), pipelineAggregators, metaData);
|
return new InternalTopMetrics(name, SortOrder.ASC, metricNames, 0, emptyList(), pipelineAggregators, metaData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -55,7 +59,7 @@ public class InternalTopMetrics extends InternalNumericMetricsAggregation.MultiV
|
||||||
public InternalTopMetrics(StreamInput in) throws IOException {
|
public InternalTopMetrics(StreamInput in) throws IOException {
|
||||||
super(in);
|
super(in);
|
||||||
sortOrder = SortOrder.readFromStream(in);
|
sortOrder = SortOrder.readFromStream(in);
|
||||||
metricName = in.readString();
|
metricNames = in.readStringList();
|
||||||
size = in.readVInt();
|
size = in.readVInt();
|
||||||
topMetrics = in.readList(TopMetric::new);
|
topMetrics = in.readList(TopMetric::new);
|
||||||
}
|
}
|
||||||
|
@ -63,7 +67,7 @@ public class InternalTopMetrics extends InternalNumericMetricsAggregation.MultiV
|
||||||
@Override
|
@Override
|
||||||
protected void doWriteTo(StreamOutput out) throws IOException {
|
protected void doWriteTo(StreamOutput out) throws IOException {
|
||||||
sortOrder.writeTo(out);
|
sortOrder.writeTo(out);
|
||||||
out.writeString(metricName);
|
out.writeStringCollection(metricNames);
|
||||||
out.writeVInt(size);
|
out.writeVInt(size);
|
||||||
out.writeList(topMetrics);
|
out.writeList(topMetrics);
|
||||||
}
|
}
|
||||||
|
@ -78,15 +82,19 @@ public class InternalTopMetrics extends InternalNumericMetricsAggregation.MultiV
|
||||||
if (path.isEmpty()) {
|
if (path.isEmpty()) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
if (path.size() == 1 && metricName.contentEquals(path.get(1))) {
|
if (path.size() != 1) {
|
||||||
if (topMetrics.isEmpty()) {
|
throw new IllegalArgumentException("path not supported for [" + getName() + "]: " + path);
|
||||||
// Unmapped.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
assert topMetrics.size() == 1 : "property paths should only resolve against top metrics with size == 1.";
|
|
||||||
return topMetrics.get(0).metricValue;
|
|
||||||
}
|
}
|
||||||
throw new IllegalArgumentException("path not supported for [" + getName() + "]: " + path);
|
int index = metricNames.indexOf(path.get(0));
|
||||||
|
if (index < 0) {
|
||||||
|
throw new IllegalArgumentException("path not supported for [" + getName() + "]: " + path);
|
||||||
|
}
|
||||||
|
if (topMetrics.isEmpty()) {
|
||||||
|
// Unmapped.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
assert topMetrics.size() == 1 : "property paths should only resolve against top metrics with size == 1.";
|
||||||
|
return topMetrics.get(0).metricValues[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -116,7 +124,7 @@ public class InternalTopMetrics extends InternalNumericMetricsAggregation.MultiV
|
||||||
queue.updateTop();
|
queue.updateTop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new InternalTopMetrics(getName(), sortOrder, metricName, size, merged, pipelineAggregators(), getMetaData());
|
return new InternalTopMetrics(getName(), sortOrder, metricNames, size, merged, pipelineAggregators(), getMetaData());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -128,7 +136,7 @@ public class InternalTopMetrics extends InternalNumericMetricsAggregation.MultiV
|
||||||
public XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException {
|
public XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException {
|
||||||
builder.startArray("top");
|
builder.startArray("top");
|
||||||
for (TopMetric top : topMetrics) {
|
for (TopMetric top : topMetrics) {
|
||||||
top.toXContent(builder, metricName);
|
top.toXContent(builder, metricNames);
|
||||||
}
|
}
|
||||||
builder.endArray();
|
builder.endArray();
|
||||||
return builder;
|
return builder;
|
||||||
|
@ -136,7 +144,7 @@ public class InternalTopMetrics extends InternalNumericMetricsAggregation.MultiV
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(super.hashCode(), sortOrder, metricName, size, topMetrics);
|
return Objects.hash(super.hashCode(), sortOrder, metricNames, size, topMetrics);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -144,21 +152,22 @@ public class InternalTopMetrics extends InternalNumericMetricsAggregation.MultiV
|
||||||
if (super.equals(obj) == false) return false;
|
if (super.equals(obj) == false) return false;
|
||||||
InternalTopMetrics other = (InternalTopMetrics) obj;
|
InternalTopMetrics other = (InternalTopMetrics) obj;
|
||||||
return sortOrder.equals(other.sortOrder) &&
|
return sortOrder.equals(other.sortOrder) &&
|
||||||
metricName.equals(other.metricName) &&
|
metricNames.equals(other.metricNames) &&
|
||||||
size == other.size &&
|
size == other.size &&
|
||||||
topMetrics.equals(other.topMetrics);
|
topMetrics.equals(other.topMetrics);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public double value(String name) {
|
public double value(String name) {
|
||||||
if (metricName.equals(name)) {
|
int index = metricNames.indexOf(name);
|
||||||
if (topMetrics.isEmpty()) {
|
if (index < 0) {
|
||||||
return Double.NaN;
|
throw new IllegalArgumentException("unknown metric [" + name + "]");
|
||||||
}
|
|
||||||
assert topMetrics.size() == 1 : "property paths should only resolve against top metrics with size == 1.";
|
|
||||||
return topMetrics.get(0).metricValue;
|
|
||||||
}
|
}
|
||||||
throw new IllegalArgumentException("known metric [" + name + "]");
|
if (topMetrics.isEmpty()) {
|
||||||
|
return Double.NaN;
|
||||||
|
}
|
||||||
|
assert topMetrics.size() == 1 : "property paths should only resolve against top metrics with size == 1.";
|
||||||
|
return topMetrics.get(0).metricValues[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
SortOrder getSortOrder() {
|
SortOrder getSortOrder() {
|
||||||
|
@ -169,8 +178,8 @@ public class InternalTopMetrics extends InternalNumericMetricsAggregation.MultiV
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getMetricName() {
|
List<String> getMetricNames() {
|
||||||
return metricName;
|
return metricNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<TopMetric> getTopMetrics() {
|
List<TopMetric> getTopMetrics() {
|
||||||
|
@ -197,18 +206,25 @@ public class InternalTopMetrics extends InternalNumericMetricsAggregation.MultiV
|
||||||
static class TopMetric implements Writeable, Comparable<TopMetric> {
|
static class TopMetric implements Writeable, Comparable<TopMetric> {
|
||||||
private final DocValueFormat sortFormat;
|
private final DocValueFormat sortFormat;
|
||||||
private final SortValue sortValue;
|
private final SortValue sortValue;
|
||||||
private final double metricValue;
|
private final double[] metricValues;
|
||||||
|
|
||||||
TopMetric(DocValueFormat sortFormat, SortValue sortValue, double metricValue) {
|
TopMetric(DocValueFormat sortFormat, SortValue sortValue, double[] metricValues) {
|
||||||
this.sortFormat = sortFormat;
|
this.sortFormat = sortFormat;
|
||||||
this.sortValue = sortValue;
|
this.sortValue = sortValue;
|
||||||
this.metricValue = metricValue;
|
this.metricValues = metricValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
TopMetric(StreamInput in) throws IOException {
|
TopMetric(StreamInput in) throws IOException {
|
||||||
sortFormat = in.readNamedWriteable(DocValueFormat.class);
|
sortFormat = in.readNamedWriteable(DocValueFormat.class);
|
||||||
sortValue = in.readNamedWriteable(SortValue.class);
|
sortValue = in.readNamedWriteable(SortValue.class);
|
||||||
metricValue = in.readDouble();
|
metricValues = in.readDoubleArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
|
out.writeNamedWriteable(sortFormat);
|
||||||
|
out.writeNamedWriteable(sortValue);
|
||||||
|
out.writeDoubleArray(metricValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
DocValueFormat getSortFormat() {
|
DocValueFormat getSortFormat() {
|
||||||
|
@ -219,26 +235,21 @@ public class InternalTopMetrics extends InternalNumericMetricsAggregation.MultiV
|
||||||
return sortValue;
|
return sortValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
double getMetricValue() {
|
double[] getMetricValues() {
|
||||||
return metricValue;
|
return metricValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public XContentBuilder toXContent(XContentBuilder builder, List<String> metricNames) throws IOException {
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
|
||||||
out.writeNamedWriteable(sortFormat);
|
|
||||||
out.writeNamedWriteable(sortValue);
|
|
||||||
out.writeDouble(metricValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public XContentBuilder toXContent(XContentBuilder builder, String metricName) throws IOException {
|
|
||||||
builder.startObject();
|
builder.startObject();
|
||||||
{
|
{
|
||||||
builder.startArray("sort");
|
builder.startArray(SORT_FIELD.getPreferredName());
|
||||||
sortValue.toXContent(builder, sortFormat);
|
sortValue.toXContent(builder, sortFormat);
|
||||||
builder.endArray();
|
builder.endArray();
|
||||||
builder.startObject("metrics");
|
builder.startObject(METRIC_FIELD.getPreferredName());
|
||||||
{
|
{
|
||||||
builder.field(metricName, Double.isNaN(metricValue) ? null : metricValue);
|
for (int i = 0; i < metricValues.length; i++) {
|
||||||
|
builder.field(metricNames.get(i), Double.isNaN(metricValues[i]) ? null : metricValues[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
builder.endObject();
|
builder.endObject();
|
||||||
}
|
}
|
||||||
|
@ -258,17 +269,17 @@ public class InternalTopMetrics extends InternalNumericMetricsAggregation.MultiV
|
||||||
TopMetric other = (TopMetric) obj;
|
TopMetric other = (TopMetric) obj;
|
||||||
return sortFormat.equals(other.sortFormat)
|
return sortFormat.equals(other.sortFormat)
|
||||||
&& sortValue.equals(other.sortValue)
|
&& sortValue.equals(other.sortValue)
|
||||||
&& metricValue == other.metricValue;
|
&& Arrays.equals(metricValues, other.metricValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(sortFormat, sortValue, metricValue);
|
return Objects.hash(sortFormat, sortValue, Arrays.hashCode(metricValues));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "TopMetric[" + sortFormat + "," + sortValue + "," + metricValue + "]";
|
return "TopMetric[" + sortFormat + "," + sortValue + "," + Arrays.toString(metricValues) + "]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ import static org.elasticsearch.search.builder.SearchSourceBuilder.SORT_FIELD;
|
||||||
|
|
||||||
public class TopMetricsAggregationBuilder extends AbstractAggregationBuilder<TopMetricsAggregationBuilder> {
|
public class TopMetricsAggregationBuilder extends AbstractAggregationBuilder<TopMetricsAggregationBuilder> {
|
||||||
public static final String NAME = "top_metrics";
|
public static final String NAME = "top_metrics";
|
||||||
public static final ParseField METRIC_FIELD = new ParseField("metric");
|
public static final ParseField METRIC_FIELD = new ParseField("metrics");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default to returning only a single top metric.
|
* Default to returning only a single top metric.
|
||||||
|
@ -47,34 +47,35 @@ public class TopMetricsAggregationBuilder extends AbstractAggregationBuilder<Top
|
||||||
if (size < 1) {
|
if (size < 1) {
|
||||||
throw new IllegalArgumentException("[size] must be more than 0 but was [" + size + "]");
|
throw new IllegalArgumentException("[size] must be more than 0 but was [" + size + "]");
|
||||||
}
|
}
|
||||||
MultiValuesSourceFieldConfig metricField = (MultiValuesSourceFieldConfig) args[2];
|
@SuppressWarnings("unchecked")
|
||||||
return new TopMetricsAggregationBuilder(name, sorts, size, metricField);
|
List<MultiValuesSourceFieldConfig> metricFields = (List<MultiValuesSourceFieldConfig>) args[2];
|
||||||
|
return new TopMetricsAggregationBuilder(name, sorts, size, metricFields);
|
||||||
});
|
});
|
||||||
static {
|
static {
|
||||||
PARSER.declareField(constructorArg(), (p, n) -> SortBuilder.fromXContent(p), SORT_FIELD,
|
PARSER.declareField(constructorArg(), (p, n) -> SortBuilder.fromXContent(p), SORT_FIELD,
|
||||||
ObjectParser.ValueType.OBJECT_ARRAY_OR_STRING);
|
ObjectParser.ValueType.OBJECT_ARRAY_OR_STRING);
|
||||||
PARSER.declareInt(optionalConstructorArg(), SIZE_FIELD);
|
PARSER.declareInt(optionalConstructorArg(), SIZE_FIELD);
|
||||||
ContextParser<Void, MultiValuesSourceFieldConfig.Builder> metricParser = MultiValuesSourceFieldConfig.PARSER.apply(true, false);
|
ContextParser<Void, MultiValuesSourceFieldConfig.Builder> metricParser = MultiValuesSourceFieldConfig.PARSER.apply(true, false);
|
||||||
PARSER.declareObject(constructorArg(), (p, n) -> metricParser.parse(p, null).build(), METRIC_FIELD);
|
PARSER.declareObjectArray(constructorArg(), (p, n) -> metricParser.parse(p, null).build(), METRIC_FIELD);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final List<SortBuilder<?>> sortBuilders;
|
private final List<SortBuilder<?>> sortBuilders;
|
||||||
// TODO MultiValuesSourceFieldConfig has more things than we support and less things than we want to support
|
|
||||||
private final int size;
|
private final int size;
|
||||||
private final MultiValuesSourceFieldConfig metricField;
|
private final List<MultiValuesSourceFieldConfig> metricFields;
|
||||||
|
// TODO replace with ValuesSourceConfig once the value source refactor has landed
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a {@code top_metrics} aggregation request.
|
* Build a {@code top_metrics} aggregation request.
|
||||||
*/
|
*/
|
||||||
public TopMetricsAggregationBuilder(String name, List<SortBuilder<?>> sortBuilders, int size,
|
public TopMetricsAggregationBuilder(String name, List<SortBuilder<?>> sortBuilders, int size,
|
||||||
MultiValuesSourceFieldConfig metricField) {
|
List<MultiValuesSourceFieldConfig> metricFields) {
|
||||||
super(name);
|
super(name);
|
||||||
if (sortBuilders.size() != 1) {
|
if (sortBuilders.size() != 1) {
|
||||||
throw new IllegalArgumentException("[sort] must contain exactly one sort");
|
throw new IllegalArgumentException("[sort] must contain exactly one sort");
|
||||||
}
|
}
|
||||||
this.sortBuilders = sortBuilders;
|
this.sortBuilders = sortBuilders;
|
||||||
this.size = size;
|
this.size = size;
|
||||||
this.metricField = metricField;
|
this.metricFields = metricFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -85,7 +86,7 @@ public class TopMetricsAggregationBuilder extends AbstractAggregationBuilder<Top
|
||||||
super(clone, factoriesBuilder, metaData);
|
super(clone, factoriesBuilder, metaData);
|
||||||
this.sortBuilders = clone.sortBuilders;
|
this.sortBuilders = clone.sortBuilders;
|
||||||
this.size = clone.size;
|
this.size = clone.size;
|
||||||
this.metricField = clone.metricField;
|
this.metricFields = clone.metricFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -97,14 +98,14 @@ public class TopMetricsAggregationBuilder extends AbstractAggregationBuilder<Top
|
||||||
List<SortBuilder<?>> sortBuilders = (List<SortBuilder<?>>) (List<?>) in.readNamedWriteableList(SortBuilder.class);
|
List<SortBuilder<?>> sortBuilders = (List<SortBuilder<?>>) (List<?>) in.readNamedWriteableList(SortBuilder.class);
|
||||||
this.sortBuilders = sortBuilders;
|
this.sortBuilders = sortBuilders;
|
||||||
this.size = in.readVInt();
|
this.size = in.readVInt();
|
||||||
this.metricField = new MultiValuesSourceFieldConfig(in);
|
this.metricFields = in.readList(MultiValuesSourceFieldConfig::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doWriteTo(StreamOutput out) throws IOException {
|
protected void doWriteTo(StreamOutput out) throws IOException {
|
||||||
out.writeNamedWriteableList(sortBuilders);
|
out.writeNamedWriteableList(sortBuilders);
|
||||||
out.writeVInt(size);
|
out.writeVInt(size);
|
||||||
metricField.writeTo(out);
|
out.writeList(metricFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -116,7 +117,7 @@ public class TopMetricsAggregationBuilder extends AbstractAggregationBuilder<Top
|
||||||
protected AggregatorFactory doBuild(QueryShardContext queryShardContext, AggregatorFactory parent, Builder subFactoriesBuilder)
|
protected AggregatorFactory doBuild(QueryShardContext queryShardContext, AggregatorFactory parent, Builder subFactoriesBuilder)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
return new TopMetricsAggregatorFactory(name, queryShardContext, parent, subFactoriesBuilder, metaData, sortBuilders,
|
return new TopMetricsAggregatorFactory(name, queryShardContext, parent, subFactoriesBuilder, metaData, sortBuilders,
|
||||||
size, metricField);
|
size, metricFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -129,7 +130,11 @@ public class TopMetricsAggregationBuilder extends AbstractAggregationBuilder<Top
|
||||||
}
|
}
|
||||||
builder.endArray();
|
builder.endArray();
|
||||||
builder.field(SIZE_FIELD.getPreferredName(), size);
|
builder.field(SIZE_FIELD.getPreferredName(), size);
|
||||||
builder.field(METRIC_FIELD.getPreferredName(), metricField);
|
builder.startArray(METRIC_FIELD.getPreferredName());
|
||||||
|
for (MultiValuesSourceFieldConfig metricField: metricFields) {
|
||||||
|
metricField.toXContent(builder, params);
|
||||||
|
}
|
||||||
|
builder.endArray();
|
||||||
}
|
}
|
||||||
builder.endObject();
|
builder.endObject();
|
||||||
return builder;
|
return builder;
|
||||||
|
@ -148,7 +153,7 @@ public class TopMetricsAggregationBuilder extends AbstractAggregationBuilder<Top
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiValuesSourceFieldConfig getMetricField() {
|
List<MultiValuesSourceFieldConfig> getMetricFields() {
|
||||||
return metricField;
|
return metricFields;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import org.elasticsearch.common.lease.Releasables;
|
||||||
import org.elasticsearch.common.util.BigArrays;
|
import org.elasticsearch.common.util.BigArrays;
|
||||||
import org.elasticsearch.common.util.DoubleArray;
|
import org.elasticsearch.common.util.DoubleArray;
|
||||||
import org.elasticsearch.index.fielddata.NumericDoubleValues;
|
import org.elasticsearch.index.fielddata.NumericDoubleValues;
|
||||||
|
import org.elasticsearch.index.query.QueryShardContext;
|
||||||
import org.elasticsearch.search.DocValueFormat;
|
import org.elasticsearch.search.DocValueFormat;
|
||||||
import org.elasticsearch.search.MultiValueMode;
|
import org.elasticsearch.search.MultiValueMode;
|
||||||
import org.elasticsearch.search.aggregations.Aggregator;
|
import org.elasticsearch.search.aggregations.Aggregator;
|
||||||
|
@ -46,25 +47,23 @@ import java.util.Map;
|
||||||
*/
|
*/
|
||||||
class TopMetricsAggregator extends NumericMetricsAggregator.MultiValue {
|
class TopMetricsAggregator extends NumericMetricsAggregator.MultiValue {
|
||||||
private final int size;
|
private final int size;
|
||||||
private final String metricName;
|
|
||||||
private final BucketedSort sort;
|
private final BucketedSort sort;
|
||||||
private final Values values;
|
private final Metrics metrics;
|
||||||
private final ValuesSource.Numeric metricValueSource;
|
|
||||||
|
|
||||||
TopMetricsAggregator(String name, SearchContext context, Aggregator parent, List<PipelineAggregator> pipelineAggregators,
|
TopMetricsAggregator(String name, SearchContext context, Aggregator parent, List<PipelineAggregator> pipelineAggregators,
|
||||||
Map<String, Object> metaData, int size, String metricName,
|
Map<String, Object> metaData, int size,
|
||||||
SortBuilder<?> sort, ValuesSource.Numeric metricValueSource) throws IOException {
|
SortBuilder<?> sort, List<String> metricNames, List<ValuesSource.Numeric> metricValuesSources) throws IOException {
|
||||||
super(name, context, parent, pipelineAggregators, metaData);
|
super(name, context, parent, pipelineAggregators, metaData);
|
||||||
this.size = size;
|
this.size = size;
|
||||||
this.metricName = metricName;
|
assert metricNames.size() == metricValuesSources.size();
|
||||||
this.metricValueSource = metricValueSource;
|
metrics = new Metrics(size, context.getQueryShardContext(), metricNames, metricValuesSources);
|
||||||
if (metricValueSource != null) {
|
/*
|
||||||
values = new Values(size, context.bigArrays(), metricValueSource);
|
* If we're only collecting a single value then only provided *that*
|
||||||
this.sort = sort.buildBucketedSort(context.getQueryShardContext(), size, values);
|
* value to the sort so that swaps and loads are just a little faster
|
||||||
} else {
|
* in that *very* common case.
|
||||||
values = null;
|
*/
|
||||||
this.sort = null;
|
BucketedSort.ExtraData values = metricValuesSources.size() == 1 ? metrics.values[0] : metrics;
|
||||||
}
|
this.sort = sort.buildBucketedSort(context.getQueryShardContext(), size, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -72,7 +71,7 @@ class TopMetricsAggregator extends NumericMetricsAggregator.MultiValue {
|
||||||
if (size != 1) {
|
if (size != 1) {
|
||||||
throw new IllegalArgumentException("[top_metrics] can only the be target if [size] is [1] but was [" + size + "]");
|
throw new IllegalArgumentException("[top_metrics] can only the be target if [size] is [1] but was [" + size + "]");
|
||||||
}
|
}
|
||||||
return metricName.equals(name);
|
return metrics.names.contains(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -84,12 +83,12 @@ class TopMetricsAggregator extends NumericMetricsAggregator.MultiValue {
|
||||||
* be called after we've collected a bucket, so it won't just fetch
|
* be called after we've collected a bucket, so it won't just fetch
|
||||||
* garbage.
|
* garbage.
|
||||||
*/
|
*/
|
||||||
return values.values.get(owningBucketOrd);
|
return metrics.metric(name, owningBucketOrd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ScoreMode scoreMode() {
|
public ScoreMode scoreMode() {
|
||||||
boolean needs = (sort != null && sort.needsScores()) || (metricValueSource != null && metricValueSource.needsScores());
|
boolean needs = sort.needsScores() || metrics.needsScores();
|
||||||
return needs ? ScoreMode.COMPLETE : ScoreMode.COMPLETE_NO_SCORES;
|
return needs ? ScoreMode.COMPLETE : ScoreMode.COMPLETE_NO_SCORES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,9 +96,6 @@ class TopMetricsAggregator extends NumericMetricsAggregator.MultiValue {
|
||||||
public LeafBucketCollector getLeafCollector(LeafReaderContext ctx, LeafBucketCollector sub) throws IOException {
|
public LeafBucketCollector getLeafCollector(LeafReaderContext ctx, LeafBucketCollector sub) throws IOException {
|
||||||
assert sub == LeafBucketCollector.NO_OP_COLLECTOR : "Expected noop but was " + sub.toString();
|
assert sub == LeafBucketCollector.NO_OP_COLLECTOR : "Expected noop but was " + sub.toString();
|
||||||
|
|
||||||
if (metricValueSource == null) {
|
|
||||||
return LeafBucketCollector.NO_OP_COLLECTOR;
|
|
||||||
}
|
|
||||||
BucketedSort.Leaf leafSort = sort.forLeaf(ctx);
|
BucketedSort.Leaf leafSort = sort.forLeaf(ctx);
|
||||||
|
|
||||||
return new LeafBucketCollector() {
|
return new LeafBucketCollector() {
|
||||||
|
@ -117,41 +113,115 @@ class TopMetricsAggregator extends NumericMetricsAggregator.MultiValue {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InternalAggregation buildAggregation(long bucket) throws IOException {
|
public InternalAggregation buildAggregation(long bucket) throws IOException {
|
||||||
if (metricValueSource == null) {
|
List<InternalTopMetrics.TopMetric> topMetrics = sort.getValues(bucket, metrics.resultBuilder(sort.getFormat()));
|
||||||
return buildEmptyAggregation();
|
|
||||||
}
|
|
||||||
List<InternalTopMetrics.TopMetric> topMetrics = sort.getValues(bucket, values.resultBuilder(sort.getFormat()));
|
|
||||||
assert topMetrics.size() <= size;
|
assert topMetrics.size() <= size;
|
||||||
return new InternalTopMetrics(name, sort.getOrder(), metricName, size, topMetrics, pipelineAggregators(), metaData());
|
return new InternalTopMetrics(name, sort.getOrder(), metrics.names, size, topMetrics, pipelineAggregators(), metaData());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InternalTopMetrics buildEmptyAggregation() {
|
public InternalTopMetrics buildEmptyAggregation() {
|
||||||
// The sort format and sort order aren't used in reduction so we pass the simplest thing.
|
return InternalTopMetrics.buildEmptyAggregation(name, metrics.names, pipelineAggregators(), metaData());
|
||||||
return InternalTopMetrics.buildEmptyAggregation(name, metricName, pipelineAggregators(),
|
|
||||||
metaData());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doClose() {
|
public void doClose() {
|
||||||
Releasables.close(sort, values);
|
Releasables.close(sort, metrics);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Values implements BucketedSort.ExtraData, Releasable {
|
private static class Metrics implements BucketedSort.ExtraData, Releasable {
|
||||||
|
private final List<String> names;
|
||||||
|
private final MetricValues[] values;
|
||||||
|
|
||||||
|
Metrics(int size, QueryShardContext ctx, List<String> names, List<ValuesSource.Numeric> valuesSources) {
|
||||||
|
this.names = names;
|
||||||
|
values = new MetricValues[valuesSources.size()];
|
||||||
|
int i = 0;
|
||||||
|
for (ValuesSource.Numeric valuesSource : valuesSources) {
|
||||||
|
if (valuesSource == null) {
|
||||||
|
values[i++] = new MissingMetricValues();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
values[i++] = new CollectMetricValues(size, ctx.bigArrays(), valuesSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean needsScores() {
|
||||||
|
for (int i = 0; i < values.length; i++) {
|
||||||
|
if (values[i].needsScores()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
double metric(String name, long index) {
|
||||||
|
int valueIndex = names.indexOf(name);
|
||||||
|
if (valueIndex < 0) {
|
||||||
|
throw new IllegalArgumentException("[" + name + "] not found");
|
||||||
|
}
|
||||||
|
return values[valueIndex].value(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
BucketedSort.ResultBuilder<InternalTopMetrics.TopMetric> resultBuilder(DocValueFormat sortFormat) {
|
||||||
|
return (index, sortValue) -> {
|
||||||
|
double[] result = new double[values.length];
|
||||||
|
for (int i = 0; i < values.length; i++) {
|
||||||
|
result[i] = values[i].value(index);
|
||||||
|
}
|
||||||
|
return new InternalTopMetrics.TopMetric(sortFormat, sortValue, result);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void swap(long lhs, long rhs) {
|
||||||
|
for (int i = 0; i < values.length; i++) {
|
||||||
|
values[i].swap(lhs, rhs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Loader loader(LeafReaderContext ctx) throws IOException {
|
||||||
|
Loader[] loaders = new Loader[values.length];
|
||||||
|
for (int i = 0; i < values.length; i++) {
|
||||||
|
loaders[i] = values[i].loader(ctx);
|
||||||
|
}
|
||||||
|
return (index, doc) -> {
|
||||||
|
for (int i = 0; i < loaders.length; i++) {
|
||||||
|
loaders[i].loadFromDoc(index, doc);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
Releasables.close(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface MetricValues extends BucketedSort.ExtraData, Releasable {
|
||||||
|
boolean needsScores();
|
||||||
|
double value(long index);
|
||||||
|
}
|
||||||
|
private static class CollectMetricValues implements MetricValues {
|
||||||
private final BigArrays bigArrays;
|
private final BigArrays bigArrays;
|
||||||
private final ValuesSource.Numeric metricValueSource;
|
private final ValuesSource.Numeric metricValueSource;
|
||||||
|
|
||||||
private DoubleArray values;
|
private DoubleArray values;
|
||||||
|
|
||||||
Values(int size, BigArrays bigArrays, ValuesSource.Numeric metricValueSource) {
|
CollectMetricValues(int size, BigArrays bigArrays, ValuesSource.Numeric metricValueSource) {
|
||||||
this.bigArrays = bigArrays;
|
this.bigArrays = bigArrays;
|
||||||
this.metricValueSource = metricValueSource;
|
this.metricValueSource = metricValueSource;
|
||||||
values = bigArrays.newDoubleArray(size, false);
|
values = bigArrays.newDoubleArray(size, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
BucketedSort.ResultBuilder<InternalTopMetrics.TopMetric> resultBuilder(DocValueFormat sortFormat) {
|
@Override
|
||||||
return (index, sortValue) ->
|
public boolean needsScores() {
|
||||||
new InternalTopMetrics.TopMetric(sortFormat, sortValue, values.get(index));
|
return metricValueSource.needsScores();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double value(long index) {
|
||||||
|
return values.get(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -179,4 +249,27 @@ class TopMetricsAggregator extends NumericMetricsAggregator.MultiValue {
|
||||||
values.close();
|
values.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private static class MissingMetricValues implements MetricValues {
|
||||||
|
@Override
|
||||||
|
public double value(long index) {
|
||||||
|
return Double.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean needsScores() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void swap(long lhs, long rhs) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Loader loader(LeafReaderContext ctx) throws IOException {
|
||||||
|
return (index, doc) -> {};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@ import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
|
|
||||||
public class TopMetricsAggregatorFactory extends AggregatorFactory {
|
public class TopMetricsAggregatorFactory extends AggregatorFactory {
|
||||||
/**
|
/**
|
||||||
* Index setting describing the maximum number of top metrics that
|
* Index setting describing the maximum number of top metrics that
|
||||||
|
@ -35,40 +37,34 @@ public class TopMetricsAggregatorFactory extends AggregatorFactory {
|
||||||
|
|
||||||
private final List<SortBuilder<?>> sortBuilders;
|
private final List<SortBuilder<?>> sortBuilders;
|
||||||
private final int size;
|
private final int size;
|
||||||
private final MultiValuesSourceFieldConfig metricField;
|
private final List<MultiValuesSourceFieldConfig> metricFields;
|
||||||
|
|
||||||
public TopMetricsAggregatorFactory(String name, QueryShardContext queryShardContext, AggregatorFactory parent,
|
public TopMetricsAggregatorFactory(String name, QueryShardContext queryShardContext, AggregatorFactory parent,
|
||||||
Builder subFactoriesBuilder, Map<String, Object> metaData, List<SortBuilder<?>> sortBuilders,
|
Builder subFactoriesBuilder, Map<String, Object> metaData, List<SortBuilder<?>> sortBuilders,
|
||||||
int size, MultiValuesSourceFieldConfig metricField) throws IOException {
|
int size, List<MultiValuesSourceFieldConfig> metricFields) throws IOException {
|
||||||
super(name, queryShardContext, parent, subFactoriesBuilder, metaData);
|
super(name, queryShardContext, parent, subFactoriesBuilder, metaData);
|
||||||
this.sortBuilders = sortBuilders;
|
this.sortBuilders = sortBuilders;
|
||||||
this.size = size;
|
this.size = size;
|
||||||
this.metricField = metricField;
|
this.metricFields = metricFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected TopMetricsAggregator createInternal(SearchContext searchContext, Aggregator parent, boolean collectsFromSingleBucket,
|
protected TopMetricsAggregator createInternal(SearchContext searchContext, Aggregator parent, boolean collectsFromSingleBucket,
|
||||||
List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData) throws IOException {
|
List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData) throws IOException {
|
||||||
ValuesSourceConfig<ValuesSource.Numeric> metricFieldSource = ValuesSourceConfig.resolve(queryShardContext, ValueType.NUMERIC,
|
|
||||||
metricField.getFieldName(), metricField.getScript(), metricField.getMissing(), metricField.getTimeZone(), null);
|
|
||||||
ValuesSource.Numeric metricValueSource = metricFieldSource.toValuesSource(queryShardContext);
|
|
||||||
int maxBucketSize = MAX_BUCKET_SIZE.get(searchContext.getQueryShardContext().getIndexSettings().getSettings());
|
int maxBucketSize = MAX_BUCKET_SIZE.get(searchContext.getQueryShardContext().getIndexSettings().getSettings());
|
||||||
if (size > maxBucketSize) {
|
if (size > maxBucketSize) {
|
||||||
throw new IllegalArgumentException("[top_metrics.size] must not be more than [" + maxBucketSize + "] but was [" + size
|
throw new IllegalArgumentException("[top_metrics.size] must not be more than [" + maxBucketSize + "] but was [" + size
|
||||||
+ "]. This limit can be set by changing the [" + MAX_BUCKET_SIZE.getKey()
|
+ "]. This limit can be set by changing the [" + MAX_BUCKET_SIZE.getKey()
|
||||||
+ "] index level setting.");
|
+ "] index level setting.");
|
||||||
}
|
}
|
||||||
if (metricValueSource == null) {
|
List<String> metricNames = metricFields.stream().map(MultiValuesSourceFieldConfig::getFieldName).collect(toList());
|
||||||
return createUnmapped(searchContext, parent, pipelineAggregators, metaData);
|
List<ValuesSource.Numeric> metricValuesSources = metricFields.stream().map(config -> {
|
||||||
}
|
ValuesSourceConfig<ValuesSource.Numeric> resolved = ValuesSourceConfig.resolve(
|
||||||
return new TopMetricsAggregator(name, searchContext, parent, pipelineAggregators, metaData, size, metricField.getFieldName(),
|
searchContext.getQueryShardContext(), ValueType.NUMERIC,
|
||||||
sortBuilders.get(0), metricValueSource);
|
config.getFieldName(), config.getScript(), config.getMissing(), config.getTimeZone(), null);
|
||||||
|
return resolved.toValuesSource(searchContext.getQueryShardContext());
|
||||||
|
}).collect(toList());
|
||||||
|
return new TopMetricsAggregator(name, searchContext, parent, pipelineAggregators, metaData, size,
|
||||||
|
sortBuilders.get(0), metricNames, metricValuesSources);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TopMetricsAggregator createUnmapped(SearchContext searchContext, Aggregator parent,
|
|
||||||
List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData) throws IOException {
|
|
||||||
return new TopMetricsAggregator(name, searchContext, parent, pipelineAggregators, metaData, size, metricField.getFieldName(),
|
|
||||||
null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import org.elasticsearch.test.ESTestCase;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.sameInstance;
|
import static org.hamcrest.Matchers.sameInstance;
|
||||||
|
|
||||||
|
@ -45,7 +46,7 @@ public class InternalTopMetricsReduceTests extends ESTestCase {
|
||||||
InternalTopMetrics winner = first.getSortOrder() == SortOrder.ASC ? min : max;
|
InternalTopMetrics winner = first.getSortOrder() == SortOrder.ASC ? min : max;
|
||||||
InternalTopMetrics reduced = reduce(metrics);
|
InternalTopMetrics reduced = reduce(metrics);
|
||||||
assertThat(reduced.getName(), equalTo("test"));
|
assertThat(reduced.getName(), equalTo("test"));
|
||||||
assertThat(reduced.getMetricName(), equalTo("test"));
|
assertThat(reduced.getMetricNames(), equalTo(singletonList("test")));
|
||||||
assertThat(reduced.getSortOrder(), equalTo(first.getSortOrder()));
|
assertThat(reduced.getSortOrder(), equalTo(first.getSortOrder()));
|
||||||
assertThat(reduced.getSize(), equalTo(first.getSize()));
|
assertThat(reduced.getSize(), equalTo(first.getSize()));
|
||||||
assertThat(reduced.getTopMetrics(), equalTo(winner.getTopMetrics()));
|
assertThat(reduced.getTopMetrics(), equalTo(winner.getTopMetrics()));
|
||||||
|
@ -60,7 +61,7 @@ public class InternalTopMetricsReduceTests extends ESTestCase {
|
||||||
};
|
};
|
||||||
InternalTopMetrics reduced = reduce(metrics);
|
InternalTopMetrics reduced = reduce(metrics);
|
||||||
assertThat(reduced.getName(), equalTo("test"));
|
assertThat(reduced.getName(), equalTo("test"));
|
||||||
assertThat(reduced.getMetricName(), equalTo("test"));
|
assertThat(reduced.getMetricNames(), equalTo(singletonList("test")));
|
||||||
assertThat(reduced.getSortOrder(), equalTo(first.getSortOrder()));
|
assertThat(reduced.getSortOrder(), equalTo(first.getSortOrder()));
|
||||||
assertThat(reduced.getSize(), equalTo(first.getSize()));
|
assertThat(reduced.getSize(), equalTo(first.getSize()));
|
||||||
assertThat(reduced.getTopMetrics(), equalTo(Arrays.asList(
|
assertThat(reduced.getTopMetrics(), equalTo(Arrays.asList(
|
||||||
|
@ -74,14 +75,14 @@ public class InternalTopMetricsReduceTests extends ESTestCase {
|
||||||
// Doubles sort first.
|
// Doubles sort first.
|
||||||
InternalTopMetrics winner = doubleMetrics.getSortOrder() == SortOrder.ASC ? doubleMetrics : longMetrics;
|
InternalTopMetrics winner = doubleMetrics.getSortOrder() == SortOrder.ASC ? doubleMetrics : longMetrics;
|
||||||
assertThat(reduced.getName(), equalTo("test"));
|
assertThat(reduced.getName(), equalTo("test"));
|
||||||
assertThat(reduced.getMetricName(), equalTo("test"));
|
assertThat(reduced.getMetricNames(), equalTo(singletonList("test")));
|
||||||
assertThat(reduced.getSortOrder(), equalTo(doubleMetrics.getSortOrder()));
|
assertThat(reduced.getSortOrder(), equalTo(doubleMetrics.getSortOrder()));
|
||||||
assertThat(reduced.getSize(), equalTo(doubleMetrics.getSize()));
|
assertThat(reduced.getSize(), equalTo(doubleMetrics.getSize()));
|
||||||
assertThat(reduced.getTopMetrics(), equalTo(winner.getTopMetrics()));
|
assertThat(reduced.getTopMetrics(), equalTo(winner.getTopMetrics()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private InternalTopMetrics buildEmpty() {
|
private InternalTopMetrics buildEmpty() {
|
||||||
return InternalTopMetrics.buildEmptyAggregation("test", "test", emptyList(), null);
|
return InternalTopMetrics.buildEmptyAggregation("test", singletonList("test"), emptyList(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private InternalTopMetrics buildFilled(int size, InternalTopMetrics.TopMetric... metrics) {
|
private InternalTopMetrics buildFilled(int size, InternalTopMetrics.TopMetric... metrics) {
|
||||||
|
@ -89,12 +90,12 @@ public class InternalTopMetricsReduceTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
private InternalTopMetrics buildFilled(SortOrder sortOrder, int size, InternalTopMetrics.TopMetric... metrics) {
|
private InternalTopMetrics buildFilled(SortOrder sortOrder, int size, InternalTopMetrics.TopMetric... metrics) {
|
||||||
return new InternalTopMetrics("test", sortOrder, "test", size, Arrays.asList(metrics), emptyList(), null);
|
return new InternalTopMetrics("test", sortOrder, singletonList("test"), size, Arrays.asList(metrics), emptyList(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private InternalTopMetrics.TopMetric top(SortValue sortValue, double metricValue) {
|
private InternalTopMetrics.TopMetric top(SortValue sortValue, double metricValue) {
|
||||||
DocValueFormat sortFormat = randomFrom(DocValueFormat.RAW, DocValueFormat.BINARY, DocValueFormat.BOOLEAN, DocValueFormat.IP);
|
DocValueFormat sortFormat = randomFrom(DocValueFormat.RAW, DocValueFormat.BINARY, DocValueFormat.BOOLEAN, DocValueFormat.IP);
|
||||||
return new InternalTopMetrics.TopMetric(sortFormat, sortValue, metricValue);
|
return new InternalTopMetrics.TopMetric(sortFormat, sortValue, new double[] {metricValue});
|
||||||
}
|
}
|
||||||
|
|
||||||
private InternalTopMetrics reduce(InternalTopMetrics... results) {
|
private InternalTopMetrics reduce(InternalTopMetrics... results) {
|
||||||
|
|
|
@ -27,16 +27,18 @@ import java.time.ZonedDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.Collections.singletonMap;
|
|
||||||
import static java.util.stream.Collectors.toList;
|
import static java.util.stream.Collectors.toList;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.hamcrest.Matchers.hasEntry;
|
||||||
import static org.hamcrest.Matchers.hasSize;
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
|
|
||||||
public class InternalTopMetricsTests extends InternalAggregationTestCase<InternalTopMetrics> {
|
public class InternalTopMetricsTests extends InternalAggregationTestCase<InternalTopMetrics> {
|
||||||
|
@ -51,7 +53,7 @@ public class InternalTopMetricsTests extends InternalAggregationTestCase<Interna
|
||||||
|
|
||||||
public void testEmptyIsNotMapped() {
|
public void testEmptyIsNotMapped() {
|
||||||
InternalTopMetrics empty = InternalTopMetrics.buildEmptyAggregation(
|
InternalTopMetrics empty = InternalTopMetrics.buildEmptyAggregation(
|
||||||
randomAlphaOfLength(5), randomAlphaOfLength(2), emptyList(), null);
|
randomAlphaOfLength(5), randomMetricNames(between(1, 5)), emptyList(), null);
|
||||||
assertFalse(empty.isMapped());
|
assertFalse(empty.isMapped());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,8 +63,9 @@ public class InternalTopMetricsTests extends InternalAggregationTestCase<Interna
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testToXContentDoubleSortValue() throws IOException {
|
public void testToXContentDoubleSortValue() throws IOException {
|
||||||
InternalTopMetrics tm = new InternalTopMetrics("test", sortOrder, "test", 1,
|
InternalTopMetrics tm = new InternalTopMetrics("test", sortOrder, singletonList("test"), 1,
|
||||||
Arrays.asList(new InternalTopMetrics.TopMetric(DocValueFormat.RAW, SortValue.from(1.0), 1.0)), emptyList(), null);
|
singletonList(new InternalTopMetrics.TopMetric(DocValueFormat.RAW, SortValue.from(1.0), new double[] {1.0})),
|
||||||
|
emptyList(), null);
|
||||||
assertThat(Strings.toString(tm, true, true), equalTo(
|
assertThat(Strings.toString(tm, true, true), equalTo(
|
||||||
"{\n" +
|
"{\n" +
|
||||||
" \"test\" : {\n" +
|
" \"test\" : {\n" +
|
||||||
|
@ -84,8 +87,8 @@ public class InternalTopMetricsTests extends InternalAggregationTestCase<Interna
|
||||||
DocValueFormat sortFormat = new DocValueFormat.DateTime(DateFormatter.forPattern("strict_date_time"), ZoneId.of("UTC"),
|
DocValueFormat sortFormat = new DocValueFormat.DateTime(DateFormatter.forPattern("strict_date_time"), ZoneId.of("UTC"),
|
||||||
DateFieldMapper.Resolution.MILLISECONDS);
|
DateFieldMapper.Resolution.MILLISECONDS);
|
||||||
SortValue sortValue = SortValue.from(ZonedDateTime.parse("2007-12-03T10:15:30Z").toInstant().toEpochMilli());
|
SortValue sortValue = SortValue.from(ZonedDateTime.parse("2007-12-03T10:15:30Z").toInstant().toEpochMilli());
|
||||||
InternalTopMetrics tm = new InternalTopMetrics("test", sortOrder, "test", 1,
|
InternalTopMetrics tm = new InternalTopMetrics("test", sortOrder, singletonList("test"), 1,
|
||||||
Arrays.asList(new InternalTopMetrics.TopMetric(sortFormat, sortValue, 1.0)), emptyList(), null);
|
singletonList(new InternalTopMetrics.TopMetric(sortFormat, sortValue, new double[] {1.0})), emptyList(), null);
|
||||||
assertThat(Strings.toString(tm, true, true), equalTo(
|
assertThat(Strings.toString(tm, true, true), equalTo(
|
||||||
"{\n" +
|
"{\n" +
|
||||||
" \"test\" : {\n" +
|
" \"test\" : {\n" +
|
||||||
|
@ -103,11 +106,34 @@ public class InternalTopMetricsTests extends InternalAggregationTestCase<Interna
|
||||||
"}"));
|
"}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testToXContentManyValues() throws IOException {
|
public void testToXContentManyMetrics() throws IOException {
|
||||||
InternalTopMetrics tm = new InternalTopMetrics("test", sortOrder, "test", 2,
|
InternalTopMetrics tm = new InternalTopMetrics("test", sortOrder, Arrays.asList("foo", "bar", "baz"), 1,
|
||||||
|
singletonList(new InternalTopMetrics.TopMetric(DocValueFormat.RAW, SortValue.from(1.0), new double[] {1.0, 2.0, 3.0})),
|
||||||
|
emptyList(), null);
|
||||||
|
assertThat(Strings.toString(tm, true, true), equalTo(
|
||||||
|
"{\n" +
|
||||||
|
" \"test\" : {\n" +
|
||||||
|
" \"top\" : [\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"sort\" : [\n" +
|
||||||
|
" 1.0\n" +
|
||||||
|
" ],\n" +
|
||||||
|
" \"metrics\" : {\n" +
|
||||||
|
" \"foo\" : 1.0,\n" +
|
||||||
|
" \"bar\" : 2.0,\n" +
|
||||||
|
" \"baz\" : 3.0\n" +
|
||||||
|
" }\n" +
|
||||||
|
" }\n" +
|
||||||
|
" ]\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testToXContentManyTopMetrics() throws IOException {
|
||||||
|
InternalTopMetrics tm = new InternalTopMetrics("test", sortOrder, singletonList("test"), 2,
|
||||||
Arrays.asList(
|
Arrays.asList(
|
||||||
new InternalTopMetrics.TopMetric(DocValueFormat.RAW, SortValue.from(1.0), 1.0),
|
new InternalTopMetrics.TopMetric(DocValueFormat.RAW, SortValue.from(1.0), new double[] {1.0}),
|
||||||
new InternalTopMetrics.TopMetric(DocValueFormat.RAW, SortValue.from(2.0), 2.0)),
|
new InternalTopMetrics.TopMetric(DocValueFormat.RAW, SortValue.from(2.0), new double[] {2.0})),
|
||||||
emptyList(), null);
|
emptyList(), null);
|
||||||
assertThat(Strings.toString(tm, true, true), equalTo(
|
assertThat(Strings.toString(tm, true, true), equalTo(
|
||||||
"{\n" +
|
"{\n" +
|
||||||
|
@ -145,17 +171,18 @@ public class InternalTopMetricsTests extends InternalAggregationTestCase<Interna
|
||||||
@Override
|
@Override
|
||||||
protected InternalTopMetrics createTestInstance(String name, List<PipelineAggregator> pipelineAggregators,
|
protected InternalTopMetrics createTestInstance(String name, List<PipelineAggregator> pipelineAggregators,
|
||||||
Map<String, Object> metaData) {
|
Map<String, Object> metaData) {
|
||||||
String metricName = randomAlphaOfLength(5);
|
int metricCount = between(1, 5);
|
||||||
|
List<String> metricNames = randomMetricNames(metricCount);
|
||||||
int size = between(1, 100);
|
int size = between(1, 100);
|
||||||
List<InternalTopMetrics.TopMetric> topMetrics = randomTopMetrics(between(0, size));
|
List<InternalTopMetrics.TopMetric> topMetrics = randomTopMetrics(between(0, size), metricCount);
|
||||||
return new InternalTopMetrics(name, sortOrder, metricName, size, topMetrics, pipelineAggregators, metaData);
|
return new InternalTopMetrics(name, sortOrder, metricNames, size, topMetrics, pipelineAggregators, metaData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected InternalTopMetrics mutateInstance(InternalTopMetrics instance) throws IOException {
|
protected InternalTopMetrics mutateInstance(InternalTopMetrics instance) throws IOException {
|
||||||
String name = instance.getName();
|
String name = instance.getName();
|
||||||
SortOrder sortOrder = instance.getSortOrder();
|
SortOrder sortOrder = instance.getSortOrder();
|
||||||
String metricName = instance.getMetricName();
|
List<String> metricNames = instance.getMetricNames();
|
||||||
int size = instance.getSize();
|
int size = instance.getSize();
|
||||||
List<InternalTopMetrics.TopMetric> topMetrics = instance.getTopMetrics();
|
List<InternalTopMetrics.TopMetric> topMetrics = instance.getTopMetrics();
|
||||||
switch (randomInt(4)) {
|
switch (randomInt(4)) {
|
||||||
|
@ -167,19 +194,21 @@ public class InternalTopMetricsTests extends InternalAggregationTestCase<Interna
|
||||||
Collections.reverse(topMetrics);
|
Collections.reverse(topMetrics);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
metricName = randomAlphaOfLength(6);
|
metricNames = new ArrayList<>(metricNames);
|
||||||
|
metricNames.set(randomInt(metricNames.size() - 1), randomAlphaOfLength(6));
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
size = randomValueOtherThan(size, () -> between(1, 100));
|
size = randomValueOtherThan(size, () -> between(1, 100));
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
int fixedSize = size;
|
int fixedSize = size;
|
||||||
topMetrics = randomValueOtherThan(topMetrics, () -> randomTopMetrics(between(1, fixedSize)));
|
int fixedMetricsSize = metricNames.size();
|
||||||
|
topMetrics = randomValueOtherThan(topMetrics, () -> randomTopMetrics(between(1, fixedSize), fixedMetricsSize));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("bad mutation");
|
throw new IllegalArgumentException("bad mutation");
|
||||||
}
|
}
|
||||||
return new InternalTopMetrics(name, sortOrder, metricName, size, topMetrics,
|
return new InternalTopMetrics(name, sortOrder, metricNames, size, topMetrics,
|
||||||
instance.pipelineAggregators(), instance.getMetaData());
|
instance.pipelineAggregators(), instance.getMetaData());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,7 +228,11 @@ public class InternalTopMetricsTests extends InternalAggregationTestCase<Interna
|
||||||
Object expectedSort = internalTop.getSortFormat() == DocValueFormat.RAW ?
|
Object expectedSort = internalTop.getSortFormat() == DocValueFormat.RAW ?
|
||||||
internalTop.getSortValue().getKey() : internalTop.getSortValue().format(internalTop.getSortFormat());
|
internalTop.getSortValue().getKey() : internalTop.getSortValue().format(internalTop.getSortFormat());
|
||||||
assertThat(parsedTop.getSort(), equalTo(singletonList(expectedSort)));
|
assertThat(parsedTop.getSort(), equalTo(singletonList(expectedSort)));
|
||||||
assertThat(parsedTop.getMetrics(), equalTo(singletonMap(aggregation.getMetricName(), internalTop.getMetricValue())));
|
assertThat(parsedTop.getMetrics().keySet(), hasSize(aggregation.getMetricNames().size()));
|
||||||
|
for (int m = 0; m < aggregation.getMetricNames().size(); m++) {
|
||||||
|
assertThat(parsedTop.getMetrics(),
|
||||||
|
hasEntry(aggregation.getMetricNames().get(m), internalTop.getMetricValues()[m]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,17 +247,31 @@ public class InternalTopMetricsTests extends InternalAggregationTestCase<Interna
|
||||||
List<InternalTopMetrics.TopMetric> winners = metrics.size() > first.getSize() ? metrics.subList(0, first.getSize()) : metrics;
|
List<InternalTopMetrics.TopMetric> winners = metrics.size() > first.getSize() ? metrics.subList(0, first.getSize()) : metrics;
|
||||||
assertThat(reduced.getName(), equalTo(first.getName()));
|
assertThat(reduced.getName(), equalTo(first.getName()));
|
||||||
assertThat(reduced.getSortOrder(), equalTo(first.getSortOrder()));
|
assertThat(reduced.getSortOrder(), equalTo(first.getSortOrder()));
|
||||||
assertThat(reduced.getMetricName(), equalTo(first.getMetricName()));
|
assertThat(reduced.getMetricNames(), equalTo(first.getMetricNames()));
|
||||||
assertThat(reduced.getTopMetrics(), equalTo(winners));
|
assertThat(reduced.getTopMetrics(), equalTo(winners));
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<InternalTopMetrics.TopMetric> randomTopMetrics(int length) {
|
private List<InternalTopMetrics.TopMetric> randomTopMetrics(int length, int metricCount) {
|
||||||
return IntStream.range(0, length)
|
return IntStream.range(0, length)
|
||||||
.mapToObj(i -> new InternalTopMetrics.TopMetric(randomNumericDocValueFormat(), randomSortValue(), randomDouble()))
|
.mapToObj(i -> new InternalTopMetrics.TopMetric(
|
||||||
|
randomNumericDocValueFormat(), randomSortValue(), randomMetricValues(metricCount)
|
||||||
|
))
|
||||||
.sorted((lhs, rhs) -> sortOrder.reverseMul() * lhs.getSortValue().compareTo(rhs.getSortValue()))
|
.sorted((lhs, rhs) -> sortOrder.reverseMul() * lhs.getSortValue().compareTo(rhs.getSortValue()))
|
||||||
.collect(toList());
|
.collect(toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static List<String> randomMetricNames(int metricCount) {
|
||||||
|
Set<String> names = new HashSet<>(metricCount);
|
||||||
|
while (names.size() < metricCount) {
|
||||||
|
names.add(randomAlphaOfLength(5));
|
||||||
|
}
|
||||||
|
return new ArrayList<>(names);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double[] randomMetricValues(int metricCount) {
|
||||||
|
return IntStream.range(0, metricCount).mapToDouble(i -> randomDouble()).toArray();
|
||||||
|
}
|
||||||
|
|
||||||
private static SortValue randomSortValue() {
|
private static SortValue randomSortValue() {
|
||||||
if (randomBoolean()) {
|
if (randomBoolean()) {
|
||||||
return SortValue.from(randomLong());
|
return SortValue.from(randomLong());
|
||||||
|
|
|
@ -27,6 +27,7 @@ import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
import static org.hamcrest.Matchers.empty;
|
import static org.hamcrest.Matchers.empty;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.hasSize;
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
|
@ -68,9 +69,14 @@ public class TopMetricsAggregationBuilderTests extends AbstractSerializingTestCa
|
||||||
protected TopMetricsAggregationBuilder createTestInstance() {
|
protected TopMetricsAggregationBuilder createTestInstance() {
|
||||||
List<SortBuilder<?>> sortBuilders = singletonList(
|
List<SortBuilder<?>> sortBuilders = singletonList(
|
||||||
new FieldSortBuilder(randomAlphaOfLength(5)).order(randomFrom(SortOrder.values())));
|
new FieldSortBuilder(randomAlphaOfLength(5)).order(randomFrom(SortOrder.values())));
|
||||||
MultiValuesSourceFieldConfig.Builder metricField = new MultiValuesSourceFieldConfig.Builder();
|
List<MultiValuesSourceFieldConfig> metricFields = InternalTopMetricsTests.randomMetricNames(between(1, 5)).stream()
|
||||||
metricField.setFieldName(randomAlphaOfLength(5)).setMissing(1.0);
|
.map(name -> {
|
||||||
return new TopMetricsAggregationBuilder(randomAlphaOfLength(5), sortBuilders, between(1, 100), metricField.build());
|
MultiValuesSourceFieldConfig.Builder metricField = new MultiValuesSourceFieldConfig.Builder();
|
||||||
|
metricField.setFieldName(randomAlphaOfLength(5)).setMissing(1.0);
|
||||||
|
return metricField.build();
|
||||||
|
})
|
||||||
|
.collect(toList());
|
||||||
|
return new TopMetricsAggregationBuilder(randomAlphaOfLength(5), sortBuilders, between(1, 100), metricFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testClientBuilder() throws IOException {
|
public void testClientBuilder() throws IOException {
|
||||||
|
@ -98,6 +104,6 @@ public class TopMetricsAggregationBuilderTests extends AbstractSerializingTestCa
|
||||||
serverBuilder.getName(),
|
serverBuilder.getName(),
|
||||||
serverBuilder.getSortBuilders().get(0),
|
serverBuilder.getSortBuilders().get(0),
|
||||||
serverBuilder.getSize(),
|
serverBuilder.getSize(),
|
||||||
serverBuilder.getMetricField().getFieldName());
|
serverBuilder.getMetricFields().stream().map(MultiValuesSourceFieldConfig::getFieldName).toArray(String[]::new));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,12 +68,14 @@ import org.elasticsearch.search.sort.SortValue;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
import static java.util.Collections.emptyMap;
|
import static java.util.Collections.emptyMap;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.Collections.singletonMap;
|
import static java.util.Collections.singletonMap;
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.hasSize;
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
import static org.hamcrest.Matchers.notANumber;
|
import static org.hamcrest.Matchers.notANumber;
|
||||||
|
@ -95,7 +97,7 @@ public class TopMetricsAggregatorTests extends AggregatorTestCase {
|
||||||
},
|
},
|
||||||
numberFieldType(NumberType.DOUBLE, "s"));
|
numberFieldType(NumberType.DOUBLE, "s"));
|
||||||
assertThat(result.getSortOrder(), equalTo(SortOrder.ASC));
|
assertThat(result.getSortOrder(), equalTo(SortOrder.ASC));
|
||||||
assertThat(result.getTopMetrics(), equalTo(emptyList()));
|
assertThat(result.getTopMetrics(), equalTo(singletonList(top(1.0, Double.NaN))));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testMissingValueForMetric() throws IOException {
|
public void testMissingValueForMetric() throws IOException {
|
||||||
|
@ -106,7 +108,8 @@ public class TopMetricsAggregatorTests extends AggregatorTestCase {
|
||||||
assertThat(result.getSortOrder(), equalTo(SortOrder.ASC));
|
assertThat(result.getSortOrder(), equalTo(SortOrder.ASC));
|
||||||
assertThat(result.getTopMetrics(), hasSize(1));
|
assertThat(result.getTopMetrics(), hasSize(1));
|
||||||
assertThat(result.getTopMetrics().get(0).getSortValue(), equalTo(SortValue.from(1.0)));
|
assertThat(result.getTopMetrics().get(0).getSortValue(), equalTo(SortValue.from(1.0)));
|
||||||
assertThat(result.getTopMetrics().get(0).getMetricValue(), notANumber());
|
assertThat(result.getTopMetrics().get(0).getMetricValues().length, equalTo(1));
|
||||||
|
assertThat(result.getTopMetrics().get(0).getMetricValues()[0], notANumber());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testActualValueForMetric() throws IOException {
|
public void testActualValueForMetric() throws IOException {
|
||||||
|
@ -130,7 +133,7 @@ public class TopMetricsAggregatorTests extends AggregatorTestCase {
|
||||||
InternalTopMetrics result = collectFromDoubles(simpleBuilder(new FieldSortBuilder("s").order(SortOrder.ASC)));
|
InternalTopMetrics result = collectFromDoubles(simpleBuilder(new FieldSortBuilder("s").order(SortOrder.ASC)));
|
||||||
assertThat(result.getSortOrder(), equalTo(SortOrder.ASC));
|
assertThat(result.getSortOrder(), equalTo(SortOrder.ASC));
|
||||||
assertThat(result.getTopMetrics(), equalTo(singletonList(
|
assertThat(result.getTopMetrics(), equalTo(singletonList(
|
||||||
new InternalTopMetrics.TopMetric(DocValueFormat.RAW, SortValue.from(1.0), 2.0))));
|
new InternalTopMetrics.TopMetric(DocValueFormat.RAW, SortValue.from(1.0), new double[] {2.0}))));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSortByDoubleDescending() throws IOException {
|
public void testSortByDoubleDescending() throws IOException {
|
||||||
|
@ -349,21 +352,50 @@ public class TopMetricsAggregatorTests extends AggregatorTestCase {
|
||||||
* breaker. The number of buckets feels fairly arbitrary but
|
* breaker. The number of buckets feels fairly arbitrary but
|
||||||
* it comes from:
|
* it comes from:
|
||||||
* budget = 15k = 20k - 5k for the "default weight" of ever agg
|
* budget = 15k = 20k - 5k for the "default weight" of ever agg
|
||||||
* The 922th bucket causes a resize which requests puts the total
|
* The 646th bucket causes a resize which requests puts the total
|
||||||
* just over 15k.O
|
* just over 15k. This works out to more like 190 bits per bucket
|
||||||
|
* when we're fairly sure this should take about 129 bits per
|
||||||
|
* bucket. The difference is because, for arrays in of this size,
|
||||||
|
* BigArrays allocates the new array before freeing the old one.
|
||||||
|
* That causes us to trip when we're about 2/3 of the way to the
|
||||||
|
* limit. And 2/3 of 190 is 126. Which is pretty much what we
|
||||||
|
* expect. Sort of.
|
||||||
*/
|
*/
|
||||||
int bucketThatBreaks = 922;
|
int bucketThatBreaks = 646;
|
||||||
for (int b = 0; b < bucketThatBreaks; b++) {
|
for (int b = 0; b < bucketThatBreaks; b++) {
|
||||||
leaf.collect(0, b);
|
try {
|
||||||
|
leaf.collect(0, b);
|
||||||
|
} catch (CircuitBreakingException e) {
|
||||||
|
throw new AssertionError("Unexpected circuit break at [" + b + "]. Expected at [" + bucketThatBreaks + "]", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
CircuitBreakingException e = expectThrows(CircuitBreakingException.class, () -> leaf.collect(0, bucketThatBreaks));
|
CircuitBreakingException e = expectThrows(CircuitBreakingException.class, () -> leaf.collect(0, bucketThatBreaks));
|
||||||
assertThat(e.getMessage(), equalTo("test error"));
|
assertThat(e.getMessage(), equalTo("test error"));
|
||||||
assertThat(e.getByteLimit(), equalTo(max.getBytes()));
|
assertThat(e.getByteLimit(), equalTo(max.getBytes()));
|
||||||
assertThat(e.getBytesWanted(), equalTo(16440L));
|
assertThat(e.getBytesWanted(), equalTo(5872L));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testManyMetrics() throws IOException {
|
||||||
|
List<SortBuilder<?>> sorts = singletonList(new FieldSortBuilder("s").order(SortOrder.ASC));
|
||||||
|
TopMetricsAggregationBuilder builder = new TopMetricsAggregationBuilder("test", sorts, 1,
|
||||||
|
Arrays.asList(
|
||||||
|
new MultiValuesSourceFieldConfig.Builder().setFieldName("m1").build(),
|
||||||
|
new MultiValuesSourceFieldConfig.Builder().setFieldName("m2").build(),
|
||||||
|
new MultiValuesSourceFieldConfig.Builder().setFieldName("m3").build()
|
||||||
|
));
|
||||||
|
InternalTopMetrics result = collect(builder, new MatchAllDocsQuery(), writer -> {
|
||||||
|
writer.addDocument(Arrays.asList(doubleField("s", 1.0),
|
||||||
|
doubleField("m1", 12.0), doubleField("m2", 22.0), doubleField("m3", 32.0)));
|
||||||
|
writer.addDocument(Arrays.asList(doubleField("s", 2.0),
|
||||||
|
doubleField("m1", 13.0), doubleField("m2", 23.0), doubleField("m3", 33.0)));
|
||||||
|
}, manyMetricsFields());
|
||||||
|
assertThat(result.getSortOrder(), equalTo(SortOrder.ASC));
|
||||||
|
assertThat(result.getTopMetrics(), equalTo(singletonList(
|
||||||
|
new InternalTopMetrics.TopMetric(DocValueFormat.RAW, SortValue.from(1.0), new double[] {12.0, 22.0, 32.0}))));
|
||||||
|
}
|
||||||
|
|
||||||
private TopMetricsAggregationBuilder simpleBuilder() {
|
private TopMetricsAggregationBuilder simpleBuilder() {
|
||||||
return simpleBuilder(new FieldSortBuilder("s"));
|
return simpleBuilder(new FieldSortBuilder("s"));
|
||||||
}
|
}
|
||||||
|
@ -374,7 +406,7 @@ public class TopMetricsAggregatorTests extends AggregatorTestCase {
|
||||||
|
|
||||||
private TopMetricsAggregationBuilder simpleBuilder(SortBuilder<?> sort, int size) {
|
private TopMetricsAggregationBuilder simpleBuilder(SortBuilder<?> sort, int size) {
|
||||||
return new TopMetricsAggregationBuilder("test", singletonList(sort), size,
|
return new TopMetricsAggregationBuilder("test", singletonList(sort), size,
|
||||||
new MultiValuesSourceFieldConfig.Builder().setFieldName("m").build());
|
singletonList(new MultiValuesSourceFieldConfig.Builder().setFieldName("m").build()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -394,6 +426,16 @@ public class TopMetricsAggregatorTests extends AggregatorTestCase {
|
||||||
return new MappedFieldType[] {numberFieldType(NumberType.DOUBLE, "s"), numberFieldType(NumberType.DOUBLE, "m")};
|
return new MappedFieldType[] {numberFieldType(NumberType.DOUBLE, "s"), numberFieldType(NumberType.DOUBLE, "m")};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MappedFieldType[] manyMetricsFields() {
|
||||||
|
return new MappedFieldType[] {
|
||||||
|
numberFieldType(NumberType.DOUBLE, "s"),
|
||||||
|
numberFieldType(NumberType.DOUBLE, "m1"),
|
||||||
|
numberFieldType(NumberType.DOUBLE, "m2"),
|
||||||
|
numberFieldType(NumberType.DOUBLE, "m3"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private MappedFieldType[] floatAndDoubleField() {
|
private MappedFieldType[] floatAndDoubleField() {
|
||||||
return new MappedFieldType[] {numberFieldType(NumberType.FLOAT, "s"), numberFieldType(NumberType.DOUBLE, "m")};
|
return new MappedFieldType[] {numberFieldType(NumberType.FLOAT, "s"), numberFieldType(NumberType.DOUBLE, "m")};
|
||||||
}
|
}
|
||||||
|
@ -452,7 +494,10 @@ public class TopMetricsAggregatorTests extends AggregatorTestCase {
|
||||||
private InternalTopMetrics collect(TopMetricsAggregationBuilder builder, Query query,
|
private InternalTopMetrics collect(TopMetricsAggregationBuilder builder, Query query,
|
||||||
CheckedConsumer<RandomIndexWriter, IOException> buildIndex, MappedFieldType... fields) throws IOException {
|
CheckedConsumer<RandomIndexWriter, IOException> buildIndex, MappedFieldType... fields) throws IOException {
|
||||||
InternalTopMetrics result = (InternalTopMetrics) collect((AggregationBuilder) builder, query, buildIndex, fields);
|
InternalTopMetrics result = (InternalTopMetrics) collect((AggregationBuilder) builder, query, buildIndex, fields);
|
||||||
assertThat(result.getMetricName(), equalTo(builder.getMetricField().getFieldName()));
|
List<String> expectedFieldNames = builder.getMetricFields().stream()
|
||||||
|
.map(MultiValuesSourceFieldConfig::getFieldName)
|
||||||
|
.collect(toList());
|
||||||
|
assertThat(result.getMetricNames(), equalTo(expectedFieldNames));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -470,12 +515,12 @@ public class TopMetricsAggregatorTests extends AggregatorTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private InternalTopMetrics.TopMetric top(long sortValue, double metricValue) {
|
private InternalTopMetrics.TopMetric top(long sortValue, double... metricValues) {
|
||||||
return new InternalTopMetrics.TopMetric(DocValueFormat.RAW, SortValue.from(sortValue), metricValue);
|
return new InternalTopMetrics.TopMetric(DocValueFormat.RAW, SortValue.from(sortValue), metricValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
private InternalTopMetrics.TopMetric top(double sortValue, double metricValue) {
|
private InternalTopMetrics.TopMetric top(double sortValue, double... metricValues) {
|
||||||
return new InternalTopMetrics.TopMetric(DocValueFormat.RAW, SortValue.from(sortValue), metricValue);
|
return new InternalTopMetrics.TopMetric(DocValueFormat.RAW, SortValue.from(sortValue), metricValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
aggs:
|
aggs:
|
||||||
tm:
|
tm:
|
||||||
top_metrics:
|
top_metrics:
|
||||||
metric:
|
metrics:
|
||||||
field: v
|
field: v
|
||||||
sort:
|
sort:
|
||||||
s: desc
|
s: desc
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
aggs:
|
aggs:
|
||||||
tm:
|
tm:
|
||||||
top_metrics:
|
top_metrics:
|
||||||
metric:
|
metrics:
|
||||||
field: v
|
field: v
|
||||||
sort:
|
sort:
|
||||||
s: asc
|
s: asc
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
aggs:
|
aggs:
|
||||||
tm:
|
tm:
|
||||||
top_metrics:
|
top_metrics:
|
||||||
metric:
|
metrics:
|
||||||
field: v
|
field: v
|
||||||
sort:
|
sort:
|
||||||
s:
|
s:
|
||||||
|
@ -92,7 +92,7 @@
|
||||||
aggs:
|
aggs:
|
||||||
tm:
|
tm:
|
||||||
top_metrics:
|
top_metrics:
|
||||||
metric:
|
metrics:
|
||||||
field: v
|
field: v
|
||||||
sort:
|
sort:
|
||||||
s: desc
|
s: desc
|
||||||
|
@ -105,7 +105,7 @@
|
||||||
aggs:
|
aggs:
|
||||||
tm:
|
tm:
|
||||||
top_metrics:
|
top_metrics:
|
||||||
metric:
|
metrics:
|
||||||
field: v
|
field: v
|
||||||
sort:
|
sort:
|
||||||
s: asc
|
s: asc
|
||||||
|
@ -146,7 +146,7 @@
|
||||||
aggs:
|
aggs:
|
||||||
tm:
|
tm:
|
||||||
top_metrics:
|
top_metrics:
|
||||||
metric:
|
metrics:
|
||||||
field: v
|
field: v
|
||||||
sort:
|
sort:
|
||||||
s: desc
|
s: desc
|
||||||
|
@ -159,7 +159,7 @@
|
||||||
aggs:
|
aggs:
|
||||||
tm:
|
tm:
|
||||||
top_metrics:
|
top_metrics:
|
||||||
metric:
|
metrics:
|
||||||
field: v
|
field: v
|
||||||
sort:
|
sort:
|
||||||
s: asc
|
s: asc
|
||||||
|
@ -196,7 +196,7 @@
|
||||||
aggs:
|
aggs:
|
||||||
tm:
|
tm:
|
||||||
top_metrics:
|
top_metrics:
|
||||||
metric:
|
metrics:
|
||||||
field: v
|
field: v
|
||||||
sort: s.keyword
|
sort: s.keyword
|
||||||
- match: { error.root_cause.0.reason: "error building sort for field [s.keyword] of type [keyword] in index [test]: only supported on numeric fields" }
|
- match: { error.root_cause.0.reason: "error building sort for field [s.keyword] of type [keyword] in index [test]: only supported on numeric fields" }
|
||||||
|
@ -236,7 +236,7 @@
|
||||||
aggs:
|
aggs:
|
||||||
tm:
|
tm:
|
||||||
top_metrics:
|
top_metrics:
|
||||||
metric:
|
metrics:
|
||||||
field: v
|
field: v
|
||||||
sort: _score
|
sort: _score
|
||||||
- match: { aggregations.tm.top.0.metrics.v: 3.1414999961853027 }
|
- match: { aggregations.tm.top.0.metrics.v: 3.1414999961853027 }
|
||||||
|
@ -263,7 +263,7 @@
|
||||||
aggs:
|
aggs:
|
||||||
tm:
|
tm:
|
||||||
top_metrics:
|
top_metrics:
|
||||||
metric:
|
metrics:
|
||||||
field: v
|
field: v
|
||||||
sort:
|
sort:
|
||||||
_script:
|
_script:
|
||||||
|
@ -303,7 +303,7 @@
|
||||||
aggs:
|
aggs:
|
||||||
tm:
|
tm:
|
||||||
top_metrics:
|
top_metrics:
|
||||||
metric:
|
metrics:
|
||||||
field: v
|
field: v
|
||||||
sort:
|
sort:
|
||||||
_script:
|
_script:
|
||||||
|
@ -345,7 +345,7 @@
|
||||||
aggs:
|
aggs:
|
||||||
pop:
|
pop:
|
||||||
top_metrics:
|
top_metrics:
|
||||||
metric:
|
metrics:
|
||||||
field: population
|
field: population
|
||||||
sort:
|
sort:
|
||||||
_geo_distance:
|
_geo_distance:
|
||||||
|
@ -361,7 +361,7 @@
|
||||||
pop:
|
pop:
|
||||||
top_metrics:
|
top_metrics:
|
||||||
size: 3
|
size: 3
|
||||||
metric:
|
metrics:
|
||||||
field: population
|
field: population
|
||||||
sort:
|
sort:
|
||||||
_geo_distance:
|
_geo_distance:
|
||||||
|
@ -412,7 +412,7 @@
|
||||||
aggs:
|
aggs:
|
||||||
tm:
|
tm:
|
||||||
top_metrics:
|
top_metrics:
|
||||||
metric:
|
metrics:
|
||||||
field: v
|
field: v
|
||||||
sort:
|
sort:
|
||||||
date: desc
|
date: desc
|
||||||
|
@ -437,7 +437,7 @@
|
||||||
aggs:
|
aggs:
|
||||||
tm:
|
tm:
|
||||||
top_metrics:
|
top_metrics:
|
||||||
metric:
|
metrics:
|
||||||
field: v
|
field: v
|
||||||
sort:
|
sort:
|
||||||
date: desc
|
date: desc
|
||||||
|
@ -483,7 +483,7 @@
|
||||||
tm:
|
tm:
|
||||||
top_metrics:
|
top_metrics:
|
||||||
size: 100
|
size: 100
|
||||||
metric:
|
metrics:
|
||||||
field: v
|
field: v
|
||||||
sort:
|
sort:
|
||||||
s: desc
|
s: desc
|
||||||
|
@ -503,7 +503,7 @@
|
||||||
tm:
|
tm:
|
||||||
top_metrics:
|
top_metrics:
|
||||||
size: 100
|
size: 100
|
||||||
metric:
|
metrics:
|
||||||
field: v
|
field: v
|
||||||
sort:
|
sort:
|
||||||
s: desc
|
s: desc
|
||||||
|
|
Loading…
Reference in New Issue