Expose agg usage in Feature Usage API (#55732) (#56048)

Counts usage of the aggs and exposes them on the _nodes/usage/.

Closes #53746
This commit is contained in:
Igor Motov 2020-04-30 12:53:36 -04:00 committed by GitHub
parent 3dada1e2d3
commit d8f9df771d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 521 additions and 92 deletions

View File

@ -19,11 +19,15 @@
package org.elasticsearch.test.rest;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.ResponseException;
import org.elasticsearch.client.Request;
import org.elasticsearch.common.Strings;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@ -41,14 +45,7 @@ public class NodeRestUsageIT extends ESRestTestCase {
Response beforeResponse = client().performRequest(new Request("GET", path));
Map<String, Object> beforeResponseBodyMap = entityAsMap(beforeResponse);
assertThat(beforeResponseBodyMap, notNullValue());
Map<String, Object> before_nodesMap = (Map<String, Object>) beforeResponseBodyMap.get("_nodes");
assertThat(before_nodesMap, notNullValue());
Integer beforeTotal = (Integer) before_nodesMap.get("total");
Integer beforeSuccessful = (Integer) before_nodesMap.get("successful");
Integer beforeFailed = (Integer) before_nodesMap.get("failed");
assertThat(beforeTotal, greaterThan(0));
assertThat(beforeSuccessful, equalTo(beforeTotal));
assertThat(beforeFailed, equalTo(0));
int beforeSuccessful = assertSuccess(beforeResponseBodyMap);
Map<String, Object> beforeNodesMap = (Map<String, Object>) beforeResponseBodyMap.get("nodes");
assertThat(beforeNodesMap, notNullValue());
@ -98,14 +95,7 @@ public class NodeRestUsageIT extends ESRestTestCase {
Response response = client().performRequest(new Request("GET", "_nodes/usage"));
Map<String, Object> responseBodyMap = entityAsMap(response);
assertThat(responseBodyMap, notNullValue());
Map<String, Object> _nodesMap = (Map<String, Object>) responseBodyMap.get("_nodes");
assertThat(_nodesMap, notNullValue());
Integer total = (Integer) _nodesMap.get("total");
Integer successful = (Integer) _nodesMap.get("successful");
Integer failed = (Integer) _nodesMap.get("failed");
assertThat(total, greaterThan(0));
assertThat(successful, equalTo(total));
assertThat(failed, equalTo(0));
int successful = assertSuccess(responseBodyMap);
Map<String, Object> nodesMap = (Map<String, Object>) responseBodyMap.get("nodes");
assertThat(nodesMap, notNullValue());
@ -143,4 +133,97 @@ public class NodeRestUsageIT extends ESRestTestCase {
+ "\"reason\":\"request [_nodes/usage/_all,rest_actions] contains _all and individual metrics [_all,rest_actions]\""));
}
@SuppressWarnings("unchecked")
public void testAggregationUsage() throws IOException {
// First get the current usage figures
String path = randomFrom("_nodes/usage", "_nodes/usage/aggregations", "_nodes/usage/_all");
Response beforeResponse = client().performRequest(new Request("GET", path));
Map<String, Object> beforeResponseBodyMap = entityAsMap(beforeResponse);
assertThat(beforeResponseBodyMap, notNullValue());
int beforeSuccessful = assertSuccess(beforeResponseBodyMap);
Map<String, Object> beforeNodesMap = (Map<String, Object>) beforeResponseBodyMap.get("nodes");
assertThat(beforeNodesMap, notNullValue());
assertThat(beforeNodesMap.size(), equalTo(beforeSuccessful));
Map<String, Map<String, Long>> beforeCombinedAggsUsage = getTotalUsage(beforeNodesMap);
// Do some requests to get some rest usage stats
Request create = new Request("PUT", "/test");
create.setJsonEntity("{\"mappings\": {\"properties\": { \"str\": {\"type\": \"keyword\"}, " +
"\"foo\": {\"type\": \"keyword\"}, \"num\": {\"type\": \"long\"}, \"start\": {\"type\": \"date\"} } }}");
client().performRequest(create);
Request searchRequest = new Request("GET", "/test/_search");
SearchSourceBuilder searchSource = new SearchSourceBuilder()
.aggregation(AggregationBuilders.terms("str_terms").field("str.keyword"))
.aggregation(AggregationBuilders.terms("num_terms").field("num"))
.aggregation(AggregationBuilders.avg("num_avg").field("num"));
searchRequest.setJsonEntity(Strings.toString(searchSource));
searchRequest.setJsonEntity(Strings.toString(searchSource));
client().performRequest(searchRequest);
searchRequest = new Request("GET", "/test/_search");
searchSource = new SearchSourceBuilder()
.aggregation(AggregationBuilders.terms("start").field("start"))
.aggregation(AggregationBuilders.avg("num1").field("num"))
.aggregation(AggregationBuilders.avg("num2").field("num"))
.aggregation(AggregationBuilders.terms("foo").field("foo.keyword"));
String r = Strings.toString(searchSource);
searchRequest.setJsonEntity(Strings.toString(searchSource));
client().performRequest(searchRequest);
Response response = client().performRequest(new Request("GET", "_nodes/usage"));
Map<String, Object> responseBodyMap = entityAsMap(response);
assertThat(responseBodyMap, notNullValue());
int successful = assertSuccess(responseBodyMap);
Map<String, Object> nodesMap = (Map<String, Object>) responseBodyMap.get("nodes");
assertThat(nodesMap, notNullValue());
assertThat(nodesMap.size(), equalTo(successful));
Map<String, Map<String, Long>> afterCombinedAggsUsage = getTotalUsage(nodesMap);
assertDiff(beforeCombinedAggsUsage, afterCombinedAggsUsage, "terms", "numeric", 1L);
assertDiff(beforeCombinedAggsUsage, afterCombinedAggsUsage, "terms", "date", 1L);
assertDiff(beforeCombinedAggsUsage, afterCombinedAggsUsage, "terms", "bytes", 2L);
assertDiff(beforeCombinedAggsUsage, afterCombinedAggsUsage, "avg", "numeric", 3L);
}
private void assertDiff(Map<String, Map<String, Long>> before, Map<String, Map<String, Long>> after, String agg, String vst,
long diff) {
Long valBefore = before.getOrDefault(agg, Collections.emptyMap()).getOrDefault(vst, 0L);
Long valAfter = after.getOrDefault(agg, Collections.emptyMap()).getOrDefault(vst, 0L);
assertThat(agg + "." + vst, valAfter - valBefore, equalTo(diff) );
}
private Map<String, Map<String, Long>> getTotalUsage(Map<String, Object> nodeUsage) {
Map<String, Map<String, Long>> combined = new HashMap<>();
for (Map.Entry<String, Object> nodeEntry : nodeUsage.entrySet()) {
@SuppressWarnings("unchecked")
Map<String, Object> beforeAggsUsage = (Map<String, Object>) ((Map<String, Object>) nodeEntry.getValue()).get("aggregations");
assertThat(beforeAggsUsage, notNullValue());
for (Map.Entry<String, Object> aggEntry : beforeAggsUsage.entrySet()) {
@SuppressWarnings("unchecked") Map<String, Object> aggMap = (Map<String, Object>) aggEntry.getValue();
Map<String, Long> combinedAggMap = combined.computeIfAbsent(aggEntry.getKey(), k -> new HashMap<>());
for (Map.Entry<String, Object> valSourceEntry : aggMap.entrySet()) {
combinedAggMap.put(valSourceEntry.getKey(),
combinedAggMap.getOrDefault(valSourceEntry.getKey(), 0L) + ((Number) valSourceEntry.getValue()).longValue());
}
}
}
return combined;
}
private int assertSuccess(Map<String, Object> responseBodyMap) {
@SuppressWarnings("unchecked") Map<String, Object> nodesResultMap = (Map<String, Object>) responseBodyMap.get("_nodes");
assertThat(nodesResultMap, notNullValue());
Integer total = (Integer) nodesResultMap.get("total");
Integer successful = (Integer) nodesResultMap.get("successful");
Integer failed = (Integer) nodesResultMap.get("failed");
assertThat(total, greaterThan(0));
assertThat(successful, equalTo(total));
assertThat(failed, equalTo(0));
return successful;
}
}

