@@ -20,6 +20,7 @@ import (
20
20
"github.com/prometheus/prometheus/promql/parser"
21
21
"github.com/prometheus/prometheus/promql/promqltest"
22
22
"github.com/prometheus/prometheus/storage"
23
+ "github.com/prometheus/prometheus/util/stats"
23
24
"github.com/prometheus/prometheus/util/teststorage"
24
25
"github.com/stretchr/testify/require"
25
26
@@ -32,9 +33,31 @@ import (
32
33
const testRuns = 100
33
34
34
35
type testCase struct {
35
- query string
36
- loads []string
37
- oldRes , newRes * promql.Result
36
+ query string
37
+ loads []string
38
+ oldRes , newRes * promql.Result
39
+ oldStats , newStats * stats.Statistics
40
+ start , end time.Time
41
+ interval time.Duration
42
+ validateSamples bool
43
+ }
44
+
45
+ // shouldValidateSamples checks if the samples can be compared for the expr.
46
+ // For certain known cases, prometheus engine and thanos engine returns different samples.
47
+ func shouldValidateSamples (expr parser.Expr ) bool {
48
+ valid := true
49
+
50
+ parser .Inspect (expr , func (node parser.Node , path []parser.Node ) error {
51
+ switch n := node .(type ) {
52
+ case * parser.Call :
53
+ if n .Func .Name == "scalar" {
54
+ valid = false
55
+ return errors .New ("error" )
56
+ }
57
+ }
58
+ return nil
59
+ })
60
+ return valid
38
61
}
39
62
40
63
func FuzzEnginePromQLSmithRangeQuery (f * testing.F ) {
@@ -61,7 +84,9 @@ func FuzzEnginePromQLSmithRangeQuery(f *testing.F) {
61
84
MaxSamples : 1e10 ,
62
85
EnableNegativeOffset : true ,
63
86
EnableAtModifier : true ,
87
+ EnablePerStepStats : true ,
64
88
}
89
+ qOpts := promql .NewPrometheusQueryOpts (true , 0 )
65
90
66
91
storage := promqltest .LoadedStorage (t , load )
67
92
defer storage .Close ()
@@ -80,22 +105,22 @@ func FuzzEnginePromQLSmithRangeQuery(f *testing.F) {
80
105
}
81
106
ps := promqlsmith .New (rnd , seriesSet , psOpts ... )
82
107
83
- newEngine := engine .New (engine.Opts {EngineOpts : opts , DisableFallback : true })
108
+ newEngine := engine .New (engine.Opts {EngineOpts : opts , DisableFallback : true , EnableAnalysis : true })
84
109
oldEngine := promql .NewEngine (opts )
85
110
86
111
var (
87
- q1 promql.Query
88
- query string
112
+ q1 promql.Query
113
+ query string
114
+ validateSamples bool
89
115
)
90
116
cases := make ([]* testCase , testRuns )
91
117
for i := 0 ; i < testRuns ; i ++ {
92
- // Since we disabled fallback, keep trying until we find a query
93
- // that can be natively executed by the engine.
94
- // Parsing experimental function, like mad_over_time, will lead to a parser.ParseErrors, so we also ignore those.
95
118
for {
96
119
expr := ps .WalkRangeQuery ()
120
+ validateSamples = shouldValidateSamples (expr )
121
+
97
122
query = expr .Pretty (0 )
98
- q1 , err = newEngine .NewRangeQuery (context .Background (), storage , nil , query , start , end , interval )
123
+ q1 , err = newEngine .NewRangeQuery (context .Background (), storage , qOpts , query , start , end , interval )
99
124
if errors .Is (err , parse .ErrNotSupportedExpr ) || errors .Is (err , parse .ErrNotImplemented ) || errors .As (err , & parser.ParseErrors {}) {
100
125
continue
101
126
} else {
@@ -105,17 +130,27 @@ func FuzzEnginePromQLSmithRangeQuery(f *testing.F) {
105
130
106
131
testutil .Ok (t , err )
107
132
newResult := q1 .Exec (context .Background ())
133
+ newStats := q1 .Stats ()
134
+ stats .NewQueryStats (newStats )
108
135
109
- q2 , err := oldEngine .NewRangeQuery (context .Background (), storage , nil , query , start , end , interval )
136
+ q2 , err := oldEngine .NewRangeQuery (context .Background (), storage , qOpts , query , start , end , interval )
110
137
testutil .Ok (t , err )
111
138
112
139
oldResult := q2 .Exec (context .Background ())
140
+ oldStats := q2 .Stats ()
141
+ stats .NewQueryStats (oldStats )
113
142
114
143
cases [i ] = & testCase {
115
- query : query ,
116
- newRes : newResult ,
117
- oldRes : oldResult ,
118
- loads : []string {load },
144
+ query : query ,
145
+ newRes : newResult ,
146
+ newStats : newStats ,
147
+ oldRes : oldResult ,
148
+ oldStats : oldStats ,
149
+ loads : []string {load },
150
+ start : start ,
151
+ end : end ,
152
+ interval : interval ,
153
+ validateSamples : validateSamples ,
119
154
}
120
155
}
121
156
validateTestCases (t , cases )
@@ -141,7 +176,9 @@ func FuzzEnginePromQLSmithInstantQuery(f *testing.F) {
141
176
MaxSamples : 1e10 ,
142
177
EnableNegativeOffset : true ,
143
178
EnableAtModifier : true ,
179
+ EnablePerStepStats : true ,
144
180
}
181
+ qOpts := promql .NewPrometheusQueryOpts (true , 0 )
145
182
146
183
storage := promqltest .LoadedStorage (t , load )
147
184
defer storage .Close ()
@@ -151,6 +188,7 @@ func FuzzEnginePromQLSmithInstantQuery(f *testing.F) {
151
188
EngineOpts : opts ,
152
189
DisableFallback : true ,
153
190
LogicalOptimizers : logicalplan .AllOptimizers ,
191
+ EnableAnalysis : true ,
154
192
})
155
193
oldEngine := promql .NewEngine (opts )
156
194
@@ -176,8 +214,11 @@ func FuzzEnginePromQLSmithInstantQuery(f *testing.F) {
176
214
// Parsing experimental function, like mad_over_time, will lead to a parser.ParseErrors, so we also ignore those.
177
215
for {
178
216
expr := ps .WalkInstantQuery ()
217
+ if ! shouldValidateSamples (expr ) {
218
+ continue
219
+ }
179
220
query = expr .Pretty (0 )
180
- q1 , err = newEngine .NewInstantQuery (context .Background (), storage , nil , query , queryTime )
221
+ q1 , err = newEngine .NewInstantQuery (context .Background (), storage , qOpts , query , queryTime )
181
222
if errors .Is (err , parse .ErrNotSupportedExpr ) || errors .Is (err , parse .ErrNotImplemented ) || errors .As (err , & parser.ParseErrors {}) {
182
223
continue
183
224
} else {
@@ -187,17 +228,26 @@ func FuzzEnginePromQLSmithInstantQuery(f *testing.F) {
187
228
188
229
testutil .Ok (t , err )
189
230
newResult := q1 .Exec (context .Background ())
231
+ newStats := q1 .Stats ()
232
+ stats .NewQueryStats (newStats )
190
233
191
- q2 , err := oldEngine .NewInstantQuery (context .Background (), storage , nil , query , queryTime )
234
+ q2 , err := oldEngine .NewInstantQuery (context .Background (), storage , qOpts , query , queryTime )
192
235
testutil .Ok (t , err )
193
236
194
237
oldResult := q2 .Exec (context .Background ())
238
+ oldStats := q2 .Stats ()
239
+ stats .NewQueryStats (oldStats )
195
240
196
241
cases [i ] = & testCase {
197
- query : query ,
198
- newRes : newResult ,
199
- oldRes : oldResult ,
200
- loads : []string {load },
242
+ query : query ,
243
+ newRes : newResult ,
244
+ newStats : newStats ,
245
+ oldRes : oldResult ,
246
+ oldStats : oldStats ,
247
+ loads : []string {load },
248
+ start : queryTime ,
249
+ end : queryTime ,
250
+ validateSamples : true ,
201
251
}
202
252
}
203
253
validateTestCases (t , cases )
@@ -444,14 +494,27 @@ func getSeries(ctx context.Context, q storage.Queryable) ([]labels.Labels, error
444
494
445
495
func validateTestCases (t * testing.T , cases []* testCase ) {
446
496
failures := 0
497
+ logQuery := func (c * testCase ) {
498
+ for _ , load := range c .loads {
499
+ t .Logf (load )
500
+ }
501
+ t .Logf ("query: %s, start: %d, end: %d, interval: %v" , c .query , c .start .UnixMilli (), c .end .UnixMilli (), c .interval )
502
+ }
447
503
for i , c := range cases {
448
504
if ! cmp .Equal (c .oldRes , c .newRes , comparer ) {
449
- for _ , load := range c .loads {
450
- t .Logf (load )
451
- }
452
- t .Logf (c .query )
505
+ logQuery (c )
453
506
454
507
t .Logf ("case %d error mismatch.\n new result: %s\n old result: %s\n " , i , c .newRes .String (), c .oldRes .String ())
508
+ //failures++
509
+ continue
510
+ }
511
+ if ! c .validateSamples || c .oldRes .Err != nil {
512
+ // Skip sample comparison
513
+ continue
514
+ }
515
+ if ! cmp .Equal (c .oldStats .Samples , c .newStats .Samples , samplesComparer ) {
516
+ logQuery (c )
517
+ t .Logf ("case: %d, samples mismatch. total samples: old: %v, new: %v. samples per step: old: %v, new: %v" , i , c .oldStats .Samples .TotalSamples , c .newStats .Samples .TotalSamples , c .oldStats .Samples .TotalSamplesPerStep , c .newStats .Samples .TotalSamplesPerStep )
455
518
failures ++
456
519
}
457
520
}
0 commit comments