Skip to content

Commit

Permalink
Expose telemetry about search usage (#91528)
Browse files Browse the repository at this point in the history
This is the continuation of #90176 which leverages #90425 to count query types. This PR adds search usage stats to the existing telemetry by counting sections being used as part of a search request, as well as query types. Each distinct query type is counted once per search request.

The counting is performed while parsing, for the following REST search endpoints:

- _search
- _msearch
- _async_search
- _search/template
- _msearch/template
- _fleet/_fleet_search
- _fleet/_fleet_msearch

All other API using search internally, like reindex, ML transform, rank eval, sql etc. are not counted as part of these search usage stats. Such additional functionalities should have its own dedicated telemetry if needed.

The counting of the search sections is not extensive, only the ones that are interesting to collect counts for are tracked.

The following is the new section added to the cluster stats API response, including some sample stats:

```
"search" : {
  "total" : 63,
  "sections" : {
    "knn" : 42,
    "query" : 21, 
    "aggs" : 46
  }, 
  "query" : {
    "match" : 58
  }
}
```

A big part of the change is actually the plumbing to make a common service class that holds the counters available to all the different callers of the parsing methods, especially plugins. Ideally, there would be a separate component that exposes the search parsing functionality rather than static methods, but changing that would require making the additional component available to the REST layer which is not trivial. I reused the existing UsageService which the RestController already holds, and is already used to count access to the different REST endpoints.

Co-authored-by: Mayya Sharipova mayya.sharipova@elastic.co
  • Loading branch information
javanna committed Nov 15, 2022
1 parent a6fc135 commit 238163c
Show file tree
Hide file tree
Showing 43 changed files with 1,424 additions and 104 deletions.
5 changes: 5 additions & 0 deletions docs/changelog/91528.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 91528
summary: Expose telemetry about search usage
area: Search
type: enhancement
issues: []
36 changes: 36 additions & 0 deletions docs/reference/cluster/stats.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,38 @@ Occurrences of the built-in analyzer in selected nodes.
(integer)
Number of indices using the built-in analyzer in selected nodes.
======
=====
`search`::
(object)
Contains usage statistics about search requests submitted to selected nodes
that acted as coordinator during the search execution. Search requests are
tracked when they are successfully parsed, regardless of their results:
requests that yield errors after parsing contribute to the usage stats, as
well as requests that don't access any data.
+
.Properties of `search` objects
[%collapsible%open]
=====
`total`::
(integer)
Total number of incoming search requests. Search requests that don't specify a
request body are not counted.

`queries`::
(object)
Query types used in selected nodes. For each query, name and number of times
it's been used within the `query` or `post_filter` section is reported. Queries
are counted once per search request, meaning that if the same query type is used
multiple times in the same search request, its counter will be incremented by 1
rather than by the number of times it's been used in that individual search request.

`sections`::
(object)
Search sections used in selected nodes. For each section, name and number of times
it's been used is reported.

=====
====

Expand Down Expand Up @@ -1334,6 +1366,9 @@ The API returns the following response:
"reserved": "0b",
"reserved_in_bytes": 0
},
"search": {
...
},
"fielddata": {
"memory_size": "0b",
"memory_size_in_bytes": 0,
Expand Down Expand Up @@ -1563,6 +1598,7 @@ The API returns the following response:
// TESTRESPONSE[s/"packaging_types": \[[^\]]*\]/"packaging_types": $body.$_path/]
// TESTRESPONSE[s/"field_types": \[[^\]]*\]/"field_types": $body.$_path/]
// TESTRESPONSE[s/"runtime_field_types": \[[^\]]*\]/"runtime_field_types": $body.$_path/]
// TESTRESPONSE[s/"search": \{[^\}]*\}/"search": $body.$_path/]
// TESTRESPONSE[s/: true|false/: $body.$_path/]
// TESTRESPONSE[s/: (\-)?[0-9]+/: $body.$_path/]
// TESTRESPONSE[s/: "[^"]*"/: $body.$_path/]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,5 +378,4 @@ public void testCCSCheckCompatibility() throws Exception {
assertThat(ex.getCause().getMessage(), containsString("'search.check_ccs_compatibility' setting is enabled."));
assertEquals("This query isn't serializable to nodes before " + Version.CURRENT, ex.getCause().getCause().getMessage());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

package org.elasticsearch.script.mustache;

import org.elasticsearch.action.admin.cluster.stats.SearchUsageStats;
import org.elasticsearch.client.Request;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.xcontent.XContentType;

import java.io.IOException;
import java.util.Collection;
import java.util.List;

import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;

@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 1)
public class SearchUsageStatsIT extends ESIntegTestCase {

@Override
protected boolean addMockHttpTransport() {
return false; // enable http
}

@Override
protected Collection<Class<? extends Plugin>> nodePlugins() {
return List.of(MustachePlugin.class);
}

public void testSearchUsageStats() throws IOException {
{
SearchUsageStats stats = client().admin().cluster().prepareClusterStats().get().getIndicesStats().getSearchUsageStats();
assertEquals(0, stats.getTotalSearchCount());
assertEquals(0, stats.getQueryUsage().size());
assertEquals(0, stats.getSectionsUsage().size());
}

{
Request request = new Request("GET", "/_search/template");
request.setJsonEntity("""
{
"source": {
"query": {
"{{query_type}}": {
"message" : "{{query_string}}"
}
}
},
"params": {
"query_string": "text query",
"query_type" : "match"
}
}
""");
getRestClient().performRequest(request);
}
{
assertAcked(client().admin().cluster().preparePutStoredScript().setId("testTemplate").setContent(new BytesArray("""
{
"script": {
"lang": "mustache",
"source": {
"query": {
"match": {
"theField": "{{fieldParam}}"
}
}
}
}
}"""), XContentType.JSON));
Request request = new Request("GET", "/_search/template");
request.setJsonEntity("""
{
"id": "testTemplate",
"params": {
"fieldParam": "text query"
}
}
""");
getRestClient().performRequest(request);
}
{
Request request = new Request("POST", "/_msearch/template");
request.setJsonEntity("""
{}
{"id": "testTemplate", "params": {"fieldParam":"text query"}}
{}
{"source": {"query": { "match": {"message" : "{{query_string}}"}}},"params": {"query_string": "text query"}}
""");
getRestClient().performRequest(request);
}

SearchUsageStats stats = client().admin().cluster().prepareClusterStats().get().getIndicesStats().getSearchUsageStats();
assertEquals(4, stats.getTotalSearchCount());
assertEquals(1, stats.getQueryUsage().size());
assertEquals(4, stats.getQueryUsage().get("match").longValue());
assertEquals(1, stats.getSectionsUsage().size());
assertEquals(4, stats.getSectionsUsage().get("query").longValue());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,12 @@ public ActionRequestValidationException validate() {
return validationException;
}

private static ParseField ID_FIELD = new ParseField("id");
private static ParseField SOURCE_FIELD = new ParseField("source", "inline", "template");
private static final ParseField ID_FIELD = new ParseField("id");
private static final ParseField SOURCE_FIELD = new ParseField("source", "inline", "template");

private static ParseField PARAMS_FIELD = new ParseField("params");
private static ParseField EXPLAIN_FIELD = new ParseField("explain");
private static ParseField PROFILE_FIELD = new ParseField("profile");
private static final ParseField PARAMS_FIELD = new ParseField("params");
private static final ParseField EXPLAIN_FIELD = new ParseField("explain");
private static final ParseField PROFILE_FIELD = new ParseField("profile");

private static final ObjectParser<SearchTemplateRequest, Void> PARSER;
static {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.usage.SearchUsageHolder;
import org.elasticsearch.usage.UsageService;
import org.elasticsearch.xcontent.NamedXContentRegistry;

import java.util.ArrayList;
Expand All @@ -31,19 +33,22 @@ public class TransportMultiSearchTemplateAction extends HandledTransportAction<M
private final ScriptService scriptService;
private final NamedXContentRegistry xContentRegistry;
private final NodeClient client;
private final SearchUsageHolder searchUsageHolder;

@Inject
public TransportMultiSearchTemplateAction(
TransportService transportService,
ActionFilters actionFilters,
ScriptService scriptService,
NamedXContentRegistry xContentRegistry,
NodeClient client
NodeClient client,
UsageService usageService
) {
super(MultiSearchTemplateAction.NAME, transportService, actionFilters, MultiSearchTemplateRequest::new);
this.scriptService = scriptService;
this.xContentRegistry = xContentRegistry;
this.client = client;
this.searchUsageHolder = usageService.getSearchUsageHolder();
}

@Override
Expand All @@ -61,7 +66,7 @@ protected void doExecute(Task task, MultiSearchTemplateRequest request, ActionLi
SearchTemplateResponse searchTemplateResponse = new SearchTemplateResponse();
SearchRequest searchRequest;
try {
searchRequest = convert(searchTemplateRequest, searchTemplateResponse, scriptService, xContentRegistry);
searchRequest = convert(searchTemplateRequest, searchTemplateResponse, scriptService, xContentRegistry, searchUsageHolder);
} catch (Exception e) {
items[i] = new MultiSearchTemplateResponse.Item(null, e);
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import org.elasticsearch.search.internal.SearchContext;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.usage.SearchUsageHolder;
import org.elasticsearch.usage.UsageService;
import org.elasticsearch.xcontent.NamedXContentRegistry;
import org.elasticsearch.xcontent.XContentFactory;
import org.elasticsearch.xcontent.XContentParser;
Expand All @@ -41,26 +43,29 @@ public class TransportSearchTemplateAction extends HandledTransportAction<Search
private final ScriptService scriptService;
private final NamedXContentRegistry xContentRegistry;
private final NodeClient client;
private final SearchUsageHolder searchUsageHolder;

@Inject
public TransportSearchTemplateAction(
TransportService transportService,
ActionFilters actionFilters,
ScriptService scriptService,
NamedXContentRegistry xContentRegistry,
NodeClient client
NodeClient client,
UsageService usageService
) {
super(SearchTemplateAction.NAME, transportService, actionFilters, SearchTemplateRequest::new);
this.scriptService = scriptService;
this.xContentRegistry = xContentRegistry;
this.client = client;
this.searchUsageHolder = usageService.getSearchUsageHolder();
}

@Override
protected void doExecute(Task task, SearchTemplateRequest request, ActionListener<SearchTemplateResponse> listener) {
final SearchTemplateResponse response = new SearchTemplateResponse();
try {
SearchRequest searchRequest = convert(request, response, scriptService, xContentRegistry);
SearchRequest searchRequest = convert(request, response, scriptService, xContentRegistry, searchUsageHolder);
if (searchRequest != null) {
client.search(searchRequest, listener.delegateFailure((l, searchResponse) -> {
try {
Expand All @@ -82,7 +87,8 @@ static SearchRequest convert(
SearchTemplateRequest searchTemplateRequest,
SearchTemplateResponse response,
ScriptService scriptService,
NamedXContentRegistry xContentRegistry
NamedXContentRegistry xContentRegistry,
SearchUsageHolder searchUsageHolder
) throws IOException {
Script script = new Script(
searchTemplateRequest.getScriptType(),
Expand All @@ -103,7 +109,7 @@ static SearchRequest convert(
.withDeprecationHandler(LoggingDeprecationHandler.INSTANCE);
try (XContentParser parser = XContentFactory.xContent(XContentType.JSON).createParser(parserConfig, source)) {
SearchSourceBuilder builder = SearchSourceBuilder.searchSource();
builder.parseXContent(parser, false);
builder.parseXContent(parser, false, searchUsageHolder);
builder.explain(searchTemplateRequest.isExplain());
builder.profile(searchTemplateRequest.isProfile());
checkRestTotalHitsAsInt(searchRequest, builder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,14 +259,10 @@ public void addSummaryFields(List<String> summaryFieldsToAdd) {

static {
PARSER.declareString(ConstructingObjectParser.constructorArg(), ID_FIELD);
PARSER.declareObjectArray(
ConstructingObjectParser.constructorArg(),
(p, c) -> { return RatedDocument.fromXContent(p); },
RATINGS_FIELD
);
PARSER.declareObjectArray(ConstructingObjectParser.constructorArg(), (p, c) -> RatedDocument.fromXContent(p), RATINGS_FIELD);
PARSER.declareObject(
ConstructingObjectParser.optionalConstructorArg(),
(p, c) -> SearchSourceBuilder.fromXContent(p, false),
(p, c) -> new SearchSourceBuilder().parseXContent(p, false),
REQUEST_FIELD
);
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), (p, c) -> p.map(), PARAMS_FIELD);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
*/
public class RestRankEvalAction extends BaseRestHandler {

public static String ENDPOINT = "_rank_eval";
public static final String ENDPOINT = "_rank_eval";

@Override
public List<Route> routes() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ protected void doExecute(Task task, RankEvalRequest request, ActionListener<Rank
XContentType.JSON
)
) {
evaluationRequest = SearchSourceBuilder.fromXContent(subParser, false);
evaluationRequest = new SearchSourceBuilder().parseXContent(subParser, false);
// check for parts that should not be part of a ranking evaluation request
validateEvaluatedQuery(evaluationRequest);
} catch (IOException e) {
Expand Down

0 comments on commit 238163c

Please sign in to comment.