From 85dbc3cc76a32cc9537303b8ed40e0886be1e045 Mon Sep 17 00:00:00 2001 From: Julien Pivotto <291750+roidelapluie@users.noreply.github.com> Date: Thu, 16 Apr 2026 13:40:48 +0200 Subject: [PATCH] promql: fix smoothSeries() @ modifier timestamp mismatch smoothSeries() was stamping output points at offset-adjusted timestamps instead of evaluator timestamps. When the @ modifier is used, this causes gatherVector() to miss the points because it matches by exact timestamp equality against evaluator step timestamps. Fix by iterating over evaluator timestamps and deriving data timestamps by subtracting the offset, so output points align with what gatherVector() expects. Signed-off-by: Julien Pivotto <291750+roidelapluie@users.noreply.github.com> --- promql/engine.go | 26 +++++++++---------- .../promqltest/testdata/extended_vectors.test | 8 ++++++ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/promql/engine.go b/promql/engine.go index 7039adfc9f..d7c0c7edf1 100644 --- a/promql/engine.go +++ b/promql/engine.go @@ -1712,8 +1712,6 @@ func (ev *evaluator) smoothSeries(series []storage.Series, offset time.Duration) it := storage.NewBuffer(dur + 2*durationMilliseconds(ev.lookbackDelta)) offMS := offset.Milliseconds() - start := ev.startTimestamp - offMS - end := ev.endTimestamp - offMS step := ev.interval lb := durationMilliseconds(ev.lookbackDelta) @@ -1729,9 +1727,11 @@ func (ev *evaluator) smoothSeries(series []storage.Series, offset time.Duration) var floats []FPoint var hists []HPoint - for ts := start; ts <= end; ts += step { - matrixStart := ts - lb - matrixEnd := ts + lb + for evalTS := ev.startTimestamp; evalTS <= ev.endTimestamp; evalTS += step { + // Apply offset to get the data timestamp. + dataTS := evalTS - offMS + matrixStart := dataTS - lb + matrixEnd := dataTS + lb floats, hists = ev.matrixIterSlice(it, matrixStart, matrixEnd, floats, hists) if len(floats) == 0 && len(hists) == 0 { @@ -1743,28 +1743,28 @@ func (ev *evaluator) smoothSeries(series []storage.Series, offset time.Duration) ev.errorf("smoothed and anchored modifiers do not work with native histograms") } - // Binary search for the first index with T >= ts. - i := sort.Search(len(floats), func(i int) bool { return floats[i].T >= ts }) + // Binary search for the first index with T >= dataTS. + i := sort.Search(len(floats), func(i int) bool { return floats[i].T >= dataTS }) switch { - case i < len(floats) && floats[i].T == ts: + case i < len(floats) && floats[i].T == dataTS: // Exact match. - ss.Floats = append(ss.Floats, floats[i]) + ss.Floats = append(ss.Floats, FPoint{F: floats[i].F, T: evalTS}) case i > 0 && i < len(floats): // Interpolate between prev and next. // TODO: detect if the sample is a counter, based on __type__ or metadata. prev, next := floats[i-1], floats[i] - val := interpolate(prev, next, ts, false) - ss.Floats = append(ss.Floats, FPoint{F: val, T: ts}) + val := interpolate(prev, next, dataTS, false) + ss.Floats = append(ss.Floats, FPoint{F: val, T: evalTS}) case i > 0: // No next point yet; carry forward previous value. prev := floats[i-1] - ss.Floats = append(ss.Floats, FPoint{F: prev.F, T: ts}) + ss.Floats = append(ss.Floats, FPoint{F: prev.F, T: evalTS}) default: - // i == 0 and floats[0].T > ts: there is no previous data yet; skip. + // i == 0 and floats[0].T > dataTS: there is no previous data yet; skip. } } diff --git a/promql/promqltest/testdata/extended_vectors.test b/promql/promqltest/testdata/extended_vectors.test index 06f70913b7..416945aada 100644 --- a/promql/promqltest/testdata/extended_vectors.test +++ b/promql/promqltest/testdata/extended_vectors.test @@ -434,3 +434,11 @@ eval instant at 15s increase(metric[10s] smoothed) eval instant at 60s rate(metric[5s] smoothed) eval instant at 60s increase(metric[5s] smoothed) + +# Smoothed vector selector with @ modifier with binary op. +clear +load 10s + metric 0+1x20 + +eval range from 0s to 60s step 15s metric @ 100 smoothed + 0 + {} 10 10 10 10 10