Skip to content

Commit 31813c2

Browse files
committed
Add time range bucketing attribute to APM shard search latency metrics
We recently added relevant attributes to the existing shard search latency metrics (elastic#134798). This commit introduces an additional attribute that analyzes the parsed time range filter against the @timestamp field and reports back whether it is within the last 15 minutes, last hour, last 12 hours, last day, last three days, last seven days, or last 14 days.
1 parent 1aed2bc commit 31813c2

File tree

7 files changed

+186
-43
lines changed

7 files changed

+186
-43
lines changed

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

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,18 @@ private SearchRequestAttributesExtractor() {}
4747
* Introspects the provided search request and extracts metadata from it about some of its characteristics.
4848
*/
4949
public static Map<String, Object> extractAttributes(SearchRequest searchRequest, String[] localIndices) {
50-
return extractAttributes(searchRequest.source(), searchRequest.scroll(), localIndices);
50+
return extractAttributes(searchRequest.source(), searchRequest.scroll(), null, -1, localIndices);
5151
}
5252

5353
/**
5454
* Introspects the provided shard search request and extracts metadata from it about some of its characteristics.
5555
*/
56-
public static Map<String, Object> extractAttributes(ShardSearchRequest shardSearchRequest) {
56+
public static Map<String, Object> extractAttributes(ShardSearchRequest shardSearchRequest, Long rangeTimestampFrom, long nowInMillis) {
5757
Map<String, Object> attributes = extractAttributes(
5858
shardSearchRequest.source(),
5959
shardSearchRequest.scroll(),
60+
rangeTimestampFrom,
61+
nowInMillis,
6062
shardSearchRequest.shardId().getIndexName()
6163
);
6264
boolean isSystem = ((EsExecutors.EsThread) Thread.currentThread()).isSystem();
@@ -67,6 +69,8 @@ public static Map<String, Object> extractAttributes(ShardSearchRequest shardSear
6769
private static Map<String, Object> extractAttributes(
6870
SearchSourceBuilder searchSourceBuilder,
6971
TimeValue scroll,
72+
Long rangeTimestampFrom,
73+
long nowInMillis,
7074
String... localIndices
7175
) {
7276
String target = extractIndices(localIndices);
@@ -77,7 +81,7 @@ private static Map<String, Object> extractAttributes(
7781
}
7882

7983
if (searchSourceBuilder == null) {
80-
return buildAttributesMap(target, ScoreSortBuilder.NAME, HITS_ONLY, false, false, false, pitOrScroll);
84+
return buildAttributesMap(target, ScoreSortBuilder.NAME, HITS_ONLY, false, false, false, pitOrScroll, null);
8185
}
8286

8387
if (searchSourceBuilder.pointInTimeBuilder() != null) {
@@ -103,14 +107,19 @@ private static Map<String, Object> extractAttributes(
103107
}
104108

105109
final boolean hasKnn = searchSourceBuilder.knnSearch().isEmpty() == false || queryMetadataBuilder.knnQuery;
110+
String timestampRangeFilter = null;
111+
if (rangeTimestampFrom != null) {
112+
timestampRangeFilter = introspectTimeRange(rangeTimestampFrom, nowInMillis);
113+
}
106114
return buildAttributesMap(
107115
target,
108116
primarySort,
109117
queryType,
110118
hasKnn,
111119
queryMetadataBuilder.rangeOnTimestamp,
112120
queryMetadataBuilder.rangeOnEventIngested,
113-
pitOrScroll
121+
pitOrScroll,
122+
timestampRangeFilter
114123
);
115124
}
116125

@@ -121,7 +130,8 @@ private static Map<String, Object> buildAttributesMap(
121130
boolean knn,
122131
boolean rangeOnTimestamp,
123132
boolean rangeOnEventIngested,
124-
String pitOrScroll
133+
String pitOrScroll,
134+
String timestampRangeFilter
125135
) {
126136
Map<String, Object> attributes = new HashMap<>(5, 1.0f);
127137
attributes.put(TARGET_ATTRIBUTE, target);
@@ -139,6 +149,9 @@ private static Map<String, Object> buildAttributesMap(
139149
if (rangeOnEventIngested) {
140150
attributes.put(RANGE_EVENT_INGESTED_ATTRIBUTE, rangeOnEventIngested);
141151
}
152+
if (timestampRangeFilter != null) {
153+
attributes.put(TIMESTAMP_RANGE_FILTER_ATTRIBUTE, timestampRangeFilter);
154+
}
142155
return attributes;
143156
}
144157

@@ -155,6 +168,7 @@ private static final class QueryMetadataBuilder {
155168
static final String KNN_ATTRIBUTE = "knn";
156169
static final String RANGE_TIMESTAMP_ATTRIBUTE = "range_timestamp";
157170
static final String RANGE_EVENT_INGESTED_ATTRIBUTE = "range_event_ingested";
171+
static final String TIMESTAMP_RANGE_FILTER_ATTRIBUTE = "timestamp_range_filter";
158172

159173
private static final String TARGET_KIBANA = ".kibana";
160174
private static final String TARGET_ML = ".ml";
@@ -310,4 +324,31 @@ private static void introspectQueryBuilder(QueryBuilder queryBuilder, QueryMetad
310324
default:
311325
}
312326
}
327+
328+
private enum TimeRangeBucket {
329+
FifteenMinutes(TimeValue.timeValueMinutes(15).getMillis(), "15_minutes"),
330+
OneHour(TimeValue.timeValueHours(1).getMillis(), "1_hour"),
331+
TwelveHours(TimeValue.timeValueHours(12).getMillis(), "12_hours"),
332+
OneDay(TimeValue.timeValueDays(1).getMillis(), "1_day"),
333+
ThreeDays(TimeValue.timeValueDays(3).getMillis(), "3_days"),
334+
SevenDays(TimeValue.timeValueDays(7).getMillis(), "7_days"),
335+
FourteenDays(TimeValue.timeValueDays(14).getMillis(), "14_days");
336+
337+
private final long millis;
338+
private final String bucketName;
339+
340+
TimeRangeBucket(long millis, String bucketName) {
341+
this.millis = millis;
342+
this.bucketName = bucketName;
343+
}
344+
}
345+
346+
static String introspectTimeRange(long timeRangeFrom, long nowInMillis) {
347+
for (TimeRangeBucket value : TimeRangeBucket.values()) {
348+
if (timeRangeFrom >= nowInMillis - value.millis) {
349+
return value.bucketName;
350+
}
351+
}
352+
return "older_than_14_days";
353+
}
313354
}

server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.apache.lucene.search.IndexSortSortedNumericDocValuesRangeQuery;
2626
import org.apache.lucene.search.Query;
2727
import org.elasticsearch.ElasticsearchParseException;
28+
import org.elasticsearch.cluster.metadata.DataStream;
2829
import org.elasticsearch.common.geo.ShapeRelation;
2930
import org.elasticsearch.common.logging.DeprecationCategory;
3031
import org.elasticsearch.common.logging.DeprecationLogger;
@@ -747,33 +748,34 @@ public Query rangeQuery(
747748
if (relation == ShapeRelation.DISJOINT) {
748749
throw new IllegalArgumentException("Field [" + name() + "] of type [" + typeName() + "] does not support DISJOINT ranges");
749750
}
750-
DateMathParser parser;
751-
if (forcedDateParser == null) {
752-
if (lowerTerm instanceof Number || upperTerm instanceof Number) {
753-
// force epoch_millis
754-
parser = EPOCH_MILLIS_PARSER;
755-
} else {
756-
parser = dateMathParser;
757-
}
758-
} else {
759-
parser = forcedDateParser;
760-
}
761-
return dateRangeQuery(lowerTerm, upperTerm, includeLower, includeUpper, timeZone, parser, context, resolution, (l, u) -> {
762-
Query query;
763-
if (isIndexed()) {
764-
query = LongPoint.newRangeQuery(name(), l, u);
765-
if (hasDocValues()) {
766-
Query dvQuery = SortedNumericDocValuesField.newSlowRangeQuery(name(), l, u);
767-
query = new IndexOrDocValuesQuery(query, dvQuery);
751+
DateMathParser parser = resolveDateMathParser(forcedDateParser, lowerTerm, upperTerm);
752+
return dateRangeQuery(
753+
lowerTerm,
754+
upperTerm,
755+
includeLower,
756+
includeUpper,
757+
timeZone,
758+
parser,
759+
context,
760+
resolution,
761+
name(),
762+
(l, u) -> {
763+
Query query;
764+
if (isIndexed()) {
765+
query = LongPoint.newRangeQuery(name(), l, u);
766+
if (hasDocValues()) {
767+
Query dvQuery = SortedNumericDocValuesField.newSlowRangeQuery(name(), l, u);
768+
query = new IndexOrDocValuesQuery(query, dvQuery);
769+
}
770+
} else {
771+
query = SortedNumericDocValuesField.newSlowRangeQuery(name(), l, u);
768772
}
769-
} else {
770-
query = SortedNumericDocValuesField.newSlowRangeQuery(name(), l, u);
771-
}
772-
if (hasDocValues() && context.indexSortedOnField(name())) {
773-
query = new IndexSortSortedNumericDocValuesRangeQuery(name(), l, u, query);
773+
if (hasDocValues() && context.indexSortedOnField(name())) {
774+
query = new IndexSortSortedNumericDocValuesRangeQuery(name(), l, u, query);
775+
}
776+
return query;
774777
}
775-
return query;
776-
});
778+
);
777779
}
778780

779781
public static Query dateRangeQuery(
@@ -785,6 +787,7 @@ public static Query dateRangeQuery(
785787
DateMathParser parser,
786788
SearchExecutionContext context,
787789
Resolution resolution,
790+
String fieldName,
788791
BiFunction<Long, Long, Query> builder
789792
) {
790793
return handleNow(context, nowSupplier -> {
@@ -796,6 +799,9 @@ public static Query dateRangeQuery(
796799
if (includeLower == false) {
797800
++l;
798801
}
802+
if (fieldName.equals(DataStream.TIMESTAMP_FIELD_NAME)) {
803+
context.setRangeTimestampFrom(l);
804+
}
799805
}
800806
if (upperTerm == null) {
801807
u = Long.MAX_VALUE;
@@ -951,6 +957,17 @@ public Relation isFieldWithinQuery(
951957
return isFieldWithinQuery(minValue, maxValue, from, to, includeLower, includeUpper, timeZone, dateParser, context);
952958
}
953959

960+
public DateMathParser resolveDateMathParser(DateMathParser dateParser, Object from, Object to) {
961+
if (dateParser == null) {
962+
if (from instanceof Number || to instanceof Number) {
963+
// force epoch_millis
964+
return EPOCH_MILLIS_PARSER;
965+
}
966+
return this.dateMathParser;
967+
}
968+
return dateParser;
969+
}
970+
954971
public Relation isFieldWithinQuery(
955972
long minValue,
956973
long maxValue,
@@ -962,14 +979,7 @@ public Relation isFieldWithinQuery(
962979
DateMathParser dateParser,
963980
QueryRewriteContext context
964981
) {
965-
if (dateParser == null) {
966-
if (from instanceof Number || to instanceof Number) {
967-
// force epoch_millis
968-
dateParser = EPOCH_MILLIS_PARSER;
969-
} else {
970-
dateParser = this.dateMathParser;
971-
}
972-
}
982+
dateParser = resolveDateMathParser(dateParser, from, to);
973983

974984
long fromInclusive = Long.MIN_VALUE;
975985
if (from != null) {

server/src/main/java/org/elasticsearch/index/mapper/DateScriptFieldType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ public Query rangeQuery(
297297
parser,
298298
context,
299299
DateFieldMapper.Resolution.MILLISECONDS,
300+
name(),
300301
(l, u) -> new LongScriptFieldRangeQuery(script, leafFactory(context)::newInstance, name(), l, u)
301302
);
302303
}

server/src/main/java/org/elasticsearch/index/query/SearchExecutionContext.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ public class SearchExecutionContext extends QueryRewriteContext {
109109
private final Integer requestSize;
110110
private final MapperMetrics mapperMetrics;
111111

112+
private Long rangeTimestampFrom;
113+
112114
/**
113115
* Build a {@linkplain SearchExecutionContext}.
114116
*/
@@ -236,6 +238,9 @@ public SearchExecutionContext(SearchExecutionContext source) {
236238
source.requestSize,
237239
source.mapperMetrics
238240
);
241+
// TODO address this
242+
// setRangeEventIngestedFrom(source.rangeEventIngestedFrom);
243+
// setRangeTimestampFrom(source.rangeTimestampFrom);
239244
}
240245

241246
private SearchExecutionContext(
@@ -300,6 +305,7 @@ private void reset() {
300305
this.lookup = null;
301306
this.namedQueries.clear();
302307
this.nestedScope = new NestedScope();
308+
303309
}
304310

305311
// Set alias filter, so it can be applied for queries that need it (e.g. knn query)
@@ -742,4 +748,22 @@ public void setRewriteToNamedQueries() {
742748
public boolean rewriteToNamedQuery() {
743749
return rewriteToNamedQueries;
744750
}
751+
752+
/**
753+
* Returns the minimum lower bound across the time ranges filters against the @timestamp field included in the query
754+
*/
755+
public Long getRangeTimestampFrom() {
756+
return rangeTimestampFrom;
757+
}
758+
759+
/**
760+
* Records the lower bound of a time range filter against the @timestamp field included in the query. For telemetry purposes.
761+
*/
762+
public void setRangeTimestampFrom(Long rangeTimestampFrom) {
763+
if (this.rangeTimestampFrom == null) {
764+
this.rangeTimestampFrom = rangeTimestampFrom;
765+
} else {
766+
this.rangeTimestampFrom = Math.min(rangeTimestampFrom, this.rangeTimestampFrom);
767+
}
768+
}
745769
}

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

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
package org.elasticsearch.index.search.stats;
1111

1212
import org.elasticsearch.action.search.SearchRequestAttributesExtractor;
13+
import org.elasticsearch.index.query.SearchExecutionContext;
1314
import org.elasticsearch.index.shard.SearchOperationListener;
1415
import org.elasticsearch.search.internal.SearchContext;
1516
import org.elasticsearch.search.internal.ShardSearchRequest;
@@ -42,16 +43,29 @@ public ShardSearchPhaseAPMMetrics(MeterRegistry meterRegistry) {
4243

4344
@Override
4445
public void onQueryPhase(SearchContext searchContext, long tookInNanos) {
45-
recordPhaseLatency(queryPhaseMetric, tookInNanos, searchContext.request());
46+
SearchExecutionContext searchExecutionContext = searchContext.getSearchExecutionContext();
47+
Long rangeTimestampFrom = searchExecutionContext.getRangeTimestampFrom();
48+
recordPhaseLatency(queryPhaseMetric, tookInNanos, searchContext.request(), rangeTimestampFrom);
4649
}
4750

4851
@Override
4952
public void onFetchPhase(SearchContext searchContext, long tookInNanos) {
50-
recordPhaseLatency(fetchPhaseMetric, tookInNanos, searchContext.request());
53+
SearchExecutionContext searchExecutionContext = searchContext.getSearchExecutionContext();
54+
Long rangeTimestampFrom = searchExecutionContext.getRangeTimestampFrom();
55+
recordPhaseLatency(fetchPhaseMetric, tookInNanos, searchContext.request(), rangeTimestampFrom);
5156
}
5257

53-
private static void recordPhaseLatency(LongHistogram histogramMetric, long tookInNanos, ShardSearchRequest request) {
54-
Map<String, Object> attributes = SearchRequestAttributesExtractor.extractAttributes(request);
58+
private static void recordPhaseLatency(
59+
LongHistogram histogramMetric,
60+
long tookInNanos,
61+
ShardSearchRequest request,
62+
Long rangeTimestampFrom
63+
) {
64+
Map<String, Object> attributes = SearchRequestAttributesExtractor.extractAttributes(
65+
request,
66+
rangeTimestampFrom,
67+
request.nowInMillis()
68+
);
5569
histogramMetric.record(TimeUnit.NANOSECONDS.toMillis(tookInNanos), attributes);
5670
}
5771
}

server/src/test/java/org/elasticsearch/action/search/SearchRequestAttributesExtractorTests.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,4 +361,56 @@ public void testDepthLimit() {
361361
assertAttributes(stringObjectMap, "user", "_score", "hits_only", false, false, false, null);
362362
}
363363
}
364+
365+
public void testIntrospectTimeRange() {
366+
long nowInMillis = System.currentTimeMillis();
367+
assertEquals("15_minutes", SearchRequestAttributesExtractor.introspectTimeRange(nowInMillis, nowInMillis));
368+
369+
long fifteenMinutesAgo = nowInMillis - (15 * 60 * 1000);
370+
assertEquals(
371+
"15_minutes",
372+
SearchRequestAttributesExtractor.introspectTimeRange(randomLongBetween(fifteenMinutesAgo, nowInMillis), nowInMillis)
373+
);
374+
375+
long oneHourAgo = nowInMillis - (60 * 60 * 1000);
376+
assertEquals(
377+
"1_hour",
378+
SearchRequestAttributesExtractor.introspectTimeRange(randomLongBetween(oneHourAgo, fifteenMinutesAgo), nowInMillis)
379+
);
380+
381+
long twelveHoursAgo = nowInMillis - (12 * 60 * 60 * 1000);
382+
assertEquals(
383+
"12_hours",
384+
SearchRequestAttributesExtractor.introspectTimeRange(randomLongBetween(twelveHoursAgo, oneHourAgo), nowInMillis)
385+
);
386+
387+
long oneDayAgo = nowInMillis - (24 * 60 * 60 * 1000);
388+
assertEquals(
389+
"1_day",
390+
SearchRequestAttributesExtractor.introspectTimeRange(randomLongBetween(oneDayAgo, twelveHoursAgo), nowInMillis)
391+
);
392+
393+
long threeDaysAgo = nowInMillis - (3 * 24 * 60 * 60 * 1000);
394+
assertEquals(
395+
"3_days",
396+
SearchRequestAttributesExtractor.introspectTimeRange(randomLongBetween(threeDaysAgo, oneDayAgo), nowInMillis)
397+
);
398+
399+
long sevenDaysAgo = nowInMillis - (7 * 24 * 60 * 60 * 1000);
400+
assertEquals(
401+
"7_days",
402+
SearchRequestAttributesExtractor.introspectTimeRange(randomLongBetween(sevenDaysAgo, threeDaysAgo), nowInMillis)
403+
);
404+
405+
long fourteenDaysAgo = nowInMillis - (14 * 24 * 60 * 60 * 1000);
406+
assertEquals(
407+
"14_days",
408+
SearchRequestAttributesExtractor.introspectTimeRange(randomLongBetween(fourteenDaysAgo, sevenDaysAgo), nowInMillis)
409+
);
410+
411+
assertEquals(
412+
"older_than_14_days",
413+
SearchRequestAttributesExtractor.introspectTimeRange(randomLongBetween(0, fourteenDaysAgo), nowInMillis)
414+
);
415+
}
364416
}

0 commit comments

Comments
 (0)