Skip to content

Commit 7682f37

Browse files
committed
Add relevant attributes to shard search latency APM metrics
We already record latency of the query and fetch phase as APM metrics. We'd like to be able to slice such latencies based on some recurring categories of the request: - does it have agg or hit only? - is it sorted by field or by score? - does it have a time range filter? - does it target user data or internal indices? This commit introduces introspection for a shard search request and stores the extracted attributes together with the shard phase latency metrics. This builds on top of elastic#134232 to use the same infra and store the same attributes for shard level latency metrics.
1 parent 136321f commit 7682f37

File tree

3 files changed

+181
-77
lines changed

3 files changed

+181
-77
lines changed

server/src/main/java/org/elasticsearch/action/search/SearchRequestAttributesExtractor.java

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import org.apache.logging.log4j.LogManager;
1313
import org.apache.logging.log4j.Logger;
1414
import org.elasticsearch.common.regex.Regex;
15+
import org.elasticsearch.common.util.concurrent.EsExecutors;
16+
import org.elasticsearch.core.TimeValue;
1517
import org.elasticsearch.index.query.BoolQueryBuilder;
1618
import org.elasticsearch.index.query.BoostingQueryBuilder;
1719
import org.elasticsearch.index.query.ConstantScoreQueryBuilder;
@@ -20,6 +22,7 @@
2022
import org.elasticsearch.index.query.RangeQueryBuilder;
2123
import org.elasticsearch.search.SearchService;
2224
import org.elasticsearch.search.builder.SearchSourceBuilder;
25+
import org.elasticsearch.search.internal.ShardSearchRequest;
2326
import org.elasticsearch.search.sort.FieldSortBuilder;
2427
import org.elasticsearch.search.sort.ScoreSortBuilder;
2528
import org.elasticsearch.search.sort.SortBuilder;
@@ -42,17 +45,37 @@ private SearchRequestAttributesExtractor() {}
4245