View File

@ -79,11 +79,14 @@ The API returns the following response:
"timestamp": 1492553961812, <1>
"since": 1492553906606, <2>
"rest_actions": {
"org.elasticsearch.rest.action.admin.cluster.RestNodesUsageAction": 1,
"org.elasticsearch.rest.action.admin.indices.RestCreateIndexAction": 1,
"org.elasticsearch.rest.action.document.RestGetAction": 1,
"org.elasticsearch.rest.action.search.RestSearchAction": 19, <3>
"org.elasticsearch.rest.action.admin.cluster.RestNodesInfoAction": 36
"nodes_usage_action": 1,
"create_index_action": 1,
"document_get_action": 1,
"search_action": 19, <3>
"nodes_info_action": 36
},
"aggregations": {
...
}
}
}
@ -94,7 +97,9 @@ The API returns the following response:
// TESTRESPONSE[s/1492553961812/$body.$_path/]
// TESTRESPONSE[s/1492553906606/$body.$_path/]
// TESTRESPONSE[s/"rest_actions": [^}]+}/"rest_actions": $body.$_path/]
// TESTRESPONSE[s/"aggregations": [^}]+}/"aggregations": $body.$_path/]
<1> Timestamp for when this nodes usage request was performed.
<2> Timestamp for when the usage information recording was started. This is
equivalent to the time that the node was started.
<3> Search action has been called 19 times for this node.

View File

@ -30,10 +30,13 @@ import org.apache.lucene.store.Directory;
import org.apache.lucene.util.NumericUtils;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.NumberFieldMapper;
import org.elasticsearch.plugins.SearchPlugin;
import org.elasticsearch.search.aggregations.AggregatorTestCase;
import org.elasticsearch.search.aggregations.matrix.MatrixAggregationPlugin;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class MatrixStatsAggregatorTests extends AggregatorTestCase {
@ -136,4 +139,8 @@ public class MatrixStatsAggregatorTests extends AggregatorTestCase {
}
}
@Override
protected List<SearchPlugin> getSearchPlugins() {
return Collections.singletonList(new MatrixAggregationPlugin());
}
}

View File

@ -36,6 +36,8 @@ import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException;
import java.util.Map;
import static org.elasticsearch.search.aggregations.support.AggregationUsageService.OTHER_SUBTYPE;
public class ChildrenAggregatorFactory extends ValuesSourceAggregatorFactory {
private final Query parentFilter;
@ -84,4 +86,10 @@ public class ChildrenAggregatorFactory extends ValuesSourceAggregatorFactory {
return asMultiBucketAggregator(this, searchContext, parent);
}
}
@Override
public String getStatsSubtype() {
// Child Aggregation is registered in non-standard way, so it might return child's values type
return OTHER_SUBTYPE;
}
}

View File

@ -36,6 +36,8 @@ import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException;
import java.util.Map;
import static org.elasticsearch.search.aggregations.support.AggregationUsageService.OTHER_SUBTYPE;
public class ParentAggregatorFactory extends ValuesSourceAggregatorFactory {
private final Query parentFilter;
@ -85,4 +87,10 @@ public class ParentAggregatorFactory extends ValuesSourceAggregatorFactory {
return asMultiBucketAggregator(this, searchContext, children);
}
}
@Override
public String getStatsSubtype() {
// Parent Aggregation is registered in non-standard way
return OTHER_SUBTYPE;
}
}

View File

