Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
fad3c42
Add time range bucketing attribute to APM took time latency metrics
javanna Sep 26, 2025
e81fb6c
iter
javanna Sep 26, 2025
6470960
iter
javanna Sep 26, 2025
1aed53d
Update docs/changelog/135549.yaml
javanna Sep 26, 2025
6eb53a3
[CI] Auto commit changes from spotless
Sep 26, 2025
3967f72
iter
javanna Sep 26, 2025
3eb28fa
iter
javanna Sep 26, 2025
d9fdc5b
iter
javanna Sep 26, 2025
cc195d4
[CI] Auto commit changes from spotless
Sep 26, 2025
b5f144d
iter
javanna Sep 26, 2025
d81c985
Merge branch 'main' into enhancement/took_time_time_range_bucketing
javanna Sep 26, 2025
532eb96
iter
javanna Sep 26, 2025
0684e86
Merge branch 'main' into enhancement/took_time_time_range_bucketing
javanna Sep 28, 2025
ff41cb0
Update server/src/main/java/org/elasticsearch/search/query/QueryPhase…
javanna Sep 29, 2025
f985361
Update server/src/main/java/org/elasticsearch/search/query/QuerySearc…
javanna Sep 29, 2025
2ba5023
iter
javanna Sep 29, 2025
bcffd12
Merge branch 'main' into enhancement/took_time_time_range_bucketing
javanna Sep 29, 2025
d663723
iter
javanna Sep 29, 2025
5eaf6db
iter
javanna Sep 29, 2025
233db24
[CI] Auto commit changes from spotless
Sep 29, 2025
ba23911
Merge branch 'main' into enhancement/took_time_time_range_bucketing
javanna Sep 29, 2025
53a80d0
iter
javanna Sep 29, 2025
54a8a4f
[CI] Auto commit changes from spotless
Sep 29, 2025
97cb335
[CI] Update transport version definitions
Sep 29, 2025
3f47f9a
iter
javanna Sep 30, 2025
6948a16
Merge branch 'main' into enhancement/took_time_time_range_bucketing
javanna Sep 30, 2025
e28898f
iter
javanna Sep 30, 2025
edffb03
Merge branch 'main' into enhancement/took_time_time_range_bucketing
javanna Oct 1, 2025
ca4ecc0
Merge branch 'main' into enhancement/took_time_time_range_bucketing
javanna Oct 6, 2025
415c1cf
iter
javanna Oct 6, 2025
5d9621e
rename
javanna Oct 6, 2025
05d30d1
Merge branch 'main' into enhancement/took_time_time_range_bucketing
javanna Oct 6, 2025
dfb60c6
[CI] Auto commit changes from spotless
Oct 6, 2025
86d5746
iter
javanna Oct 6, 2025
8f1e680
iter
javanna Oct 6, 2025
8fb2134
iter
javanna Oct 6, 2025
c103fbe
[CI] Auto commit changes from spotless
Oct 6, 2025
260da9e
iter
javanna Oct 6, 2025
bb0b0f3
Merge branch 'main' into enhancement/took_time_time_range_bucketing
javanna Oct 6, 2025
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/135549.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 135549
summary: Add time range bucketing attribute to APM took time latency metrics
area: Search
type: enhancement
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ public SearchPhaseController.ReducedQueryPhase reduce() throws Exception {
1,
0,
0,
results.isEmpty()
results.isEmpty(),
null
);
if (progressListener != SearchProgressListener.NOOP) {
progressListener.notifyFinalReduce(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,8 @@ private SearchPhaseController.ReducedQueryPhase newReducedQueryPhaseResults(
reducedQueryPhase.numReducePhases(),
reducedQueryPhase.size(),
reducedQueryPhase.from(),
reducedQueryPhase.isEmptyResult()
reducedQueryPhase.isEmptyResult(),
reducedQueryPhase.timeRangeFilterFromMillis()
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,8 @@ static ReducedQueryPhase reducedQueryPhase(
numReducePhases,
0,
0,
true
true,
null
);
}
final List<QuerySearchResult> nonNullResults = new ArrayList<>();
Expand Down Expand Up @@ -516,6 +517,7 @@ static ReducedQueryPhase reducedQueryPhase(
: Collections.emptyMap();
int from = 0;
int size = 0;
Long timeRangeFilterFromMillis = null;
DocValueFormat[] sortValueFormats = null;
for (QuerySearchResult result : nonNullResults) {
from = result.from();
Expand All @@ -525,6 +527,16 @@ static ReducedQueryPhase reducedQueryPhase(
sortValueFormats = result.sortValueFormats();
}

if (result.getTimeRangeFilterFromMillis() != null) {
if (timeRangeFilterFromMillis == null) {
timeRangeFilterFromMillis = result.getTimeRangeFilterFromMillis();
} else {
// all shards should hold the same value, besides edge cases like different mappings
// for event.ingested and @timestamp across indices being searched
timeRangeFilterFromMillis = Math.min(result.getTimeRangeFilterFromMillis(), timeRangeFilterFromMillis);
}
}

if (hasSuggest) {
assert result.suggest() != null;
for (Suggestion<? extends Suggestion.Entry<? extends Suggestion.Entry.Option>> suggestion : result.suggest()) {
Expand Down Expand Up @@ -579,7 +591,8 @@ static ReducedQueryPhase reducedQueryPhase(
numReducePhases,
size,
from,
false
false,
timeRangeFilterFromMillis
);
}

Expand Down Expand Up @@ -662,7 +675,8 @@ public record ReducedQueryPhase(
// the offset into the merged top hits
int from,
// <code>true</code> iff the query phase had no results. Otherwise <code>false</code>
boolean isEmptyResult
boolean isEmptyResult,
Long timeRangeFilterFromMillis
) {

public ReducedQueryPhase {
Expand All @@ -683,7 +697,8 @@ public SearchResponseSections buildResponse(SearchHits hits, Collection<? extend
timedOut,
terminatedEarly,
buildSearchProfileResults(fetchResults),
numReducePhases
numReducePhases,
timeRangeFilterFromMillis
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.cluster.metadata.DataStream;
import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.util.concurrent.EsExecutors;
import org.elasticsearch.core.TimeValue;
Expand Down Expand Up @@ -53,11 +55,15 @@ public static Map<String, Object> extractAttributes(SearchRequest searchRequest,
/**
* Introspects the provided shard search request and extracts metadata from it about some of its characteristics.
*/
public static Map<String, Object> extractAttributes(ShardSearchRequest shardSearchRequest, Long rangeTimestampFrom, long nowInMillis) {
public static Map<String, Object> extractAttributes(
ShardSearchRequest shardSearchRequest,
Long timeRangeFilterFromMillis,
long nowInMillis
) {
Map<String, Object> attributes = extractAttributes(
shardSearchRequest.source(),
shardSearchRequest.scroll(),
rangeTimestampFrom,
timeRangeFilterFromMillis,
nowInMillis,
shardSearchRequest.shardId().getIndexName()
);
Expand All @@ -69,7 +75,7 @@ public static Map<String, Object> extractAttributes(ShardSearchRequest shardSear
private static Map<String, Object> extractAttributes(
SearchSourceBuilder searchSourceBuilder,
TimeValue scroll,
Long rangeTimestampFrom,
Long timeRangeFilterFromMillis,
long nowInMillis,
String... localIndices
) {
Expand Down Expand Up @@ -107,9 +113,9 @@ private static Map<String, Object> extractAttributes(
}

final boolean hasKnn = searchSourceBuilder.knnSearch().isEmpty() == false || queryMetadataBuilder.knnQuery;
String timestampRangeFilter = null;
if (rangeTimestampFrom != null) {
timestampRangeFilter = introspectTimeRange(rangeTimestampFrom, nowInMillis);
String timeRangeFilterFrom = null;
if (timeRangeFilterFromMillis != null) {
timeRangeFilterFrom = introspectTimeRange(timeRangeFilterFromMillis, nowInMillis);
}
return buildAttributesMap(
target,
Expand All @@ -119,7 +125,7 @@ private static Map<String, Object> extractAttributes(
queryMetadataBuilder.rangeOnTimestamp,
queryMetadataBuilder.rangeOnEventIngested,
pitOrScroll,
timestampRangeFilter
timeRangeFilterFrom
);
}

Expand All @@ -131,7 +137,7 @@ private static Map<String, Object> buildAttributesMap(
boolean rangeOnTimestamp,
boolean rangeOnEventIngested,
String pitOrScroll,
String timestampRangeFilter
String timeRangeFilterFrom
) {
Map<String, Object> attributes = new HashMap<>(5, 1.0f);
attributes.put(TARGET_ATTRIBUTE, target);
Expand All @@ -143,14 +149,18 @@ private static Map<String, Object> buildAttributesMap(
if (knn) {
attributes.put(KNN_ATTRIBUTE, knn);
}
if (rangeOnTimestamp) {
attributes.put(RANGE_TIMESTAMP_ATTRIBUTE, rangeOnTimestamp);
}
if (rangeOnEventIngested) {
attributes.put(RANGE_EVENT_INGESTED_ATTRIBUTE, rangeOnEventIngested);
if (rangeOnTimestamp && rangeOnEventIngested) {
attributes.put(
TIME_RANGE_FILTER_FIELD_ATTRIBUTE,
DataStream.TIMESTAMP_FIELD_NAME + "_AND_" + IndexMetadata.EVENT_INGESTED_FIELD_NAME
);
} else if (rangeOnEventIngested) {
attributes.put(TIME_RANGE_FILTER_FIELD_ATTRIBUTE, IndexMetadata.EVENT_INGESTED_FIELD_NAME);
} else if (rangeOnTimestamp) {
attributes.put(TIME_RANGE_FILTER_FIELD_ATTRIBUTE, DataStream.TIMESTAMP_FIELD_NAME);
}
if (timestampRangeFilter != null) {
attributes.put(TIMESTAMP_RANGE_FILTER_ATTRIBUTE, timestampRangeFilter);
if (timeRangeFilterFrom != null) {
attributes.put(TIME_RANGE_FILTER_FROM_ATTRIBUTE, timeRangeFilterFrom);
}
return attributes;
}
Expand All @@ -166,9 +176,8 @@ private static final class QueryMetadataBuilder {
static final String QUERY_TYPE_ATTRIBUTE = "query_type";
static final String PIT_SCROLL_ATTRIBUTE = "pit_scroll";
static final String KNN_ATTRIBUTE = "knn";
static final String RANGE_TIMESTAMP_ATTRIBUTE = "range_timestamp";
static final String RANGE_EVENT_INGESTED_ATTRIBUTE = "range_event_ingested";
static final String TIMESTAMP_RANGE_FILTER_ATTRIBUTE = "timestamp_range_filter";
static final String TIME_RANGE_FILTER_FIELD_ATTRIBUTE = "time_range_filter_field";
static final String TIME_RANGE_FILTER_FROM_ATTRIBUTE = "time_range_filter_from";

private static final String TARGET_KIBANA = ".kibana";
private static final String TARGET_ML = ".ml";
Expand Down Expand Up @@ -303,6 +312,10 @@ private static void introspectQueryBuilder(QueryBuilder queryBuilder, QueryMetad
introspectQueryBuilder(nested.query(), queryMetadataBuilder, ++level);
break;
case RangeQueryBuilder range:
// Note that the outcome of this switch differs depending on whether it is executed on the coord node, or data node.
// Data nodes perform query rewrite on each shard. That means that a query that reports a certain time range filter at the
// coordinator, may not report the same for all the shards it targets, but rather only for those that do end up executing
// a true range query at the shard level.
switch (range.fieldName()) {
// 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
Expand Down Expand Up @@ -343,9 +356,16 @@ private enum TimeRangeBucket {
}
}

static String introspectTimeRange(long timeRangeFrom, long nowInMillis) {
public static void addTimeRangeAttribute(Long timeRangeFrom, long nowInMillis, Map<String, Object> attributes) {
if (timeRangeFrom != null) {
String timestampRangeFilter = introspectTimeRange(timeRangeFrom, nowInMillis);
attributes.put(TIME_RANGE_FILTER_FROM_ATTRIBUTE, timestampRangeFilter);
}
}

static String introspectTimeRange(long timeRangeFromMillis, long nowInMillis) {
for (TimeRangeBucket value : TimeRangeBucket.values()) {
if (timeRangeFrom >= nowInMillis - value.millis) {
if (timeRangeFromMillis >= nowInMillis - value.millis) {
return value.bucketName;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ public class SearchResponse extends ActionResponse implements ChunkedToXContentO
private final ShardSearchFailure[] shardFailures;
private final Clusters clusters;
private final long tookInMillis;
// only used for telemetry purposes on the coordinating node, where the search response gets created
private transient Long timeRangeFilterFromMillis;

private final RefCounted refCounted = LeakTracker.wrap(new SimpleRefCounted());

Expand Down Expand Up @@ -187,6 +189,7 @@ public SearchResponse(
clusters,
pointInTimeId
);
this.timeRangeFilterFromMillis = searchResponseSections.timeRangeFilterFromMillis;
}

public SearchResponse(
Expand Down Expand Up @@ -464,6 +467,10 @@ public void writeTo(StreamOutput out) throws IOException {
out.writeOptionalBytesReference(pointInTimeId);
}

public Long getTimeRangeFilterFromMillis() {
return timeRangeFilterFromMillis;
}

@Override
public String toString() {
return hasReferences() == false ? "SearchResponse[released]" : Strings.toString(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ public class SearchResponseSections implements Releasable {
false,
null,
null,
1
1,
null
);
public static final SearchResponseSections EMPTY_WITHOUT_TOTAL_HITS = new SearchResponseSections(
SearchHits.EMPTY_WITHOUT_TOTAL_HITS,
Expand All @@ -41,7 +42,8 @@ public class SearchResponseSections implements Releasable {
false,
null,
null,
1
1,
null
);
protected final SearchHits hits;
protected final InternalAggregations aggregations;
Expand All @@ -50,6 +52,7 @@ public class SearchResponseSections implements Releasable {
protected final boolean timedOut;
protected final Boolean terminatedEarly;
protected final int numReducePhases;
protected final Long timeRangeFilterFromMillis;

public SearchResponseSections(
SearchHits hits,
Expand All @@ -58,7 +61,8 @@ public SearchResponseSections(
boolean timedOut,
Boolean terminatedEarly,
SearchProfileResults profileResults,
int numReducePhases
int numReducePhases,
Long timeRangeFilterFromMillis
) {
this.hits = hits;
this.aggregations = aggregations;
Expand All @@ -67,6 +71,7 @@ public SearchResponseSections(
this.timedOut = timedOut;
this.terminatedEarly = terminatedEarly;
this.numReducePhases = numReducePhases;
this.timeRangeFilterFromMillis = timeRangeFilterFromMillis;
}

public final SearchHits hits() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,12 @@ public void onFailure(Exception e) {
Arrays.stream(resolvedIndices.getConcreteLocalIndices()).map(Index::getName).toArray(String[]::new)
);
if (collectCCSTelemetry == false || resolvedIndices.getRemoteClusterIndices().isEmpty()) {
searchResponseActionListener = new SearchTelemetryListener(delegate, searchResponseMetrics, searchRequestAttributes);
searchResponseActionListener = new SearchTelemetryListener(
delegate,
searchResponseMetrics,
searchRequestAttributes,
timeProvider.absoluteStartMillis()
);
} else {
CCSUsage.Builder usageBuilder = new CCSUsage.Builder();
usageBuilder.setRemotesCount(resolvedIndices.getRemoteClusterIndices().size());
Expand Down Expand Up @@ -459,6 +464,7 @@ public void onFailure(Exception e) {
delegate,
searchResponseMetrics,
searchRequestAttributes,
timeProvider.absoluteStartMillis(),
usageService,
usageBuilder
);
Expand Down Expand Up @@ -2046,6 +2052,7 @@ static String[] ignoreBlockedIndices(ProjectState projectState, String[] concret
private static class SearchTelemetryListener extends DelegatingActionListener<SearchResponse, SearchResponse> {
private final CCSUsage.Builder usageBuilder;
private final SearchResponseMetrics searchResponseMetrics;
private final long nowInMillis;
private final UsageService usageService;
private final boolean collectCCSTelemetry;
private final Map<String, Object> searchRequestAttributes;
Expand All @@ -2054,12 +2061,14 @@ private static class SearchTelemetryListener extends DelegatingActionListener<Se
ActionListener<SearchResponse> listener,
SearchResponseMetrics searchResponseMetrics,
Map<String, Object> searchRequestAttributes,
long nowInMillis,
UsageService usageService,
CCSUsage.Builder usageBuilder
) {
super(listener);
this.searchResponseMetrics = searchResponseMetrics;
this.searchRequestAttributes = searchRequestAttributes;
this.nowInMillis = nowInMillis;
this.collectCCSTelemetry = true;
this.usageService = usageService;
this.usageBuilder = usageBuilder;
Expand All @@ -2068,11 +2077,13 @@ private static class SearchTelemetryListener extends DelegatingActionListener<Se
SearchTelemetryListener(
ActionListener<SearchResponse> listener,
SearchResponseMetrics searchResponseMetrics,
Map<String, Object> searchRequestAttributes
Map<String, Object> searchRequestAttributes,
long nowInMillis
) {
super(listener);
this.searchResponseMetrics = searchResponseMetrics;
this.searchRequestAttributes = searchRequestAttributes;
this.nowInMillis = nowInMillis;
this.collectCCSTelemetry = false;
this.usageService = null;
this.usageBuilder = null;
Expand All @@ -2081,7 +2092,12 @@ private static class SearchTelemetryListener extends DelegatingActionListener<Se
@Override
public void onResponse(SearchResponse searchResponse) {
try {
searchResponseMetrics.recordTookTime(searchResponse.getTookInMillis(), searchRequestAttributes);
searchResponseMetrics.recordTookTime(
searchResponse.getTookInMillis(),
searchResponse.getTimeRangeFilterFromMillis(),
nowInMillis,
searchRequestAttributes
);
SearchResponseMetrics.ResponseCountTotalStatus responseCountTotalStatus =
SearchResponseMetrics.ResponseCountTotalStatus.SUCCESS;
if (searchResponse.getShardFailures() != null && searchResponse.getShardFailures().length > 0) {
Expand Down
Loading