4346
/**
4447
* Introspects the provided search request and extracts metadata from it about some of its characteristics.
45-
*
4648
*/
4749
public static Map<String, Object> extractAttributes(SearchRequest searchRequest, String[] localIndices) {
50+
return extractAttributes(searchRequest.source(), searchRequest.scroll(), localIndices);
51+
}
52+
53+
/**
54+
* Introspects the provided shard search request and extracts metadata from it about some of its characteristics.
55+
*/
56+
public static Map<String, Object> extractAttributes(ShardSearchRequest shardSearchRequest) {
57+
Map<String, Object> attributes = extractAttributes(
58+
shardSearchRequest.source(),
59+
shardSearchRequest.scroll(),
60+
shardSearchRequest.shardId().getIndexName()
61+
);
62+
boolean isSystem = ((EsExecutors.EsThread) Thread.currentThread()).isSystem();
63+
attributes.put(SYSTEM_THREAD_ATTRIBUTE_NAME, isSystem);
64+
return attributes;
65+
}
66+
67+
private static Map<String, Object> extractAttributes(
68+
SearchSourceBuilder searchSourceBuilder,
69+
TimeValue scroll,
70+
String... localIndices
71+
) {
4872
String target = extractIndices(localIndices);
4973

5074
String pitOrScroll = null;
51-
if (searchRequest.scroll() != null) {
75+
if (scroll != null) {
5276
pitOrScroll = SCROLL;
5377
}
5478

55-
SearchSourceBuilder searchSourceBuilder = searchRequest.source();
5679
if (searchSourceBuilder == null) {
5780
return buildAttributesMap(target, ScoreSortBuilder.NAME, HITS_ONLY, false, false, false, pitOrScroll);
5881
}
@@ -144,7 +167,7 @@ private static final class QueryMetadataBuilder {
144167
private static final String TARGET_USER = "user";
145168
private static final String ERROR = "error";
146169

147-
static String extractIndices(String[] indices) {
170+
static String extractIndices(String... indices) {
148171
try {
149172
// Note that indices are expected to be resolved, meaning wildcards are not handled on purpose
150173
// If indices resolve to data streams, the name of the data stream is returned as opposed to its backing indices
@@ -213,6 +236,7 @@ static String extractPrimarySort(SortBuilder<?> primarySortBuilder) {
213236
private static final String PIT = "pit";
214237
private static final String SCROLL = "scroll";
215238

239+
public static final String SYSTEM_THREAD_ATTRIBUTE_NAME = "system_thread";
216240
public static final Map<String, Object> SEARCH_SCROLL_ATTRIBUTES = Map.of(QUERY_TYPE_ATTRIBUTE, SCROLL);
217241

218242
static String extractQueryType(SearchSourceBuilder searchSourceBuilder) {

server/src/main/java/org/elasticsearch/index/search/stats/ShardSearchPhaseAPMMetrics.java

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99

1010
package org.elasticsearch.index.search.stats;
1111

12-
import org.elasticsearch.common.util.concurrent.EsExecutors;
12+
import org.elasticsearch.action.search.SearchRequestAttributesExtractor;
1313
import org.elasticsearch.index.shard.SearchOperationListener;
1414
import org.elasticsearch.search.internal.SearchContext;
15+
import org.elasticsearch.search.internal.ShardSearchRequest;
1516
import org.elasticsearch.telemetry.metric.LongHistogram;
1617
import org.elasticsearch.telemetry.metric.MeterRegistry;
1718

18-
import java.util.HashMap;
1919
import java.util.Map;
2020
import java.util.concurrent.TimeUnit;
2121

@@ -24,14 +24,9 @@ public final class ShardSearchPhaseAPMMetrics implements SearchOperationListener
2424
public static final String QUERY_SEARCH_PHASE_METRIC = "es.search.shards.phases.query.duration.histogram";
2525
public static final String FETCH_SEARCH_PHASE_METRIC = "es.search.shards.phases.fetch.duration.histogram";
2626

27-
public static final String SYSTEM_THREAD_ATTRIBUTE_NAME = "system_thread";
28-
2927
private final LongHistogram queryPhaseMetric;
3028
private final LongHistogram fetchPhaseMetric;
3129

32-
// Avoid allocating objects in the search path and multithreading clashes
33-
private static final ThreadLocal<Map<String, Object>> THREAD_LOCAL_ATTRS = ThreadLocal.withInitial(() -> new HashMap<>(1));
34-
3530
public ShardSearchPhaseAPMMetrics(MeterRegistry meterRegistry) {
3631
this.queryPhaseMetric = meterRegistry.registerLongHistogram(
3732
QUERY_SEARCH_PHASE_METRIC,
@@ -47,18 +42,16 @@ public ShardSearchPhaseAPMMetrics(MeterRegistry meterRegistry) {
4742

4843
@Override
4944
public void onQueryPhase(SearchContext searchContext, long tookInNanos) {
50-
recordPhaseLatency(queryPhaseMetric, tookInNanos);
45+
recordPhaseLatency(queryPhaseMetric, tookInNanos, searchContext.request());
5146
}
5247

5348
@Override
5449
public void onFetchPhase(SearchContext searchContext, long tookInNanos) {
55-
recordPhaseLatency(fetchPhaseMetric, tookInNanos);
50+
recordPhaseLatency(fetchPhaseMetric, tookInNanos, searchContext.request());
5651
}
5752

58-
private static void recordPhaseLatency(LongHistogram histogramMetric, long tookInNanos) {
59-
Map<String, Object> attrs = ShardSearchPhaseAPMMetrics.THREAD_LOCAL_ATTRS.get();
60-
boolean isSystem = ((EsExecutors.EsThread) Thread.currentThread()).isSystem();
61-
attrs.put(SYSTEM_THREAD_ATTRIBUTE_NAME, isSystem);
62-
histogramMetric.record(TimeUnit.NANOSECONDS.toMillis(tookInNanos), attrs);
53+
private static void recordPhaseLatency(LongHistogram histogramMetric, long tookInNanos, ShardSearchRequest request) {
54+
Map<String, Object> attributes = SearchRequestAttributesExtractor.extractAttributes(request);
55+
histogramMetric.record(TimeUnit.NANOSECONDS.toMillis(tookInNanos), attributes);
6356
}
6457
}

0 commit comments

Comments
 (0)