From 2bf0bf42479f58c343c31aebf977e586ca9c640b Mon Sep 17 00:00:00 2001 From: Dennis Zhuang Date: Sat, 17 Oct 2026 22:30:39 +0800 Subject: [PATCH] fix(promql): drop metadata labels from range function results Prometheus drops __name__, __type__ and __unit__ from the result of every range function except last_over_time and first_over_time; the evaluator passed the metric through unchanged. Signed-off-by: Dennis Zhuang --- src/promql/src/range_eval.rs | 15 ++++++- src/promql/src/range_eval/function.rs | 6 +++ src/promql/src/range_eval/tests.rs | 58 ++++++++++++++++++++++++++- 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/src/promql/src/range_eval.rs b/src/promql/src/range_eval.rs index a0034fff0b9..5fa9aa26bbd 100644 --- a/src/promql/src/range_eval.rs +++ b/src/promql/src/range_eval.rs @@ -70,8 +70,12 @@ pub struct RangeEvaluator { series: Option, fields: Vec, row: Vec>, + drops_metric_name: bool, } +/// Prometheus `schema.IsMetadataLabel`: the labels removed with the metric name. +const METADATA_LABELS: [&str; 3] = ["__name__", "__type__", "__unit__"]; + struct SeriesState { primary_key: Buffer, metric: MetricName, @@ -119,7 +123,10 @@ impl RangeEvaluator { } } } - Self::with_kernel(input, grid, window, function.kernel(window.offset_ms)) + let mut evaluator = + Self::with_kernel(input, grid, window, function.kernel(window.offset_ms))?; + evaluator.drops_metric_name = function.drops_metric_name(); + Ok(evaluator) } pub(crate) fn with_kernel( @@ -156,6 +163,7 @@ impl RangeEvaluator { .map(|_| FieldSamples::default()) .collect(), row: Vec::with_capacity(input.fields.len()), + drops_metric_name: false, }) } @@ -182,12 +190,15 @@ impl RangeEvaluator { return Ok(None); } self.evaluate(None)?; - let state = self.series.take().expect("checked by check_chunk"); + let mut state = self.series.take().expect("checked by check_chunk"); for field in &mut self.fields { field.timestamps.clear(); field.values.clear(); field.head = 0; } + if self.drops_metric_name { + state.metric.remove_all(&METADATA_LABELS); + } self.output.finish(state.metric) } diff --git a/src/promql/src/range_eval/function.rs b/src/promql/src/range_eval/function.rs index ca6dbf9d0b1..f48ea7a469e 100644 --- a/src/promql/src/range_eval/function.rs +++ b/src/promql/src/range_eval/function.rs @@ -53,6 +53,12 @@ pub enum RangeFunction { } impl RangeFunction { + /// Whether the result drops the metric name, as Prometheus does for every range function + /// except `last_over_time` and `first_over_time`. + pub(crate) fn drops_metric_name(self) -> bool { + !matches!(self, Self::LastOverTime) + } + /// Returns the kernel computing this function over windows read with `offset_ms`. pub(crate) fn kernel(self, offset_ms: i64) -> WindowKernel { match self { diff --git a/src/promql/src/range_eval/tests.rs b/src/promql/src/range_eval/tests.rs index 23d787581a4..d19f8bdd4f8 100644 --- a/src/promql/src/range_eval/tests.rs +++ b/src/promql/src/range_eval/tests.rs @@ -99,6 +99,15 @@ fn probe_evaluator(fields: usize, grid: Arc, window: RangeWindow) -> R } /// Builds a chunk whose timestamp and value arrays are slices at a nonzero offset. +/// The metric of a result of `function` over the series of `metric(host)`. +fn output_metric(function: RangeFunction, host: u64) -> MetricName { + let mut metric = metric(host); + if function.drops_metric_name() { + metric.remove("__name__"); + } + metric +} + fn chunk(rows: &[Row], fields: usize, host: u64, is_last: bool) -> RawSeriesChunk { let padding = 3; let timestamps: Vec<_> = (0..padding) @@ -408,7 +417,7 @@ fn functions_match_range_udfs() { for piece in random_chunks(&mut rng, &rows, fields, host) { result = evaluator.push(&piece).unwrap(); } - assert_series_eq(result.as_ref(), &expected, &metric(host)); + assert_series_eq(result.as_ref(), &expected, &output_metric(function, host)); } } } @@ -690,7 +699,12 @@ fn range_functions_match_prometheus() { .push(&chunk(&rows[split..], 1, 0, true)) .unwrap() .or(result); - assert_series_eq_any_nan(result.as_ref(), &expected, &metric(0), line); + assert_series_eq_any_nan( + result.as_ref(), + &expected, + &output_metric(function, 0), + line, + ); checked += expected_points.len(); } assert!(checked > 1_000, "{checked} points checked"); @@ -714,3 +728,43 @@ fn rejects_double_exponential_smoothing_factors_outside_the_unit_interval() { )); } } + +/// Range functions drop the metric name and the other metadata labels, except +/// `last_over_time`, as Prometheus does. +#[test] +fn range_functions_drop_metadata_labels() { + let labels = |names: &[&str]| -> Vec