@ -46,8 +46,10 @@ import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.mapper.NumberFieldMapper;
import org.elasticsearch.index.mapper.Uid;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.join.ParentJoinPlugin;
import org.elasticsearch.join.mapper.MetaJoinFieldMapper;
import org.elasticsearch.join.mapper.ParentJoinFieldMapper;
import org.elasticsearch.plugins.SearchPlugin;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregatorTestCase;
@ -328,4 +330,9 @@ public class ChildrenToParentAggregatorTests extends AggregatorTestCase {
LongTerms result = search(indexSearcher, query, aggregationBuilder, fieldType, subFieldType);
verify.accept(result);
}
@Override
protected List<SearchPlugin> getSearchPlugins() {
return Collections.singletonList(new ParentJoinPlugin());
}
}

View File

@ -46,8 +46,10 @@ import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.mapper.NumberFieldMapper;
import org.elasticsearch.index.mapper.Uid;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.join.ParentJoinPlugin;
import org.elasticsearch.join.mapper.MetaJoinFieldMapper;
import org.elasticsearch.join.mapper.ParentJoinFieldMapper;
import org.elasticsearch.plugins.SearchPlugin;
import org.elasticsearch.search.aggregations.AggregatorTestCase;
import org.elasticsearch.search.aggregations.metrics.InternalMin;
import org.elasticsearch.search.aggregations.metrics.MinAggregationBuilder;
@ -187,4 +189,9 @@ public class ParentToChildrenAggregatorTests extends AggregatorTestCase {
InternalChildren result = search(indexSearcher, query, aggregationBuilder, fieldType);
verify.accept(result);
}
@Override
protected List<SearchPlugin> getSearchPlugins() {
return Collections.singletonList(new ParentJoinPlugin());
}
}

View File

@ -67,3 +67,9 @@
- match: { hits.total: 1 }
- match: { hits.hits.0._id: q3 }
---
"Verify nodes usage works":
- do:
nodes.usage: {}
- is_true: nodes
- match: { _nodes.failed: 0 }

View File

