Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

query-tee: Add an option to skip samples for comparison before a given timestamp #9515

Merged
merged 12 commits into from
Oct 9, 2024
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@

### Query-tee

* [FEATURE] Added `-proxy.compare-skip-samples-before` to skip samples before the given time when comparing responses. The time must be in RFC3339 format. #9515

### Documentation

### Tools
Expand Down
2 changes: 2 additions & 0 deletions cmd/query-tee/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/grafana/dskit/tracing"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/collectors"
"github.com/prometheus/common/model"
jaegercfg "github.com/uber/jaeger-client-go/config"

"github.com/grafana/mimir/pkg/util/instrumentation"
Expand Down Expand Up @@ -106,6 +107,7 @@ func mimirReadRoutes(cfg Config) []querytee.Route {
Tolerance: cfg.ProxyConfig.ValueComparisonTolerance,
UseRelativeError: cfg.ProxyConfig.UseRelativeError,
SkipRecentSamples: cfg.ProxyConfig.SkipRecentSamples,
SkipSamplesBefore: model.Time(cfg.ProxyConfig.SkipSamplesBefore.UnixMilli()),
RequireExactErrorMatch: cfg.ProxyConfig.RequireExactErrorMatch,
})

Expand Down
11 changes: 11 additions & 0 deletions tools/querytee/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type ProxyConfig struct {
UseRelativeError bool
PassThroughNonRegisteredRoutes bool
SkipRecentSamples time.Duration
SkipSamplesBefore time.Time
RequireExactErrorMatch bool
BackendSkipTLSVerify bool
AddMissingTimeParamToInstantQueries bool
Expand Down Expand Up @@ -69,6 +70,16 @@ func (cfg *ProxyConfig) RegisterFlags(f *flag.FlagSet) {
f.BoolVar(&cfg.PassThroughNonRegisteredRoutes, "proxy.passthrough-non-registered-routes", false, "Passthrough requests for non-registered routes to preferred backend.")
f.BoolVar(&cfg.AddMissingTimeParamToInstantQueries, "proxy.add-missing-time-parameter-to-instant-queries", true, "Add a 'time' parameter to proxied instant query requests if they do not have one.")
f.Float64Var(&cfg.SecondaryBackendsRequestProportion, "proxy.secondary-backends-request-proportion", 1.0, "Proportion of requests to send to secondary backends. Must be between 0 and 1 (inclusive), and if not 1, then -backend.preferred must be set.")

var skipSamplesBefore string
f.StringVar(&skipSamplesBefore, "proxy.compare-skip-samples-before", "", "Skip the samples before the given time for comparison. The time must be in RFC3339 format.")
codesome marked this conversation as resolved.
Show resolved Hide resolved
if skipSamplesBefore != "" {
var err error
cfg.SkipSamplesBefore, err = time.Parse(time.RFC3339, skipSamplesBefore)
if err != nil {
cfg.SkipSamplesBefore = time.Time{}
}
}
}

type Route struct {
Expand Down
82 changes: 68 additions & 14 deletions tools/querytee/response_comparator.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type SampleComparisonOptions struct {
Tolerance float64
UseRelativeError bool
SkipRecentSamples time.Duration
SkipSamplesBefore model.Time
RequireExactErrorMatch bool
}

Expand Down Expand Up @@ -220,7 +221,7 @@ func compareMatrix(expectedRaw, actualRaw json.RawMessage, queryEvaluationTime t
return err
}

if allMatrixSamplesWithinRecentSampleWindow(expected, queryEvaluationTime, opts) && allMatrixSamplesWithinRecentSampleWindow(actual, queryEvaluationTime, opts) {
if allMatrixSamplesOutsideComparableWindow(expected, queryEvaluationTime, opts) && allMatrixSamplesOutsideComparableWindow(actual, queryEvaluationTime, opts) {
return nil
}

Expand Down Expand Up @@ -250,6 +251,19 @@ func compareMatrix(expectedRaw, actualRaw json.RawMessage, queryEvaluationTime t
}

func compareMatrixSamples(expected, actual *model.SampleStream, queryEvaluationTime time.Time, opts SampleComparisonOptions) error {
expected.Values = trimBeginning(expected.Values, func(p model.SamplePair) bool {
return p.Timestamp < opts.SkipSamplesBefore
})
actual.Values = trimBeginning(actual.Values, func(p model.SamplePair) bool {
return p.Timestamp < opts.SkipSamplesBefore
})
expected.Histograms = trimBeginning(expected.Histograms, func(p model.SampleHistogramPair) bool {
return p.Timestamp < opts.SkipSamplesBefore
})
actual.Histograms = trimBeginning(actual.Histograms, func(p model.SampleHistogramPair) bool {
return p.Timestamp < opts.SkipSamplesBefore
})

expectedSamplesTail, actualSamplesTail, err := comparePairs(expected.Values, actual.Values, func(p1 model.SamplePair, p2 model.SamplePair) error {
err := compareSamplePair(p1, p2, queryEvaluationTime, opts)
if err != nil {
Expand Down Expand Up @@ -281,15 +295,15 @@ func compareMatrixSamples(expected, actual *model.SampleStream, queryEvaluationT
return nil
}

skipAllRecentFloatSamples := canSkipAllSamples(func(p model.SamplePair) bool {
return queryEvaluationTime.Sub(p.Timestamp.Time())-opts.SkipRecentSamples < 0
skipAllFloatSamples := canSkipAllSamples(func(p model.SamplePair) bool {
return queryEvaluationTime.Sub(p.Timestamp.Time())-opts.SkipRecentSamples < 0 || p.Timestamp < opts.SkipSamplesBefore
}, expectedSamplesTail, actualSamplesTail)

skipAllRecentHistogramSamples := canSkipAllSamples(func(p model.SampleHistogramPair) bool {
return queryEvaluationTime.Sub(p.Timestamp.Time())-opts.SkipRecentSamples < 0
skipAllHistogramSamples := canSkipAllSamples(func(p model.SampleHistogramPair) bool {
return queryEvaluationTime.Sub(p.Timestamp.Time())-opts.SkipRecentSamples < 0 || p.Timestamp < opts.SkipSamplesBefore
}, expectedHistogramSamplesTail, actualHistogramSamplesTail)

if skipAllRecentFloatSamples && skipAllRecentHistogramSamples {
if skipAllFloatSamples && skipAllHistogramSamples {
return nil
}

Expand Down Expand Up @@ -345,6 +359,29 @@ func comparePairs[S ~[]M, M any](s1, s2 S, compare func(M, M) error) (S, S, erro
return s1[i:], s2[i:], nil
}

// trimBeginning returns s without the prefix that satisfies skip().
func trimBeginning[S ~[]M, M any](s S, skip func(M) bool) S {
var i int
for ; i < len(s); i++ {
if !skip(s[i]) {
break
}
}
return s[i:]
}

// filterSlice returns a new slice with elements from s removed that satisfy skip().
func filterSlice[S ~[]M, M any](s S, skip func(M) bool) S {
var i int
var res S
codesome marked this conversation as resolved.
Show resolved Hide resolved
for ; i < len(s); i++ {
if !skip(s[i]) {
res = append(res, s[i])
}
}
return res
}

func canSkipAllSamples[S ~[]M, M any](skip func(M) bool, ss ...S) bool {
for _, s := range ss {
for _, p := range s {
Expand All @@ -356,20 +393,22 @@ func canSkipAllSamples[S ~[]M, M any](skip func(M) bool, ss ...S) bool {
return true
}

func allMatrixSamplesWithinRecentSampleWindow(m model.Matrix, queryEvaluationTime time.Time, opts SampleComparisonOptions) bool {
if opts.SkipRecentSamples == 0 {
func allMatrixSamplesOutsideComparableWindow(m model.Matrix, queryEvaluationTime time.Time, opts SampleComparisonOptions) bool {
if opts.SkipRecentSamples == 0 && opts.SkipSamplesBefore == 0 {
return false
}

for _, series := range m {
for _, sample := range series.Values {
if queryEvaluationTime.Sub(sample.Timestamp.Time()) > opts.SkipRecentSamples {
st := sample.Timestamp
if queryEvaluationTime.Sub(st.Time()) > opts.SkipRecentSamples && st >= opts.SkipSamplesBefore {
return false
}
}

for _, sample := range series.Histograms {
if queryEvaluationTime.Sub(sample.Timestamp.Time()) > opts.SkipRecentSamples {
st := sample.Timestamp
if queryEvaluationTime.Sub(st.Time()) > opts.SkipRecentSamples && st >= opts.SkipSamplesBefore {
return false
}
}
Expand All @@ -391,10 +430,17 @@ func compareVector(expectedRaw, actualRaw json.RawMessage, queryEvaluationTime t
return err
}

if allVectorSamplesWithinRecentSampleWindow(expected, queryEvaluationTime, opts) && allVectorSamplesWithinRecentSampleWindow(actual, queryEvaluationTime, opts) {
if allVectorSamplesOutsideComparableWindow(expected, queryEvaluationTime, opts) && allVectorSamplesOutsideComparableWindow(actual, queryEvaluationTime, opts) {
return nil
}

expected = filterSlice(expected, func(p *model.Sample) bool {
return p.Timestamp < opts.SkipSamplesBefore
})
actual = filterSlice(actual, func(p *model.Sample) bool {
return p.Timestamp < opts.SkipSamplesBefore
})

if len(expected) != len(actual) {
return fmt.Errorf("expected %d metrics but got %d", len(expected), len(actual))
}
Expand Down Expand Up @@ -454,13 +500,14 @@ func compareVector(expectedRaw, actualRaw json.RawMessage, queryEvaluationTime t
return nil
}

func allVectorSamplesWithinRecentSampleWindow(v model.Vector, queryEvaluationTime time.Time, opts SampleComparisonOptions) bool {
if opts.SkipRecentSamples == 0 {
func allVectorSamplesOutsideComparableWindow(v model.Vector, queryEvaluationTime time.Time, opts SampleComparisonOptions) bool {
if opts.SkipRecentSamples == 0 && opts.SkipSamplesBefore == 0 {
return false
}

for _, sample := range v {
if queryEvaluationTime.Sub(sample.Timestamp.Time()) > opts.SkipRecentSamples {
st := sample.Timestamp
if queryEvaluationTime.Sub(st.Time()) > opts.SkipRecentSamples && st >= opts.SkipSamplesBefore {
return false
}
}
Expand Down Expand Up @@ -495,6 +542,9 @@ func compareScalar(expectedRaw, actualRaw json.RawMessage, queryEvaluationTime t
}

func compareSamplePair(expected, actual model.SamplePair, queryEvaluationTime time.Time, opts SampleComparisonOptions) error {
if expected.Timestamp < opts.SkipSamplesBefore && actual.Timestamp < opts.SkipSamplesBefore {
return nil
}
codesome marked this conversation as resolved.
Show resolved Hide resolved
if expected.Timestamp != actual.Timestamp {
return fmt.Errorf("expected timestamp %v but got %v", expected.Timestamp, actual.Timestamp)
}
Expand Down Expand Up @@ -523,6 +573,10 @@ func compareSampleValue(first, second float64, opts SampleComparisonOptions) boo
}

func compareSampleHistogramPair(expected, actual model.SampleHistogramPair, queryEvaluationTime time.Time, opts SampleComparisonOptions) error {
if expected.Timestamp < opts.SkipSamplesBefore {
codesome marked this conversation as resolved.
Show resolved Hide resolved
return nil
}

if expected.Timestamp != actual.Timestamp {
return fmt.Errorf("expected timestamp %v but got %v", expected.Timestamp, actual.Timestamp)
}
Expand Down
Loading
Loading