From 5c2e43f09c03a02474016c98bcbd073ac8d70cea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linas=20Med=C5=BEi=C5=ABnas?= Date: Fri, 5 Sep 2025 15:46:57 +0300 Subject: [PATCH] [BUGFIX] PromQL: fix slice indexing bug in info function (#17135) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [BUGFIX] PromQL: fix slice indexing bug in info function --------- Signed-off-by: Linas Medziunas Signed-off-by: Linas Medžiūnas Co-authored-by: Arve Knudsen --- CHANGELOG.md | 1 + promql/info.go | 14 +++++++------- promql/info_test.go | 15 +++++++++++++++ 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd3a163c06..833c776975 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * [BUGFIX] OTLP receiver: Generate `target_info` samples between the earliest and latest samples per resource. #16737 * [BUGFIX] Config: Infer escaping scheme when scrape config validation scheme is set. +* [BUGFIX] PromQL: Fix info function on churning series. #17135 ## 3.5.0 / 2025-07-14 diff --git a/promql/info.go b/promql/info.go index 0197330822..318dae399a 100644 --- a/promql/info.go +++ b/promql/info.go @@ -258,10 +258,10 @@ func (ev *evaluator) combineWithInfoSeries(ctx context.Context, mat, infoMat Mat baseSigs = append(baseSigs, sigs) } - infoSigs := make([]string, 0, len(infoMat)) + infoSigs := make(map[uint64]string, len(infoMat)) for _, s := range infoMat { name := s.Metric.Map()[labels.MetricName] - infoSigs = append(infoSigs, sigfs[name](s.Metric)) + infoSigs[s.Metric.Hash()] = sigfs[name](s.Metric) } var warnings annotations.Annotations @@ -331,7 +331,7 @@ func (ev *evaluator) combineWithInfoSeries(ctx context.Context, mat, infoMat Mat // combineWithInfoVector combines base and info Vectors. // Base series in ignoreSeries are not combined. -func (ev *evaluator) combineWithInfoVector(base, info Vector, ignoreSeries map[int]struct{}, baseSigs []map[string]string, infoSigs []string, enh *EvalNodeHelper, dataLabelMatchers map[string][]*labels.Matcher) (Vector, error) { +func (ev *evaluator) combineWithInfoVector(base, info Vector, ignoreSeries map[int]struct{}, baseSigs []map[string]string, infoSigs map[uint64]string, enh *EvalNodeHelper, dataLabelMatchers map[string][]*labels.Matcher) (Vector, error) { if len(base) == 0 { return nil, nil // Short-circuit: nothing is going to match. } @@ -343,14 +343,14 @@ func (ev *evaluator) combineWithInfoVector(base, info Vector, ignoreSeries map[i clear(enh.rightSigs) } - for i, s := range info { + for _, s := range info { if s.H != nil { ev.error(errors.New("info sample should be float")) } // We encode original info sample timestamps via the float value. origT := int64(s.F) - sig := infoSigs[i] + sig := infoSigs[s.Metric.Hash()] if existing, exists := enh.rightSigs[sig]; exists { // We encode original info sample timestamps via the float value. existingOrigT := int64(existing.F) @@ -362,8 +362,8 @@ func (ev *evaluator) combineWithInfoVector(base, info Vector, ignoreSeries map[i enh.rightSigs[sig] = s default: // The two info samples have the same timestamp - conflict. - name := s.Metric.Map()[labels.MetricName] - ev.errorf("found duplicate series for info metric %s", name) + ev.errorf("found duplicate series for info metric: existing %s @ %d, new %s @ %d", + existing.Metric.String(), existingOrigT, s.Metric.String(), origT) } } else { enh.rightSigs[sig] = s diff --git a/promql/info_test.go b/promql/info_test.go index 2e7a67172f..f473b3c2f9 100644 --- a/promql/info_test.go +++ b/promql/info_test.go @@ -136,5 +136,20 @@ eval range from 1m to 4m step 1m info(metric @ 60) # offset operator works also with info. eval range from 1m to 4m step 1m info(metric offset 1m) metric{data="info", instance="a", job="1", label="value"} 0 0 2 3 + +clear + +load 1m + data_metric{instance="a", job="work"} 10 20 30 + data_metric{instance="b", job="work"} 11 21 31 + info_metric{instance="b", job="work", state="stopped"} 1 1 _ + info_metric{instance="b", job="work", state="running"} _ _ 1 + info_metric{instance="a", job="work", state="running"} 1 1 1 + +eval range from 0 to 2m step 1m info(data_metric, {__name__="info_metric"}) + data_metric{instance="a", job="work", state="running"} 10 20 30 + data_metric{instance="b", job="work", state="stopped"} 11 21 _ + data_metric{instance="b", job="work", state="running"} _ _ 31 + `, engine) }