Skip to content

Commit

Permalink
Preserve metric types in top_metrics (backport of #53288) (#53440)
Browse files Browse the repository at this point in the history
This changes the `top_metrics` aggregation to return metrics in their
original type. Since it only supports numerics, that means that dates,
longs, and doubles will come back as stored, with their appropriate
formatter applied.
  • Loading branch information
nik9000 authored Mar 12, 2020
1 parent 97621e7 commit 9dcd64c
Show file tree
Hide file tree
Showing 11 changed files with 825 additions and 132 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,10 @@
import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentParserUtils;
import org.elasticsearch.search.aggregations.ParsedAggregation;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -88,9 +86,9 @@ public static class TopMetrics implements ToXContent {
private static final ParseField METRICS_FIELD = new ParseField("metrics");

private final List<Object> sort;
private final Map<String, Double> metrics;
private final Map<String, Object> metrics;

private TopMetrics(List<Object> sort, Map<String, Double> metrics) {
private TopMetrics(List<Object> sort, Map<String, Object> metrics) {
this.sort = sort;
this.metrics = metrics;
}
Expand All @@ -105,7 +103,7 @@ public List<Object> getSort() {
/**
* The top metric values returned by the aggregation.
*/
public Map<String, Double> getMetrics() {
public Map<String, Object> getMetrics() {
return metrics;
}

Expand All @@ -114,13 +112,13 @@ public Map<String, Double> getMetrics() {
@SuppressWarnings("unchecked")
List<Object> sort = (List<Object>) args[0];
@SuppressWarnings("unchecked")
Map<String, Double> metrics = (Map<String, Double>) args[1];
Map<String, Object> metrics = (Map<String, Object>) args[1];
return new TopMetrics(sort, metrics);
});
static {
PARSER.declareFieldArray(constructorArg(), (p, c) -> XContentParserUtils.parseFieldsValue(p),
SORT_FIELD, ObjectParser.ValueType.VALUE_ARRAY);
PARSER.declareObject(constructorArg(), (p, c) -> p.map(HashMap::new, XContentParser::doubleValue), METRICS_FIELD);
PARSER.declareObject(constructorArg(), (p, c) -> p.map(), METRICS_FIELD);
}

public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.elasticsearch.action.support.WriteRequest.RefreshPolicy;
import org.elasticsearch.client.ESRestHighLevelClientTestCase;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
Expand Down Expand Up @@ -61,8 +62,8 @@ public void testStringStats() throws IOException {
assertThat(stats.getDistribution(), hasEntry(equalTo("t"), closeTo(.09, .005)));
}

public void testTopMetricsSizeOne() throws IOException {
indexTopMetricsData();
public void testTopMetricsDoubleMetric() throws IOException {
indexTopMetricsDoubleTestData();
SearchRequest search = new SearchRequest("test");
search.source().aggregation(new TopMetricsAggregationBuilder(
"test", new FieldSortBuilder("s").order(SortOrder.DESC), 1, "v"));
Expand All @@ -74,8 +75,34 @@ public void testTopMetricsSizeOne() throws IOException {
assertThat(metric.getMetrics(), equalTo(singletonMap("v", 3.0)));
}

public void testTopMetricsLongMetric() throws IOException {
indexTopMetricsLongTestData();
SearchRequest search = new SearchRequest("test");
search.source().aggregation(new TopMetricsAggregationBuilder(
"test", new FieldSortBuilder("s").order(SortOrder.DESC), 1, "v"));
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(), equalTo(singletonMap("v", 3)));
}

public void testTopMetricsDateMetric() throws IOException {
indexTopMetricsDateTestData();
SearchRequest search = new SearchRequest("test");
search.source().aggregation(new TopMetricsAggregationBuilder(
"test", new FieldSortBuilder("s").order(SortOrder.DESC), 1, "v"));
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(), equalTo(singletonMap("v", "2020-01-02T01:01:00.000Z")));
}

public void testTopMetricsManyMetrics() throws IOException {
indexTopMetricsData();
indexTopMetricsDoubleTestData();
SearchRequest search = new SearchRequest("test");
search.source().aggregation(new TopMetricsAggregationBuilder(
"test", new FieldSortBuilder("s").order(SortOrder.DESC), 1, "v", "m"));
Expand All @@ -89,7 +116,7 @@ public void testTopMetricsManyMetrics() throws IOException {
}

public void testTopMetricsSizeTwo() throws IOException {
indexTopMetricsData();
indexTopMetricsDoubleTestData();
SearchRequest search = new SearchRequest("test");
search.source().aggregation(new TopMetricsAggregationBuilder(
"test", new FieldSortBuilder("s").order(SortOrder.DESC), 2, "v"));
Expand All @@ -104,10 +131,28 @@ public void testTopMetricsSizeTwo() throws IOException {
assertThat(metric.getMetrics(), equalTo(singletonMap("v", 2.0)));
}

private void indexTopMetricsData() throws IOException {
private void indexTopMetricsDoubleTestData() throws IOException {
BulkRequest bulk = new BulkRequest("test").setRefreshPolicy(RefreshPolicy.IMMEDIATE);
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.0, "m", 13.0));
highLevelClient().bulk(bulk, RequestOptions.DEFAULT);
}

private void indexTopMetricsLongTestData() throws IOException {
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", 2, "v", 3));
highLevelClient().bulk(bulk, RequestOptions.DEFAULT);
}

private void indexTopMetricsDateTestData() throws IOException {
CreateIndexRequest create = new CreateIndexRequest("test");
create.mapping("{\"properties\": {\"v\": {\"type\": \"date\"}}}", XContentType.JSON);
highLevelClient().indices().create(create, RequestOptions.DEFAULT);
BulkRequest bulk = new BulkRequest("test").setRefreshPolicy(RefreshPolicy.IMMEDIATE);
bulk.add(new IndexRequest().source(XContentType.JSON, "s", 1, "v", "2020-01-01T01:01:00Z"));
bulk.add(new IndexRequest().source(XContentType.JSON, "s", 2, "v", "2020-01-02T01:01:00Z"));
highLevelClient().bulk(bulk, RequestOptions.DEFAULT);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -70,31 +70,36 @@ the same sort values then this aggregation could return either document's fields

==== `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.
`metrics` selects the fields to of the "top" document to return.

You can return multiple metrics by providing a list:

[source,console,id=search-aggregations-metrics-top-metrics-list-of-metrics]
----
PUT /test
{
"mappings": {
"properties": {
"d": {"type": "date"}
}
}
}
POST /test/_bulk?refresh
{"index": {}}
{"s": 1, "v": 3.1415, "m": 1.9}
{"s": 1, "v": 3.1415, "m": 1, "d": "2020-01-01T00:12:12Z"}
{"index": {}}
{"s": 2, "v": 1.0, "m": 6.7}
{"s": 2, "v": 1.0, "m": 6, "d": "2020-01-02T00:12:12Z"}
{"index": {}}
{"s": 3, "v": 2.71828, "m": -12.2}
{"s": 3, "v": 2.71828, "m": -12, "d": "2019-12-31T00:12:12Z"}
POST /test/_search?filter_path=aggregations
{
"aggs": {
"tm": {
"top_metrics": {
"metrics": [
{"field": "v"},
{"field": "m"}
{"field": "m"},
{"field": "d"}
],
"sort": {"s": "desc"}
}
Expand All @@ -114,7 +119,8 @@ Which returns:
"sort": [3],
"metrics": {
"v": 2.718280076980591,
"m": -12.199999809265137
"m": -12,
"d": "2019-12-31T00:12:12.000Z"
}
} ]
}
Expand All @@ -123,7 +129,6 @@ Which returns:
----
// TESTRESPONSE


==== `size`

`top_metrics` can return the top few document's worth of metrics using the size parameter:
Expand Down Expand Up @@ -246,14 +251,14 @@ Which returns:
"key": "192.168.0.1",
"doc_count": 2,
"tm": {
"top": [ {"sort": ["2020-01-01T02:01:01.000Z"], "metrics": {"v": 2.0 } } ]
"top": [ {"sort": ["2020-01-01T02:01:01.000Z"], "metrics": {"v": 2 } } ]
}
},
{
"key": "192.168.0.2",
"doc_count": 1,
"tm": {
"top": [ {"sort": ["2020-01-01T02:01:01.000Z"], "metrics": {"v": 3.0 } } ]
"top": [ {"sort": ["2020-01-01T02:01:01.000Z"], "metrics": {"v": 3 } } ]
}
}
],
Expand Down Expand Up @@ -303,14 +308,14 @@ Which returns:
"key": "192.168.0.2",
"doc_count": 1,
"tm": {
"top": [ {"sort": ["2020-01-01T02:01:01.000Z"], "metrics": {"v": 3.0 } } ]
"top": [ {"sort": ["2020-01-01T02:01:01.000Z"], "metrics": {"v": 3 } } ]
}
},
{
"key": "192.168.0.1",
"doc_count": 2,
"tm": {
"top": [ {"sort": ["2020-01-01T02:01:01.000Z"], "metrics": {"v": 2.0 } } ]
"top": [ {"sort": ["2020-01-01T02:01:01.000Z"], "metrics": {"v": 2 } } ]
}
}
],
Expand Down
15 changes: 15 additions & 0 deletions server/src/main/java/org/elasticsearch/search/sort/SortValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ public final XContentBuilder toXContent(XContentBuilder builder, DocValueFormat
@Override
public abstract String toString();

/**
* Return this {@linkplain SortValue} as a boxed {@linkplain Number}.
*/
public abstract Number numberValue();

private static class DoubleSortValue extends SortValue {
public static final String NAME = "double";

Expand Down Expand Up @@ -179,6 +184,11 @@ public int hashCode() {
public String toString() {
return Double.toString(key);
}

@Override
public Number numberValue() {
return key;
}
}

private static class LongSortValue extends SortValue {
Expand Down Expand Up @@ -243,5 +253,10 @@ public int hashCode() {
public String toString() {
return Long.toString(key);
}

@Override
public Number numberValue() {
return key;
}
}
}
Loading

0 comments on commit 9dcd64c

Please sign in to comment.