Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/changelog/134798.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 134798
summary: Add relevant attributes to shard search latency APM metrics
area: Search
type: enhancement
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.util.concurrent.EsExecutors;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.BoostingQueryBuilder;
import org.elasticsearch.index.query.ConstantScoreQueryBuilder;
Expand All @@ -20,6 +22,7 @@
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchService;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.internal.ShardSearchRequest;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.ScoreSortBuilder;
import org.elasticsearch.search.sort.SortBuilder;
Expand All @@ -42,17 +45,37 @@ private SearchRequestAttributesExtractor() {}

/**
* Introspects the provided search request and extracts metadata from it about some of its characteristics.
*
*/
public static Map<String, Object> extractAttributes(SearchRequest searchRequest, String[] localIndices) {
return extractAttributes(searchRequest.source(), searchRequest.scroll(), localIndices);
}

/**
* Introspects the provided shard search request and extracts metadata from it about some of its characteristics.
*/
public static Map<String, Object> extractAttributes(ShardSearchRequest shardSearchRequest) {
Map<String, Object> attributes = extractAttributes(
shardSearchRequest.source(),
shardSearchRequest.scroll(),
shardSearchRequest.shardId().getIndexName()
);
boolean isSystem = ((EsExecutors.EsThread) Thread.currentThread()).isSystem();
attributes.put(SYSTEM_THREAD_ATTRIBUTE_NAME, isSystem);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The isSystem attribute I moved here from the ShardSearchPhaseAPMMetrics class.

return attributes;
}

