Skip to content

Commit a780cf5

Browse files
authored
[ES|QL] Add support for counters to First/LastOverTime (#135676)
This commit adds support for running FirstOverTime and LastOverTime on counter fields (counter_long, counter_integer, counter_double). The return value is the non-counter variation. For example, the following is a valid query and will return a double: ``` TS my_tsdb | STATS max(first_over_time(my_counter_double_field)) BY my_dimension, bucket(@timestamp, 10minute) ```
1 parent 0f2b336 commit a780cf5

File tree

13 files changed

+226
-35
lines changed

13 files changed

+226
-35
lines changed

docs/reference/query-languages/esql/_snippets/functions/types/first_over_time.md

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/query-languages/esql/_snippets/functions/types/last_over_time.md

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/query-languages/esql/kibana/definition/functions/first_over_time.json

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/query-languages/esql/kibana/definition/functions/last_over_time.json

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/DataType.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,15 @@ public DataType noText() {
796796
return isString(this) ? KEYWORD : this;
797797
}
798798

799+
public DataType noCounter() {
800+
return switch (this) {
801+
case COUNTER_DOUBLE -> DOUBLE;
802+
case COUNTER_INTEGER -> INTEGER;
803+
case COUNTER_LONG -> LONG;
804+
default -> this;
805+
};
806+
}
807+
799808
public boolean isDate() {
800809
return switch (this) {
801810
case DATETIME, DATE_NANOS -> true;

x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/EsqlQueryGenerator.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,12 +295,15 @@ yield switch ((randomIntBetween(0, 6))) {
295295
if (counterField == null) {
296296
yield null;
297297
}
298-
yield "rate(" + counterField + ")";
298+
yield switch ((randomIntBetween(0, 2))) {
299+
case 0 -> "rate(" + counterField + ")";
300+
case 1 -> "first_over_time(" + counterField + ")";
301+
default -> "last_over_time(" + counterField + ")";
302+
};
299303
}
300304
case 2 -> {
301305
// numerics except aggregate_metric_double
302306
// TODO: add to case 0 when support for aggregate_metric_double is added to these functions
303-
// TODO: add to case 1 when support for counters is added
304307
String numericFieldName = randomNumericField(previousOutput);
305308
if (numericFieldName == null) {
306309
yield null;

x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-first-over-time.csv-spec

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,42 @@ events:long | pod:keyword | time_bucket:datetime
126126
6 | two | 2024-05-10T00:20:00.000Z
127127
;
128128

129+
first_over_time_counter_double
130+
required_capability: ts_command_v0
131+
required_capability: first_last_over_time_counter_support
132+
TS k8s
133+
| STATS sum = sum(first_over_time(network.total_cost)) by pod, time_bucket = bucket(@timestamp, 10minute)
134+
| SORT time_bucket, pod
135+
;
136+
137+
sum:double | pod:keyword | time_bucket:datetime
138+
35.75 | one | 2024-05-10T00:00:00.000Z
139+
34.375 | three | 2024-05-10T00:00:00.000Z
140+
30.375 | two | 2024-05-10T00:00:00.000Z
141+
96.5 | one | 2024-05-10T00:10:00.000Z
142+
191.75 | three | 2024-05-10T00:10:00.000Z
143+
163.25 | two | 2024-05-10T00:10:00.000Z
144+
224.875 | one | 2024-05-10T00:20:00.000Z
145+
142.875 | three | 2024-05-10T00:20:00.000Z
146+
163.625 | two | 2024-05-10T00:20:00.000Z
147+
;
148+
149+
first_over_time_counter_long
150+
required_capability: ts_command_v0
151+
required_capability: first_last_over_time_counter_support
152+
TS k8s
153+
| STATS max = max(first_over_time(network.total_bytes_in)) by pod, time_bucket = bucket(@timestamp, 10minute)
154+
| SORT time_bucket, pod
155+
;
156+
157+
max:long | pod:keyword | time_bucket:datetime
158+
1103 | one | 2024-05-10T00:00:00.000Z
159+
1441 | three | 2024-05-10T00:00:00.000Z
160+
1395 | two | 2024-05-10T00:00:00.000Z
161+
6077 | one | 2024-05-10T00:10:00.000Z
162+
4200 | three | 2024-05-10T00:10:00.000Z
163+
3478 | two | 2024-05-10T00:10:00.000Z
164+
10207 | one | 2024-05-10T00:20:00.000Z
165+
9969 | three | 2024-05-10T00:20:00.000Z
166+
8709 | two | 2024-05-10T00:20:00.000Z
167+
;

x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-last-over-time.csv-spec

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,3 +276,42 @@ events:long | pod:keyword | time_bucket:datetime
276276
5 | two | 2024-05-10T00:20:00.000Z
277277
;
278278

279+
last_over_time_counter_double
280+
required_capability: ts_command_v0
281+
required_capability: first_last_over_time_counter_support
282+
TS k8s
283+
| STATS sum = sum(last_over_time(network.total_cost)) by pod, time_bucket = bucket(@timestamp, 10minute)
284+
| SORT time_bucket, pod
285+
;
286+
287+
sum:double | pod:keyword | time_bucket:datetime
288+
190.125 | one | 2024-05-10T00:00:00.000Z
289+
158.75 | three | 2024-05-10T00:00:00.000Z
290+
148.25 | two | 2024-05-10T00:00:00.000Z
291+
220.125 | one | 2024-05-10T00:10:00.000Z
292+
271.75 | three | 2024-05-10T00:10:00.000Z
293+
160.75 | two | 2024-05-10T00:10:00.000Z
294+
190.625 | one | 2024-05-10T00:20:00.000Z
295+
180.375 | three | 2024-05-10T00:20:00.000Z
296+
191.0 | two | 2024-05-10T00:20:00.000Z
297+
;
298+
299+
last_over_time_counter_long
300+
required_capability: ts_command_v0
301+
required_capability: first_last_over_time_counter_support
302+
TS k8s
303+
| STATS max = max(last_over_time(network.total_bytes_in)) by pod, time_bucket = bucket(@timestamp, 10minute)
304+
| SORT time_bucket, pod
305+
;
306+
307+
max:long | pod:keyword | time_bucket:datetime
308+
5963 | one | 2024-05-10T00:00:00.000Z
309+
4169 | three | 2024-05-10T00:00:00.000Z
310+
7900 | two | 2024-05-10T00:00:00.000Z
311+
9254 | one | 2024-05-10T00:10:00.000Z
312+
9195 | three | 2024-05-10T00:10:00.000Z
313+
8031 | two | 2024-05-10T00:10:00.000Z
314+
7286 | one | 2024-05-10T00:20:00.000Z
315+
10797 | three | 2024-05-10T00:20:00.000Z
316+
10277 | two | 2024-05-10T00:20:00.000Z
317+
;

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1562,6 +1562,11 @@ public enum Cap {
15621562
*/
15631563
TS_COMMAND_V0(),
15641564

1565+
/**
1566+
* Add support for counter doubles, ints, and longs in first_ and last_over_time
1567+
*/
1568+
FIRST_LAST_OVER_TIME_COUNTER_SUPPORT,
1569+
15651570
FIX_ALIAS_ID_WHEN_DROP_ALL_AGGREGATES,
15661571

15671572
/**

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/FirstOverTime.java

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@ public class FirstOverTime extends TimeSeriesAggregateFunction implements Option
5656
preview = true,
5757
examples = { @Example(file = "k8s-timeseries", tag = "first_over_time") }
5858
)
59-
public FirstOverTime(Source source, @Param(name = "field", type = { "long", "integer", "double" }) Expression field) {
59+
public FirstOverTime(
60+
Source source,
61+
@Param(name = "field", type = { "counter_long", "counter_integer", "counter_double", "long", "integer", "double" }) Expression field
62+
) {
6063
this(source, field, new UnresolvedAttribute(source, "@timestamp"));
6164
}
6265

@@ -109,21 +112,20 @@ public FirstOverTime withFilter(Expression filter) {
109112

110113
@Override
111114
public DataType dataType() {
112-
return field().dataType();
115+
return field().dataType().noCounter();
113116
}
114117

115118
@Override
116119
protected TypeResolution resolveType() {
117-
return isType(field(), dt -> dt.isNumeric() && dt != DataType.UNSIGNED_LONG, sourceText(), DEFAULT, "numeric except unsigned_long")
118-
.and(
119-
isType(
120-
timestamp,
121-
dt -> dt == DataType.DATETIME || dt == DataType.DATE_NANOS,
122-
sourceText(),
123-
SECOND,
124-
"date_nanos or datetime"
125-
)
126-
);
120+
return isType(
121+
field(),
122+
dt -> (dt.noCounter().isNumeric() && dt != DataType.UNSIGNED_LONG) || dt == DataType.AGGREGATE_METRIC_DOUBLE,
123+
sourceText(),
124+
DEFAULT,
125+
"numeric except unsigned_long"
126+
).and(
127+
isType(timestamp, dt -> dt == DataType.DATETIME || dt == DataType.DATE_NANOS, sourceText(), SECOND, "date_nanos or datetime")
128+
);
127129
}
128130

129131
@Override
@@ -132,9 +134,9 @@ public AggregatorFunctionSupplier supplier() {
132134
// we can read the first encountered value for each group of `_tsid` and time bucket.
133135
final DataType type = field().dataType();
134136
return switch (type) {
135-
case LONG -> new FirstLongByTimestampAggregatorFunctionSupplier();
136-
case INTEGER -> new FirstIntByTimestampAggregatorFunctionSupplier();
137-
case DOUBLE -> new FirstDoubleByTimestampAggregatorFunctionSupplier();
137+
case LONG, COUNTER_LONG -> new FirstLongByTimestampAggregatorFunctionSupplier();
138+
case INTEGER, COUNTER_INTEGER -> new FirstIntByTimestampAggregatorFunctionSupplier();
139+
case DOUBLE, COUNTER_DOUBLE -> new FirstDoubleByTimestampAggregatorFunctionSupplier();
138140
case FLOAT -> new FirstFloatByTimestampAggregatorFunctionSupplier();
139141
default -> throw EsqlIllegalArgumentException.illegalDataType(type);
140142
};

0 commit comments

Comments
 (0)