@ -19,6 +19,7 @@
package org.elasticsearch.action.admin.cluster.node.usage;
import org.elasticsearch.Version;
import org.elasticsearch.action.support.nodes.BaseNodeResponse;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.io.stream.StreamInput;
@ -31,15 +32,22 @@ import java.util.Map;
public class NodeUsage extends BaseNodeResponse implements ToXContentFragment {
private long timestamp;
private long sinceTime;
private Map<String, Long> restUsage;
private final long timestamp;
private final long sinceTime;
private final Map<String, Long> restUsage;
private final Map<String, Object> aggregationUsage;
@SuppressWarnings("unchecked")
public NodeUsage(StreamInput in) throws IOException {
super(in);
timestamp = in.readLong();
sinceTime = in.readLong();
restUsage = (Map<String, Long>) in.readGenericValue();
if (in.getVersion().onOrAfter(Version.V_7_8_0)) {
aggregationUsage = (Map<String, Object>) in.readGenericValue();
} else {
aggregationUsage = null;
}
}
/**
@ -54,11 +62,13 @@ public class NodeUsage extends BaseNodeResponse implements ToXContentFragment {
* a map containing the counts of the number of times each REST
* endpoint has been called
*/
public NodeUsage(DiscoveryNode node, long timestamp, long sinceTime, Map<String, Long> restUsage) {
public NodeUsage(DiscoveryNode node, long timestamp, long sinceTime, Map<String, Long> restUsage,
Map<String, Object> aggregationUsage) {
super(node);
this.timestamp = timestamp;
this.sinceTime = sinceTime;
this.restUsage = restUsage;
this.aggregationUsage = aggregationUsage;
}
/**
@ -83,6 +93,14 @@ public class NodeUsage extends BaseNodeResponse implements ToXContentFragment {
return restUsage;
}
/**
* @return a map containing the counts of the number of times each REST
* endpoint has been called
*/
public Map<String, Object> getAggregationUsage() {
return aggregationUsage;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.field("since", sinceTime);
@ -90,6 +108,10 @@ public class NodeUsage extends BaseNodeResponse implements ToXContentFragment {
builder.field("rest_actions");
builder.map(restUsage);
}
if (aggregationUsage != null) {
builder.field("aggregations");
builder.map(aggregationUsage);
}
return builder;
}
@ -99,6 +121,9 @@ public class NodeUsage extends BaseNodeResponse implements ToXContentFragment {
out.writeLong(timestamp);
out.writeLong(sinceTime);
out.writeGenericValue(restUsage);
if (out.getVersion().onOrAfter(Version.V_7_8_0)) {
out.writeGenericValue(aggregationUsage);
}
}
}

View File

@ -19,6 +19,7 @@
package org.elasticsearch.action.admin.cluster.node.usage;
import org.elasticsearch.Version;
import org.elasticsearch.action.support.nodes.BaseNodesRequest;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
@ -28,10 +29,14 @@ import java.io.IOException;
public class NodesUsageRequest extends BaseNodesRequest<NodesUsageRequest> {
private boolean restActions;
private boolean aggregations;
public NodesUsageRequest(StreamInput in) throws IOException {
super(in);
this.restActions = in.readBoolean();
if (in.getVersion().onOrAfter(Version.V_7_8_0)) {
this.aggregations = in.readBoolean();
}
}
/**
@ -47,6 +52,7 @@ public class NodesUsageRequest extends BaseNodesRequest<NodesUsageRequest> {
*/
public NodesUsageRequest all() {
this.restActions = true;
this.aggregations = true;
return this;
}
@ -73,9 +79,28 @@ public class NodesUsageRequest extends BaseNodesRequest<NodesUsageRequest> {
return this;
}
/**
* Should the node rest actions usage statistics be returned.
*/
public boolean aggregations() {
return this.aggregations;
}
/**
* Should the node rest actions usage statistics be returned.
*/
public NodesUsageRequest aggregations(boolean aggregations) {
this.aggregations = aggregations;
return this;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeBoolean(restActions);
if (out.getVersion().onOrAfter(Version.V_7_8_0)) {
out.writeBoolean(aggregations);
}
}
}

View File

@ -27,24 +27,31 @@ import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.search.aggregations.support.AggregationUsageService;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.usage.UsageService;
import java.io.IOException;
import java.util.List;
import java.util.Map;
public class TransportNodesUsageAction
extends TransportNodesAction<NodesUsageRequest, NodesUsageResponse, TransportNodesUsageAction.NodeUsageRequest, NodeUsage> {
private UsageService usageService;
private final UsageService restUsageService;
private final AggregationUsageService aggregationUsageService;
private final long sinceTime;
@Inject
public TransportNodesUsageAction(ThreadPool threadPool, ClusterService clusterService, TransportService transportService,
ActionFilters actionFilters, UsageService usageService) {
ActionFilters actionFilters, UsageService restUsageService,
AggregationUsageService aggregationUsageService) {
super(NodesUsageAction.NAME, threadPool, clusterService, transportService, actionFilters,
NodesUsageRequest::new, NodeUsageRequest::new, ThreadPool.Names.MANAGEMENT, NodeUsage.class);
this.usageService = usageService;
this.restUsageService = restUsageService;
this.aggregationUsageService = aggregationUsageService;
this.sinceTime = System.currentTimeMillis();
}
@Override
@ -65,7 +72,9 @@ public class TransportNodesUsageAction
@Override
protected NodeUsage nodeOperation(NodeUsageRequest nodeUsageRequest) {
NodesUsageRequest request = nodeUsageRequest.request;
return usageService.getUsageStats(clusterService.localNode(), request.restActions());
Map<String, Long> restUsage = request.restActions() ? restUsageService.getRestUsageStats() : null;
Map<String, Object> aggsUsage = request.aggregations() ? aggregationUsageService.getUsageStats() : null;
return new NodeUsage(clusterService.localNode(), System.currentTimeMillis(), sinceTime, restUsage, aggsUsage);
}
public static class NodeUsageRequest extends BaseNodeRequest {

View File

@ -58,6 +58,7 @@ import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptContext;
import org.elasticsearch.script.ScriptFactory;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.search.aggregations.support.AggregationUsageService;
import org.elasticsearch.search.aggregations.support.ValuesSourceRegistry;
import org.elasticsearch.search.lookup.SearchLookup;
import org.elasticsearch.transport.RemoteClusterAware;
@ -116,7 +117,7 @@ public class QueryShardContext extends QueryRewriteContext {
private boolean allowUnmappedFields;
private boolean mapUnmappedFieldAsString;
private NestedScope nestedScope;
private ValuesSourceRegistry valuesSourceRegistry;
private final ValuesSourceRegistry valuesSourceRegistry;
public QueryShardContext(int shardId,
IndexSettings indexSettings,
@ -504,4 +505,8 @@ public class QueryShardContext extends QueryRewriteContext {
public BitsetFilterCache getBitsetFilterCache() {
return bitsetFilterCache;
}
public AggregationUsageService getUsageService() {
return valuesSourceRegistry.getUsageService();
}
}

View File

@ -150,6 +150,7 @@ import org.elasticsearch.script.ScriptModule;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.search.SearchModule;
import org.elasticsearch.search.SearchService;
import org.elasticsearch.search.aggregations.support.AggregationUsageService;
import org.elasticsearch.search.fetch.FetchPhase;
import org.elasticsearch.snapshots.RestoreService;
import org.elasticsearch.snapshots.SnapshotShardsService;
@ -581,6 +582,7 @@ public class Node implements Closeable {
b.bind(AnalysisRegistry.class).toInstance(analysisModule.getAnalysisRegistry());
b.bind(IngestService.class).toInstance(ingestService);
b.bind(UsageService.class).toInstance(usageService);
b.bind(AggregationUsageService.class).toInstance(searchModule.getValuesSourceRegistry().getUsageService());
b.bind(NamedWriteableRegistry.class).toInstance(namedWriteableRegistry);
b.bind(MetadataUpgrader.class).toInstance(metadataUpgrader);
b.bind(MetaStateService.class).toInstance(metaStateService);

View File

@ -68,6 +68,7 @@ public class RestNodesUsageAction extends BaseRestHandler {
} else {
nodesUsageRequest.clear();
nodesUsageRequest.restActions(metrics.contains("rest_actions"));
nodesUsageRequest.aggregations(metrics.contains("aggregations"));
}
return channel -> client.admin().cluster().nodesUsage(nodesUsageRequest, new RestBuilderListener<NodesUsageResponse>(channel) {

View File

@ -307,14 +307,13 @@ public class SearchModule {
private final Settings settings;
private final List<NamedWriteableRegistry.Entry> namedWriteables = new ArrayList<>();
private final List<NamedXContentRegistry.Entry> namedXContents = new ArrayList<>();
private ValuesSourceRegistry valuesSourceRegistry;
private final ValuesSourceRegistry valuesSourceRegistry;
/**
* Constructs a new SearchModule object
*
* NOTE: This constructor should not be called in production unless an accurate {@link Settings} object is provided.
* When constructed, a static flag is set in Lucene {@link BooleanQuery#setMaxClauseCount} according to the settings.
*
* @param settings Current settings
* @param transportClient Is this being constructed in the TransportClient or not
* @param plugins List of included {@link SearchPlugin} objects.
@ -372,7 +371,8 @@ public class SearchModule {
.addResultReader(InternalAvg::new)
.setAggregatorRegistrar(AvgAggregationBuilder::registerAggregators), builder);
registerAggregation(new AggregationSpec(WeightedAvgAggregationBuilder.NAME, WeightedAvgAggregationBuilder::new,
WeightedAvgAggregationBuilder.PARSER).addResultReader(InternalWeightedAvg::new), builder);
WeightedAvgAggregationBuilder.PARSER).addResultReader(InternalWeightedAvg::new)
.setAggregatorRegistrar(WeightedAvgAggregationBuilder::registerUsage), builder);
registerAggregation(new AggregationSpec(SumAggregationBuilder.NAME, SumAggregationBuilder::new, SumAggregationBuilder.PARSER)
.addResultReader(InternalSum::new)
.setAggregatorRegistrar(SumAggregationBuilder::registerAggregators), builder);
@ -529,6 +529,10 @@ public class SearchModule {
Consumer<ValuesSourceRegistry.Builder> register = spec.getAggregatorRegistrar();
if (register != null) {
register.accept(builder);
} else {
// Register is typically handling usage registration, but for the older aggregations that don't use register, we
// have to register usage explicitly here.
builder.registerUsage(spec.getName().getPreferredName());
}
}

View File

@ -137,6 +137,7 @@ public abstract class AbstractAggregationBuilder<AB extends AbstractAggregationB
@Override
public final AggregatorFactory build(QueryShardContext queryShardContext, AggregatorFactory parent) throws IOException {
AggregatorFactory factory = doBuild(queryShardContext, parent, factoriesBuilder);
queryShardContext.getUsageService().incAggregationUsage(getType(), factory.getStatsSubtype());
return factory;
}

View File

@ -32,6 +32,8 @@ import org.elasticsearch.search.internal.SearchContext.Lifetime;
import java.io.IOException;
import java.util.Map;
import static org.elasticsearch.search.aggregations.support.AggregationUsageService.OTHER_SUBTYPE;
public abstract class AggregatorFactory {
public static final class MultiBucketAggregatorWrapper extends Aggregator {
@ -238,4 +240,13 @@ public abstract class AggregatorFactory {
return new MultiBucketAggregatorWrapper(bigArrays, searchContext, parent, factory, first);
}
/**
* Returns the aggregation subtype for nodes usage stats.
* <p>
* It should match the types registered by calling {@linkplain org.elasticsearch.search.aggregations.support.AggregationUsageService}.
* In other words, it should be ValueSourcesType for the VST aggregations OTHER_SUBTYPE for all other aggregations.
*/
public String getStatsSubtype() {
return OTHER_SUBTYPE;
}
}

View File

@ -42,6 +42,8 @@ import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException;
import java.util.Map;
import static org.elasticsearch.search.aggregations.support.AggregationUsageService.OTHER_SUBTYPE;
public class GeoDistanceRangeAggregatorFactory
extends ValuesSourceAggregatorFactory {
@ -128,4 +130,9 @@ public class GeoDistanceRangeAggregatorFactory
}
@Override
public String getStatsSubtype() {
// GeoDistanceRangeAggregatorFactory doesn't register itself with ValuesSourceRegistry
return OTHER_SUBTYPE;
}
}

View File

@ -36,6 +36,8 @@ import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException;
import java.util.Map;
import static org.elasticsearch.search.aggregations.support.AggregationUsageService.OTHER_SUBTYPE;
public class DiversifiedAggregatorFactory extends ValuesSourceAggregatorFactory {
private final int shardSize;
@ -98,4 +100,9 @@ public class DiversifiedAggregatorFactory extends ValuesSourceAggregatorFactory
};
}
@Override
public String getStatsSubtype() {
// DiversifiedAggregatorFactory doesn't register itself with ValuesSourceRegistry
return OTHER_SUBTYPE;
}
}

View File

@ -38,6 +38,7 @@ import org.elasticsearch.search.aggregations.support.MultiValuesSourceFieldConfi
import org.elasticsearch.search.aggregations.support.MultiValuesSourceParseHelper;
import org.elasticsearch.search.aggregations.support.ValueType;
import org.elasticsearch.search.aggregations.support.ValuesSourceConfig;
import org.elasticsearch.search.aggregations.support.ValuesSourceRegistry;
import org.elasticsearch.search.aggregations.support.ValuesSourceType;
import java.io.IOException;
@ -57,6 +58,10 @@ public class WeightedAvgAggregationBuilder extends MultiValuesSourceAggregationB
MultiValuesSourceParseHelper.declareField(WEIGHT_FIELD.getPreferredName(), PARSER, true, false, false);
}
public static void registerUsage(ValuesSourceRegistry.Builder builder) {
builder.registerUsage(NAME, CoreValuesSourceType.NUMERIC);
}
public WeightedAvgAggregationBuilder(String name) {
super(name);
}

View File

@ -32,6 +32,8 @@ import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException;
import java.util.Map;
import static org.elasticsearch.search.aggregations.metrics.WeightedAvgAggregationBuilder.VALUE_FIELD;
class WeightedAvgAggregatorFactory extends MultiValuesSourceAggregatorFactory {
WeightedAvgAggregatorFactory(String name, Map<String, ValuesSourceConfig> configs,
@ -62,4 +64,9 @@ class WeightedAvgAggregatorFactory extends MultiValuesSourceAggregatorFactory {
}
return new WeightedAvgAggregator(name, numericMultiVS, format, searchContext, parent, metadata);
}
@Override
public String getStatsSubtype() {
return configs.get(VALUE_FIELD.getPreferredName()).valueSourceType().typeName();
}
}

View File

@ -0,0 +1,88 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.search.aggregations.support;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.LongAdder;
public class AggregationUsageService {
private final Map<String, Map<String, LongAdder>> aggs;
public static final String OTHER_SUBTYPE = "other";
public static class Builder {
private final Map<String, Map<String, LongAdder>> aggs;
public Builder() {
aggs = new HashMap<>();
}
public void registerAggregationUsage(String aggregationName) {
registerAggregationUsage(aggregationName, OTHER_SUBTYPE);
}
public void registerAggregationUsage(String aggregationName, String valuesSourceType) {
Map<String, LongAdder> subAgg = aggs.computeIfAbsent(aggregationName, k -> new HashMap<>());
if (subAgg.put(valuesSourceType, new LongAdder()) != null) {
throw new IllegalArgumentException("stats for aggregation [" + aggregationName + "][" + valuesSourceType +
"] already registered");
}
}
public AggregationUsageService build() {
return new AggregationUsageService(this);
}
}
private AggregationUsageService(Builder builder) {
this.aggs = builder.aggs;
}
public void incAggregationUsage(String aggregationName, String valuesSourceType) {
Map<String, LongAdder> valuesSourceMap = aggs.get(aggregationName);
// Not all aggs register their usage at the moment we also don't register them in test context
if (valuesSourceMap != null) {
LongAdder adder = valuesSourceMap.get(valuesSourceType);
if (adder != null) {
adder.increment();
}
assert adder != null : "Unknown subtype [" + aggregationName + "][" + valuesSourceType + "]";
}
assert valuesSourceMap != null : "Unknown aggregation [" + aggregationName + "][" + valuesSourceType + "]";
}
public Map<String, Object> getUsageStats() {
Map<String, Object> aggsUsageMap = new HashMap<>();
aggs.forEach((name, agg) -> {
Map<String, Long> aggUsageMap = new HashMap<>();
agg.forEach((k, v) -> {
long val = v.longValue();
if (val > 0) {
aggUsageMap.put(k, val);
}
});
if (aggUsageMap.isEmpty() == false) {
aggsUsageMap.put(name, aggUsageMap);
}
});
return aggsUsageMap;
}
}

View File

@ -288,6 +288,11 @@ public enum CoreValuesSourceType implements ValuesSourceType {
return name().toLowerCase(Locale.ROOT);
}
@Override
public String typeName() {
return value();
}
/** List containing all members of the enumeration. */
public static List<ValuesSourceType> ALL_CORE = Arrays.asList(CoreValuesSourceType.values());
}

View File

@ -59,4 +59,8 @@ public abstract class ValuesSourceAggregatorFactory extends AggregatorFactory {
boolean collectsFromSingleBucket,
Map<String, Object> metadata) throws IOException;
@Override
public String getStatsSubtype() {
return config.valueSourceType().typeName();
}
}

View File

@ -41,7 +41,14 @@ import java.util.Map;
*
*/
public class ValuesSourceRegistry {
public static class Builder {
private final AggregationUsageService.Builder usageServiceBuilder;
public Builder() {
this.usageServiceBuilder = new AggregationUsageService.Builder();
}
private final Map<String, List<Map.Entry<ValuesSourceType, AggregatorSupplier>>> aggregatorRegistry = new HashMap<>();
/**
@ -59,6 +66,7 @@ public class ValuesSourceRegistry {
aggregatorRegistry.put(aggregationName, new ArrayList<>());
}
aggregatorRegistry.get(aggregationName).add(new AbstractMap.SimpleEntry<>(valuesSourceType, aggregatorSupplier));
registerUsage(aggregationName, valuesSourceType);
}
/**
@ -76,18 +84,29 @@ public class ValuesSourceRegistry {
}
}
public void registerUsage(String aggregationName, ValuesSourceType valuesSourceType) {
usageServiceBuilder.registerAggregationUsage(aggregationName, valuesSourceType.typeName());
}
public void registerUsage(String aggregationName) {
usageServiceBuilder.registerAggregationUsage(aggregationName);
}
public ValuesSourceRegistry build() {
return new ValuesSourceRegistry(aggregatorRegistry);
return new ValuesSourceRegistry(aggregatorRegistry, usageServiceBuilder.build());
}
}
/** Maps Aggregation names to (ValuesSourceType, Supplier) pairs, keyed by ValuesSourceType */
private final AggregationUsageService usageService;
private Map<String, List<Map.Entry<ValuesSourceType, AggregatorSupplier>>> aggregatorRegistry;
public ValuesSourceRegistry(Map<String, List<Map.Entry<ValuesSourceType, AggregatorSupplier>>> aggregatorRegistry) {
public ValuesSourceRegistry(Map<String, List<Map.Entry<ValuesSourceType, AggregatorSupplier>>> aggregatorRegistry,
AggregationUsageService usageService) {
Map<String, List<Map.Entry<ValuesSourceType, AggregatorSupplier>>> tmp = new HashMap<>();
aggregatorRegistry.forEach((key, value) -> tmp.put(key, Collections.unmodifiableList(value)));
this.aggregatorRegistry = Collections.unmodifiableMap(tmp);
this.usageService = usageService;
}
private AggregatorSupplier findMatchingSuppier(ValuesSourceType valuesSourceType,
@ -114,7 +133,7 @@ public class ValuesSourceRegistry {
public ValuesSourceType getValuesSourceType(MappedFieldType fieldType, String aggregationName,
// TODO: the following arguments are only needed for the legacy case
IndexFieldData indexFieldData,
IndexFieldData<?> indexFieldData,
ValueType valueType,
ValuesSourceType defaultValuesSourceType) {
if (aggregationName != null && aggregatorRegistry.containsKey(aggregationName)) {
@ -144,4 +163,8 @@ public class ValuesSourceRegistry {
}
}
}
public AggregationUsageService getUsageService() {
return usageService;
}
}

View File

@ -99,4 +99,10 @@ public interface ValuesSourceType {
default DocValueFormat getFormatter(String format, ZoneId tz) {
return DocValueFormat.RAW;
}
/**
* Returns the name of the Values Source Type for stats purposes
* @return the name of the Values Source Type
*/
String typeName();
}

View File

@ -39,7 +39,6 @@
package org.elasticsearch.usage;
import org.elasticsearch.action.admin.cluster.node.usage.NodeUsage;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.rest.BaseRestHandler;
import java.util.HashMap;
@ -53,18 +52,15 @@ import java.util.Objects;
public class UsageService {
private final Map<String, BaseRestHandler> handlers;
private final long sinceTime;
public UsageService() {
this.handlers = new HashMap<>();
this.sinceTime = System.currentTimeMillis();
}
/**
* Add a REST handler to this service.
*
* @param handler
* the {@link BaseRestHandler} to add to the usage service.
* @param handler the {@link BaseRestHandler} to add to the usage service.
*/
public void addRestHandler(BaseRestHandler handler) {
Objects.requireNonNull(handler);
@ -92,17 +88,11 @@ public class UsageService {
/**
* Get the current usage statistics for this node.
*
* @param localNode
* the {@link DiscoveryNode} for this node
* @param restActions
* whether to include rest action usage in the returned
* statistics
* @return the {@link NodeUsage} representing the usage statistics for this
* node
*/
public NodeUsage getUsageStats(DiscoveryNode localNode, boolean restActions) {
public Map<String, Long> getRestUsageStats() {
Map<String, Long> restUsageMap;
if (restActions) {
restUsageMap = new HashMap<>();
handlers.values().forEach(handler -> {
long usageCount = handler.getUsageCount();
@ -110,10 +100,7 @@ public class UsageService {
restUsageMap.put(handler.getName(), usageCount);
}
});
} else {
restUsageMap = null;
}
return new NodeUsage(localNode, System.currentTimeMillis(), sinceTime, restUsageMap);
return restUsageMap;
}
}

View File

@ -39,6 +39,8 @@ import org.elasticsearch.script.ScriptModule;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.script.ScriptType;
import org.elasticsearch.search.aggregations.AggregatorTestCase;
import org.elasticsearch.search.aggregations.support.AggregationUsageService;
import org.elasticsearch.search.aggregations.support.ValuesSourceRegistry;
import org.junit.BeforeClass;
import java.io.IOException;
@ -50,6 +52,8 @@ import java.util.Map;
import java.util.function.Function;
import static java.util.Collections.singleton;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class ScriptedMetricAggregatorTests extends AggregatorTestCase {
@ -426,8 +430,12 @@ public class ScriptedMetricAggregatorTests extends AggregatorTestCase {
MockScriptEngine scriptEngine = new MockScriptEngine(MockScriptEngine.NAME, SCRIPTS, Collections.emptyMap());
Map<String, ScriptEngine> engines = Collections.singletonMap(scriptEngine.getType(), scriptEngine);
ScriptService scriptService = new ScriptService(Settings.EMPTY, engines, ScriptModule.CORE_CONTEXTS);
ValuesSourceRegistry valuesSourceRegistry = mock(ValuesSourceRegistry.class);
AggregationUsageService.Builder builder = new AggregationUsageService.Builder();
builder.registerAggregationUsage(ScriptedMetricAggregationBuilder.NAME);
when(valuesSourceRegistry.getUsageService()).thenReturn(builder.build());
return new QueryShardContext(0, indexSettings, BigArrays.NON_RECYCLING_INSTANCE, null,
null, mapperService, null, scriptService, xContentRegistry(), writableRegistry(),
null, null, System::currentTimeMillis, null, null, () -> true, null);
null, null, System::currentTimeMillis, null, null, () -> true, valuesSourceRegistry);
}
}

View File

@ -19,26 +19,21 @@
package org.elasticsearch.usage;
import org.elasticsearch.Version;
import org.elasticsearch.action.admin.cluster.node.usage.NodeUsage;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.search.aggregations.support.AggregationUsageService;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.rest.FakeRestRequest;
import java.net.InetAddress;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import static org.elasticsearch.search.aggregations.support.AggregationUsageService.OTHER_SUBTYPE;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance;
public class UsageServiceTests extends ESTestCase {
@ -95,8 +90,6 @@ public class UsageServiceTests extends ESTestCase {
}
public void testRestUsage() throws Exception {
DiscoveryNode discoveryNode = new DiscoveryNode("foo", new TransportAddress(InetAddress.getByName("localhost"), 12345),
Version.CURRENT);
RestRequest restRequest = new FakeRestRequest();
BaseRestHandler handlerA = new MockRestHandler("a");
BaseRestHandler handlerB = new MockRestHandler("b");
@ -125,9 +118,7 @@ public class UsageServiceTests extends ESTestCase {
handlerF.handleRequest(restRequest, null, null);
handlerC.handleRequest(restRequest, null, null);
handlerD.handleRequest(restRequest, null, null);
NodeUsage usage = usageService.getUsageStats(discoveryNode, true);
assertThat(usage.getNode(), sameInstance(discoveryNode));
Map<String, Long> restUsage = usage.getRestUsage();
Map<String, Long> restUsage = usageService.getRestUsageStats();
assertThat(restUsage, notNullValue());
assertThat(restUsage.size(), equalTo(6));
assertThat(restUsage.get("a"), equalTo(4L));
@ -136,10 +127,48 @@ public class UsageServiceTests extends ESTestCase {
assertThat(restUsage.get("d"), equalTo(2L));
assertThat(restUsage.get("e"), equalTo(1L));
assertThat(restUsage.get("f"), equalTo(1L));
}
usage = usageService.getUsageStats(discoveryNode, false);
assertThat(usage.getNode(), sameInstance(discoveryNode));
assertThat(usage.getRestUsage(), nullValue());
@SuppressWarnings("unchecked")
public void testAggsUsage() throws Exception {
AggregationUsageService.Builder builder = new AggregationUsageService.Builder();
builder.registerAggregationUsage("a", "x");
builder.registerAggregationUsage("a", "y");
builder.registerAggregationUsage("b", "x");
builder.registerAggregationUsage("c");
builder.registerAggregationUsage("b", "y");
builder.registerAggregationUsage("a", "z");
AggregationUsageService usageService = builder.build();
usageService.incAggregationUsage("a", "x");
for (int i = 0; i < 2; i++) {
usageService.incAggregationUsage("a", "y");
}
for (int i = 0; i < 3; i++) {
usageService.incAggregationUsage("a", "z");
}
for (int i = 0; i < 4; i++) {
usageService.incAggregationUsage("b", "x");
}
for (int i = 0; i < 5; i++) {
usageService.incAggregationUsage("b", "y");
}
for (int i = 0; i < 6; i++) {
usageService.incAggregationUsage("c", OTHER_SUBTYPE);
}
Map<String, Object> aggsUsage = usageService.getUsageStats();
assertThat(aggsUsage, notNullValue());
assertThat(aggsUsage.size(), equalTo(3));
assertThat(((Map<String, Object>) aggsUsage.get("a")).get("x"), equalTo(1L));
assertThat(((Map<String, Object>) aggsUsage.get("a")).get("y"), equalTo(2L));
assertThat(((Map<String, Object>) aggsUsage.get("a")).get("z"), equalTo(3L));
assertThat(((Map<String, Object>) aggsUsage.get("b")).get("x"), equalTo(4L));
assertThat(((Map<String, Object>) aggsUsage.get("b")).get("y"), equalTo(5L));
assertThat(((Map<String, Object>) aggsUsage.get("c")).get(OTHER_SUBTYPE), equalTo(6L));
}
private class MockRestHandler extends BaseRestHandler {

View File

@ -109,6 +109,7 @@ public class AnalyticsPlugin extends Plugin implements SearchPlugin, ActionPlugi
TTestAggregationBuilder::new,
usage.track(AnalyticsStatsAction.Item.T_TEST, checkLicense(TTestAggregationBuilder.PARSER)))
.addResultReader(InternalTTest::new)
.setAggregatorRegistrar(TTestAggregationBuilder::registerUsage)
);
}

View File

@ -56,4 +56,9 @@ public enum AnalyticsValuesSourceType implements ValuesSourceType {
public String value() {
return name().toLowerCase(Locale.ROOT);
}
@Override
public String typeName() {
return value();
}
}

View File

@ -26,6 +26,7 @@ import org.elasticsearch.search.aggregations.support.MultiValuesSourceFieldConfi
import org.elasticsearch.search.aggregations.support.MultiValuesSourceParseHelper;
import org.elasticsearch.search.aggregations.support.ValueType;
import org.elasticsearch.search.aggregations.support.ValuesSourceConfig;
import org.elasticsearch.search.aggregations.support.ValuesSourceRegistry;
import org.elasticsearch.search.aggregations.support.ValuesSourceType;
import java.io.IOException;
@ -54,6 +55,10 @@ public class TTestAggregationBuilder extends MultiValuesSourceAggregationBuilder
private int tails = 2;
public static void registerUsage(ValuesSourceRegistry.Builder builder) {
builder.registerUsage(NAME, CoreValuesSourceType.NUMERIC);
}
public TTestAggregationBuilder(String name) {
super(name);
}

View File

@ -26,6 +26,8 @@ import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException;
import java.util.Map;
import static org.elasticsearch.xpack.analytics.ttest.TTestAggregationBuilder.A_FIELD;
class TTestAggregatorFactory extends MultiValuesSourceAggregatorFactory {
private final TTestType testType;
@ -118,4 +120,9 @@ class TTestAggregatorFactory extends MultiValuesSourceAggregatorFactory {
}
return null;
}
@Override
public String getStatsSubtype() {
return configs.get(A_FIELD.getPreferredName()).valueSourceType().typeName();
}
}

View File

@ -39,6 +39,7 @@ import org.elasticsearch.index.mapper.NumberFieldMapper;
import org.elasticsearch.index.mapper.NumberFieldMapper.NumberType;
import org.elasticsearch.index.mapper.TextFieldMapper;
import org.elasticsearch.indices.breaker.CircuitBreakerService;
import org.elasticsearch.plugins.SearchPlugin;
import org.elasticsearch.script.MockScriptEngine;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptEngine;
@ -64,9 +65,11 @@ import org.elasticsearch.search.sort.ScriptSortBuilder.ScriptSortType;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.sort.SortValue;
import org.elasticsearch.xpack.analytics.AnalyticsPlugin;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@ -584,4 +587,9 @@ public class TopMetricsAggregatorTests extends AggregatorTestCase {
Map<String, ScriptEngine> engines = singletonMap(scriptEngine.getType(), scriptEngine);
return new ScriptService(Settings.EMPTY, engines, ScriptModule.CORE_CONTEXTS);
}
@Override
protected List<SearchPlugin> getSearchPlugins() {
return Collections.singletonList(new AnalyticsPlugin(Settings.EMPTY));
}
}

View File

@ -19,6 +19,7 @@ import org.elasticsearch.index.mapper.KeywordFieldMapper;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.NumberFieldMapper;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.plugins.SearchPlugin;
import org.elasticsearch.script.MockScriptEngine;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptEngine;
@ -35,10 +36,12 @@ import org.elasticsearch.search.aggregations.bucket.histogram.InternalHistogram;
import org.elasticsearch.search.aggregations.support.AggregationInspectionHelper;
import org.elasticsearch.search.aggregations.support.MultiValuesSourceFieldConfig;
import org.elasticsearch.search.lookup.LeafDocLookup;
import org.elasticsearch.xpack.analytics.AnalyticsPlugin;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
@ -651,4 +654,9 @@ public class TTestAggregatorTests extends AggregatorTestCase {
}
testCase(aggregationBuilder, query, buildIndex, verify, fieldType1, fieldType2);
}
@Override
protected List<SearchPlugin> getSearchPlugins() {
return Collections.singletonList(new AnalyticsPlugin(Settings.EMPTY));
}
}

View File

@ -115,6 +115,11 @@ public class GeoShapeValuesSourceType implements Writeable, ValuesSourceType {
};
}
@Override
public String typeName() {
return "geoshape";
}
@Override
public void writeTo(StreamOutput out) throws IOException {