@@ -6,16 +6,22 @@ package engine_test
66import (
77 "context"
88 "fmt"
9+ "sync"
910 "testing"
1011 "time"
1112
1213 "github.com/efficientgo/core/testutil"
1314 "github.com/prometheus/prometheus/promql"
1415 "github.com/prometheus/prometheus/promql/parser"
1516 "github.com/prometheus/prometheus/promql/promqltest"
17+ promstorage "github.com/prometheus/prometheus/storage"
1618
1719 "github.com/thanos-io/promql-engine/engine"
20+ "github.com/thanos-io/promql-engine/execution/model"
1821 "github.com/thanos-io/promql-engine/logicalplan"
22+ "github.com/thanos-io/promql-engine/query"
23+ engstorage "github.com/thanos-io/promql-engine/storage"
24+ promscan "github.com/thanos-io/promql-engine/storage/prometheus"
1925)
2026
2127func TestAnchoredSmoothedModifiers (t * testing.T ) {
@@ -289,3 +295,126 @@ func TestAnchoredSmoothedWhitelist(t *testing.T) {
289295 })
290296 }
291297}
298+
299+ // hintsSpy wraps real scanners and records the SelectHints passed to NewMatrixSelector.
300+ type hintsSpy struct {
301+ inner engstorage.Scanners
302+ mu sync.Mutex
303+ matrixHintsCap []promstorage.SelectHints
304+ }
305+
306+ func (s * hintsSpy ) Close () error { return s .inner .Close () }
307+
308+ func (s * hintsSpy ) NewVectorSelector (ctx context.Context , opts * query.Options , hints promstorage.SelectHints , selector logicalplan.VectorSelector ) (model.VectorOperator , error ) {
309+ return s .inner .NewVectorSelector (ctx , opts , hints , selector )
310+ }
311+
312+ func (s * hintsSpy ) NewMatrixSelector (ctx context.Context , opts * query.Options , hints promstorage.SelectHints , selector logicalplan.MatrixSelector , call logicalplan.FunctionCall ) (model.VectorOperator , error ) {
313+ s .mu .Lock ()
314+ s .matrixHintsCap = append (s .matrixHintsCap , hints )
315+ s .mu .Unlock ()
316+ return s .inner .NewMatrixSelector (ctx , opts , hints , selector , call )
317+ }
318+
319+ func (s * hintsSpy ) capturedHints () []promstorage.SelectHints {
320+ s .mu .Lock ()
321+ defer s .mu .Unlock ()
322+ return s .matrixHintsCap
323+ }
324+
325+ func TestAnchoredSmoothedSelectHints (t * testing.T ) {
326+ t .Parallel ()
327+ parser .EnableExtendedRangeSelectors = true
328+
329+ load := `load 10s
330+ metric 0 10 20 30 40 50 60 70 80 90 100`
331+ storage := promqltest .LoadedStorage (t , load )
332+ defer storage .Close ()
333+
334+ lookbackDelta := 5 * time .Minute // engine default
335+
336+ cases := []struct {
337+ name string
338+ query string
339+ expectedRange int64 // hints.Range in ms — should always be original selector range
340+ }{
341+ {
342+ name : "standard rate — Range equals selector range" ,
343+ query : `rate(metric[30s])` ,
344+ expectedRange : 30000 ,
345+ },
346+ {
347+ name : "anchored rate — Range equals selector range, not widened" ,
348+ query : `rate(metric[30s] anchored)` ,
349+ expectedRange : 30000 ,
350+ },
351+ {
352+ name : "smoothed rate — Range equals selector range, not widened" ,
353+ query : `rate(metric[30s] smoothed)` ,
354+ expectedRange : 30000 ,
355+ },
356+ {
357+ name : "anchored increase — Range equals selector range" ,
358+ query : `increase(metric[1m] anchored)` ,
359+ expectedRange : 60000 ,
360+ },
361+ {
362+ name : "smoothed delta — Range equals selector range" ,
363+ query : `delta(metric[1m] smoothed)` ,
364+ expectedRange : 60000 ,
365+ },
366+ }
367+
368+ for _ , tc := range cases {
369+ t .Run (tc .name , func (t * testing.T ) {
370+ opts := engine.Opts {
371+ EngineOpts : promql.EngineOpts {
372+ Timeout : 1 * time .Hour ,
373+ MaxSamples : 1e10 ,
374+ },
375+ EnableExtendedRangeSelectors : true ,
376+ }
377+ qOpts := & query.Options {
378+ Start : time .Unix (0 , 0 ),
379+ End : time .Unix (100 , 0 ),
380+ Step : 10 * time .Second ,
381+ LookbackDelta : lookbackDelta ,
382+ ExtLookbackDelta : 1 * time .Hour ,
383+ }
384+
385+ // Parse and build the logical plan to create real scanners.
386+ parser .EnableExtendedRangeSelectors = true
387+ expr , err := parser .NewParser (tc .query ).ParseExpr ()
388+ testutil .Ok (t , err )
389+
390+ planOpts := logicalplan.PlanOptions {}
391+ lplan , err := logicalplan .NewFromAST (expr , qOpts , planOpts )
392+ testutil .Ok (t , err )
393+ optimizedPlan , _ := lplan .Optimize (logicalplan .AllOptimizers )
394+
395+ realScanners , err := promscan .NewPrometheusScanners (storage , qOpts , optimizedPlan )
396+ testutil .Ok (t , err )
397+ defer realScanners .Close ()
398+
399+ spy := & hintsSpy {inner : realScanners }
400+
401+ eng := engine .NewWithScanners (opts , spy )
402+ q , err := eng .NewRangeQuery (context .Background (), storage , nil , tc .query , time .Unix (0 , 0 ), time .Unix (100 , 0 ), 10 * time .Second )
403+ testutil .Ok (t , err )
404+ defer q .Close ()
405+
406+ res := q .Exec (context .Background ())
407+ testutil .Ok (t , res .Err )
408+
409+ hints := spy .capturedHints ()
410+ if len (hints ) == 0 {
411+ t .Fatal ("expected at least one NewMatrixSelector call, got none" )
412+ }
413+ for i , h := range hints {
414+ if h .Range != tc .expectedRange {
415+ t .Errorf ("hints[%d].Range = %d, want %d (original selector range)" , i , h .Range , tc .expectedRange )
416+ }
417+ }
418+ })
419+ }
420+ }
0 commit comments