diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/analytics/TopMetricsAggregationBuilder.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/analytics/TopMetricsAggregationBuilder.java index 39f6944c2df..24b2a5ed3a7 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/analytics/TopMetricsAggregationBuilder.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/analytics/TopMetricsAggregationBuilder.java @@ -32,6 +32,8 @@ import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.sort.SortBuilder; import java.io.IOException; +import java.util.Arrays; +import java.util.List; import java.util.Map; /** @@ -49,20 +51,20 @@ public class TopMetricsAggregationBuilder extends AbstractAggregationBuilder sort; private final int size; - private final String metric; + private final List metrics; /** * Build the request. * @param name the name of the metric * @param sort the sort key used to select the top metrics * @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); this.sort = sort; this.size = size; - this.metric = metric; + this.metrics = Arrays.asList(metrics); } @Override @@ -78,7 +80,11 @@ public class TopMetricsAggregationBuilder extends AbstractAggregationBuilder engines = Collections.singletonMap(scriptEngine.getType(), scriptEngine); 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); } } diff --git a/test/framework/src/main/java/org/elasticsearch/search/aggregations/AggregatorTestCase.java b/test/framework/src/main/java/org/elasticsearch/search/aggregations/AggregatorTestCase.java index ff5408a2ab8..73a6f900fc2 100644 --- a/test/framework/src/main/java/org/elasticsearch/search/aggregations/AggregatorTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/search/aggregations/AggregatorTestCase.java @@ -243,7 +243,7 @@ public abstract class AggregatorTestCase extends ESTestCase { when(searchContext.lookup()).thenReturn(searchLookup); QueryShardContext queryShardContext = - queryShardContextMock(contextIndexSearcher, mapperService, indexSettings, circuitBreakerService); + queryShardContextMock(contextIndexSearcher, mapperService, indexSettings, circuitBreakerService, bigArrays); when(searchContext.getQueryShardContext()).thenReturn(queryShardContext); when(queryShardContext.getObjectMapper(anyString())).thenAnswer(invocation -> { String fieldName = (String) invocation.getArguments()[0]; @@ -293,9 +293,10 @@ public abstract class AggregatorTestCase extends ESTestCase { protected QueryShardContext queryShardContextMock(IndexSearcher searcher, MapperService mapperService, 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), mapperService, null, getMockScriptService(), xContentRegistry(), writableRegistry(), null, searcher, System::currentTimeMillis, null, null, () -> true); diff --git a/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/topmetrics/InternalTopMetrics.java b/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/topmetrics/InternalTopMetrics.java index e76b83e519a..610cf896411 100644 --- a/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/topmetrics/InternalTopMetrics.java +++ b/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/topmetrics/InternalTopMetrics.java @@ -20,23 +20,27 @@ import org.elasticsearch.search.sort.SortValue; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; 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 { private final SortOrder sortOrder; private final int size; - private final String metricName; + private final List metricNames; private final List topMetrics; - public InternalTopMetrics(String name, @Nullable SortOrder sortOrder, String metricName, + public InternalTopMetrics(String name, @Nullable SortOrder sortOrder, List metricNames, int size, List topMetrics, List pipelineAggregators, Map metaData) { super(name, pipelineAggregators, metaData); this.sortOrder = sortOrder; - this.metricName = metricName; + this.metricNames = metricNames; /* * 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; } - static InternalTopMetrics buildEmptyAggregation(String name, String metricField, + static InternalTopMetrics buildEmptyAggregation(String name, List metricNames, List pipelineAggregators, Map 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 { super(in); sortOrder = SortOrder.readFromStream(in); - metricName = in.readString(); + metricNames = in.readStringList(); size = in.readVInt(); topMetrics = in.readList(TopMetric::new); } @@ -63,7 +67,7 @@ public class InternalTopMetrics extends InternalNumericMetricsAggregation.MultiV @Override protected void doWriteTo(StreamOutput out) throws IOException { sortOrder.writeTo(out); - out.writeString(metricName); + out.writeStringCollection(metricNames); out.writeVInt(size); out.writeList(topMetrics); } @@ -78,15 +82,19 @@ public class InternalTopMetrics extends InternalNumericMetricsAggregation.MultiV if (path.isEmpty()) { return this; } - if (path.size() == 1 && metricName.contentEquals(path.get(1))) { - 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).metricValue; + if (path.size() != 1) { + throw new IllegalArgumentException("path not supported for [" + getName() + "]: " + path); } - 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 @@ -116,7 +124,7 @@ public class InternalTopMetrics extends InternalNumericMetricsAggregation.MultiV queue.updateTop(); } } - return new InternalTopMetrics(getName(), sortOrder, metricName, size, merged, pipelineAggregators(), getMetaData()); + return new InternalTopMetrics(getName(), sortOrder, metricNames, size, merged, pipelineAggregators(), getMetaData()); } @Override @@ -128,7 +136,7 @@ public class InternalTopMetrics extends InternalNumericMetricsAggregation.MultiV public XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException { builder.startArray("top"); for (TopMetric top : topMetrics) { - top.toXContent(builder, metricName); + top.toXContent(builder, metricNames); } builder.endArray(); return builder; @@ -136,7 +144,7 @@ public class InternalTopMetrics extends InternalNumericMetricsAggregation.MultiV @Override public int hashCode() { - return Objects.hash(super.hashCode(), sortOrder, metricName, size, topMetrics); + return Objects.hash(super.hashCode(), sortOrder, metricNames, size, topMetrics); } @Override @@ -144,21 +152,22 @@ public class InternalTopMetrics extends InternalNumericMetricsAggregation.MultiV if (super.equals(obj) == false) return false; InternalTopMetrics other = (InternalTopMetrics) obj; return sortOrder.equals(other.sortOrder) && - metricName.equals(other.metricName) && + metricNames.equals(other.metricNames) && size == other.size && topMetrics.equals(other.topMetrics); } @Override public double value(String name) { - if (metricName.equals(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).metricValue; + int index = metricNames.indexOf(name); + if (index < 0) { + throw new IllegalArgumentException("unknown metric [" + name + "]"); } - 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() { @@ -169,8 +178,8 @@ public class InternalTopMetrics extends InternalNumericMetricsAggregation.MultiV return size; } - String getMetricName() { - return metricName; + List getMetricNames() { + return metricNames; } List getTopMetrics() { @@ -197,18 +206,25 @@ public class InternalTopMetrics extends InternalNumericMetricsAggregation.MultiV static class TopMetric implements Writeable, Comparable { private final DocValueFormat sortFormat; 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.sortValue = sortValue; - this.metricValue = metricValue; + this.metricValues = metricValues; } TopMetric(StreamInput in) throws IOException { sortFormat = in.readNamedWriteable(DocValueFormat.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() { @@ -219,26 +235,21 @@ public class InternalTopMetrics extends InternalNumericMetricsAggregation.MultiV return sortValue; } - double getMetricValue() { - return metricValue; + double[] getMetricValues() { + return metricValues; } - @Override - 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 { + public XContentBuilder toXContent(XContentBuilder builder, List metricNames) throws IOException { builder.startObject(); { - builder.startArray("sort"); + builder.startArray(SORT_FIELD.getPreferredName()); sortValue.toXContent(builder, sortFormat); 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(); } @@ -258,17 +269,17 @@ public class InternalTopMetrics extends InternalNumericMetricsAggregation.MultiV TopMetric other = (TopMetric) obj; return sortFormat.equals(other.sortFormat) && sortValue.equals(other.sortValue) - && metricValue == other.metricValue; + && Arrays.equals(metricValues, other.metricValues); } @Override public int hashCode() { - return Objects.hash(sortFormat, sortValue, metricValue); + return Objects.hash(sortFormat, sortValue, Arrays.hashCode(metricValues)); } @Override public String toString() { - return "TopMetric[" + sortFormat + "," + sortValue + "," + metricValue + "]"; + return "TopMetric[" + sortFormat + "," + sortValue + "," + Arrays.toString(metricValues) + "]"; } } } diff --git a/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/topmetrics/TopMetricsAggregationBuilder.java b/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/topmetrics/TopMetricsAggregationBuilder.java index 6b7e1cfaf0f..659c5ac50fb 100644 --- a/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/topmetrics/TopMetricsAggregationBuilder.java +++ b/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/topmetrics/TopMetricsAggregationBuilder.java @@ -32,7 +32,7 @@ import static org.elasticsearch.search.builder.SearchSourceBuilder.SORT_FIELD; public class TopMetricsAggregationBuilder extends AbstractAggregationBuilder { 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. @@ -47,34 +47,35 @@ public class TopMetricsAggregationBuilder extends AbstractAggregationBuilder metricFields = (List) args[2]; + return new TopMetricsAggregationBuilder(name, sorts, size, metricFields); }); static { PARSER.declareField(constructorArg(), (p, n) -> SortBuilder.fromXContent(p), SORT_FIELD, ObjectParser.ValueType.OBJECT_ARRAY_OR_STRING); PARSER.declareInt(optionalConstructorArg(), SIZE_FIELD); ContextParser 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> sortBuilders; - // TODO MultiValuesSourceFieldConfig has more things than we support and less things than we want to support private final int size; - private final MultiValuesSourceFieldConfig metricField; + private final List metricFields; + // TODO replace with ValuesSourceConfig once the value source refactor has landed /** * Build a {@code top_metrics} aggregation request. */ public TopMetricsAggregationBuilder(String name, List> sortBuilders, int size, - MultiValuesSourceFieldConfig metricField) { + List metricFields) { super(name); if (sortBuilders.size() != 1) { throw new IllegalArgumentException("[sort] must contain exactly one sort"); } this.sortBuilders = sortBuilders; this.size = size; - this.metricField = metricField; + this.metricFields = metricFields; } /** @@ -85,7 +86,7 @@ public class TopMetricsAggregationBuilder extends AbstractAggregationBuilder> sortBuilders = (List>) (List) in.readNamedWriteableList(SortBuilder.class); this.sortBuilders = sortBuilders; this.size = in.readVInt(); - this.metricField = new MultiValuesSourceFieldConfig(in); + this.metricFields = in.readList(MultiValuesSourceFieldConfig::new); } @Override protected void doWriteTo(StreamOutput out) throws IOException { out.writeNamedWriteableList(sortBuilders); out.writeVInt(size); - metricField.writeTo(out); + out.writeList(metricFields); } @Override @@ -116,7 +117,7 @@ public class TopMetricsAggregationBuilder extends AbstractAggregationBuilder getMetricFields() { + return metricFields; } } diff --git a/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/topmetrics/TopMetricsAggregator.java b/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/topmetrics/TopMetricsAggregator.java index 5491b4b0768..300f5830508 100644 --- a/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/topmetrics/TopMetricsAggregator.java +++ b/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/topmetrics/TopMetricsAggregator.java @@ -14,6 +14,7 @@ import org.elasticsearch.common.lease.Releasables; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.DoubleArray; import org.elasticsearch.index.fielddata.NumericDoubleValues; +import org.elasticsearch.index.query.QueryShardContext; import org.elasticsearch.search.DocValueFormat; import org.elasticsearch.search.MultiValueMode; import org.elasticsearch.search.aggregations.Aggregator; @@ -46,25 +47,23 @@ import java.util.Map; */ class TopMetricsAggregator extends NumericMetricsAggregator.MultiValue { private final int size; - private final String metricName; private final BucketedSort sort; - private final Values values; - private final ValuesSource.Numeric metricValueSource; + private final Metrics metrics; TopMetricsAggregator(String name, SearchContext context, Aggregator parent, List pipelineAggregators, - Map metaData, int size, String metricName, - SortBuilder sort, ValuesSource.Numeric metricValueSource) throws IOException { + Map metaData, int size, + SortBuilder sort, List metricNames, List metricValuesSources) throws IOException { super(name, context, parent, pipelineAggregators, metaData); this.size = size; - this.metricName = metricName; - this.metricValueSource = metricValueSource; - if (metricValueSource != null) { - values = new Values(size, context.bigArrays(), metricValueSource); - this.sort = sort.buildBucketedSort(context.getQueryShardContext(), size, values); - } else { - values = null; - this.sort = null; - } + assert metricNames.size() == metricValuesSources.size(); + metrics = new Metrics(size, context.getQueryShardContext(), metricNames, metricValuesSources); + /* + * If we're only collecting a single value then only provided *that* + * value to the sort so that swaps and loads are just a little faster + * in that *very* common case. + */ + BucketedSort.ExtraData values = metricValuesSources.size() == 1 ? metrics.values[0] : metrics; + this.sort = sort.buildBucketedSort(context.getQueryShardContext(), size, values); } @Override @@ -72,7 +71,7 @@ class TopMetricsAggregator extends NumericMetricsAggregator.MultiValue { if (size != 1) { 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 @@ -84,12 +83,12 @@ class TopMetricsAggregator extends NumericMetricsAggregator.MultiValue { * be called after we've collected a bucket, so it won't just fetch * garbage. */ - return values.values.get(owningBucketOrd); + return metrics.metric(name, owningBucketOrd); } @Override 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; } @@ -97,9 +96,6 @@ class TopMetricsAggregator extends NumericMetricsAggregator.MultiValue { public LeafBucketCollector getLeafCollector(LeafReaderContext ctx, LeafBucketCollector sub) throws IOException { 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); return new LeafBucketCollector() { @@ -117,41 +113,115 @@ class TopMetricsAggregator extends NumericMetricsAggregator.MultiValue { @Override public InternalAggregation buildAggregation(long bucket) throws IOException { - if (metricValueSource == null) { - return buildEmptyAggregation(); - } - List topMetrics = sort.getValues(bucket, values.resultBuilder(sort.getFormat())); + List topMetrics = sort.getValues(bucket, metrics.resultBuilder(sort.getFormat())); 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 public InternalTopMetrics buildEmptyAggregation() { - // The sort format and sort order aren't used in reduction so we pass the simplest thing. - return InternalTopMetrics.buildEmptyAggregation(name, metricName, pipelineAggregators(), - metaData()); + return InternalTopMetrics.buildEmptyAggregation(name, metrics.names, pipelineAggregators(), metaData()); } @Override 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 names; + private final MetricValues[] values; + + Metrics(int size, QueryShardContext ctx, List names, List 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 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 ValuesSource.Numeric metricValueSource; private DoubleArray values; - Values(int size, BigArrays bigArrays, ValuesSource.Numeric metricValueSource) { + CollectMetricValues(int size, BigArrays bigArrays, ValuesSource.Numeric metricValueSource) { this.bigArrays = bigArrays; this.metricValueSource = metricValueSource; values = bigArrays.newDoubleArray(size, false); } - BucketedSort.ResultBuilder resultBuilder(DocValueFormat sortFormat) { - return (index, sortValue) -> - new InternalTopMetrics.TopMetric(sortFormat, sortValue, values.get(index)); + @Override + public boolean needsScores() { + return metricValueSource.needsScores(); + } + + @Override + public double value(long index) { + return values.get(index); } @Override @@ -179,4 +249,27 @@ class TopMetricsAggregator extends NumericMetricsAggregator.MultiValue { 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() { + } + } } diff --git a/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/topmetrics/TopMetricsAggregatorFactory.java b/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/topmetrics/TopMetricsAggregatorFactory.java index ce224bd7610..be63f886ded 100644 --- a/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/topmetrics/TopMetricsAggregatorFactory.java +++ b/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/topmetrics/TopMetricsAggregatorFactory.java @@ -24,6 +24,8 @@ import java.io.IOException; import java.util.List; import java.util.Map; +import static java.util.stream.Collectors.toList; + public class TopMetricsAggregatorFactory extends AggregatorFactory { /** * Index setting describing the maximum number of top metrics that @@ -35,40 +37,34 @@ public class TopMetricsAggregatorFactory extends AggregatorFactory { private final List> sortBuilders; private final int size; - private final MultiValuesSourceFieldConfig metricField; + private final List metricFields; public TopMetricsAggregatorFactory(String name, QueryShardContext queryShardContext, AggregatorFactory parent, Builder subFactoriesBuilder, Map metaData, List> sortBuilders, - int size, MultiValuesSourceFieldConfig metricField) throws IOException { + int size, List metricFields) throws IOException { super(name, queryShardContext, parent, subFactoriesBuilder, metaData); this.sortBuilders = sortBuilders; this.size = size; - this.metricField = metricField; + this.metricFields = metricFields; } @Override protected TopMetricsAggregator createInternal(SearchContext searchContext, Aggregator parent, boolean collectsFromSingleBucket, List pipelineAggregators, Map metaData) throws IOException { - ValuesSourceConfig 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()); if (size > maxBucketSize) { 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() + "] index level setting."); } - if (metricValueSource == null) { - return createUnmapped(searchContext, parent, pipelineAggregators, metaData); - } - return new TopMetricsAggregator(name, searchContext, parent, pipelineAggregators, metaData, size, metricField.getFieldName(), - sortBuilders.get(0), metricValueSource); + List metricNames = metricFields.stream().map(MultiValuesSourceFieldConfig::getFieldName).collect(toList()); + List metricValuesSources = metricFields.stream().map(config -> { + ValuesSourceConfig resolved = ValuesSourceConfig.resolve( + searchContext.getQueryShardContext(), ValueType.NUMERIC, + 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 pipelineAggregators, Map metaData) throws IOException { - return new TopMetricsAggregator(name, searchContext, parent, pipelineAggregators, metaData, size, metricField.getFieldName(), - null, null); - } - } diff --git a/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/topmetrics/InternalTopMetricsReduceTests.java b/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/topmetrics/InternalTopMetricsReduceTests.java index 82c108caa19..40a9f1275bd 100644 --- a/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/topmetrics/InternalTopMetricsReduceTests.java +++ b/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/topmetrics/InternalTopMetricsReduceTests.java @@ -14,6 +14,7 @@ import org.elasticsearch.test.ESTestCase; import java.util.Arrays; import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.sameInstance; @@ -45,7 +46,7 @@ public class InternalTopMetricsReduceTests extends ESTestCase { InternalTopMetrics winner = first.getSortOrder() == SortOrder.ASC ? min : max; InternalTopMetrics reduced = reduce(metrics); assertThat(reduced.getName(), equalTo("test")); - assertThat(reduced.getMetricName(), equalTo("test")); + assertThat(reduced.getMetricNames(), equalTo(singletonList("test"))); assertThat(reduced.getSortOrder(), equalTo(first.getSortOrder())); assertThat(reduced.getSize(), equalTo(first.getSize())); assertThat(reduced.getTopMetrics(), equalTo(winner.getTopMetrics())); @@ -60,7 +61,7 @@ public class InternalTopMetricsReduceTests extends ESTestCase { }; InternalTopMetrics reduced = reduce(metrics); assertThat(reduced.getName(), equalTo("test")); - assertThat(reduced.getMetricName(), equalTo("test")); + assertThat(reduced.getMetricNames(), equalTo(singletonList("test"))); assertThat(reduced.getSortOrder(), equalTo(first.getSortOrder())); assertThat(reduced.getSize(), equalTo(first.getSize())); assertThat(reduced.getTopMetrics(), equalTo(Arrays.asList( @@ -74,14 +75,14 @@ public class InternalTopMetricsReduceTests extends ESTestCase { // Doubles sort first. InternalTopMetrics winner = doubleMetrics.getSortOrder() == SortOrder.ASC ? doubleMetrics : longMetrics; assertThat(reduced.getName(), equalTo("test")); - assertThat(reduced.getMetricName(), equalTo("test")); + assertThat(reduced.getMetricNames(), equalTo(singletonList("test"))); assertThat(reduced.getSortOrder(), equalTo(doubleMetrics.getSortOrder())); assertThat(reduced.getSize(), equalTo(doubleMetrics.getSize())); assertThat(reduced.getTopMetrics(), equalTo(winner.getTopMetrics())); } 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) { @@ -89,12 +90,12 @@ public class InternalTopMetricsReduceTests extends ESTestCase { } 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) { 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) { diff --git a/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/topmetrics/InternalTopMetricsTests.java b/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/topmetrics/InternalTopMetricsTests.java index 80fd4bca6ba..9fcea3a158f 100644 --- a/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/topmetrics/InternalTopMetricsTests.java +++ b/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/topmetrics/InternalTopMetricsTests.java @@ -27,16 +27,18 @@ import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Predicate; import java.util.stream.IntStream; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; -import static java.util.Collections.singletonMap; import static java.util.stream.Collectors.toList; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.hasSize; public class InternalTopMetricsTests extends InternalAggregationTestCase { @@ -51,7 +53,7 @@ public class InternalTopMetricsTests extends InternalAggregationTestCase pipelineAggregators, Map metaData) { - String metricName = randomAlphaOfLength(5); + int metricCount = between(1, 5); + List metricNames = randomMetricNames(metricCount); int size = between(1, 100); - List topMetrics = randomTopMetrics(between(0, size)); - return new InternalTopMetrics(name, sortOrder, metricName, size, topMetrics, pipelineAggregators, metaData); + List topMetrics = randomTopMetrics(between(0, size), metricCount); + return new InternalTopMetrics(name, sortOrder, metricNames, size, topMetrics, pipelineAggregators, metaData); } @Override protected InternalTopMetrics mutateInstance(InternalTopMetrics instance) throws IOException { String name = instance.getName(); SortOrder sortOrder = instance.getSortOrder(); - String metricName = instance.getMetricName(); + List metricNames = instance.getMetricNames(); int size = instance.getSize(); List topMetrics = instance.getTopMetrics(); switch (randomInt(4)) { @@ -167,19 +194,21 @@ public class InternalTopMetricsTests extends InternalAggregationTestCase(metricNames); + metricNames.set(randomInt(metricNames.size() - 1), randomAlphaOfLength(6)); break; case 3: size = randomValueOtherThan(size, () -> between(1, 100)); break; case 4: int fixedSize = size; - topMetrics = randomValueOtherThan(topMetrics, () -> randomTopMetrics(between(1, fixedSize))); + int fixedMetricsSize = metricNames.size(); + topMetrics = randomValueOtherThan(topMetrics, () -> randomTopMetrics(between(1, fixedSize), fixedMetricsSize)); break; default: 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()); } @@ -199,7 +228,11 @@ public class InternalTopMetricsTests extends InternalAggregationTestCase winners = metrics.size() > first.getSize() ? metrics.subList(0, first.getSize()) : metrics; assertThat(reduced.getName(), equalTo(first.getName())); assertThat(reduced.getSortOrder(), equalTo(first.getSortOrder())); - assertThat(reduced.getMetricName(), equalTo(first.getMetricName())); + assertThat(reduced.getMetricNames(), equalTo(first.getMetricNames())); assertThat(reduced.getTopMetrics(), equalTo(winners)); } - private List randomTopMetrics(int length) { + private List randomTopMetrics(int length, int metricCount) { 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())) .collect(toList()); } + static List randomMetricNames(int metricCount) { + Set 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() { if (randomBoolean()) { return SortValue.from(randomLong()); diff --git a/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/topmetrics/TopMetricsAggregationBuilderTests.java b/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/topmetrics/TopMetricsAggregationBuilderTests.java index 5866a17f0bb..7dd977fdb93 100644 --- a/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/topmetrics/TopMetricsAggregationBuilderTests.java +++ b/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/topmetrics/TopMetricsAggregationBuilderTests.java @@ -27,6 +27,7 @@ import java.util.Arrays; import java.util.List; import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; @@ -68,9 +69,14 @@ public class TopMetricsAggregationBuilderTests extends AbstractSerializingTestCa protected TopMetricsAggregationBuilder createTestInstance() { List> sortBuilders = singletonList( new FieldSortBuilder(randomAlphaOfLength(5)).order(randomFrom(SortOrder.values()))); - MultiValuesSourceFieldConfig.Builder metricField = new MultiValuesSourceFieldConfig.Builder(); - metricField.setFieldName(randomAlphaOfLength(5)).setMissing(1.0); - return new TopMetricsAggregationBuilder(randomAlphaOfLength(5), sortBuilders, between(1, 100), metricField.build()); + List metricFields = InternalTopMetricsTests.randomMetricNames(between(1, 5)).stream() + .map(name -> { + 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 { @@ -98,6 +104,6 @@ public class TopMetricsAggregationBuilderTests extends AbstractSerializingTestCa serverBuilder.getName(), serverBuilder.getSortBuilders().get(0), serverBuilder.getSize(), - serverBuilder.getMetricField().getFieldName()); + serverBuilder.getMetricFields().stream().map(MultiValuesSourceFieldConfig::getFieldName).toArray(String[]::new)); } } diff --git a/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/topmetrics/TopMetricsAggregatorTests.java b/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/topmetrics/TopMetricsAggregatorTests.java index 7fc922f4bbb..af59aee4332 100644 --- a/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/topmetrics/TopMetricsAggregatorTests.java +++ b/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/topmetrics/TopMetricsAggregatorTests.java @@ -68,12 +68,14 @@ import org.elasticsearch.search.sort.SortValue; import java.io.IOException; import java.util.Arrays; +import java.util.List; import java.util.Map; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; +import static java.util.stream.Collectors.toList; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.notANumber; @@ -95,7 +97,7 @@ public class TopMetricsAggregatorTests extends AggregatorTestCase { }, numberFieldType(NumberType.DOUBLE, "s")); 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 { @@ -106,7 +108,8 @@ public class TopMetricsAggregatorTests extends AggregatorTestCase { assertThat(result.getSortOrder(), equalTo(SortOrder.ASC)); assertThat(result.getTopMetrics(), hasSize(1)); 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 { @@ -130,7 +133,7 @@ public class TopMetricsAggregatorTests extends AggregatorTestCase { InternalTopMetrics result = collectFromDoubles(simpleBuilder(new FieldSortBuilder("s").order(SortOrder.ASC))); assertThat(result.getSortOrder(), equalTo(SortOrder.ASC)); 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 { @@ -349,21 +352,50 @@ public class TopMetricsAggregatorTests extends AggregatorTestCase { * breaker. The number of buckets feels fairly arbitrary but * it comes from: * budget = 15k = 20k - 5k for the "default weight" of ever agg - * The 922th bucket causes a resize which requests puts the total - * just over 15k.O + * The 646th bucket causes a resize which requests puts the total + * 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++) { - 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)); assertThat(e.getMessage(), equalTo("test error")); assertThat(e.getByteLimit(), equalTo(max.getBytes())); - assertThat(e.getBytesWanted(), equalTo(16440L)); + assertThat(e.getBytesWanted(), equalTo(5872L)); } } } + public void testManyMetrics() throws IOException { + List> 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() { return simpleBuilder(new FieldSortBuilder("s")); } @@ -374,7 +406,7 @@ public class TopMetricsAggregatorTests extends AggregatorTestCase { private TopMetricsAggregationBuilder simpleBuilder(SortBuilder sort, int 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")}; } + 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() { 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, CheckedConsumer buildIndex, MappedFieldType... fields) throws IOException { InternalTopMetrics result = (InternalTopMetrics) collect((AggregationBuilder) builder, query, buildIndex, fields); - assertThat(result.getMetricName(), equalTo(builder.getMetricField().getFieldName())); + List expectedFieldNames = builder.getMetricFields().stream() + .map(MultiValuesSourceFieldConfig::getFieldName) + .collect(toList()); + assertThat(result.getMetricNames(), equalTo(expectedFieldNames)); return result; } @@ -470,12 +515,12 @@ public class TopMetricsAggregatorTests extends AggregatorTestCase { } } - private InternalTopMetrics.TopMetric top(long sortValue, double metricValue) { - return new InternalTopMetrics.TopMetric(DocValueFormat.RAW, SortValue.from(sortValue), metricValue); + private InternalTopMetrics.TopMetric top(long sortValue, double... metricValues) { + return new InternalTopMetrics.TopMetric(DocValueFormat.RAW, SortValue.from(sortValue), metricValues); } - private InternalTopMetrics.TopMetric top(double sortValue, double metricValue) { - return new InternalTopMetrics.TopMetric(DocValueFormat.RAW, SortValue.from(sortValue), metricValue); + private InternalTopMetrics.TopMetric top(double sortValue, double... metricValues) { + return new InternalTopMetrics.TopMetric(DocValueFormat.RAW, SortValue.from(sortValue), metricValues); } /** diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/analytics/top_metrics.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/analytics/top_metrics.yml index c11d224731b..678640a5b0e 100644 --- a/x-pack/plugin/src/test/resources/rest-api-spec/test/analytics/top_metrics.yml +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/analytics/top_metrics.yml @@ -23,7 +23,7 @@ aggs: tm: top_metrics: - metric: + metrics: field: v sort: s: desc @@ -36,7 +36,7 @@ aggs: tm: top_metrics: - metric: + metrics: field: v sort: s: asc @@ -49,7 +49,7 @@ aggs: tm: top_metrics: - metric: + metrics: field: v sort: s: @@ -92,7 +92,7 @@ aggs: tm: top_metrics: - metric: + metrics: field: v sort: s: desc @@ -105,7 +105,7 @@ aggs: tm: top_metrics: - metric: + metrics: field: v sort: s: asc @@ -146,7 +146,7 @@ aggs: tm: top_metrics: - metric: + metrics: field: v sort: s: desc @@ -159,7 +159,7 @@ aggs: tm: top_metrics: - metric: + metrics: field: v sort: s: asc @@ -196,7 +196,7 @@ aggs: tm: top_metrics: - metric: + metrics: field: v 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" } @@ -236,7 +236,7 @@ aggs: tm: top_metrics: - metric: + metrics: field: v sort: _score - match: { aggregations.tm.top.0.metrics.v: 3.1414999961853027 } @@ -263,7 +263,7 @@ aggs: tm: top_metrics: - metric: + metrics: field: v sort: _script: @@ -303,7 +303,7 @@ aggs: tm: top_metrics: - metric: + metrics: field: v sort: _script: @@ -345,7 +345,7 @@ aggs: pop: top_metrics: - metric: + metrics: field: population sort: _geo_distance: @@ -361,7 +361,7 @@ pop: top_metrics: size: 3 - metric: + metrics: field: population sort: _geo_distance: @@ -412,7 +412,7 @@ aggs: tm: top_metrics: - metric: + metrics: field: v sort: date: desc @@ -437,7 +437,7 @@ aggs: tm: top_metrics: - metric: + metrics: field: v sort: date: desc @@ -483,7 +483,7 @@ tm: top_metrics: size: 100 - metric: + metrics: field: v sort: s: desc @@ -503,7 +503,7 @@ tm: top_metrics: size: 100 - metric: + metrics: field: v sort: s: desc