private static Map<String, Object> extractAttributes(
SearchSourceBuilder searchSourceBuilder,
TimeValue scroll,
String... localIndices
) {
String target = extractIndices(localIndices);

String pitOrScroll = null;
if (searchRequest.scroll() != null) {
if (scroll != null) {
pitOrScroll = SCROLL;
}

SearchSourceBuilder searchSourceBuilder = searchRequest.source();
if (searchSourceBuilder == null) {
return buildAttributesMap(target, ScoreSortBuilder.NAME, HITS_ONLY, false, false, false, pitOrScroll);
}
Expand Down Expand Up @@ -144,7 +167,7 @@ private static final class QueryMetadataBuilder {
private static final String TARGET_USER = "user";
private static final String ERROR = "error";

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

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

static String extractQueryType(SearchSourceBuilder searchSourceBuilder) {
Expand Down Expand Up @@ -266,8 +290,18 @@ private static void introspectQueryBuilder(QueryBuilder queryBuilder, QueryMetad
break;
case RangeQueryBuilder range:
switch (range.fieldName()) {
case TIMESTAMP -> queryMetadataBuilder.rangeOnTimestamp = true;
case EVENT_INGESTED -> queryMetadataBuilder.rangeOnEventIngested = true;
// don't track unbounded ranges, they translate to either match_none if the field does not exist
// or match_all if the field is mapped
case TIMESTAMP -> {
if (range.to() != null || range.from() != null) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something I discovered as part of working on this chance: rewrite for range queries is a bit funky (I plan on fixing that as a follow-up). A range query may rewrite to one without bounds, if all data is within the range. In that case, it is equivalent to a match_all hence I don't report it as a range. I decided to rather report only "true" range queries at the shard level.

queryMetadataBuilder.rangeOnTimestamp = true;
}
}
case EVENT_INGESTED -> {
if (range.to() != null || range.from() != null) {
queryMetadataBuilder.rangeOnEventIngested = true;
}
}
}
break;
case KnnVectorQueryBuilder knn:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@

package org.elasticsearch.index.search.stats;

import org.elasticsearch.common.util.concurrent.EsExecutors;
import org.elasticsearch.action.search.SearchRequestAttributesExtractor;
import org.elasticsearch.index.shard.SearchOperationListener;
import org.elasticsearch.search.internal.SearchContext;
import org.elasticsearch.search.internal.ShardSearchRequest;
import org.elasticsearch.telemetry.metric.LongHistogram;
import org.elasticsearch.telemetry.metric.MeterRegistry;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

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

public static final String SYSTEM_THREAD_ATTRIBUTE_NAME = "system_thread";

private final LongHistogram queryPhaseMetric;
private final LongHistogram fetchPhaseMetric;

// Avoid allocating objects in the search path and multithreading clashes
private static final ThreadLocal<Map<String, Object>> THREAD_LOCAL_ATTRS = ThreadLocal.withInitial(() -> new HashMap<>(1));

public ShardSearchPhaseAPMMetrics(MeterRegistry meterRegistry) {
this.queryPhaseMetric = meterRegistry.registerLongHistogram(
QUERY_SEARCH_PHASE_METRIC,
Expand All @@ -47,18 +42,16 @@ public ShardSearchPhaseAPMMetrics(MeterRegistry meterRegistry) {

@Override
public void onQueryPhase(SearchContext searchContext, long tookInNanos) {
recordPhaseLatency(queryPhaseMetric, tookInNanos);
recordPhaseLatency(queryPhaseMetric, tookInNanos, searchContext.request());
}

@Override
public void onFetchPhase(SearchContext searchContext, long tookInNanos) {
recordPhaseLatency(fetchPhaseMetric, tookInNanos);
recordPhaseLatency(fetchPhaseMetric, tookInNanos, searchContext.request());
}

private static void recordPhaseLatency(LongHistogram histogramMetric, long tookInNanos) {
Map<String, Object> attrs = ShardSearchPhaseAPMMetrics.THREAD_LOCAL_ATTRS.get();
boolean isSystem = ((EsExecutors.EsThread) Thread.currentThread()).isSystem();
attrs.put(SYSTEM_THREAD_ATTRIBUTE_NAME, isSystem);
histogramMetric.record(TimeUnit.NANOSECONDS.toMillis(tookInNanos), attrs);
private static void recordPhaseLatency(LongHistogram histogramMetric, long tookInNanos, ShardSearchRequest request) {
Map<String, Object> attributes = SearchRequestAttributesExtractor.extractAttributes(request);
histogramMetric.record(TimeUnit.NANOSECONDS.toMillis(tookInNanos), attributes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,18 @@ public void testExtractAttributes() {
searchRequest,
searchRequest.indices()
);
assertAttributes(stringObjectMap, "user", "@timestamp", "hits_only", false, false, false, null);
}
{
SearchRequest searchRequest = new SearchRequest(randomAlphaOfLengthBetween(3, 10));
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchRequest.source(searchSourceBuilder);
searchSourceBuilder.sort("@timestamp");
searchSourceBuilder.query(new RangeQueryBuilder("@timestamp").from("2021-11-11"));
Map<String, Object> stringObjectMap = SearchRequestAttributesExtractor.extractAttributes(
searchRequest,
searchRequest.indices()
);
assertAttributes(stringObjectMap, "user", "@timestamp", "hits_only", false, true, false, null);
}
{
Expand All @@ -202,10 +214,10 @@ public void testExtractAttributes() {
boolQueryBuilder.must(boolQueryBuilderNew);
boolQueryBuilder = boolQueryBuilderNew;
}
boolQueryBuilder.must(new RangeQueryBuilder("@timestamp"));
boolQueryBuilder.must(new RangeQueryBuilder("@timestamp").from("2021-11-11"));
searchSourceBuilder.query(boolQueryBuilder);
if (randomBoolean()) {
boolQueryBuilder.should(new RangeQueryBuilder("event.ingested"));
boolQueryBuilder.should(new RangeQueryBuilder("event.ingested").from("2021-11-11"));
}
Map<String, Object> stringObjectMap = SearchRequestAttributesExtractor.extractAttributes(
searchRequest,
Expand All @@ -229,7 +241,7 @@ public void testExtractAttributes() {
boolQueryBuilder.should(new RangeQueryBuilder("event.ingested"));
}

boolQueryBuilder.filter(new RangeQueryBuilder("@timestamp"));
boolQueryBuilder.filter(new RangeQueryBuilder("@timestamp").from("2021-11-11"));
searchSourceBuilder.query(boolQueryBuilder);
Map<String, Object> stringObjectMap = SearchRequestAttributesExtractor.extractAttributes(
searchRequest,
Expand All @@ -243,8 +255,8 @@ public void testExtractAttributes() {
searchRequest.source(searchSourceBuilder);
searchSourceBuilder.sort("@timestamp");
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
boolQueryBuilder.must(new RangeQueryBuilder("@timestamp"));
boolQueryBuilder.must(new RangeQueryBuilder("event.ingested"));
boolQueryBuilder.must(new RangeQueryBuilder("@timestamp").from("2021-11-11"));
boolQueryBuilder.must(new RangeQueryBuilder("event.ingested").from("2021-11-11"));
boolQueryBuilder.must(new RangeQueryBuilder(randomAlphaOfLengthBetween(3, 10)));
searchSourceBuilder.query(boolQueryBuilder);
Map<String, Object> stringObjectMap = SearchRequestAttributesExtractor.extractAttributes(
Expand All @@ -259,7 +271,7 @@ public void testExtractAttributes() {
searchRequest.source(searchSourceBuilder);
searchSourceBuilder.sort("@timestamp");
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
boolQueryBuilder.should(new RangeQueryBuilder("@timestamp"));
boolQueryBuilder.should(new RangeQueryBuilder("@timestamp").from("2021-11-11"));
searchSourceBuilder.query(boolQueryBuilder);
Map<String, Object> stringObjectMap = SearchRequestAttributesExtractor.extractAttributes(
searchRequest,
Expand All @@ -273,7 +285,7 @@ public void testExtractAttributes() {
searchRequest.source(searchSourceBuilder);
searchSourceBuilder.sort("@timestamp");
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
boolQueryBuilder.should(new RangeQueryBuilder(randomAlphaOfLengthBetween(3, 10)));
boolQueryBuilder.should(new RangeQueryBuilder(randomAlphaOfLengthBetween(3, 10)).from("2021-11-11"));
searchSourceBuilder.query(boolQueryBuilder);
Map<String, Object> stringObjectMap = SearchRequestAttributesExtractor.extractAttributes(
searchRequest,
Expand All @@ -286,7 +298,7 @@ public void testExtractAttributes() {
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchRequest.source(searchSourceBuilder);
searchSourceBuilder.sort("@timestamp");
searchSourceBuilder.query(new ConstantScoreQueryBuilder(new RangeQueryBuilder("@timestamp")));
searchSourceBuilder.query(new ConstantScoreQueryBuilder(new RangeQueryBuilder("@timestamp").from("2021-11-11")));
Map<String, Object> stringObjectMap = SearchRequestAttributesExtractor.extractAttributes(
searchRequest,
searchRequest.indices()
Expand All @@ -298,7 +310,7 @@ public void testExtractAttributes() {
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchRequest.source(searchSourceBuilder);
searchSourceBuilder.sort("@timestamp");
searchSourceBuilder.query(new BoostingQueryBuilder(new RangeQueryBuilder("@timestamp"), new MatchAllQueryBuilder()));
searchSourceBuilder.query(new BoostingQueryBuilder(new RangeQueryBuilder("@timestamp").from("2021-11-11"), new MatchAllQueryBuilder()));
Map<String, Object> stringObjectMap = SearchRequestAttributesExtractor.extractAttributes(
searchRequest,
searchRequest.indices()
Expand All @@ -320,7 +332,7 @@ public void testDepthLimit() {
newBoolQueryBuilder.must(innerBoolQueryBuilder);
newBoolQueryBuilder = innerBoolQueryBuilder;
}
newBoolQueryBuilder.must(new RangeQueryBuilder("@timestamp"));
newBoolQueryBuilder.must(new RangeQueryBuilder("@timestamp").from("2021-11-11"));
Map<String, Object> stringObjectMap = SearchRequestAttributesExtractor.extractAttributes(
searchRequest,
searchRequest.indices()
Expand All @@ -339,7 +351,7 @@ public void testDepthLimit() {
newBoolQueryBuilder.must(innerBoolQueryBuilder);
newBoolQueryBuilder = innerBoolQueryBuilder;
}
newBoolQueryBuilder.must(new RangeQueryBuilder("@timestamp"));
newBoolQueryBuilder.must(new RangeQueryBuilder("@timestamp").from("2021-11-11"));
Map<String, Object> stringObjectMap = SearchRequestAttributesExtractor.extractAttributes(
searchRequest,
searchRequest.indices()
Expand Down
Loading