From 98d9012f00b62e0685cdf13d4be573ff393e520f Mon Sep 17 00:00:00 2001 From: Niladri Adhikary <91966855+niladrix719@users.noreply.github.com> Date: Fri, 9 Oct 2026 10:03:36 +0000 Subject: [PATCH] fix(promql): return no point for zero-interval irate/idelta (#9455) fix(promql): irate/idelta return no point for a zero-ms sample gap Signed-off-by: Niladri Adhikary (cherry picked from commit 0dde3a7b7091381856d9e974cfc7aaf033891aab) Signed-off-by: WenyXu --- src/promql/src/functions/idelta.rs | 35 ++++++++- .../promql/sub_millisecond_interval.result | 77 +++++++++++++++++++ .../promql/sub_millisecond_interval.sql | 30 ++++++++ 3 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 tests/cases/standalone/common/promql/sub_millisecond_interval.result create mode 100644 tests/cases/standalone/common/promql/sub_millisecond_interval.sql diff --git a/src/promql/src/functions/idelta.rs b/src/promql/src/functions/idelta.rs index 35ce44ba270..5734733234d 100644 --- a/src/promql/src/functions/idelta.rs +++ b/src/promql/src/functions/idelta.rs @@ -140,8 +140,13 @@ impl IDelta { let last_offset = ts_offset + last_position; let prev_offset = ts_offset + prev_position; - let sampled_interval = - (ts_values[last_offset] - ts_values[prev_offset]) as f64 / 1000.0; + let sampled_interval_ms = ts_values[last_offset] - ts_values[prev_offset]; + // Sub-millisecond samples round to the same ms; skip instead of dividing by zero. + if sampled_interval_ms == 0 { + result_builder.append_null(); + continue; + } + let sampled_interval = sampled_interval_ms as f64 / 1000.0; let last_value = value_values[value_offset + last_position]; let prev_value = value_values[value_offset + prev_position]; @@ -263,4 +268,30 @@ mod test { vec![Some(3.0), None, None], ); } + + #[test] + fn idelta_skips_samples_in_same_millisecond() { + // The last two samples of the first range share 999ms; the second range is 1ms apart. + let ts_array = Arc::new(TimestampMillisecondArray::from_iter_values([ + 999i64, 999, 999, 1000, + ])); + let values_array = Arc::new(Float64Array::from_iter([10.0, 13.0, 999.0, 1000.0])); + let ranges = [(0, 3), (2, 2)]; + + simple_range_udf_runner( + IDelta::::scalar_udf(), + RangeArray::from_ranges(ts_array.clone(), ranges).unwrap(), + RangeArray::from_ranges(values_array.clone(), ranges).unwrap(), + vec![], + vec![None, Some(1.0)], + ); + + simple_range_udf_runner( + IDelta::::scalar_udf(), + RangeArray::from_ranges(ts_array, ranges).unwrap(), + RangeArray::from_ranges(values_array, ranges).unwrap(), + vec![], + vec![None, Some(1000.0)], + ); + } } diff --git a/tests/cases/standalone/common/promql/sub_millisecond_interval.result b/tests/cases/standalone/common/promql/sub_millisecond_interval.result new file mode 100644 index 00000000000..6461c8e0b0d --- /dev/null +++ b/tests/cases/standalone/common/promql/sub_millisecond_interval.result @@ -0,0 +1,77 @@ +-- The last two 'sub_ms' samples are 400ns apart and share one millisecond, which +-- irate and idelta cannot resolve, so they return no point instead of inf. +-- The 'one_ms' samples are 1ms apart and still produce a rate. +CREATE TABLE sub_ms_interval ( + job STRING, + `value` DOUBLE, + ts TIMESTAMP(9) TIME INDEX, + PRIMARY KEY(job) +); + +Affected Rows: 0 + +INSERT INTO sub_ms_interval VALUES + ('sub_ms', 999, 999999900), + ('sub_ms', 10, 999999100), + ('sub_ms', 13, 999999500), + ('one_ms', 10, 998000000), + ('one_ms', 13, 999000000); + +Affected Rows: 5 + +SELECT * FROM sub_ms_interval ORDER BY job, ts; + ++--------+-------+-------------------------------+ +| job | value | ts | ++--------+-------+-------------------------------+ +| one_ms | 10.0 | 1970-01-01T00:00:00.998 | +| one_ms | 13.0 | 1970-01-01T00:00:00.999 | +| sub_ms | 10.0 | 1970-01-01T00:00:00.999999100 | +| sub_ms | 13.0 | 1970-01-01T00:00:00.999999500 | +| sub_ms | 999.0 | 1970-01-01T00:00:00.999999900 | ++--------+-------+-------------------------------+ + +TQL EVAL (1, 1, '1s') irate(sub_ms_interval[5s]); + ++---------------------+----------------------------+--------+ +| ts | prom_irate(ts_range,value) | job | ++---------------------+----------------------------+--------+ +| 1970-01-01T00:00:01 | 3000.0 | one_ms | ++---------------------+----------------------------+--------+ + +TQL EVAL (1, 1, '1s') idelta(sub_ms_interval[5s]); + ++---------------------+-----------------------------+--------+ +| ts | prom_idelta(ts_range,value) | job | ++---------------------+-----------------------------+--------+ +| 1970-01-01T00:00:01 | 3.0 | one_ms | ++---------------------+-----------------------------+--------+ + +ADMIN FLUSH_TABLE('sub_ms_interval'); + ++--------------------------------------+ +| ADMIN FLUSH_TABLE('sub_ms_interval') | ++--------------------------------------+ +| 0 | ++--------------------------------------+ + +TQL EVAL (1, 1, '1s') irate(sub_ms_interval[5s]); + ++---------------------+----------------------------+--------+ +| ts | prom_irate(ts_range,value) | job | ++---------------------+----------------------------+--------+ +| 1970-01-01T00:00:01 | 3000.0 | one_ms | ++---------------------+----------------------------+--------+ + +TQL EVAL (1, 1, '1s') idelta(sub_ms_interval[5s]); + ++---------------------+-----------------------------+--------+ +| ts | prom_idelta(ts_range,value) | job | ++---------------------+-----------------------------+--------+ +| 1970-01-01T00:00:01 | 3.0 | one_ms | ++---------------------+-----------------------------+--------+ + +DROP TABLE sub_ms_interval; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/promql/sub_millisecond_interval.sql b/tests/cases/standalone/common/promql/sub_millisecond_interval.sql new file mode 100644 index 00000000000..d643479390d --- /dev/null +++ b/tests/cases/standalone/common/promql/sub_millisecond_interval.sql @@ -0,0 +1,30 @@ +-- The last two 'sub_ms' samples are 400ns apart and share one millisecond, which +-- irate and idelta cannot resolve, so they return no point instead of inf. +-- The 'one_ms' samples are 1ms apart and still produce a rate. +CREATE TABLE sub_ms_interval ( + job STRING, + `value` DOUBLE, + ts TIMESTAMP(9) TIME INDEX, + PRIMARY KEY(job) +); + +INSERT INTO sub_ms_interval VALUES + ('sub_ms', 999, 999999900), + ('sub_ms', 10, 999999100), + ('sub_ms', 13, 999999500), + ('one_ms', 10, 998000000), + ('one_ms', 13, 999000000); + +SELECT * FROM sub_ms_interval ORDER BY job, ts; + +TQL EVAL (1, 1, '1s') irate(sub_ms_interval[5s]); + +TQL EVAL (1, 1, '1s') idelta(sub_ms_interval[5s]); + +ADMIN FLUSH_TABLE('sub_ms_interval'); + +TQL EVAL (1, 1, '1s') irate(sub_ms_interval[5s]); + +TQL EVAL (1, 1, '1s') idelta(sub_ms_interval[5s]); + +DROP TABLE sub_ms_interval;