From e92c5fc29d81c314aed01b7cf73afd44067a822b Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Wed, 31 Dec 2025 14:50:28 +0800 Subject: [PATCH 01/12] include metric name in annotations from histogram_quantile if delayed name removal is enabled --- pkg/streamingpromql/engine_test.go | 98 ++++++++++++++++--- .../operators/functions/histogram_function.go | 38 ++++--- .../testdata/upstream/native_histograms.test | 22 ++--- 3 files changed, 118 insertions(+), 40 deletions(-) diff --git a/pkg/streamingpromql/engine_test.go b/pkg/streamingpromql/engine_test.go index 8cdf235a0f2..3b334b45bed 100644 --- a/pkg/streamingpromql/engine_test.go +++ b/pkg/streamingpromql/engine_test.go @@ -2201,12 +2201,18 @@ func (t *timeoutTestingQueryTracker) Close() error { } type annotationTestCase struct { - data string - expr string - expectedWarningAnnotations []string - expectedInfoAnnotations []string - skipComparisonWithPrometheusReason string - instantEvaluationTimestamp *time.Time + data string + expr string + expectedWarningAnnotations []string + // an alternate string for when delayed name removal is enabled. + // if not set the test will fall back to expectedWarningAnnotations + expectedWarningAnnotationsDelayedNameRemovalEnabled []string + expectedInfoAnnotations []string + // an alternate string for when delayed name removal is enabled. + // if not set the test will fall back to expectedInfoAnnotations + expectedInfoAnnotationsDelayedNameRemovalEnabled []string + skipComparisonWithPrometheusReason string + instantEvaluationTimestamp *time.Time } func runAnnotationTests(t *testing.T, testCases map[string]annotationTestCase) { @@ -2214,19 +2220,39 @@ func runAnnotationTests(t *testing.T, testCases map[string]annotationTestCase) { step := time.Minute endT := startT.Add(2 * step) - opts := NewTestEngineOpts() - planner, err := NewQueryPlanner(opts, NewMaximumSupportedVersionQueryPlanVersionProvider()) + // create 2 sets of engines - one with EnableDelayedNameRemoval=true and the other with EnableDelayedNameRemoval=false + // there are some histogram annotation test cases will be return a different warning/info depending on whether delayed name removal is enabled or not + optsDelayedNameRemovalEnabled := NewTestEngineOpts() + optsDelayedNameRemovalEnabled.CommonOpts.EnableDelayedNameRemoval = true + + plannerDelayedNameRemovalEnabled, err := NewQueryPlanner(optsDelayedNameRemovalEnabled, NewMaximumSupportedVersionQueryPlanVersionProvider()) require.NoError(t, err) - mimirEngine, err := NewEngine(opts, NewStaticQueryLimitsProvider(0), stats.NewQueryMetrics(nil), planner) + mimirEngineDelayedNameRemovalEnabled, err := NewEngine(optsDelayedNameRemovalEnabled, NewStaticQueryLimitsProvider(0), stats.NewQueryMetrics(nil), plannerDelayedNameRemovalEnabled) require.NoError(t, err) - prometheusEngine := promql.NewEngine(opts.CommonOpts) + prometheusEngineDelayedNameRemovalEnabled := promql.NewEngine(optsDelayedNameRemovalEnabled.CommonOpts) + + optsDelayedNameRemovalDisabled := NewTestEngineOpts() + optsDelayedNameRemovalDisabled.CommonOpts.EnableDelayedNameRemoval = false + + plannerDelayedNameRemovalDisabled, err := NewQueryPlanner(optsDelayedNameRemovalDisabled, NewMaximumSupportedVersionQueryPlanVersionProvider()) + require.NoError(t, err) + mimirEngineDelayedNameRemovalDisabled, err := NewEngine(optsDelayedNameRemovalDisabled, NewStaticQueryLimitsProvider(0), stats.NewQueryMetrics(nil), plannerDelayedNameRemovalDisabled) + require.NoError(t, err) + prometheusEngineDelayedNameRemovalDisabled := promql.NewEngine(optsDelayedNameRemovalDisabled.CommonOpts) const prometheusEngineName = "Prometheus' engine" - engines := map[string]promql.QueryEngine{ - "Mimir's engine": mimirEngine, + enginesDelayedNameRemovalDisabled := map[string]promql.QueryEngine{ + "Mimir's engine": mimirEngineDelayedNameRemovalDisabled, + + // Compare against Prometheus' engine to verify our test cases are valid. + prometheusEngineName: prometheusEngineDelayedNameRemovalDisabled, + } + + enginesDelayedNameRemovalEnabled := map[string]promql.QueryEngine{ + "Mimir's engine": mimirEngineDelayedNameRemovalEnabled, // Compare against Prometheus' engine to verify our test cases are valid. - prometheusEngineName: prometheusEngine, + prometheusEngineName: prometheusEngineDelayedNameRemovalEnabled, } for name, testCase := range testCases { @@ -2253,7 +2279,7 @@ func runAnnotationTests(t *testing.T, testCases map[string]annotationTestCase) { t.Run(queryType, func(t *testing.T) { results := make([]*promql.Result, 0, 2) - for engineName, engine := range engines { + for engineName, engine := range enginesDelayedNameRemovalDisabled { if engineName == prometheusEngineName && testCase.skipComparisonWithPrometheusReason != "" { t.Logf("Skipping comparison with Prometheus' engine: %v", testCase.skipComparisonWithPrometheusReason) continue @@ -2279,6 +2305,41 @@ func runAnnotationTests(t *testing.T, testCases map[string]annotationTestCase) { // or vice-versa where no result may be expected etc. testutils.RequireEqualResults(t, testCase.expr, results[0], results[1], false) } + + for engineName, engine := range enginesDelayedNameRemovalEnabled { + if engineName == prometheusEngineName && testCase.skipComparisonWithPrometheusReason != "" { + t.Logf("Skipping comparison with Prometheus' engine: %v", testCase.skipComparisonWithPrometheusReason) + continue + } + t.Run(engineName, func(t *testing.T) { + query, err := generator(engine) + require.NoError(t, err) + t.Cleanup(query.Close) + + res := query.Exec(context.Background()) + require.NoError(t, res.Err) + results = append(results, res) + + warnings, infos := res.Warnings.AsStrings(testCase.expr, 0, 0) + expectedWarningAnnotations := testCase.expectedWarningAnnotationsDelayedNameRemovalEnabled + if expectedWarningAnnotations == nil { + expectedWarningAnnotations = testCase.expectedWarningAnnotations + } + expectedInfoAnnotations := testCase.expectedInfoAnnotationsDelayedNameRemovalEnabled + if expectedInfoAnnotations == nil { + expectedInfoAnnotations = testCase.expectedInfoAnnotations + } + require.ElementsMatch(t, expectedWarningAnnotations, warnings) + require.ElementsMatch(t, expectedInfoAnnotations, infos) + }) + } + + // If both results are available, compare them (sometimes we skip prometheus) + if len(results) == 2 { + // We do this extra comparison to ensure that we don't skip a series that may be outputted during a warning + // or vice-versa where no result may be expected etc. + testutils.RequireEqualResults(t, testCase.expr, results[0], results[1], false) + } }) } }) @@ -3141,6 +3202,7 @@ func TestHistogramAnnotations(t *testing.T) { data: mixedClassicHistograms, expr: `histogram_quantile(0.5, series{host="c"})`, expectedWarningAnnotations: []string{`PromQL warning: bucket label "le" is missing or has a malformed value of "abc" (1:25)`}, + expectedWarningAnnotationsDelayedNameRemovalEnabled: []string{`PromQL warning: bucket label "le" is missing or has a malformed value of "abc" for metric name "series" (1:25)`}, }, "invalid quantile warning": { data: mixedClassicHistograms, @@ -3151,11 +3213,13 @@ func TestHistogramAnnotations(t *testing.T) { data: mixedClassicHistograms, expr: `histogram_quantile(0.5, series{host="a"})`, expectedWarningAnnotations: []string{`PromQL warning: vector contains a mix of classic and native histograms (1:25)`}, + expectedWarningAnnotationsDelayedNameRemovalEnabled: []string{`PromQL warning: vector contains a mix of classic and native histograms for metric name "series" (1:25)`}, }, "forced monotonicity info": { data: mixedClassicHistograms, expr: `histogram_quantile(0.5, series{host="d"})`, expectedInfoAnnotations: []string{`PromQL info: input to histogram_quantile needed to be fixed for monotonicity (see https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile) (1:25)`}, + expectedInfoAnnotationsDelayedNameRemovalEnabled: []string{`PromQL info: input to histogram_quantile needed to be fixed for monotonicity (see https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile) for metric name "series" (1:25)`}, }, "both mixed classic+native histogram and invalid quantile warnings": { data: mixedClassicHistograms, @@ -3164,6 +3228,10 @@ func TestHistogramAnnotations(t *testing.T) { `PromQL warning: vector contains a mix of classic and native histograms (1:23)`, `PromQL warning: quantile value should be between 0 and 1, got 9 (1:20)`, }, + expectedWarningAnnotationsDelayedNameRemovalEnabled: []string{ + `PromQL warning: vector contains a mix of classic and native histograms for metric name "series" (1:23)`, + `PromQL warning: quantile value should be between 0 and 1, got 9 (1:20)`, + }, }, "forced monotonicity info is not emitted when quantile is invalid": { data: mixedClassicHistograms, @@ -3176,6 +3244,7 @@ func TestHistogramAnnotations(t *testing.T) { `, expr: `histogram_quantile(0.5, series{})`, expectedWarningAnnotations: []string{`PromQL warning: bucket label "le" is missing or has a malformed value of "" (1:25)`}, + expectedWarningAnnotationsDelayedNameRemovalEnabled: []string{`PromQL warning: bucket label "le" is missing or has a malformed value of "" for metric name "series" (1:25)`}, }, "extra entry in series without le label": { data: ` @@ -3184,6 +3253,7 @@ func TestHistogramAnnotations(t *testing.T) { `, expr: `histogram_quantile(0.5, series{})`, expectedWarningAnnotations: []string{`PromQL warning: bucket label "le" is missing or has a malformed value of "" (1:25)`}, + expectedWarningAnnotationsDelayedNameRemovalEnabled: []string{`PromQL warning: bucket label "le" is missing or has a malformed value of "" for metric name "series" (1:25)`}, }, } diff --git a/pkg/streamingpromql/operators/functions/histogram_function.go b/pkg/streamingpromql/operators/functions/histogram_function.go index 746e602a44d..a7a2442b84e 100644 --- a/pkg/streamingpromql/operators/functions/histogram_function.go +++ b/pkg/streamingpromql/operators/functions/histogram_function.go @@ -28,16 +28,6 @@ import ( "github.com/grafana/mimir/pkg/util/pool" ) -const ( - // intentionallyEmptyMetricName exists for annotations compatibility with prometheus. - // Now prometheus has delayed __name__ removal. Mimir doesn't yet, and we always remove __name__. - // Because of complication in implementing this in prometheus (see https://github.com/prometheus/prometheus/pull/16794), - // the name is always dropped if delayed __name__ removal is disabled. - // The name is dropped even if the function is the first one invoked on the vector selector. - // Mimir doesn't have delayed __name__ removal, so to stay close to prometheus, we never display the label in some annotations and warnings. - intentionallyEmptyMetricName = "" -) - // HistogramFunction performs a function over each series in an instant vector, // with special handling for classic and native histograms. // At the moment, it supports only histogram_quantile and histogram_fraction. @@ -136,6 +126,7 @@ func NewHistogramQuantileFunction( annotations: annotations, innerSeriesMetricNames: innerSeriesMetricNames, innerExpressionPosition: inner.ExpressionPosition(), + enableDelayedNameRemoval: enableDelayedNameRemoval, }, inner: inner, memoryConsumptionTracker: memoryConsumptionTracker, @@ -298,6 +289,16 @@ func (h *HistogramFunction) NextSeries(ctx context.Context) (types.InstantVector return h.computeOutputSeriesForGroup(thisGroup) } +// getMetricNameForSeries returns the metric name from innerSeriesMetricNames for the given series index. +// If enableDelayedNameRemoval is not enabled, this func will return "" to maintain compatibility with Prometheus. +func (h *HistogramFunction) getMetricNameForSeries(seriesIndex int) string { + if h.enableDelayedNameRemoval { + return h.innerSeriesMetricNames.GetMetricNameForSeries(seriesIndex) + } else { + return "" + } +} + // accumulateUntilGroupComplete gathers all the series associated with the given bucketGroup // As each inner series is selected, it is added into its respective groups. // This means a group other than the one we are focused on may get completed first, but we @@ -346,7 +347,7 @@ func (h *HistogramFunction) saveFloatsToGroup(fPoints []promql.FPoint, le string if err != nil { // The le label was invalid. Record it: h.annotations.Add(annotations.NewBadBucketLabelWarning( - intentionallyEmptyMetricName, + h.getMetricNameForSeries(g.lastInputSeriesIdx), le, h.inner.ExpressionPosition(), )) @@ -427,7 +428,7 @@ func (h *HistogramFunction) computeOutputSeriesForGroup(g *bucketGroup) (types.I // At this data point, we have classic histogram buckets and a native histogram with the same name and labels. // No value is returned, so emit an annotation and continue. h.annotations.Add(annotations.NewMixedClassicNativeHistogramsWarning( - intentionallyEmptyMetricName, h.inner.ExpressionPosition(), + h.getMetricNameForSeries(g.lastInputSeriesIdx), h.inner.ExpressionPosition(), )) continue } @@ -546,6 +547,7 @@ type histogramQuantile struct { annotations *annotations.Annotations innerSeriesMetricNames *operators.MetricNames innerExpressionPosition posrange.PositionRange + enableDelayedNameRemoval bool } func (q *histogramQuantile) LoadArguments(ctx context.Context) error { @@ -568,13 +570,23 @@ func (q *histogramQuantile) LoadArguments(ctx context.Context) error { return nil } +// getMetricNameForSeries returns the metric name from innerSeriesMetricNames for the given series index. +// If enableDelayedNameRemoval is not enabled, this func will return "" to maintain compatibility with Prometheus. +func (q *histogramQuantile) getMetricNameForSeries(seriesIndex int) string { + if q.enableDelayedNameRemoval { + return q.innerSeriesMetricNames.GetMetricNameForSeries(seriesIndex) + } else { + return "" + } +} + func (q *histogramQuantile) ComputeClassicHistogramResult(pointIndex int, seriesIndex int, buckets promql.Buckets) float64 { ph := q.phValues.Samples[pointIndex].F res, forcedMonotonicity, _ := promql.BucketQuantile(ph, buckets) if forcedMonotonicity { q.annotations.Add(annotations.NewHistogramQuantileForcedMonotonicityInfo( - intentionallyEmptyMetricName, + q.getMetricNameForSeries(seriesIndex), q.innerExpressionPosition, )) } diff --git a/pkg/streamingpromql/testdata/upstream/native_histograms.test b/pkg/streamingpromql/testdata/upstream/native_histograms.test index e43beb45c77..644e550d5ca 100644 --- a/pkg/streamingpromql/testdata/upstream/native_histograms.test +++ b/pkg/streamingpromql/testdata/upstream/native_histograms.test @@ -1753,22 +1753,18 @@ load 1m mixedHistogram{le="1"} 0+3x10 mixedHistogram{} {{schema:0 count:10 sum:50 buckets:[1 2 3]}} -# Unsupported by streaming engine. -# eval instant at 1m histogram_quantile(0.5, nonmonotonic_bucket) -# expect info msg: PromQL info: input to histogram_quantile needed to be fixed for monotonicity (see https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile) for metric name "nonmonotonic_bucket" -# {} 8.5 +eval instant at 1m histogram_quantile(0.5, nonmonotonic_bucket) + expect info msg: PromQL info: input to histogram_quantile needed to be fixed for monotonicity (see https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile) for metric name "nonmonotonic_bucket" + {} 8.5 -# Unsupported by streaming engine. -# eval instant at 1m histogram_quantile(0.5, myHistogram1) -# expect warn msg: PromQL warning: bucket label "le" is missing or has a malformed value of "" for metric name "myHistogram1" +eval instant at 1m histogram_quantile(0.5, myHistogram1) + expect warn msg: PromQL warning: bucket label "le" is missing or has a malformed value of "" for metric name "myHistogram1" -# Unsupported by streaming engine. -# eval instant at 1m histogram_quantile(0.5, myHistogram2) -# expect warn msg: PromQL warning: bucket label "le" is missing or has a malformed value of "Hello World" for metric name "myHistogram2" +eval instant at 1m histogram_quantile(0.5, myHistogram2) + expect warn msg: PromQL warning: bucket label "le" is missing or has a malformed value of "Hello World" for metric name "myHistogram2" -# Unsupported by streaming engine. -# eval instant at 1m histogram_quantile(0.5, mixedHistogram) -# expect warn msg: PromQL warning: vector contains a mix of classic and native histograms for metric name "mixedHistogram" +eval instant at 1m histogram_quantile(0.5, mixedHistogram) + expect warn msg: PromQL warning: vector contains a mix of classic and native histograms for metric name "mixedHistogram" clear From f941eb396c5719ad538a21ba5a52aa3135e6b003 Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Wed, 31 Dec 2025 14:57:14 +0800 Subject: [PATCH 02/12] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61d70a78027..62044b8733b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -126,6 +126,7 @@ * [BUGFIX] Query-frontend: Fix silent panic when executing a remote read API request if the request has no matchers. #13745 * [BUGFIX] Ruler: Fixed `-ruler.max-rule-groups-per-tenant-by-namespace` to only count rule groups in the specified namespace instead of all namespaces. #13743 * [BUGFIX] Query-frontend: Fix race condition that could sometimes cause unnecessary resharding of queriers if querier shuffle sharding and remote execution is enabled. #13794 #13838 +* [ENHANCEMENT] MQE: The metric name will now be included in `histogram_quantile` warning and info annotations when delayed name removal is enabled. #13905 ### Mixin From c3abc4d7bbd0400c3a1b5c4c0f6bbede29fd21b4 Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Fri, 2 Jan 2026 07:45:56 +0800 Subject: [PATCH 03/12] Separate test files --- pkg/streamingpromql/engine_test.go | 4 +-- .../eliminate_deduplicate_and_merge_test.go | 3 ++ ...tograms_delayed_name_removal_disabled.test | 31 +++++++++++++++++++ ...stograms_delayed_name_removal_enabled.test | 31 +++++++++++++++++++ .../testdata/upstream/native_histograms.test | 22 +++++++------ 5 files changed, 80 insertions(+), 11 deletions(-) create mode 100644 pkg/streamingpromql/testdata/ours/native_histograms_delayed_name_removal_disabled.test create mode 100644 pkg/streamingpromql/testdata/ours/native_histograms_delayed_name_removal_enabled.test diff --git a/pkg/streamingpromql/engine_test.go b/pkg/streamingpromql/engine_test.go index 3b334b45bed..81dd8ae1eac 100644 --- a/pkg/streamingpromql/engine_test.go +++ b/pkg/streamingpromql/engine_test.go @@ -247,7 +247,7 @@ func TestOurTestCases(t *testing.T) { testScript := string(b) t.Run("Mimir's engine", func(t *testing.T) { - if strings.Contains(testFile, "name_label_dropping") { + if strings.Contains(testFile, "name_label_dropping") || strings.Contains(testFile, "delayed_name_removal_enabled") { promqltest.RunTest(t, testScript, mimirEngineWithDelayedNameRemoval) return } @@ -261,7 +261,7 @@ func TestOurTestCases(t *testing.T) { t.Skip("disabled for Prometheus' engine due to bug in Prometheus' engine") } - if strings.Contains(testFile, "name_label_dropping") { + if strings.Contains(testFile, "name_label_dropping") || strings.Contains(testFile, "delayed_name_removal_enabled") { promqltest.RunTest(t, testScript, prometheusEngineWithDelayedNameRemoval) return } diff --git a/pkg/streamingpromql/optimize/plan/eliminate_deduplicate_and_merge_test.go b/pkg/streamingpromql/optimize/plan/eliminate_deduplicate_and_merge_test.go index 07c463a986e..a97f609df45 100644 --- a/pkg/streamingpromql/optimize/plan/eliminate_deduplicate_and_merge_test.go +++ b/pkg/streamingpromql/optimize/plan/eliminate_deduplicate_and_merge_test.go @@ -1027,6 +1027,9 @@ func runTestCasesWithDelayedNameRemovalDisabled(t *testing.T, globPattern string if strings.Contains(testFile, "name_label_dropping") { t.Skip("name_label_dropping tests require delayed name removal to be enabled, but optimization pass requires it to be disabled") } + if strings.Contains(testFile, "delayed_name_removal_enabled") { + t.Skip("delayed_name_removal_enabled tests require delayed name removal to be enabled, but optimization pass requires it to be disabled") + } f, err := testdataFS.Open(testFile) require.NoError(t, err) diff --git a/pkg/streamingpromql/testdata/ours/native_histograms_delayed_name_removal_disabled.test b/pkg/streamingpromql/testdata/ours/native_histograms_delayed_name_removal_disabled.test new file mode 100644 index 00000000000..ef73c8e3539 --- /dev/null +++ b/pkg/streamingpromql/testdata/ours/native_histograms_delayed_name_removal_disabled.test @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# Provenance-includes-location: https://github.com/prometheus/prometheus/tree/main/promql/testdata/native_histograms.test +# Provenance-includes-license: Apache-2.0 +# Provenance-includes-copyright: The Prometheus Authors + +# Test histogram_quantile annotations. +load 1m + nonmonotonic_bucket{le="0.1"} 0+2x10 + nonmonotonic_bucket{le="1"} 0+1x10 + nonmonotonic_bucket{le="10"} 0+5x10 + nonmonotonic_bucket{le="100"} 0+4x10 + nonmonotonic_bucket{le="1000"} 0+9x10 + nonmonotonic_bucket{le="+Inf"} 0+8x10 + myHistogram1{abe="0.1"} 0+2x10 + myHistogram2{le="Hello World"} 0+2x10 + mixedHistogram{le="0.1"} 0+2x10 + mixedHistogram{le="1"} 0+3x10 + mixedHistogram{} {{schema:0 count:10 sum:50 buckets:[1 2 3]}} + +eval instant at 1m histogram_quantile(0.5, nonmonotonic_bucket) + expect info msg: PromQL info: input to histogram_quantile needed to be fixed for monotonicity (see https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile) + {} 8.5 + +eval instant at 1m histogram_quantile(0.5, myHistogram1) + expect warn msg: PromQL warning: bucket label "le" is missing or has a malformed value of "" + +eval instant at 1m histogram_quantile(0.5, myHistogram2) + expect warn msg: PromQL warning: bucket label "le" is missing or has a malformed value of "Hello World" + +eval instant at 1m histogram_quantile(0.5, mixedHistogram) + expect warn msg: PromQL warning: vector contains a mix of classic and native histograms \ No newline at end of file diff --git a/pkg/streamingpromql/testdata/ours/native_histograms_delayed_name_removal_enabled.test b/pkg/streamingpromql/testdata/ours/native_histograms_delayed_name_removal_enabled.test new file mode 100644 index 00000000000..c5b7bbfe64c --- /dev/null +++ b/pkg/streamingpromql/testdata/ours/native_histograms_delayed_name_removal_enabled.test @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# Provenance-includes-location: https://github.com/prometheus/prometheus/tree/main/promql/testdata/native_histograms.test +# Provenance-includes-license: Apache-2.0 +# Provenance-includes-copyright: The Prometheus Authors + +# Test histogram_quantile annotations. +load 1m + nonmonotonic_bucket{le="0.1"} 0+2x10 + nonmonotonic_bucket{le="1"} 0+1x10 + nonmonotonic_bucket{le="10"} 0+5x10 + nonmonotonic_bucket{le="100"} 0+4x10 + nonmonotonic_bucket{le="1000"} 0+9x10 + nonmonotonic_bucket{le="+Inf"} 0+8x10 + myHistogram1{abe="0.1"} 0+2x10 + myHistogram2{le="Hello World"} 0+2x10 + mixedHistogram{le="0.1"} 0+2x10 + mixedHistogram{le="1"} 0+3x10 + mixedHistogram{} {{schema:0 count:10 sum:50 buckets:[1 2 3]}} + +eval instant at 1m histogram_quantile(0.5, nonmonotonic_bucket) + expect info msg: PromQL info: input to histogram_quantile needed to be fixed for monotonicity (see https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile) for metric name "nonmonotonic_bucket" + {} 8.5 + +eval instant at 1m histogram_quantile(0.5, myHistogram1) + expect warn msg: PromQL warning: bucket label "le" is missing or has a malformed value of "" for metric name "myHistogram1" + +eval instant at 1m histogram_quantile(0.5, myHistogram2) + expect warn msg: PromQL warning: bucket label "le" is missing or has a malformed value of "Hello World" for metric name "myHistogram2" + +eval instant at 1m histogram_quantile(0.5, mixedHistogram) + expect warn msg: PromQL warning: vector contains a mix of classic and native histograms for metric name "mixedHistogram" \ No newline at end of file diff --git a/pkg/streamingpromql/testdata/upstream/native_histograms.test b/pkg/streamingpromql/testdata/upstream/native_histograms.test index 644e550d5ca..e43beb45c77 100644 --- a/pkg/streamingpromql/testdata/upstream/native_histograms.test +++ b/pkg/streamingpromql/testdata/upstream/native_histograms.test @@ -1753,18 +1753,22 @@ load 1m mixedHistogram{le="1"} 0+3x10 mixedHistogram{} {{schema:0 count:10 sum:50 buckets:[1 2 3]}} -eval instant at 1m histogram_quantile(0.5, nonmonotonic_bucket) - expect info msg: PromQL info: input to histogram_quantile needed to be fixed for monotonicity (see https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile) for metric name "nonmonotonic_bucket" - {} 8.5 +# Unsupported by streaming engine. +# eval instant at 1m histogram_quantile(0.5, nonmonotonic_bucket) +# expect info msg: PromQL info: input to histogram_quantile needed to be fixed for monotonicity (see https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile) for metric name "nonmonotonic_bucket" +# {} 8.5 -eval instant at 1m histogram_quantile(0.5, myHistogram1) - expect warn msg: PromQL warning: bucket label "le" is missing or has a malformed value of "" for metric name "myHistogram1" +# Unsupported by streaming engine. +# eval instant at 1m histogram_quantile(0.5, myHistogram1) +# expect warn msg: PromQL warning: bucket label "le" is missing or has a malformed value of "" for metric name "myHistogram1" -eval instant at 1m histogram_quantile(0.5, myHistogram2) - expect warn msg: PromQL warning: bucket label "le" is missing or has a malformed value of "Hello World" for metric name "myHistogram2" +# Unsupported by streaming engine. +# eval instant at 1m histogram_quantile(0.5, myHistogram2) +# expect warn msg: PromQL warning: bucket label "le" is missing or has a malformed value of "Hello World" for metric name "myHistogram2" -eval instant at 1m histogram_quantile(0.5, mixedHistogram) - expect warn msg: PromQL warning: vector contains a mix of classic and native histograms for metric name "mixedHistogram" +# Unsupported by streaming engine. +# eval instant at 1m histogram_quantile(0.5, mixedHistogram) +# expect warn msg: PromQL warning: vector contains a mix of classic and native histograms for metric name "mixedHistogram" clear From 2d76138ea93e1d9461e4436c56eac770f706f924 Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Fri, 2 Jan 2026 08:12:54 +0800 Subject: [PATCH 04/12] Tweaks --- CHANGELOG.md | 2 +- pkg/streamingpromql/engine_test.go | 141 +++++++----------- .../operators/functions/histogram_function.go | 10 +- 3 files changed, 64 insertions(+), 89 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62044b8733b..71d367ef181 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -126,7 +126,7 @@ * [BUGFIX] Query-frontend: Fix silent panic when executing a remote read API request if the request has no matchers. #13745 * [BUGFIX] Ruler: Fixed `-ruler.max-rule-groups-per-tenant-by-namespace` to only count rule groups in the specified namespace instead of all namespaces. #13743 * [BUGFIX] Query-frontend: Fix race condition that could sometimes cause unnecessary resharding of queriers if querier shuffle sharding and remote execution is enabled. #13794 #13838 -* [ENHANCEMENT] MQE: The metric name will now be included in `histogram_quantile` warning and info annotations when delayed name removal is enabled. #13905 +* [ENHANCEMENT] MQE: Include metric name in `histogram_quantile` warning/info annotations when delayed name removal is enabled. #13905 ### Mixin diff --git a/pkg/streamingpromql/engine_test.go b/pkg/streamingpromql/engine_test.go index 81dd8ae1eac..56f32ba2d2d 100644 --- a/pkg/streamingpromql/engine_test.go +++ b/pkg/streamingpromql/engine_test.go @@ -2215,44 +2215,46 @@ type annotationTestCase struct { instantEvaluationTimestamp *time.Time } +func (a annotationTestCase) getExpectedInfoAnnotations(delayedNameRemovalEnabled bool) []string { + if delayedNameRemovalEnabled && a.expectedInfoAnnotationsDelayedNameRemovalEnabled != nil { + return a.expectedInfoAnnotationsDelayedNameRemovalEnabled + } + return a.expectedInfoAnnotations +} + +func (a annotationTestCase) getExpectedWarningAnnotations(delayedNameRemovalEnabled bool) []string { + if delayedNameRemovalEnabled && a.expectedWarningAnnotationsDelayedNameRemovalEnabled != nil { + return a.expectedWarningAnnotationsDelayedNameRemovalEnabled + } + return a.expectedWarningAnnotations +} + func runAnnotationTests(t *testing.T, testCases map[string]annotationTestCase) { startT := timestamp.Time(0).Add(time.Minute) step := time.Minute endT := startT.Add(2 * step) - // create 2 sets of engines - one with EnableDelayedNameRemoval=true and the other with EnableDelayedNameRemoval=false - // there are some histogram annotation test cases will be return a different warning/info depending on whether delayed name removal is enabled or not - optsDelayedNameRemovalEnabled := NewTestEngineOpts() - optsDelayedNameRemovalEnabled.CommonOpts.EnableDelayedNameRemoval = true - - plannerDelayedNameRemovalEnabled, err := NewQueryPlanner(optsDelayedNameRemovalEnabled, NewMaximumSupportedVersionQueryPlanVersionProvider()) - require.NoError(t, err) - mimirEngineDelayedNameRemovalEnabled, err := NewEngine(optsDelayedNameRemovalEnabled, NewStaticQueryLimitsProvider(0), stats.NewQueryMetrics(nil), plannerDelayedNameRemovalEnabled) - require.NoError(t, err) - prometheusEngineDelayedNameRemovalEnabled := promql.NewEngine(optsDelayedNameRemovalEnabled.CommonOpts) - - optsDelayedNameRemovalDisabled := NewTestEngineOpts() - optsDelayedNameRemovalDisabled.CommonOpts.EnableDelayedNameRemoval = false - - plannerDelayedNameRemovalDisabled, err := NewQueryPlanner(optsDelayedNameRemovalDisabled, NewMaximumSupportedVersionQueryPlanVersionProvider()) - require.NoError(t, err) - mimirEngineDelayedNameRemovalDisabled, err := NewEngine(optsDelayedNameRemovalDisabled, NewStaticQueryLimitsProvider(0), stats.NewQueryMetrics(nil), plannerDelayedNameRemovalDisabled) - require.NoError(t, err) - prometheusEngineDelayedNameRemovalDisabled := promql.NewEngine(optsDelayedNameRemovalDisabled.CommonOpts) - const prometheusEngineName = "Prometheus' engine" - enginesDelayedNameRemovalDisabled := map[string]promql.QueryEngine{ - "Mimir's engine": mimirEngineDelayedNameRemovalDisabled, + const mimirEngineName = "Mimir's engine" - // Compare against Prometheus' engine to verify our test cases are valid. - prometheusEngineName: prometheusEngineDelayedNameRemovalDisabled, - } + // create 2 sets of engines - one with EnableDelayedNameRemoval=true and the other with EnableDelayedNameRemoval=false + // there are some histogram annotation test cases which will emit a different warning/info annotation string depending on the delayed name removal setting + engines := make(map[bool]map[string]promql.QueryEngine, 2) + for _, delayedNameRemovalEnabled := range []bool{true, false} { + opts := NewTestEngineOpts() + opts.CommonOpts.EnableDelayedNameRemoval = delayedNameRemovalEnabled - enginesDelayedNameRemovalEnabled := map[string]promql.QueryEngine{ - "Mimir's engine": mimirEngineDelayedNameRemovalEnabled, + planner, err := NewQueryPlanner(opts, NewMaximumSupportedVersionQueryPlanVersionProvider()) + require.NoError(t, err) + mimirEngine, err := NewEngine(opts, NewStaticQueryLimitsProvider(0), stats.NewQueryMetrics(nil), planner) + require.NoError(t, err) + prometheusEngine := promql.NewEngine(opts.CommonOpts) // Compare against Prometheus' engine to verify our test cases are valid. - prometheusEngineName: prometheusEngineDelayedNameRemovalEnabled, + engines[delayedNameRemovalEnabled] = map[string]promql.QueryEngine{ + mimirEngineName: mimirEngine, + prometheusEngineName: prometheusEngine, + } } for name, testCase := range testCases { @@ -2277,68 +2279,35 @@ func runAnnotationTests(t *testing.T, testCases map[string]annotationTestCase) { for queryType, generator := range queryTypes { t.Run(queryType, func(t *testing.T) { - results := make([]*promql.Result, 0, 2) + for _, delayedNameRemovalEnabled := range []bool{true, false} { + results := make([]*promql.Result, 0, 2) - for engineName, engine := range enginesDelayedNameRemovalDisabled { - if engineName == prometheusEngineName && testCase.skipComparisonWithPrometheusReason != "" { - t.Logf("Skipping comparison with Prometheus' engine: %v", testCase.skipComparisonWithPrometheusReason) - continue + for engineName, engine := range engines[delayedNameRemovalEnabled] { + if engineName == prometheusEngineName && testCase.skipComparisonWithPrometheusReason != "" { + t.Logf("Skipping comparison with Prometheus' engine: %v", testCase.skipComparisonWithPrometheusReason) + continue + } + t.Run(engineName, func(t *testing.T) { + query, err := generator(engine) + require.NoError(t, err) + t.Cleanup(query.Close) + + res := query.Exec(context.Background()) + require.NoError(t, res.Err) + results = append(results, res) + + warnings, infos := res.Warnings.AsStrings(testCase.expr, 0, 0) + require.ElementsMatch(t, testCase.getExpectedWarningAnnotations(delayedNameRemovalEnabled), warnings) + require.ElementsMatch(t, testCase.getExpectedInfoAnnotations(delayedNameRemovalEnabled), infos) + }) } - t.Run(engineName, func(t *testing.T) { - query, err := generator(engine) - require.NoError(t, err) - t.Cleanup(query.Close) - - res := query.Exec(context.Background()) - require.NoError(t, res.Err) - results = append(results, res) - - warnings, infos := res.Warnings.AsStrings(testCase.expr, 0, 0) - require.ElementsMatch(t, testCase.expectedWarningAnnotations, warnings) - require.ElementsMatch(t, testCase.expectedInfoAnnotations, infos) - }) - } - - // If both results are available, compare them (sometimes we skip prometheus) - if len(results) == 2 { - // We do this extra comparison to ensure that we don't skip a series that may be outputted during a warning - // or vice-versa where no result may be expected etc. - testutils.RequireEqualResults(t, testCase.expr, results[0], results[1], false) - } - for engineName, engine := range enginesDelayedNameRemovalEnabled { - if engineName == prometheusEngineName && testCase.skipComparisonWithPrometheusReason != "" { - t.Logf("Skipping comparison with Prometheus' engine: %v", testCase.skipComparisonWithPrometheusReason) - continue + // If both results are available, compare them (sometimes we skip prometheus) + if len(results) == 2 { + // We do this extra comparison to ensure that we don't skip a series that may be outputted during a warning + // or vice-versa where no result may be expected etc. + testutils.RequireEqualResults(t, testCase.expr, results[0], results[1], false) } - t.Run(engineName, func(t *testing.T) { - query, err := generator(engine) - require.NoError(t, err) - t.Cleanup(query.Close) - - res := query.Exec(context.Background()) - require.NoError(t, res.Err) - results = append(results, res) - - warnings, infos := res.Warnings.AsStrings(testCase.expr, 0, 0) - expectedWarningAnnotations := testCase.expectedWarningAnnotationsDelayedNameRemovalEnabled - if expectedWarningAnnotations == nil { - expectedWarningAnnotations = testCase.expectedWarningAnnotations - } - expectedInfoAnnotations := testCase.expectedInfoAnnotationsDelayedNameRemovalEnabled - if expectedInfoAnnotations == nil { - expectedInfoAnnotations = testCase.expectedInfoAnnotations - } - require.ElementsMatch(t, expectedWarningAnnotations, warnings) - require.ElementsMatch(t, expectedInfoAnnotations, infos) - }) - } - - // If both results are available, compare them (sometimes we skip prometheus) - if len(results) == 2 { - // We do this extra comparison to ensure that we don't skip a series that may be outputted during a warning - // or vice-versa where no result may be expected etc. - testutils.RequireEqualResults(t, testCase.expr, results[0], results[1], false) } }) } diff --git a/pkg/streamingpromql/operators/functions/histogram_function.go b/pkg/streamingpromql/operators/functions/histogram_function.go index a7a2442b84e..5cb8452ac68 100644 --- a/pkg/streamingpromql/operators/functions/histogram_function.go +++ b/pkg/streamingpromql/operators/functions/histogram_function.go @@ -28,6 +28,12 @@ import ( "github.com/grafana/mimir/pkg/util/pool" ) +const ( + // intentionallyEmptyMetricName exists for annotations compatibility with prometheus. + // This is only used for backwards compatibility when delayed __name__ removal is not enabled. + intentionallyEmptyMetricName = "" +) + // HistogramFunction performs a function over each series in an instant vector, // with special handling for classic and native histograms. // At the moment, it supports only histogram_quantile and histogram_fraction. @@ -295,7 +301,7 @@ func (h *HistogramFunction) getMetricNameForSeries(seriesIndex int) string { if h.enableDelayedNameRemoval { return h.innerSeriesMetricNames.GetMetricNameForSeries(seriesIndex) } else { - return "" + return intentionallyEmptyMetricName } } @@ -576,7 +582,7 @@ func (q *histogramQuantile) getMetricNameForSeries(seriesIndex int) string { if q.enableDelayedNameRemoval { return q.innerSeriesMetricNames.GetMetricNameForSeries(seriesIndex) } else { - return "" + return intentionallyEmptyMetricName } } From 2d6fe0ab5b4f31e9e723cd79780c18eed12d601a Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Fri, 2 Jan 2026 08:29:50 +0800 Subject: [PATCH 05/12] Update histogram_function.go --- pkg/streamingpromql/operators/functions/histogram_function.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/streamingpromql/operators/functions/histogram_function.go b/pkg/streamingpromql/operators/functions/histogram_function.go index 5cb8452ac68..2e658a475bf 100644 --- a/pkg/streamingpromql/operators/functions/histogram_function.go +++ b/pkg/streamingpromql/operators/functions/histogram_function.go @@ -602,7 +602,7 @@ func (q *histogramQuantile) ComputeClassicHistogramResult(pointIndex int, series func (q *histogramQuantile) ComputeNativeHistogramResult(pointIndex int, seriesIndex int, h *histogram.FloatHistogram) (float64, annotations.Annotations) { ph := q.phValues.Samples[pointIndex].F - return promql.HistogramQuantile(ph, h, q.innerSeriesMetricNames.GetMetricNameForSeries(seriesIndex), q.innerExpressionPosition) + return promql.HistogramQuantile(ph, h, q.getMetricNameForSeries(seriesIndex), q.innerExpressionPosition) } func (q *histogramQuantile) Prepare(ctx context.Context, params *types.PrepareParams) error { From b0a10e244f5757e168c8f577e93880face189b6c Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Fri, 2 Jan 2026 08:56:49 +0800 Subject: [PATCH 06/12] More differences --- ...tograms_delayed_name_removal_disabled.test | 37 ++++++++++++++- ...stograms_delayed_name_removal_enabled.test | 37 ++++++++++++++- .../testdata/upstream/native_histograms.test | 46 +++++++++++-------- 3 files changed, 98 insertions(+), 22 deletions(-) diff --git a/pkg/streamingpromql/testdata/ours/native_histograms_delayed_name_removal_disabled.test b/pkg/streamingpromql/testdata/ours/native_histograms_delayed_name_removal_disabled.test index ef73c8e3539..ab0ae305806 100644 --- a/pkg/streamingpromql/testdata/ours/native_histograms_delayed_name_removal_disabled.test +++ b/pkg/streamingpromql/testdata/ours/native_histograms_delayed_name_removal_disabled.test @@ -28,4 +28,39 @@ eval instant at 1m histogram_quantile(0.5, myHistogram2) expect warn msg: PromQL warning: bucket label "le" is missing or has a malformed value of "Hello World" eval instant at 1m histogram_quantile(0.5, mixedHistogram) - expect warn msg: PromQL warning: vector contains a mix of classic and native histograms \ No newline at end of file + expect warn msg: PromQL warning: vector contains a mix of classic and native histograms + +clear + +# Test native histogram quantile and fraction when the native histogram with exponential +# buckets has NaN observations. +load 1m + histogram_nan{case="100% NaNs"} {{schema:0 count:0 sum:0}} {{schema:0 count:3 sum:NaN}} + histogram_nan{case="20% NaNs"} {{schema:0 count:0 sum:0}} {{schema:0 count:15 sum:NaN buckets:[12]}} + +eval instant at 1m histogram_quantile(1, histogram_nan) + expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN + {case="100% NaNs"} NaN + {case="20% NaNs"} NaN + +eval instant at 1m histogram_quantile(0.81, histogram_nan) + expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN + {case="100% NaNs"} NaN + {case="20% NaNs"} NaN + +eval instant at 1m histogram_quantile(0.8, histogram_nan{case="100% NaNs"}) + expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN + {case="100% NaNs"} NaN + +eval instant at 1m histogram_quantile(0.8, histogram_nan{case="20% NaNs"}) + expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is skewed higher + {case="20% NaNs"} 1 + +eval instant at 1m histogram_quantile(0.4, histogram_nan{case="100% NaNs"}) + expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN + {case="100% NaNs"} NaN + +# histogram_quantile and histogram_fraction equivalence if quantile is not NaN +eval instant at 1m histogram_quantile(0.4, histogram_nan{case="20% NaNs"}) + expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is skewed higher + {case="20% NaNs"} 0.7071067811865475 \ No newline at end of file diff --git a/pkg/streamingpromql/testdata/ours/native_histograms_delayed_name_removal_enabled.test b/pkg/streamingpromql/testdata/ours/native_histograms_delayed_name_removal_enabled.test index c5b7bbfe64c..0258d63913b 100644 --- a/pkg/streamingpromql/testdata/ours/native_histograms_delayed_name_removal_enabled.test +++ b/pkg/streamingpromql/testdata/ours/native_histograms_delayed_name_removal_enabled.test @@ -28,4 +28,39 @@ eval instant at 1m histogram_quantile(0.5, myHistogram2) expect warn msg: PromQL warning: bucket label "le" is missing or has a malformed value of "Hello World" for metric name "myHistogram2" eval instant at 1m histogram_quantile(0.5, mixedHistogram) - expect warn msg: PromQL warning: vector contains a mix of classic and native histograms for metric name "mixedHistogram" \ No newline at end of file + expect warn msg: PromQL warning: vector contains a mix of classic and native histograms for metric name "mixedHistogram" + +clear + +# Test native histogram quantile and fraction when the native histogram with exponential +# buckets has NaN observations. +load 1m + histogram_nan{case="100% NaNs"} {{schema:0 count:0 sum:0}} {{schema:0 count:3 sum:NaN}} + histogram_nan{case="20% NaNs"} {{schema:0 count:0 sum:0}} {{schema:0 count:15 sum:NaN buckets:[12]}} + +eval instant at 1m histogram_quantile(1, histogram_nan) + expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan" + {case="100% NaNs"} NaN + {case="20% NaNs"} NaN + +eval instant at 1m histogram_quantile(0.81, histogram_nan) + expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan" + {case="100% NaNs"} NaN + {case="20% NaNs"} NaN + +eval instant at 1m histogram_quantile(0.8, histogram_nan{case="100% NaNs"}) + expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan" + {case="100% NaNs"} NaN + +eval instant at 1m histogram_quantile(0.8, histogram_nan{case="20% NaNs"}) + expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is skewed higher for metric name "histogram_nan" + {case="20% NaNs"} 1 + +eval instant at 1m histogram_quantile(0.4, histogram_nan{case="100% NaNs"}) + expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan" + {case="100% NaNs"} NaN + +# histogram_quantile and histogram_fraction equivalence if quantile is not NaN +eval instant at 1m histogram_quantile(0.4, histogram_nan{case="20% NaNs"}) + expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is skewed higher for metric name "histogram_nan" + {case="20% NaNs"} 0.7071067811865475 \ No newline at end of file diff --git a/pkg/streamingpromql/testdata/upstream/native_histograms.test b/pkg/streamingpromql/testdata/upstream/native_histograms.test index e43beb45c77..550bc57ec6a 100644 --- a/pkg/streamingpromql/testdata/upstream/native_histograms.test +++ b/pkg/streamingpromql/testdata/upstream/native_histograms.test @@ -1470,32 +1470,38 @@ load 1m histogram_nan{case="100% NaNs"} {{schema:0 count:0 sum:0}} {{schema:0 count:3 sum:NaN}} histogram_nan{case="20% NaNs"} {{schema:0 count:0 sum:0}} {{schema:0 count:15 sum:NaN buckets:[12]}} -eval instant at 1m histogram_quantile(1, histogram_nan) - expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan" - {case="100% NaNs"} NaN - {case="20% NaNs"} NaN +# Unsupported by streaming engine. +# eval instant at 1m histogram_quantile(1, histogram_nan) +# expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan" +# {case="100% NaNs"} NaN +# {case="20% NaNs"} NaN -eval instant at 1m histogram_quantile(0.81, histogram_nan) - expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan" - {case="100% NaNs"} NaN - {case="20% NaNs"} NaN +# Unsupported by streaming engine. +# eval instant at 1m histogram_quantile(0.81, histogram_nan) +# expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan" +# {case="100% NaNs"} NaN +# {case="20% NaNs"} NaN -eval instant at 1m histogram_quantile(0.8, histogram_nan{case="100% NaNs"}) - expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan" - {case="100% NaNs"} NaN +# Unsupported by streaming engine. +# eval instant at 1m histogram_quantile(0.8, histogram_nan{case="100% NaNs"}) +# expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan" +# {case="100% NaNs"} NaN -eval instant at 1m histogram_quantile(0.8, histogram_nan{case="20% NaNs"}) - expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is skewed higher for metric name "histogram_nan" - {case="20% NaNs"} 1 +# Unsupported by streaming engine. +# eval instant at 1m histogram_quantile(0.8, histogram_nan{case="20% NaNs"}) +# expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is skewed higher for metric name "histogram_nan" +# {case="20% NaNs"} 1 -eval instant at 1m histogram_quantile(0.4, histogram_nan{case="100% NaNs"}) - expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan" - {case="100% NaNs"} NaN +# Unsupported by streaming engine. +# eval instant at 1m histogram_quantile(0.4, histogram_nan{case="100% NaNs"}) +# expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan" +# {case="100% NaNs"} NaN # histogram_quantile and histogram_fraction equivalence if quantile is not NaN -eval instant at 1m histogram_quantile(0.4, histogram_nan{case="20% NaNs"}) - expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is skewed higher for metric name "histogram_nan" - {case="20% NaNs"} 0.7071067811865475 +# Unsupported by streaming engine. +# eval instant at 1m histogram_quantile(0.4, histogram_nan{case="20% NaNs"}) +# expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is skewed higher for metric name "histogram_nan" +# {case="20% NaNs"} 0.7071067811865475 eval instant at 1m histogram_fraction(-Inf, 0.7071067811865475, histogram_nan) expect info msg: PromQL info: input to histogram_fraction has NaN observations, which are excluded from all fractions for metric name "histogram_nan" From 22cad812bfd69289e20d1d8463c6a7e6d9dcb57b Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Mon, 19 Jan 2026 11:31:37 +0800 Subject: [PATCH 07/12] Update native_histograms.test --- .../testdata/upstream/native_histograms.test | 46 ++++++++----------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/pkg/streamingpromql/testdata/upstream/native_histograms.test b/pkg/streamingpromql/testdata/upstream/native_histograms.test index 550bc57ec6a..e43beb45c77 100644 --- a/pkg/streamingpromql/testdata/upstream/native_histograms.test +++ b/pkg/streamingpromql/testdata/upstream/native_histograms.test @@ -1470,38 +1470,32 @@ load 1m histogram_nan{case="100% NaNs"} {{schema:0 count:0 sum:0}} {{schema:0 count:3 sum:NaN}} histogram_nan{case="20% NaNs"} {{schema:0 count:0 sum:0}} {{schema:0 count:15 sum:NaN buckets:[12]}} -# Unsupported by streaming engine. -# eval instant at 1m histogram_quantile(1, histogram_nan) -# expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan" -# {case="100% NaNs"} NaN -# {case="20% NaNs"} NaN +eval instant at 1m histogram_quantile(1, histogram_nan) + expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan" + {case="100% NaNs"} NaN + {case="20% NaNs"} NaN -# Unsupported by streaming engine. -# eval instant at 1m histogram_quantile(0.81, histogram_nan) -# expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan" -# {case="100% NaNs"} NaN -# {case="20% NaNs"} NaN +eval instant at 1m histogram_quantile(0.81, histogram_nan) + expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan" + {case="100% NaNs"} NaN + {case="20% NaNs"} NaN -# Unsupported by streaming engine. -# eval instant at 1m histogram_quantile(0.8, histogram_nan{case="100% NaNs"}) -# expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan" -# {case="100% NaNs"} NaN +eval instant at 1m histogram_quantile(0.8, histogram_nan{case="100% NaNs"}) + expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan" + {case="100% NaNs"} NaN -# Unsupported by streaming engine. -# eval instant at 1m histogram_quantile(0.8, histogram_nan{case="20% NaNs"}) -# expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is skewed higher for metric name "histogram_nan" -# {case="20% NaNs"} 1 +eval instant at 1m histogram_quantile(0.8, histogram_nan{case="20% NaNs"}) + expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is skewed higher for metric name "histogram_nan" + {case="20% NaNs"} 1 -# Unsupported by streaming engine. -# eval instant at 1m histogram_quantile(0.4, histogram_nan{case="100% NaNs"}) -# expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan" -# {case="100% NaNs"} NaN +eval instant at 1m histogram_quantile(0.4, histogram_nan{case="100% NaNs"}) + expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan" + {case="100% NaNs"} NaN # histogram_quantile and histogram_fraction equivalence if quantile is not NaN -# Unsupported by streaming engine. -# eval instant at 1m histogram_quantile(0.4, histogram_nan{case="20% NaNs"}) -# expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is skewed higher for metric name "histogram_nan" -# {case="20% NaNs"} 0.7071067811865475 +eval instant at 1m histogram_quantile(0.4, histogram_nan{case="20% NaNs"}) + expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is skewed higher for metric name "histogram_nan" + {case="20% NaNs"} 0.7071067811865475 eval instant at 1m histogram_fraction(-Inf, 0.7071067811865475, histogram_nan) expect info msg: PromQL info: input to histogram_fraction has NaN observations, which are excluded from all fractions for metric name "histogram_nan" From eb62803e98046aab50990574dbd9cd172284a17a Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Mon, 19 Jan 2026 14:07:09 +0800 Subject: [PATCH 08/12] Update engine_test.go --- pkg/streamingpromql/engine_test.go | 71 ++++++++++++++++++------------ 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/pkg/streamingpromql/engine_test.go b/pkg/streamingpromql/engine_test.go index 56f32ba2d2d..cd059e20bff 100644 --- a/pkg/streamingpromql/engine_test.go +++ b/pkg/streamingpromql/engine_test.go @@ -246,13 +246,17 @@ func TestOurTestCases(t *testing.T) { testScript := string(b) - t.Run("Mimir's engine", func(t *testing.T) { - if strings.Contains(testFile, "name_label_dropping") || strings.Contains(testFile, "delayed_name_removal_enabled") { - promqltest.RunTest(t, testScript, mimirEngineWithDelayedNameRemoval) - return - } + mimirEngineToTest := mimirEngine + prometheusEngineToTest := prometheusEngine + + // switch to the alternate engines if we need delayed name removal + if strings.Contains(testFile, "name_label_dropping") || strings.Contains(testFile, "delayed_name_removal_enabled") { + mimirEngineToTest = mimirEngineWithDelayedNameRemoval + prometheusEngineToTest = prometheusEngineWithDelayedNameRemoval + } - promqltest.RunTest(t, testScript, mimirEngine) + t.Run("Mimir's engine", func(t *testing.T) { + promqltest.RunTest(t, testScript, mimirEngineToTest) }) // Run the tests against Prometheus' engine to ensure our test cases are valid. @@ -261,12 +265,7 @@ func TestOurTestCases(t *testing.T) { t.Skip("disabled for Prometheus' engine due to bug in Prometheus' engine") } - if strings.Contains(testFile, "name_label_dropping") || strings.Contains(testFile, "delayed_name_removal_enabled") { - promqltest.RunTest(t, testScript, prometheusEngineWithDelayedNameRemoval) - return - } - - promqltest.RunTest(t, testScript, prometheusEngine) + promqltest.RunTest(t, testScript, prometheusEngineToTest) }) }) } @@ -2204,11 +2203,11 @@ type annotationTestCase struct { data string expr string expectedWarningAnnotations []string - // an alternate string for when delayed name removal is enabled. + // an alternate set of annotations for when delayed name removal is enabled. // if not set the test will fall back to expectedWarningAnnotations expectedWarningAnnotationsDelayedNameRemovalEnabled []string expectedInfoAnnotations []string - // an alternate string for when delayed name removal is enabled. + // an alternate set of annotations for when delayed name removal is enabled. // if not set the test will fall back to expectedInfoAnnotations expectedInfoAnnotationsDelayedNameRemovalEnabled []string skipComparisonWithPrometheusReason string @@ -2237,9 +2236,14 @@ func runAnnotationTests(t *testing.T, testCases map[string]annotationTestCase) { const prometheusEngineName = "Prometheus' engine" const mimirEngineName = "Mimir's engine" - // create 2 sets of engines - one with EnableDelayedNameRemoval=true and the other with EnableDelayedNameRemoval=false + // create 2 sets of engines - a Mimir and Prometheus engine each with EnableDelayedNameRemoval=true and other with EnableDelayedNameRemoval=false // there are some histogram annotation test cases which will emit a different warning/info annotation string depending on the delayed name removal setting - engines := make(map[bool]map[string]promql.QueryEngine, 2) + engineSets := make([]struct { + mimirEngine promql.QueryEngine + prometheusEngine promql.QueryEngine + delayedNameRemovalEnabled bool + }, 0, 2) + for _, delayedNameRemovalEnabled := range []bool{true, false} { opts := NewTestEngineOpts() opts.CommonOpts.EnableDelayedNameRemoval = delayedNameRemovalEnabled @@ -2250,11 +2254,15 @@ func runAnnotationTests(t *testing.T, testCases map[string]annotationTestCase) { require.NoError(t, err) prometheusEngine := promql.NewEngine(opts.CommonOpts) - // Compare against Prometheus' engine to verify our test cases are valid. - engines[delayedNameRemovalEnabled] = map[string]promql.QueryEngine{ - mimirEngineName: mimirEngine, - prometheusEngineName: prometheusEngine, - } + engineSets = append(engineSets, struct { + mimirEngine promql.QueryEngine + prometheusEngine promql.QueryEngine + delayedNameRemovalEnabled bool + }{ + mimirEngine: mimirEngine, + prometheusEngine: prometheusEngine, + delayedNameRemovalEnabled: delayedNameRemovalEnabled, + }) } for name, testCase := range testCases { @@ -2278,15 +2286,20 @@ func runAnnotationTests(t *testing.T, testCases map[string]annotationTestCase) { } for queryType, generator := range queryTypes { - t.Run(queryType, func(t *testing.T) { - for _, delayedNameRemovalEnabled := range []bool{true, false} { + for _, engineSet := range engineSets { + subTestName := fmt.Sprintf("%s - delayed name removal enabled=%t", queryType, engineSet.delayedNameRemovalEnabled) + t.Run(subTestName, func(t *testing.T) { results := make([]*promql.Result, 0, 2) - for engineName, engine := range engines[delayedNameRemovalEnabled] { - if engineName == prometheusEngineName && testCase.skipComparisonWithPrometheusReason != "" { + for i, engine := range []promql.QueryEngine{engineSet.mimirEngine, engineSet.prometheusEngine} { + if i == 1 && testCase.skipComparisonWithPrometheusReason != "" { t.Logf("Skipping comparison with Prometheus' engine: %v", testCase.skipComparisonWithPrometheusReason) continue } + engineName := mimirEngineName + if i == 1 { + engineName = prometheusEngineName + } t.Run(engineName, func(t *testing.T) { query, err := generator(engine) require.NoError(t, err) @@ -2297,8 +2310,8 @@ func runAnnotationTests(t *testing.T, testCases map[string]annotationTestCase) { results = append(results, res) warnings, infos := res.Warnings.AsStrings(testCase.expr, 0, 0) - require.ElementsMatch(t, testCase.getExpectedWarningAnnotations(delayedNameRemovalEnabled), warnings) - require.ElementsMatch(t, testCase.getExpectedInfoAnnotations(delayedNameRemovalEnabled), infos) + require.ElementsMatch(t, testCase.getExpectedWarningAnnotations(engineSet.delayedNameRemovalEnabled), warnings) + require.ElementsMatch(t, testCase.getExpectedInfoAnnotations(engineSet.delayedNameRemovalEnabled), infos) }) } @@ -2308,8 +2321,8 @@ func runAnnotationTests(t *testing.T, testCases map[string]annotationTestCase) { // or vice-versa where no result may be expected etc. testutils.RequireEqualResults(t, testCase.expr, results[0], results[1], false) } - } - }) + }) + } } }) } From 73819a8d20b8f888ee5c9dea195b58458e22b26b Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Mon, 19 Jan 2026 14:40:10 +0800 Subject: [PATCH 09/12] Update eliminate_deduplicate_and_merge_test.go --- .../optimize/plan/eliminate_deduplicate_and_merge_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/streamingpromql/optimize/plan/eliminate_deduplicate_and_merge_test.go b/pkg/streamingpromql/optimize/plan/eliminate_deduplicate_and_merge_test.go index d172d0b0f28..2946e35bfc7 100644 --- a/pkg/streamingpromql/optimize/plan/eliminate_deduplicate_and_merge_test.go +++ b/pkg/streamingpromql/optimize/plan/eliminate_deduplicate_and_merge_test.go @@ -1104,6 +1104,10 @@ func runTestCasesWithDelayedNameRemovalDisabled(t *testing.T, globPattern string if strings.Contains(testFile, "delayed_name_removal_enabled") { t.Skip("delayed_name_removal_enabled tests require delayed name removal to be enabled, but optimization pass requires it to be disabled") } + // Note that we get the equivalent test coverage from ours/native_histograms_delayed_name_removal_disabled.test + if strings.Contains(testFile, "upstream/native_histograms.test") { + t.Skip("upstream/native_histograms.test tests require delayed name removal to be enabled, but optimization pass requires it to be disabled") + } f, err := testdataFS.Open(testFile) require.NoError(t, err) From 71b15b9ce594120a0dc8a2811a100de42ad8fcfe Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Tue, 20 Jan 2026 07:31:40 +0800 Subject: [PATCH 10/12] PR feedback --- CHANGELOG.md | 2 +- pkg/streamingpromql/engine_test.go | 20 ++++---- .../eliminate_deduplicate_and_merge_test.go | 4 +- .../testdata/upstream/native_histograms.test | 49 ++++++++----------- 4 files changed, 34 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1454936d2c0..f8410cb7e94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,6 +104,7 @@ * [ENHANCEMENT] Add experimental flag `common.instrument-reference-leaks-percentage` to leaked references to gRPC buffers. #13609 * [ENHANCEMENT] Ingester: Reduce likelihood of ingestion being paused while idle TSDB compaction is in progress. #13978 * [ENHANCEMENT] Ingester: Extend `cortex_ingester_tsdb_forced_compactions_in_progress` metric to report a value of 1 when there's an idle or forced TSDB head compaction in progress. #13979 +* [ENHANCEMENT] MQE: Include metric name in `histogram_quantile` warning/info annotations when delayed name removal is enabled. #13905 * [BUGFIX] Distributor: Fix issue where distributors didn't send custom values of native histograms. #13849 * [BUGFIX] Compactor: Fix potential concurrent map writes. #13053 * [BUGFIX] Query-frontend: Fix issue where queries sometimes fail with `failed to receive query result stream message: rpc error: code = Canceled desc = context canceled` if remote execution is enabled. #13084 @@ -143,7 +144,6 @@ * [BUGFIX] MQE: Map remote execution storage errors correctly. #13944 * [BUGFIX] Ingester: Fix race condition where new partition could reach Active partition ring state for a before its ingester instances reached Active ring state. #14025 * [BUGFIX] Ingester: Query all ingesters when shuffle sharding is disabled. #14041 -* [ENHANCEMENT] MQE: Include metric name in `histogram_quantile` warning/info annotations when delayed name removal is enabled. #13905 ### Mixin diff --git a/pkg/streamingpromql/engine_test.go b/pkg/streamingpromql/engine_test.go index 7daba8bbc5f..f96d8ec65b4 100644 --- a/pkg/streamingpromql/engine_test.go +++ b/pkg/streamingpromql/engine_test.go @@ -2206,15 +2206,15 @@ type annotationTestCase struct { data string expr string expectedWarningAnnotations []string + expectedInfoAnnotations []string + // an alternate set of annotations for when delayed name removal is enabled. - // if not set the test will fall back to expectedWarningAnnotations + // if not set the test will fall back to expectedWarningAnnotations / expectedInfoAnnotations expectedWarningAnnotationsDelayedNameRemovalEnabled []string - expectedInfoAnnotations []string - // an alternate set of annotations for when delayed name removal is enabled. - // if not set the test will fall back to expectedInfoAnnotations - expectedInfoAnnotationsDelayedNameRemovalEnabled []string - skipComparisonWithPrometheusReason string - instantEvaluationTimestamp *time.Time + expectedInfoAnnotationsDelayedNameRemovalEnabled []string + + skipComparisonWithPrometheusReason string + instantEvaluationTimestamp *time.Time } func (a annotationTestCase) getExpectedInfoAnnotations(delayedNameRemovalEnabled bool) []string { @@ -2294,13 +2294,13 @@ func runAnnotationTests(t *testing.T, testCases map[string]annotationTestCase) { t.Run(subTestName, func(t *testing.T) { results := make([]*promql.Result, 0, 2) - for i, engine := range []promql.QueryEngine{engineSet.mimirEngine, engineSet.prometheusEngine} { - if i == 1 && testCase.skipComparisonWithPrometheusReason != "" { + for _, engine := range []promql.QueryEngine{engineSet.mimirEngine, engineSet.prometheusEngine} { + if engine == engineSet.prometheusEngine && testCase.skipComparisonWithPrometheusReason != "" { t.Logf("Skipping comparison with Prometheus' engine: %v", testCase.skipComparisonWithPrometheusReason) continue } engineName := mimirEngineName - if i == 1 { + if engine == engineSet.prometheusEngine { engineName = prometheusEngineName } t.Run(engineName, func(t *testing.T) { diff --git a/pkg/streamingpromql/optimize/plan/eliminate_deduplicate_and_merge_test.go b/pkg/streamingpromql/optimize/plan/eliminate_deduplicate_and_merge_test.go index 2946e35bfc7..cf3efe65a89 100644 --- a/pkg/streamingpromql/optimize/plan/eliminate_deduplicate_and_merge_test.go +++ b/pkg/streamingpromql/optimize/plan/eliminate_deduplicate_and_merge_test.go @@ -1102,11 +1102,11 @@ func runTestCasesWithDelayedNameRemovalDisabled(t *testing.T, globPattern string t.Skip("name_label_dropping tests require delayed name removal to be enabled, but optimization pass requires it to be disabled") } if strings.Contains(testFile, "delayed_name_removal_enabled") { - t.Skip("delayed_name_removal_enabled tests require delayed name removal to be enabled, but optimization pass requires it to be disabled") + t.Skip("delayed_name_removal_enabled tests require delayed name removal to be enabled, but this test exercises the optimization pass with delayed name removal disabled") } // Note that we get the equivalent test coverage from ours/native_histograms_delayed_name_removal_disabled.test if strings.Contains(testFile, "upstream/native_histograms.test") { - t.Skip("upstream/native_histograms.test tests require delayed name removal to be enabled, but optimization pass requires it to be disabled") + t.Skip("upstream/native_histograms.test tests require delayed name removal to be enabled, but this test exercises the optimization pass with delayed name removal disabled") } f, err := testdataFS.Open(testFile) diff --git a/pkg/streamingpromql/testdata/upstream/native_histograms.test b/pkg/streamingpromql/testdata/upstream/native_histograms.test index e43beb45c77..5f734084604 100644 --- a/pkg/streamingpromql/testdata/upstream/native_histograms.test +++ b/pkg/streamingpromql/testdata/upstream/native_histograms.test @@ -1224,21 +1224,18 @@ eval range from 0 to 12m step 6m count(metric) eval range from 0 to 12m step 6m group(metric) {} 1 1 1 -# Unsupported by streaming engine. -# eval range from 0 to 12m step 6m count(limitk(1, metric)) -# {} 1 1 1 - -# Unsupported by streaming engine. -# eval range from 0 to 12m step 6m limitk(3, metric) -# metric{series="1"} _ {{schema:-53 sum:1 count:1 custom_values:[5 10] buckets:[1]}} {{schema:-53 sum:1 count:1 custom_values:[5 10] buckets:[1]}} -# metric{series="2"} {{schema:-53 sum:1 count:1 custom_values:[10] buckets:[1]}} _ {{schema:-53 sum:1 count:1 custom_values:[5] buckets:[1]}} -# metric{series="3"} {{schema:-53 sum:1 count:1 custom_values:[2 10] buckets:[1]}} {{schema:-53 sum:1 count:1 custom_values:[5 10] buckets:[1]}} {{schema:-53 sum:1 count:1 custom_values:[5 10] buckets:[1]}} - -# Unsupported by streaming engine. -# eval range from 0 to 12m step 6m limit_ratio(1, metric) -# metric{series="1"} _ {{schema:-53 sum:1 count:1 custom_values:[5 10] buckets:[1]}} {{schema:-53 sum:1 count:1 custom_values:[5 10] buckets:[1]}} -# metric{series="2"} {{schema:-53 sum:1 count:1 custom_values:[10] buckets:[1]}} _ {{schema:-53 sum:1 count:1 custom_values:[5] buckets:[1]}} -# metric{series="3"} {{schema:-53 sum:1 count:1 custom_values:[2 10] buckets:[1]}} {{schema:-53 sum:1 count:1 custom_values:[5 10] buckets:[1]}} {{schema:-53 sum:1 count:1 custom_values:[5 10] buckets:[1]}} +eval range from 0 to 12m step 6m count(limitk(1, metric)) + {} 1 1 1 + +eval range from 0 to 12m step 6m limitk(3, metric) + metric{series="1"} _ {{schema:-53 sum:1 count:1 custom_values:[5 10] buckets:[1]}} {{schema:-53 sum:1 count:1 custom_values:[5 10] buckets:[1]}} + metric{series="2"} {{schema:-53 sum:1 count:1 custom_values:[10] buckets:[1]}} _ {{schema:-53 sum:1 count:1 custom_values:[5] buckets:[1]}} + metric{series="3"} {{schema:-53 sum:1 count:1 custom_values:[2 10] buckets:[1]}} {{schema:-53 sum:1 count:1 custom_values:[5 10] buckets:[1]}} {{schema:-53 sum:1 count:1 custom_values:[5 10] buckets:[1]}} + +eval range from 0 to 12m step 6m limit_ratio(1, metric) + metric{series="1"} _ {{schema:-53 sum:1 count:1 custom_values:[5 10] buckets:[1]}} {{schema:-53 sum:1 count:1 custom_values:[5 10] buckets:[1]}} + metric{series="2"} {{schema:-53 sum:1 count:1 custom_values:[10] buckets:[1]}} _ {{schema:-53 sum:1 count:1 custom_values:[5] buckets:[1]}} + metric{series="3"} {{schema:-53 sum:1 count:1 custom_values:[2 10] buckets:[1]}} {{schema:-53 sum:1 count:1 custom_values:[5 10] buckets:[1]}} {{schema:-53 sum:1 count:1 custom_values:[5 10] buckets:[1]}} # Test mismatched schemas with and/or eval range from 0 to 12m step 6m metric{series="1"} and ignoring(series) metric{series="2"} @@ -1753,22 +1750,18 @@ load 1m mixedHistogram{le="1"} 0+3x10 mixedHistogram{} {{schema:0 count:10 sum:50 buckets:[1 2 3]}} -# Unsupported by streaming engine. -# eval instant at 1m histogram_quantile(0.5, nonmonotonic_bucket) -# expect info msg: PromQL info: input to histogram_quantile needed to be fixed for monotonicity (see https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile) for metric name "nonmonotonic_bucket" -# {} 8.5 +eval instant at 1m histogram_quantile(0.5, nonmonotonic_bucket) + expect info msg: PromQL info: input to histogram_quantile needed to be fixed for monotonicity (see https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile) for metric name "nonmonotonic_bucket" + {} 8.5 -# Unsupported by streaming engine. -# eval instant at 1m histogram_quantile(0.5, myHistogram1) -# expect warn msg: PromQL warning: bucket label "le" is missing or has a malformed value of "" for metric name "myHistogram1" +eval instant at 1m histogram_quantile(0.5, myHistogram1) + expect warn msg: PromQL warning: bucket label "le" is missing or has a malformed value of "" for metric name "myHistogram1" -# Unsupported by streaming engine. -# eval instant at 1m histogram_quantile(0.5, myHistogram2) -# expect warn msg: PromQL warning: bucket label "le" is missing or has a malformed value of "Hello World" for metric name "myHistogram2" +eval instant at 1m histogram_quantile(0.5, myHistogram2) + expect warn msg: PromQL warning: bucket label "le" is missing or has a malformed value of "Hello World" for metric name "myHistogram2" -# Unsupported by streaming engine. -# eval instant at 1m histogram_quantile(0.5, mixedHistogram) -# expect warn msg: PromQL warning: vector contains a mix of classic and native histograms for metric name "mixedHistogram" +eval instant at 1m histogram_quantile(0.5, mixedHistogram) + expect warn msg: PromQL warning: vector contains a mix of classic and native histograms for metric name "mixedHistogram" clear From 3bbefd1b83bcfb227227bcb112c81a5882089a16 Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Tue, 20 Jan 2026 08:13:23 +0800 Subject: [PATCH 11/12] PR feedback --- .../eliminate_deduplicate_and_merge_test.go | 2 +- ...stograms_delayed_name_removal_enabled.test | 66 ------------------- 2 files changed, 1 insertion(+), 67 deletions(-) delete mode 100644 pkg/streamingpromql/testdata/ours/native_histograms_delayed_name_removal_enabled.test diff --git a/pkg/streamingpromql/optimize/plan/eliminate_deduplicate_and_merge_test.go b/pkg/streamingpromql/optimize/plan/eliminate_deduplicate_and_merge_test.go index cf3efe65a89..21617d16897 100644 --- a/pkg/streamingpromql/optimize/plan/eliminate_deduplicate_and_merge_test.go +++ b/pkg/streamingpromql/optimize/plan/eliminate_deduplicate_and_merge_test.go @@ -1099,7 +1099,7 @@ func runTestCasesWithDelayedNameRemovalDisabled(t *testing.T, globPattern string for _, testFile := range testFiles { t.Run(testFile, func(t *testing.T) { if strings.Contains(testFile, "name_label_dropping") { - t.Skip("name_label_dropping tests require delayed name removal to be enabled, but optimization pass requires it to be disabled") + t.Skip("name_label_dropping tests require delayed name removal to be enabled, but this test exercises the optimization pass with delayed name removal disabled") } if strings.Contains(testFile, "delayed_name_removal_enabled") { t.Skip("delayed_name_removal_enabled tests require delayed name removal to be enabled, but this test exercises the optimization pass with delayed name removal disabled") diff --git a/pkg/streamingpromql/testdata/ours/native_histograms_delayed_name_removal_enabled.test b/pkg/streamingpromql/testdata/ours/native_histograms_delayed_name_removal_enabled.test deleted file mode 100644 index 0258d63913b..00000000000 --- a/pkg/streamingpromql/testdata/ours/native_histograms_delayed_name_removal_enabled.test +++ /dev/null @@ -1,66 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-only -# Provenance-includes-location: https://github.com/prometheus/prometheus/tree/main/promql/testdata/native_histograms.test -# Provenance-includes-license: Apache-2.0 -# Provenance-includes-copyright: The Prometheus Authors - -# Test histogram_quantile annotations. -load 1m - nonmonotonic_bucket{le="0.1"} 0+2x10 - nonmonotonic_bucket{le="1"} 0+1x10 - nonmonotonic_bucket{le="10"} 0+5x10 - nonmonotonic_bucket{le="100"} 0+4x10 - nonmonotonic_bucket{le="1000"} 0+9x10 - nonmonotonic_bucket{le="+Inf"} 0+8x10 - myHistogram1{abe="0.1"} 0+2x10 - myHistogram2{le="Hello World"} 0+2x10 - mixedHistogram{le="0.1"} 0+2x10 - mixedHistogram{le="1"} 0+3x10 - mixedHistogram{} {{schema:0 count:10 sum:50 buckets:[1 2 3]}} - -eval instant at 1m histogram_quantile(0.5, nonmonotonic_bucket) - expect info msg: PromQL info: input to histogram_quantile needed to be fixed for monotonicity (see https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile) for metric name "nonmonotonic_bucket" - {} 8.5 - -eval instant at 1m histogram_quantile(0.5, myHistogram1) - expect warn msg: PromQL warning: bucket label "le" is missing or has a malformed value of "" for metric name "myHistogram1" - -eval instant at 1m histogram_quantile(0.5, myHistogram2) - expect warn msg: PromQL warning: bucket label "le" is missing or has a malformed value of "Hello World" for metric name "myHistogram2" - -eval instant at 1m histogram_quantile(0.5, mixedHistogram) - expect warn msg: PromQL warning: vector contains a mix of classic and native histograms for metric name "mixedHistogram" - -clear - -# Test native histogram quantile and fraction when the native histogram with exponential -# buckets has NaN observations. -load 1m - histogram_nan{case="100% NaNs"} {{schema:0 count:0 sum:0}} {{schema:0 count:3 sum:NaN}} - histogram_nan{case="20% NaNs"} {{schema:0 count:0 sum:0}} {{schema:0 count:15 sum:NaN buckets:[12]}} - -eval instant at 1m histogram_quantile(1, histogram_nan) - expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan" - {case="100% NaNs"} NaN - {case="20% NaNs"} NaN - -eval instant at 1m histogram_quantile(0.81, histogram_nan) - expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan" - {case="100% NaNs"} NaN - {case="20% NaNs"} NaN - -eval instant at 1m histogram_quantile(0.8, histogram_nan{case="100% NaNs"}) - expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan" - {case="100% NaNs"} NaN - -eval instant at 1m histogram_quantile(0.8, histogram_nan{case="20% NaNs"}) - expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is skewed higher for metric name "histogram_nan" - {case="20% NaNs"} 1 - -eval instant at 1m histogram_quantile(0.4, histogram_nan{case="100% NaNs"}) - expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan" - {case="100% NaNs"} NaN - -# histogram_quantile and histogram_fraction equivalence if quantile is not NaN -eval instant at 1m histogram_quantile(0.4, histogram_nan{case="20% NaNs"}) - expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is skewed higher for metric name "histogram_nan" - {case="20% NaNs"} 0.7071067811865475 \ No newline at end of file From fe279ba59a9d22dec8b524f1bf882962cef24e08 Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Tue, 20 Jan 2026 08:29:11 +0800 Subject: [PATCH 12/12] Update eliminate_deduplicate_and_merge_test.go --- .../optimize/plan/eliminate_deduplicate_and_merge_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/streamingpromql/optimize/plan/eliminate_deduplicate_and_merge_test.go b/pkg/streamingpromql/optimize/plan/eliminate_deduplicate_and_merge_test.go index 21617d16897..a90fd28cd3a 100644 --- a/pkg/streamingpromql/optimize/plan/eliminate_deduplicate_and_merge_test.go +++ b/pkg/streamingpromql/optimize/plan/eliminate_deduplicate_and_merge_test.go @@ -1101,9 +1101,6 @@ func runTestCasesWithDelayedNameRemovalDisabled(t *testing.T, globPattern string if strings.Contains(testFile, "name_label_dropping") { t.Skip("name_label_dropping tests require delayed name removal to be enabled, but this test exercises the optimization pass with delayed name removal disabled") } - if strings.Contains(testFile, "delayed_name_removal_enabled") { - t.Skip("delayed_name_removal_enabled tests require delayed name removal to be enabled, but this test exercises the optimization pass with delayed name removal disabled") - } // Note that we get the equivalent test coverage from ours/native_histograms_delayed_name_removal_disabled.test if strings.Contains(testFile, "upstream/native_histograms.test") { t.Skip("upstream/native_histograms.test tests require delayed name removal to be enabled, but this test exercises the optimization pass with delayed name removal disabled")