diff --git a/src/promql/src/range_eval.rs b/src/promql/src/range_eval.rs index ffb49c29009..7c89d227dc4 100644 --- a/src/promql/src/range_eval.rs +++ b/src/promql/src/range_eval.rs @@ -79,8 +79,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 { /// `None` for evaluated input, which arrives as one complete series. primary_key: Option, @@ -144,13 +148,15 @@ impl RangeEvaluator { } } } - Self::with_kernel( + let mut evaluator = Self::with_kernel( input, grid, window, function.kernel(fold_offset), step_invariant, - ) + )?; + evaluator.drops_metric_name = function.drops_metric_name(); + Ok(evaluator) } /// `step_invariant` tells whether an `@`-anchored result holds at every grid point. @@ -192,6 +198,7 @@ impl RangeEvaluator { .map(|_| FieldSamples::default()) .collect(), row: Vec::with_capacity(input.fields.len()), + drops_metric_name: false, }) } @@ -259,12 +266,15 @@ impl RangeEvaluator { fn finish_series(&mut self) -> Result> { self.evaluate(None)?; - let state = self.series.take().expect("set by the caller"); + let mut state = self.series.take().expect("set by the caller"); 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 7d96c2b21e5..c2cf0d0636d 100644 --- a/src/promql/src/range_eval/function.rs +++ b/src/promql/src/range_eval/function.rs @@ -60,6 +60,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 e0849fea046..2c372c03387 100644 --- a/src/promql/src/range_eval/tests.rs +++ b/src/promql/src/range_eval/tests.rs @@ -110,6 +110,15 @@ fn per_point_probe_evaluator( } /// 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) @@ -467,7 +476,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)); } } } @@ -744,7 +753,7 @@ fn subquery_series_match_references() { assert_series_eq( evaluator.push_series(&series).unwrap().as_ref(), &expected, - &metric(seed), + &output_metric(function, seed), ); } } @@ -891,7 +900,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 > 2_000, "{checked} points checked"); @@ -917,6 +931,47 @@ 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