Merge remote-tracking branch 'prometheus/main' into arve/wlog-histograms

This commit is contained in:
Arve Knudsen 2024-05-09 14:40:05 +02:00
commit 38b971ed20
34 changed files with 643 additions and 543 deletions

View file

@ -56,8 +56,8 @@ import (
"github.com/prometheus/prometheus/model/rulefmt" "github.com/prometheus/prometheus/model/rulefmt"
"github.com/prometheus/prometheus/notifier" "github.com/prometheus/prometheus/notifier"
_ "github.com/prometheus/prometheus/plugins" // Register plugins. _ "github.com/prometheus/prometheus/plugins" // Register plugins.
"github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/promql/parser"
"github.com/prometheus/prometheus/promql/promqltest"
"github.com/prometheus/prometheus/scrape" "github.com/prometheus/prometheus/scrape"
"github.com/prometheus/prometheus/util/documentcli" "github.com/prometheus/prometheus/util/documentcli"
) )
@ -377,7 +377,7 @@ func main() {
case testRulesCmd.FullCommand(): case testRulesCmd.FullCommand():
os.Exit(RulesUnitTest( os.Exit(RulesUnitTest(
promql.LazyLoaderOpts{ promqltest.LazyLoaderOpts{
EnableAtModifier: true, EnableAtModifier: true,
EnableNegativeOffset: true, EnableNegativeOffset: true,
}, },

View file

@ -26,7 +26,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/promql/promqltest"
"github.com/prometheus/prometheus/tsdb" "github.com/prometheus/prometheus/tsdb"
) )
@ -88,7 +88,7 @@ func normalizeNewLine(b []byte) []byte {
} }
func TestTSDBDump(t *testing.T) { func TestTSDBDump(t *testing.T) {
storage := promql.LoadedStorage(t, ` storage := promqltest.LoadedStorage(t, `
load 1m load 1m
metric{foo="bar", baz="abc"} 1 2 3 4 5 metric{foo="bar", baz="abc"} 1 2 3 4 5
heavy_metric{foo="bar"} 5 4 3 2 1 heavy_metric{foo="bar"} 5 4 3 2 1
@ -158,7 +158,7 @@ func TestTSDBDump(t *testing.T) {
} }
func TestTSDBDumpOpenMetrics(t *testing.T) { func TestTSDBDumpOpenMetrics(t *testing.T) {
storage := promql.LoadedStorage(t, ` storage := promqltest.LoadedStorage(t, `
load 1m load 1m
my_counter{foo="bar", baz="abc"} 1 2 3 4 5 my_counter{foo="bar", baz="abc"} 1 2 3 4 5
my_gauge{bar="foo", abc="baz"} 9 8 0 4 7 my_gauge{bar="foo", abc="baz"} 9 8 0 4 7

View file

@ -36,13 +36,14 @@ import (
"github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/promql/parser"
"github.com/prometheus/prometheus/promql/promqltest"
"github.com/prometheus/prometheus/rules" "github.com/prometheus/prometheus/rules"
"github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/storage"
) )
// RulesUnitTest does unit testing of rules based on the unit testing files provided. // RulesUnitTest does unit testing of rules based on the unit testing files provided.
// More info about the file format can be found in the docs. // More info about the file format can be found in the docs.
func RulesUnitTest(queryOpts promql.LazyLoaderOpts, runStrings []string, diffFlag bool, files ...string) int { func RulesUnitTest(queryOpts promqltest.LazyLoaderOpts, runStrings []string, diffFlag bool, files ...string) int {
failed := false failed := false
var run *regexp.Regexp var run *regexp.Regexp
@ -69,7 +70,7 @@ func RulesUnitTest(queryOpts promql.LazyLoaderOpts, runStrings []string, diffFla
return successExitCode return successExitCode
} }
func ruleUnitTest(filename string, queryOpts promql.LazyLoaderOpts, run *regexp.Regexp, diffFlag bool) []error { func ruleUnitTest(filename string, queryOpts promqltest.LazyLoaderOpts, run *regexp.Regexp, diffFlag bool) []error {
fmt.Println("Unit Testing: ", filename) fmt.Println("Unit Testing: ", filename)
b, err := os.ReadFile(filename) b, err := os.ReadFile(filename)
@ -175,9 +176,9 @@ type testGroup struct {
} }
// test performs the unit tests. // test performs the unit tests.
func (tg *testGroup) test(evalInterval time.Duration, groupOrderMap map[string]int, queryOpts promql.LazyLoaderOpts, diffFlag bool, ruleFiles ...string) (outErr []error) { func (tg *testGroup) test(evalInterval time.Duration, groupOrderMap map[string]int, queryOpts promqltest.LazyLoaderOpts, diffFlag bool, ruleFiles ...string) (outErr []error) {
// Setup testing suite. // Setup testing suite.
suite, err := promql.NewLazyLoader(tg.seriesLoadingString(), queryOpts) suite, err := promqltest.NewLazyLoader(tg.seriesLoadingString(), queryOpts)
if err != nil { if err != nil {
return []error{err} return []error{err}
} }
@ -413,7 +414,7 @@ Outer:
gotSamples = append(gotSamples, parsedSample{ gotSamples = append(gotSamples, parsedSample{
Labels: s.Metric.Copy(), Labels: s.Metric.Copy(),
Value: s.F, Value: s.F,
Histogram: promql.HistogramTestExpression(s.H), Histogram: promqltest.HistogramTestExpression(s.H),
}) })
} }
@ -443,7 +444,7 @@ Outer:
expSamples = append(expSamples, parsedSample{ expSamples = append(expSamples, parsedSample{
Labels: lb, Labels: lb,
Value: s.Value, Value: s.Value,
Histogram: promql.HistogramTestExpression(hist), Histogram: promqltest.HistogramTestExpression(hist),
}) })
} }

View file

@ -18,7 +18,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/promql/promqltest"
) )
func TestRulesUnitTest(t *testing.T) { func TestRulesUnitTest(t *testing.T) {
@ -28,7 +28,7 @@ func TestRulesUnitTest(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
args args args args
queryOpts promql.LazyLoaderOpts queryOpts promqltest.LazyLoaderOpts
want int want int
}{ }{
{ {
@ -92,7 +92,7 @@ func TestRulesUnitTest(t *testing.T) {
args: args{ args: args{
files: []string{"./testdata/at-modifier-test.yml"}, files: []string{"./testdata/at-modifier-test.yml"},
}, },
queryOpts: promql.LazyLoaderOpts{ queryOpts: promqltest.LazyLoaderOpts{
EnableAtModifier: true, EnableAtModifier: true,
}, },
want: 0, want: 0,
@ -109,7 +109,7 @@ func TestRulesUnitTest(t *testing.T) {
args: args{ args: args{
files: []string{"./testdata/negative-offset-test.yml"}, files: []string{"./testdata/negative-offset-test.yml"},
}, },
queryOpts: promql.LazyLoaderOpts{ queryOpts: promqltest.LazyLoaderOpts{
EnableNegativeOffset: true, EnableNegativeOffset: true,
}, },
want: 0, want: 0,
@ -119,7 +119,7 @@ func TestRulesUnitTest(t *testing.T) {
args: args{ args: args{
files: []string{"./testdata/no-test-group-interval.yml"}, files: []string{"./testdata/no-test-group-interval.yml"},
}, },
queryOpts: promql.LazyLoaderOpts{ queryOpts: promqltest.LazyLoaderOpts{
EnableNegativeOffset: true, EnableNegativeOffset: true,
}, },
want: 0, want: 0,
@ -142,7 +142,7 @@ func TestRulesUnitTestRun(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
args args args args
queryOpts promql.LazyLoaderOpts queryOpts promqltest.LazyLoaderOpts
want int want int
}{ }{
{ {

View file

@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package promql package promql_test
import ( import (
"context" "context"
@ -23,13 +23,14 @@ import (
"github.com/prometheus/prometheus/model/histogram" "github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/promql/parser"
"github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/storage"
"github.com/prometheus/prometheus/tsdb/tsdbutil" "github.com/prometheus/prometheus/tsdb/tsdbutil"
"github.com/prometheus/prometheus/util/teststorage" "github.com/prometheus/prometheus/util/teststorage"
) )
func setupRangeQueryTestData(stor *teststorage.TestStorage, _ *Engine, interval, numIntervals int) error { func setupRangeQueryTestData(stor *teststorage.TestStorage, _ *promql.Engine, interval, numIntervals int) error {
ctx := context.Background() ctx := context.Background()
metrics := []labels.Labels{} metrics := []labels.Labels{}
@ -249,13 +250,13 @@ func BenchmarkRangeQuery(b *testing.B) {
stor := teststorage.New(b) stor := teststorage.New(b)
stor.DB.DisableCompactions() // Don't want auto-compaction disrupting timings. stor.DB.DisableCompactions() // Don't want auto-compaction disrupting timings.
defer stor.Close() defer stor.Close()
opts := EngineOpts{ opts := promql.EngineOpts{
Logger: nil, Logger: nil,
Reg: nil, Reg: nil,
MaxSamples: 50000000, MaxSamples: 50000000,
Timeout: 100 * time.Second, Timeout: 100 * time.Second,
} }
engine := NewEngine(opts) engine := promql.NewEngine(opts)
const interval = 10000 // 10s interval. const interval = 10000 // 10s interval.
// A day of data plus 10k steps. // A day of data plus 10k steps.
@ -324,7 +325,7 @@ func BenchmarkNativeHistograms(b *testing.B) {
}, },
} }
opts := EngineOpts{ opts := promql.EngineOpts{
Logger: nil, Logger: nil,
Reg: nil, Reg: nil,
MaxSamples: 50000000, MaxSamples: 50000000,
@ -338,7 +339,7 @@ func BenchmarkNativeHistograms(b *testing.B) {
for _, tc := range cases { for _, tc := range cases {
b.Run(tc.name, func(b *testing.B) { b.Run(tc.name, func(b *testing.B) {
ng := NewEngine(opts) ng := promql.NewEngine(opts)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
qry, err := ng.NewRangeQuery(context.Background(), testStorage, nil, tc.query, start, end, step) qry, err := ng.NewRangeQuery(context.Background(), testStorage, nil, tc.query, start, end, step)
if err != nil { if err != nil {

View file

@ -573,7 +573,8 @@ func (ng *Engine) validateOpts(expr parser.Expr) error {
return validationErr return validationErr
} }
func (ng *Engine) newTestQuery(f func(context.Context) error) Query { // NewTestQuery: inject special behaviour into Query for testing.
func (ng *Engine) NewTestQuery(f func(context.Context) error) Query {
qry := &query{ qry := &query{
q: "test statement", q: "test statement",
stmt: parser.TestStmt(f), stmt: parser.TestStmt(f),

View file

@ -0,0 +1,82 @@
// Copyright 2024 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package promql
import (
"errors"
"testing"
"github.com/go-kit/log"
"github.com/stretchr/testify/require"
"github.com/prometheus/prometheus/promql/parser"
"github.com/prometheus/prometheus/util/annotations"
)
func TestRecoverEvaluatorRuntime(t *testing.T) {
var output []interface{}
logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error {
output = append(output, keyvals...)
return nil
}))
ev := &evaluator{logger: logger}
expr, _ := parser.ParseExpr("sum(up)")
var err error
defer func() {
require.EqualError(t, err, "unexpected error: runtime error: index out of range [123] with length 0")
require.Contains(t, output, "sum(up)")
}()
defer ev.recover(expr, nil, &err)
// Cause a runtime panic.
var a []int
a[123] = 1
}
func TestRecoverEvaluatorError(t *testing.T) {
ev := &evaluator{logger: log.NewNopLogger()}
var err error
e := errors.New("custom error")
defer func() {
require.EqualError(t, err, e.Error())
}()
defer ev.recover(nil, nil, &err)
panic(e)
}
func TestRecoverEvaluatorErrorWithWarnings(t *testing.T) {
ev := &evaluator{logger: log.NewNopLogger()}
var err error
var ws annotations.Annotations
warnings := annotations.New().Add(errors.New("custom warning"))
e := errWithWarnings{
err: errors.New("custom error"),
warnings: warnings,
}
defer func() {
require.EqualError(t, err, e.Error())
require.Equal(t, warnings, ws, "wrong warning message")
}()
defer ev.recover(nil, &ws, &err)
panic(e)
}

File diff suppressed because it is too large Load diff

View file

@ -948,15 +948,6 @@ func funcTimestamp(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe
return enh.Out, nil return enh.Out, nil
} }
func kahanSum(samples []float64) float64 {
var sum, c float64
for _, v := range samples {
sum, c = kahanSumInc(v, sum, c)
}
return sum + c
}
func kahanSumInc(inc, sum, c float64) (newSum, newC float64) { func kahanSumInc(inc, sum, c float64) (newSum, newC float64) {
t := sum + inc t := sum + inc
// Using Neumaier improvement, swap if next term larger than sum. // Using Neumaier improvement, swap if next term larger than sum.

View file

@ -11,11 +11,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package promql package promql_test
import ( import (
"context" "context"
"math"
"testing" "testing"
"time" "time"
@ -23,6 +22,7 @@ import (
"github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/model/timestamp" "github.com/prometheus/prometheus/model/timestamp"
"github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/promql/parser"
"github.com/prometheus/prometheus/util/teststorage" "github.com/prometheus/prometheus/util/teststorage"
) )
@ -33,13 +33,13 @@ func TestDeriv(t *testing.T) {
// so we test it by hand. // so we test it by hand.
storage := teststorage.New(t) storage := teststorage.New(t)
defer storage.Close() defer storage.Close()
opts := EngineOpts{ opts := promql.EngineOpts{
Logger: nil, Logger: nil,
Reg: nil, Reg: nil,
MaxSamples: 10000, MaxSamples: 10000,
Timeout: 10 * time.Second, Timeout: 10 * time.Second,
} }
engine := NewEngine(opts) engine := promql.NewEngine(opts)
a := storage.Appender(context.Background()) a := storage.Appender(context.Background())
@ -70,19 +70,13 @@ func TestDeriv(t *testing.T) {
func TestFunctionList(t *testing.T) { func TestFunctionList(t *testing.T) {
// Test that Functions and parser.Functions list the same functions. // Test that Functions and parser.Functions list the same functions.
for i := range FunctionCalls { for i := range promql.FunctionCalls {
_, ok := parser.Functions[i] _, ok := parser.Functions[i]
require.True(t, ok, "function %s exists in promql package, but not in parser package", i) require.True(t, ok, "function %s exists in promql package, but not in parser package", i)
} }
for i := range parser.Functions { for i := range parser.Functions {
_, ok := FunctionCalls[i] _, ok := promql.FunctionCalls[i]
require.True(t, ok, "function %s exists in parser package, but not in promql package", i) require.True(t, ok, "function %s exists in parser package, but not in promql package", i)
} }
} }
func TestKahanSum(t *testing.T) {
vals := []float64{1.0, math.Pow(10, 100), 1.0, -1 * math.Pow(10, 100)}
expected := 2.0
require.Equal(t, expected, kahanSum(vals))
}

View file

@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package promql package promql_test
import ( import (
"context" "context"
@ -22,37 +22,30 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/promql/promqltest"
"github.com/prometheus/prometheus/util/teststorage" "github.com/prometheus/prometheus/util/teststorage"
) )
func newTestEngine() *Engine { func newTestEngine() *promql.Engine {
return NewEngine(EngineOpts{ return promqltest.NewTestEngine(false, 0, promqltest.DefaultMaxSamplesPerQuery)
Logger: nil,
Reg: nil,
MaxSamples: 10000,
Timeout: 100 * time.Second,
NoStepSubqueryIntervalFn: func(int64) int64 { return durationMilliseconds(1 * time.Minute) },
EnableAtModifier: true,
EnableNegativeOffset: true,
EnablePerStepStats: true,
})
} }
func TestEvaluations(t *testing.T) { func TestEvaluations(t *testing.T) {
RunBuiltinTests(t, newTestEngine()) promqltest.RunBuiltinTests(t, newTestEngine())
} }
// Run a lot of queries at the same time, to check for race conditions. // Run a lot of queries at the same time, to check for race conditions.
func TestConcurrentRangeQueries(t *testing.T) { func TestConcurrentRangeQueries(t *testing.T) {
stor := teststorage.New(t) stor := teststorage.New(t)
defer stor.Close() defer stor.Close()
opts := EngineOpts{ opts := promql.EngineOpts{
Logger: nil, Logger: nil,
Reg: nil, Reg: nil,
MaxSamples: 50000000, MaxSamples: 50000000,
Timeout: 100 * time.Second, Timeout: 100 * time.Second,
} }
engine := NewEngine(opts) engine := promql.NewEngine(opts)
const interval = 10000 // 10s interval. const interval = 10000 // 10s interval.
// A day of data plus 10k steps. // A day of data plus 10k steps.

View file

@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package promql package promqltest
import ( import (
"context" "context"
@ -19,7 +19,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io/fs" "io/fs"
"math"
"strconv" "strconv"
"strings" "strings"
"testing" "testing"
@ -33,16 +32,16 @@ import (
"github.com/prometheus/prometheus/model/histogram" "github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/model/timestamp" "github.com/prometheus/prometheus/model/timestamp"
"github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/promql/parser"
"github.com/prometheus/prometheus/promql/parser/posrange" "github.com/prometheus/prometheus/promql/parser/posrange"
"github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/storage"
"github.com/prometheus/prometheus/util/almost"
"github.com/prometheus/prometheus/util/teststorage" "github.com/prometheus/prometheus/util/teststorage"
"github.com/prometheus/prometheus/util/testutil" "github.com/prometheus/prometheus/util/testutil"
) )
var ( var (
minNormal = math.Float64frombits(0x0010000000000000) // The smallest positive normal value of type float64.
patSpace = regexp.MustCompile("[\t ]+") patSpace = regexp.MustCompile("[\t ]+")
patLoad = regexp.MustCompile(`^load\s+(.+?)$`) patLoad = regexp.MustCompile(`^load\s+(.+?)$`)
patEvalInstant = regexp.MustCompile(`^eval(?:_(fail|ordered))?\s+instant\s+(?:at\s+(.+?))?\s+(.+)$`) patEvalInstant = regexp.MustCompile(`^eval(?:_(fail|ordered))?\s+instant\s+(?:at\s+(.+?))?\s+(.+)$`)
@ -51,6 +50,7 @@ var (
const ( const (
defaultEpsilon = 0.000001 // Relative error allowed for sample values. defaultEpsilon = 0.000001 // Relative error allowed for sample values.
DefaultMaxSamplesPerQuery = 10000
) )
var testStartTime = time.Unix(0, 0).UTC() var testStartTime = time.Unix(0, 0).UTC()
@ -72,8 +72,22 @@ func LoadedStorage(t testutil.T, input string) *teststorage.TestStorage {
return test.storage return test.storage
} }
func NewTestEngine(enablePerStepStats bool, lookbackDelta time.Duration, maxSamples int) *promql.Engine {
return promql.NewEngine(promql.EngineOpts{
Logger: nil,
Reg: nil,
MaxSamples: maxSamples,
Timeout: 100 * time.Second,
NoStepSubqueryIntervalFn: func(int64) int64 { return durationMilliseconds(1 * time.Minute) },
EnableAtModifier: true,
EnableNegativeOffset: true,
EnablePerStepStats: enablePerStepStats,
LookbackDelta: lookbackDelta,
})
}
// RunBuiltinTests runs an acceptance test suite against the provided engine. // RunBuiltinTests runs an acceptance test suite against the provided engine.
func RunBuiltinTests(t *testing.T, engine QueryEngine) { func RunBuiltinTests(t *testing.T, engine promql.QueryEngine) {
t.Cleanup(func() { parser.EnableExperimentalFunctions = false }) t.Cleanup(func() { parser.EnableExperimentalFunctions = false })
parser.EnableExperimentalFunctions = true parser.EnableExperimentalFunctions = true
@ -90,11 +104,11 @@ func RunBuiltinTests(t *testing.T, engine QueryEngine) {
} }
// RunTest parses and runs the test against the provided engine. // RunTest parses and runs the test against the provided engine.
func RunTest(t testutil.T, input string, engine QueryEngine) { func RunTest(t testutil.T, input string, engine promql.QueryEngine) {
require.NoError(t, runTest(t, input, engine)) require.NoError(t, runTest(t, input, engine))
} }
func runTest(t testutil.T, input string, engine QueryEngine) error { func runTest(t testutil.T, input string, engine promql.QueryEngine) error {
test, err := newTest(t, input) test, err := newTest(t, input)
// Why do this before checking err? newTest() can create the test storage and then return an error, // Why do this before checking err? newTest() can create the test storage and then return an error,
@ -368,7 +382,7 @@ func (*evalCmd) testCmd() {}
type loadCmd struct { type loadCmd struct {
gap time.Duration gap time.Duration
metrics map[uint64]labels.Labels metrics map[uint64]labels.Labels
defs map[uint64][]Sample defs map[uint64][]promql.Sample
exemplars map[uint64][]exemplar.Exemplar exemplars map[uint64][]exemplar.Exemplar
} }
@ -376,7 +390,7 @@ func newLoadCmd(gap time.Duration) *loadCmd {
return &loadCmd{ return &loadCmd{
gap: gap, gap: gap,
metrics: map[uint64]labels.Labels{}, metrics: map[uint64]labels.Labels{},
defs: map[uint64][]Sample{}, defs: map[uint64][]promql.Sample{},
exemplars: map[uint64][]exemplar.Exemplar{}, exemplars: map[uint64][]exemplar.Exemplar{},
} }
} }
@ -389,11 +403,11 @@ func (cmd loadCmd) String() string {
func (cmd *loadCmd) set(m labels.Labels, vals ...parser.SequenceValue) { func (cmd *loadCmd) set(m labels.Labels, vals ...parser.SequenceValue) {
h := m.Hash() h := m.Hash()
samples := make([]Sample, 0, len(vals)) samples := make([]promql.Sample, 0, len(vals))
ts := testStartTime ts := testStartTime
for _, v := range vals { for _, v := range vals {
if !v.Omitted { if !v.Omitted {
samples = append(samples, Sample{ samples = append(samples, promql.Sample{
T: ts.UnixNano() / int64(time.Millisecond/time.Nanosecond), T: ts.UnixNano() / int64(time.Millisecond/time.Nanosecond),
F: v.Value, F: v.Value,
H: v.Histogram, H: v.Histogram,
@ -419,7 +433,7 @@ func (cmd *loadCmd) append(a storage.Appender) error {
return nil return nil
} }
func appendSample(a storage.Appender, s Sample, m labels.Labels) error { func appendSample(a storage.Appender, s promql.Sample, m labels.Labels) error {
if s.H != nil { if s.H != nil {
if _, err := a.AppendHistogram(0, m, s.T, nil, s.H); err != nil { if _, err := a.AppendHistogram(0, m, s.T, nil, s.H); err != nil {
return err return err
@ -503,7 +517,7 @@ func (ev *evalCmd) expectMetric(pos int, m labels.Labels, vals ...parser.Sequenc
// compareResult compares the result value with the defined expectation. // compareResult compares the result value with the defined expectation.
func (ev *evalCmd) compareResult(result parser.Value) error { func (ev *evalCmd) compareResult(result parser.Value) error {
switch val := result.(type) { switch val := result.(type) {
case Matrix: case promql.Matrix:
if ev.ordered { if ev.ordered {
return fmt.Errorf("expected ordered result, but query returned a matrix") return fmt.Errorf("expected ordered result, but query returned a matrix")
} }
@ -521,8 +535,8 @@ func (ev *evalCmd) compareResult(result parser.Value) error {
seen[hash] = true seen[hash] = true
exp := ev.expected[hash] exp := ev.expected[hash]
var expectedFloats []FPoint var expectedFloats []promql.FPoint
var expectedHistograms []HPoint var expectedHistograms []promql.HPoint
for i, e := range exp.vals { for i, e := range exp.vals {
ts := ev.start.Add(time.Duration(i) * ev.step) ts := ev.start.Add(time.Duration(i) * ev.step)
@ -534,9 +548,9 @@ func (ev *evalCmd) compareResult(result parser.Value) error {
t := ts.UnixNano() / int64(time.Millisecond/time.Nanosecond) t := ts.UnixNano() / int64(time.Millisecond/time.Nanosecond)
if e.Histogram != nil { if e.Histogram != nil {
expectedHistograms = append(expectedHistograms, HPoint{T: t, H: e.Histogram}) expectedHistograms = append(expectedHistograms, promql.HPoint{T: t, H: e.Histogram})
} else if !e.Omitted { } else if !e.Omitted {
expectedFloats = append(expectedFloats, FPoint{T: t, F: e.Value}) expectedFloats = append(expectedFloats, promql.FPoint{T: t, F: e.Value})
} }
} }
@ -551,7 +565,7 @@ func (ev *evalCmd) compareResult(result parser.Value) error {
return fmt.Errorf("expected float value at index %v for %s to have timestamp %v, but it had timestamp %v (result has %s)", i, ev.metrics[hash], expected.T, actual.T, formatSeriesResult(s)) return fmt.Errorf("expected float value at index %v for %s to have timestamp %v, but it had timestamp %v (result has %s)", i, ev.metrics[hash], expected.T, actual.T, formatSeriesResult(s))
} }
if !almostEqual(actual.F, expected.F, defaultEpsilon) { if !almost.Equal(actual.F, expected.F, defaultEpsilon) {
return fmt.Errorf("expected float value at index %v (t=%v) for %s to be %v, but got %v (result has %s)", i, actual.T, ev.metrics[hash], expected.F, actual.F, formatSeriesResult(s)) return fmt.Errorf("expected float value at index %v (t=%v) for %s to be %v, but got %v (result has %s)", i, actual.T, ev.metrics[hash], expected.F, actual.F, formatSeriesResult(s))
} }
} }
@ -575,7 +589,7 @@ func (ev *evalCmd) compareResult(result parser.Value) error {
} }
} }
case Vector: case promql.Vector:
seen := map[uint64]bool{} seen := map[uint64]bool{}
for pos, v := range val { for pos, v := range val {
fp := v.Metric.Hash() fp := v.Metric.Hash()
@ -601,7 +615,7 @@ func (ev *evalCmd) compareResult(result parser.Value) error {
if expH != nil && !expH.Compact(0).Equals(v.H) { if expH != nil && !expH.Compact(0).Equals(v.H) {
return fmt.Errorf("expected %v for %s but got %s", HistogramTestExpression(expH), v.Metric, HistogramTestExpression(v.H)) return fmt.Errorf("expected %v for %s but got %s", HistogramTestExpression(expH), v.Metric, HistogramTestExpression(v.H))
} }
if !almostEqual(exp0.Value, v.F, defaultEpsilon) { if !almost.Equal(exp0.Value, v.F, defaultEpsilon) {
return fmt.Errorf("expected %v for %s but got %v", exp0.Value, v.Metric, v.F) return fmt.Errorf("expected %v for %s but got %v", exp0.Value, v.Metric, v.F)
} }
@ -613,7 +627,7 @@ func (ev *evalCmd) compareResult(result parser.Value) error {
} }
} }
case Scalar: case promql.Scalar:
if len(ev.expected) != 1 { if len(ev.expected) != 1 {
return fmt.Errorf("expected vector result, but got scalar %s", val.String()) return fmt.Errorf("expected vector result, but got scalar %s", val.String())
} }
@ -621,7 +635,7 @@ func (ev *evalCmd) compareResult(result parser.Value) error {
if exp0.Histogram != nil { if exp0.Histogram != nil {
return fmt.Errorf("expected Histogram %v but got scalar %s", exp0.Histogram.TestExpression(), val.String()) return fmt.Errorf("expected Histogram %v but got scalar %s", exp0.Histogram.TestExpression(), val.String())
} }
if !almostEqual(exp0.Value, val.V, defaultEpsilon) { if !almost.Equal(exp0.Value, val.V, defaultEpsilon) {
return fmt.Errorf("expected Scalar %v but got %v", val.V, exp0.Value) return fmt.Errorf("expected Scalar %v but got %v", val.V, exp0.Value)
} }
@ -631,7 +645,7 @@ func (ev *evalCmd) compareResult(result parser.Value) error {
return nil return nil
} }
func formatSeriesResult(s Series) string { func formatSeriesResult(s promql.Series) string {
floatPlural := "s" floatPlural := "s"
histogramPlural := "s" histogramPlural := "s"
@ -678,8 +692,7 @@ func atModifierTestCases(exprStr string, evalTime time.Time) ([]atModifierTestCa
// If there is a subquery, then the selectors inside it don't get the @ timestamp. // If there is a subquery, then the selectors inside it don't get the @ timestamp.
// If any selector already has the @ timestamp set, then it is untouched. // If any selector already has the @ timestamp set, then it is untouched.
parser.Inspect(expr, func(node parser.Node, path []parser.Node) error { parser.Inspect(expr, func(node parser.Node, path []parser.Node) error {
_, _, subqTs := subqueryTimes(path) if hasAtModifier(path) {
if subqTs != nil {
// There is a subquery with timestamp in the path, // There is a subquery with timestamp in the path,
// hence don't change any timestamps further. // hence don't change any timestamps further.
return nil return nil
@ -701,7 +714,7 @@ func atModifierTestCases(exprStr string, evalTime time.Time) ([]atModifierTestCa
} }
case *parser.Call: case *parser.Call:
_, ok := AtModifierUnsafeFunctions[n.Func.Name] _, ok := promql.AtModifierUnsafeFunctions[n.Func.Name]
containsNonStepInvariant = containsNonStepInvariant || ok containsNonStepInvariant = containsNonStepInvariant || ok
} }
return nil return nil
@ -729,8 +742,19 @@ func atModifierTestCases(exprStr string, evalTime time.Time) ([]atModifierTestCa
return testCases, nil return testCases, nil
} }
func hasAtModifier(path []parser.Node) bool {
for _, node := range path {
if n, ok := node.(*parser.SubqueryExpr); ok {
if n.Timestamp != nil {
return true
}
}
}
return false
}
// exec processes a single step of the test. // exec processes a single step of the test.
func (t *test) exec(tc testCommand, engine QueryEngine) error { func (t *test) exec(tc testCommand, engine promql.QueryEngine) error {
switch cmd := tc.(type) { switch cmd := tc.(type) {
case *clearCmd: case *clearCmd:
t.clear() t.clear()
@ -755,7 +779,7 @@ func (t *test) exec(tc testCommand, engine QueryEngine) error {
return nil return nil
} }
func (t *test) execEval(cmd *evalCmd, engine QueryEngine) error { func (t *test) execEval(cmd *evalCmd, engine promql.QueryEngine) error {
if cmd.isRange { if cmd.isRange {
return t.execRangeEval(cmd, engine) return t.execRangeEval(cmd, engine)
} }
@ -763,7 +787,7 @@ func (t *test) execEval(cmd *evalCmd, engine QueryEngine) error {
return t.execInstantEval(cmd, engine) return t.execInstantEval(cmd, engine)
} }
func (t *test) execRangeEval(cmd *evalCmd, engine QueryEngine) error { func (t *test) execRangeEval(cmd *evalCmd, engine promql.QueryEngine) error {
q, err := engine.NewRangeQuery(t.context, t.storage, nil, cmd.expr, cmd.start, cmd.end, cmd.step) q, err := engine.NewRangeQuery(t.context, t.storage, nil, cmd.expr, cmd.start, cmd.end, cmd.step)
if err != nil { if err != nil {
return fmt.Errorf("error creating range query for %q (line %d): %w", cmd.expr, cmd.line, err) return fmt.Errorf("error creating range query for %q (line %d): %w", cmd.expr, cmd.line, err)
@ -788,7 +812,7 @@ func (t *test) execRangeEval(cmd *evalCmd, engine QueryEngine) error {
return nil return nil
} }
func (t *test) execInstantEval(cmd *evalCmd, engine QueryEngine) error { func (t *test) execInstantEval(cmd *evalCmd, engine promql.QueryEngine) error {
queries, err := atModifierTestCases(cmd.expr, cmd.start) queries, err := atModifierTestCases(cmd.expr, cmd.start)
if err != nil { if err != nil {
return err return err
@ -830,29 +854,29 @@ func (t *test) execInstantEval(cmd *evalCmd, engine QueryEngine) error {
// Range queries are always sorted by labels, so skip this test case that expects results in a particular order. // Range queries are always sorted by labels, so skip this test case that expects results in a particular order.
continue continue
} }
mat := rangeRes.Value.(Matrix) mat := rangeRes.Value.(promql.Matrix)
if err := assertMatrixSorted(mat); err != nil { if err := assertMatrixSorted(mat); err != nil {
return err return err
} }
vec := make(Vector, 0, len(mat)) vec := make(promql.Vector, 0, len(mat))
for _, series := range mat { for _, series := range mat {
// We expect either Floats or Histograms. // We expect either Floats or Histograms.
for _, point := range series.Floats { for _, point := range series.Floats {
if point.T == timeMilliseconds(iq.evalTime) { if point.T == timeMilliseconds(iq.evalTime) {
vec = append(vec, Sample{Metric: series.Metric, T: point.T, F: point.F}) vec = append(vec, promql.Sample{Metric: series.Metric, T: point.T, F: point.F})
break break
} }
} }
for _, point := range series.Histograms { for _, point := range series.Histograms {
if point.T == timeMilliseconds(iq.evalTime) { if point.T == timeMilliseconds(iq.evalTime) {
vec = append(vec, Sample{Metric: series.Metric, T: point.T, H: point.H}) vec = append(vec, promql.Sample{Metric: series.Metric, T: point.T, H: point.H})
break break
} }
} }
} }
if _, ok := res.Value.(Scalar); ok { if _, ok := res.Value.(promql.Scalar); ok {
err = cmd.compareResult(Scalar{V: vec[0].F}) err = cmd.compareResult(promql.Scalar{V: vec[0].F})
} else { } else {
err = cmd.compareResult(vec) err = cmd.compareResult(vec)
} }
@ -864,7 +888,7 @@ func (t *test) execInstantEval(cmd *evalCmd, engine QueryEngine) error {
return nil return nil
} }
func assertMatrixSorted(m Matrix) error { func assertMatrixSorted(m promql.Matrix) error {
if len(m) <= 1 { if len(m) <= 1 {
return nil return nil
} }
@ -894,29 +918,6 @@ func (t *test) clear() {
t.context, t.cancelCtx = context.WithCancel(context.Background()) t.context, t.cancelCtx = context.WithCancel(context.Background())
} }
// almostEqual returns true if a and b differ by less than their sum
// multiplied by epsilon.
func almostEqual(a, b, epsilon float64) bool {
// NaN has no equality but for testing we still want to know whether both values
// are NaN.
if math.IsNaN(a) && math.IsNaN(b) {
return true
}
// Cf. http://floating-point-gui.de/errors/comparison/
if a == b {
return true
}
absSum := math.Abs(a) + math.Abs(b)
diff := math.Abs(a - b)
if a == 0 || b == 0 || absSum < minNormal {
return diff < epsilon*minNormal
}
return diff/math.Min(absSum, math.MaxFloat64) < epsilon
}
func parseNumber(s string) (float64, error) { func parseNumber(s string) (float64, error) {
n, err := strconv.ParseInt(s, 0, 64) n, err := strconv.ParseInt(s, 0, 64)
f := float64(n) f := float64(n)
@ -937,7 +938,7 @@ type LazyLoader struct {
storage storage.Storage storage storage.Storage
SubqueryInterval time.Duration SubqueryInterval time.Duration
queryEngine *Engine queryEngine *promql.Engine
context context.Context context context.Context
cancelCtx context.CancelFunc cancelCtx context.CancelFunc
@ -1004,7 +1005,7 @@ func (ll *LazyLoader) clear() error {
return err return err
} }
opts := EngineOpts{ opts := promql.EngineOpts{
Logger: nil, Logger: nil,
Reg: nil, Reg: nil,
MaxSamples: 10000, MaxSamples: 10000,
@ -1014,7 +1015,7 @@ func (ll *LazyLoader) clear() error {
EnableNegativeOffset: ll.opts.EnableNegativeOffset, EnableNegativeOffset: ll.opts.EnableNegativeOffset,
} }
ll.queryEngine = NewEngine(opts) ll.queryEngine = promql.NewEngine(opts)
ll.context, ll.cancelCtx = context.WithCancel(context.Background()) ll.context, ll.cancelCtx = context.WithCancel(context.Background())
return nil return nil
} }
@ -1048,7 +1049,7 @@ func (ll *LazyLoader) WithSamplesTill(ts time.Time, fn func(error)) {
} }
// QueryEngine returns the LazyLoader's query engine. // QueryEngine returns the LazyLoader's query engine.
func (ll *LazyLoader) QueryEngine() *Engine { func (ll *LazyLoader) QueryEngine() *promql.Engine {
return ll.queryEngine return ll.queryEngine
} }
@ -1074,3 +1075,17 @@ func (ll *LazyLoader) Close() error {
ll.cancelCtx() ll.cancelCtx()
return ll.storage.Close() return ll.storage.Close()
} }
func makeInt64Pointer(val int64) *int64 {
valp := new(int64)
*valp = val
return valp
}
func timeMilliseconds(t time.Time) int64 {
return t.UnixNano() / int64(time.Millisecond/time.Nanosecond)
}
func durationMilliseconds(d time.Duration) int64 {
return int64(d / (time.Millisecond / time.Nanosecond))
}

View file

@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package promql package promqltest
import ( import (
"math" "math"
@ -21,13 +21,14 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/tsdb/chunkenc" "github.com/prometheus/prometheus/tsdb/chunkenc"
) )
func TestLazyLoader_WithSamplesTill(t *testing.T) { func TestLazyLoader_WithSamplesTill(t *testing.T) {
type testCase struct { type testCase struct {
ts time.Time ts time.Time
series []Series // Each series is checked separately. Need not mention all series here. series []promql.Series // Each series is checked separately. Need not mention all series here.
checkOnlyError bool // If this is true, series is not checked. checkOnlyError bool // If this is true, series is not checked.
} }
@ -44,33 +45,33 @@ func TestLazyLoader_WithSamplesTill(t *testing.T) {
testCases: []testCase{ testCases: []testCase{
{ {
ts: time.Unix(40, 0), ts: time.Unix(40, 0),
series: []Series{ series: []promql.Series{
{ {
Metric: labels.FromStrings("__name__", "metric1"), Metric: labels.FromStrings("__name__", "metric1"),
Floats: []FPoint{ Floats: []promql.FPoint{
{0, 1}, {10000, 2}, {20000, 3}, {30000, 4}, {40000, 5}, {T: 0, F: 1}, {T: 10000, F: 2}, {T: 20000, F: 3}, {T: 30000, F: 4}, {T: 40000, F: 5},
}, },
}, },
}, },
}, },
{ {
ts: time.Unix(10, 0), ts: time.Unix(10, 0),
series: []Series{ series: []promql.Series{
{ {
Metric: labels.FromStrings("__name__", "metric1"), Metric: labels.FromStrings("__name__", "metric1"),
Floats: []FPoint{ Floats: []promql.FPoint{
{0, 1}, {10000, 2}, {20000, 3}, {30000, 4}, {40000, 5}, {T: 0, F: 1}, {T: 10000, F: 2}, {T: 20000, F: 3}, {T: 30000, F: 4}, {T: 40000, F: 5},
}, },
}, },
}, },
}, },
{ {
ts: time.Unix(60, 0), ts: time.Unix(60, 0),
series: []Series{ series: []promql.Series{
{ {
Metric: labels.FromStrings("__name__", "metric1"), Metric: labels.FromStrings("__name__", "metric1"),
Floats: []FPoint{ Floats: []promql.FPoint{
{0, 1}, {10000, 2}, {20000, 3}, {30000, 4}, {40000, 5}, {50000, 6}, {60000, 7}, {T: 0, F: 1}, {T: 10000, F: 2}, {T: 20000, F: 3}, {T: 30000, F: 4}, {T: 40000, F: 5}, {T: 50000, F: 6}, {T: 60000, F: 7},
}, },
}, },
}, },
@ -86,17 +87,17 @@ func TestLazyLoader_WithSamplesTill(t *testing.T) {
testCases: []testCase{ testCases: []testCase{
{ // Adds all samples of metric1. { // Adds all samples of metric1.
ts: time.Unix(70, 0), ts: time.Unix(70, 0),
series: []Series{ series: []promql.Series{
{ {
Metric: labels.FromStrings("__name__", "metric1"), Metric: labels.FromStrings("__name__", "metric1"),
Floats: []FPoint{ Floats: []promql.FPoint{
{0, 1}, {10000, 1}, {20000, 1}, {30000, 1}, {40000, 1}, {50000, 1}, {T: 0, F: 1}, {T: 10000, F: 1}, {T: 20000, F: 1}, {T: 30000, F: 1}, {T: 40000, F: 1}, {T: 50000, F: 1},
}, },
}, },
{ {
Metric: labels.FromStrings("__name__", "metric2"), Metric: labels.FromStrings("__name__", "metric2"),
Floats: []FPoint{ Floats: []promql.FPoint{
{0, 1}, {10000, 2}, {20000, 3}, {30000, 4}, {40000, 5}, {50000, 6}, {60000, 7}, {70000, 8}, {T: 0, F: 1}, {T: 10000, F: 2}, {T: 20000, F: 3}, {T: 30000, F: 4}, {T: 40000, F: 5}, {T: 50000, F: 6}, {T: 60000, F: 7}, {T: 70000, F: 8},
}, },
}, },
}, },
@ -140,13 +141,13 @@ func TestLazyLoader_WithSamplesTill(t *testing.T) {
require.False(t, ss.Next(), "Expecting only 1 series") require.False(t, ss.Next(), "Expecting only 1 series")
// Convert `storage.Series` to `promql.Series`. // Convert `storage.Series` to `promql.Series`.
got := Series{ got := promql.Series{
Metric: storageSeries.Labels(), Metric: storageSeries.Labels(),
} }
it := storageSeries.Iterator(nil) it := storageSeries.Iterator(nil)
for it.Next() == chunkenc.ValFloat { for it.Next() == chunkenc.ValFloat {
t, v := it.At() t, v := it.At()
got.Floats = append(got.Floats, FPoint{T: t, F: v}) got.Floats = append(got.Floats, promql.FPoint{T: t, F: v})
} }
require.NoError(t, it.Err()) require.NoError(t, it.Err())
@ -450,7 +451,7 @@ eval range from 0 to 5m step 5m testmetric
for name, testCase := range testCases { for name, testCase := range testCases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
err := runTest(t, testCase.input, newTestEngine()) err := runTest(t, testCase.input, NewTestEngine(false, 0, DefaultMaxSamplesPerQuery))
if testCase.expectedError == "" { if testCase.expectedError == "" {
require.NoError(t, err) require.NoError(t, err)
@ -463,42 +464,42 @@ eval range from 0 to 5m step 5m testmetric
func TestAssertMatrixSorted(t *testing.T) { func TestAssertMatrixSorted(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
matrix Matrix matrix promql.Matrix
expectedError string expectedError string
}{ }{
"empty matrix": { "empty matrix": {
matrix: Matrix{}, matrix: promql.Matrix{},
}, },
"matrix with one series": { "matrix with one series": {
matrix: Matrix{ matrix: promql.Matrix{
Series{Metric: labels.FromStrings("the_label", "value_1")}, promql.Series{Metric: labels.FromStrings("the_label", "value_1")},
}, },
}, },
"matrix with two series, series in sorted order": { "matrix with two series, series in sorted order": {
matrix: Matrix{ matrix: promql.Matrix{
Series{Metric: labels.FromStrings("the_label", "value_1")}, promql.Series{Metric: labels.FromStrings("the_label", "value_1")},
Series{Metric: labels.FromStrings("the_label", "value_2")}, promql.Series{Metric: labels.FromStrings("the_label", "value_2")},
}, },
}, },
"matrix with two series, series in reverse order": { "matrix with two series, series in reverse order": {
matrix: Matrix{ matrix: promql.Matrix{
Series{Metric: labels.FromStrings("the_label", "value_2")}, promql.Series{Metric: labels.FromStrings("the_label", "value_2")},
Series{Metric: labels.FromStrings("the_label", "value_1")}, promql.Series{Metric: labels.FromStrings("the_label", "value_1")},
}, },
expectedError: `matrix results should always be sorted by labels, but matrix is not sorted: series at index 1 with labels {the_label="value_1"} sorts before series at index 0 with labels {the_label="value_2"}`, expectedError: `matrix results should always be sorted by labels, but matrix is not sorted: series at index 1 with labels {the_label="value_1"} sorts before series at index 0 with labels {the_label="value_2"}`,
}, },
"matrix with three series, series in sorted order": { "matrix with three series, series in sorted order": {
matrix: Matrix{ matrix: promql.Matrix{
Series{Metric: labels.FromStrings("the_label", "value_1")}, promql.Series{Metric: labels.FromStrings("the_label", "value_1")},
Series{Metric: labels.FromStrings("the_label", "value_2")}, promql.Series{Metric: labels.FromStrings("the_label", "value_2")},
Series{Metric: labels.FromStrings("the_label", "value_3")}, promql.Series{Metric: labels.FromStrings("the_label", "value_3")},
}, },
}, },
"matrix with three series, series not in sorted order": { "matrix with three series, series not in sorted order": {
matrix: Matrix{ matrix: promql.Matrix{
Series{Metric: labels.FromStrings("the_label", "value_1")}, promql.Series{Metric: labels.FromStrings("the_label", "value_1")},
Series{Metric: labels.FromStrings("the_label", "value_3")}, promql.Series{Metric: labels.FromStrings("the_label", "value_3")},
Series{Metric: labels.FromStrings("the_label", "value_2")}, promql.Series{Metric: labels.FromStrings("the_label", "value_2")},
}, },
expectedError: `matrix results should always be sorted by labels, but matrix is not sorted: series at index 2 with labels {the_label="value_2"} sorts before series at index 1 with labels {the_label="value_3"}`, expectedError: `matrix results should always be sorted by labels, but matrix is not sorted: series at index 2 with labels {the_label="value_2"} sorts before series at index 1 with labels {the_label="value_3"}`,
}, },

View file

@ -764,6 +764,14 @@ eval instant at 1m avg_over_time(metric10[1m])
eval instant at 1m sum_over_time(metric10[1m])/count_over_time(metric10[1m]) eval instant at 1m sum_over_time(metric10[1m])/count_over_time(metric10[1m])
{} 0 {} 0
# Test if very big intermediate values cause loss of detail.
clear
load 10s
metric 1 1e100 1 -1e100
eval instant at 1m sum_over_time(metric[1m])
{} 2
# Tests for stddev_over_time and stdvar_over_time. # Tests for stddev_over_time and stdvar_over_time.
clear clear
load 10s load 10s

View file

@ -20,6 +20,7 @@ import (
"github.com/prometheus/prometheus/model/histogram" "github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/util/almost"
) )
// smallDeltaTolerance is the threshold for relative deltas between classic // smallDeltaTolerance is the threshold for relative deltas between classic
@ -397,7 +398,7 @@ func ensureMonotonicAndIgnoreSmallDeltas(buckets buckets, tolerance float64) (bo
// No correction needed if the counts are identical between buckets. // No correction needed if the counts are identical between buckets.
continue continue
} }
if almostEqual(prev, curr, tolerance) { if almost.Equal(prev, curr, tolerance) {
// Silently correct numerically insignificant differences from floating // Silently correct numerically insignificant differences from floating
// point precision errors, regardless of direction. // point precision errors, regardless of direction.
// Do not update the 'prev' value as we are ignoring the difference. // Do not update the 'prev' value as we are ignoring the difference.

View file

@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package promql package promql_test
import ( import (
"testing" "testing"
@ -19,39 +19,40 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/promql"
) )
func TestVector_ContainsSameLabelset(t *testing.T) { func TestVector_ContainsSameLabelset(t *testing.T) {
for name, tc := range map[string]struct { for name, tc := range map[string]struct {
vector Vector vector promql.Vector
expected bool expected bool
}{ }{
"empty vector": { "empty vector": {
vector: Vector{}, vector: promql.Vector{},
expected: false, expected: false,
}, },
"vector with one series": { "vector with one series": {
vector: Vector{ vector: promql.Vector{
{Metric: labels.FromStrings("lbl", "a")}, {Metric: labels.FromStrings("lbl", "a")},
}, },
expected: false, expected: false,
}, },
"vector with two different series": { "vector with two different series": {
vector: Vector{ vector: promql.Vector{
{Metric: labels.FromStrings("lbl", "a")}, {Metric: labels.FromStrings("lbl", "a")},
{Metric: labels.FromStrings("lbl", "b")}, {Metric: labels.FromStrings("lbl", "b")},
}, },
expected: false, expected: false,
}, },
"vector with two equal series": { "vector with two equal series": {
vector: Vector{ vector: promql.Vector{
{Metric: labels.FromStrings("lbl", "a")}, {Metric: labels.FromStrings("lbl", "a")},
{Metric: labels.FromStrings("lbl", "a")}, {Metric: labels.FromStrings("lbl", "a")},
}, },
expected: true, expected: true,
}, },
"vector with three series, two equal": { "vector with three series, two equal": {
vector: Vector{ vector: promql.Vector{
{Metric: labels.FromStrings("lbl", "a")}, {Metric: labels.FromStrings("lbl", "a")},
{Metric: labels.FromStrings("lbl", "b")}, {Metric: labels.FromStrings("lbl", "b")},
{Metric: labels.FromStrings("lbl", "a")}, {Metric: labels.FromStrings("lbl", "a")},
@ -67,35 +68,35 @@ func TestVector_ContainsSameLabelset(t *testing.T) {
func TestMatrix_ContainsSameLabelset(t *testing.T) { func TestMatrix_ContainsSameLabelset(t *testing.T) {
for name, tc := range map[string]struct { for name, tc := range map[string]struct {
matrix Matrix matrix promql.Matrix
expected bool expected bool
}{ }{
"empty matrix": { "empty matrix": {
matrix: Matrix{}, matrix: promql.Matrix{},
expected: false, expected: false,
}, },
"matrix with one series": { "matrix with one series": {
matrix: Matrix{ matrix: promql.Matrix{
{Metric: labels.FromStrings("lbl", "a")}, {Metric: labels.FromStrings("lbl", "a")},
}, },
expected: false, expected: false,
}, },
"matrix with two different series": { "matrix with two different series": {
matrix: Matrix{ matrix: promql.Matrix{
{Metric: labels.FromStrings("lbl", "a")}, {Metric: labels.FromStrings("lbl", "a")},
{Metric: labels.FromStrings("lbl", "b")}, {Metric: labels.FromStrings("lbl", "b")},
}, },
expected: false, expected: false,
}, },
"matrix with two equal series": { "matrix with two equal series": {
matrix: Matrix{ matrix: promql.Matrix{
{Metric: labels.FromStrings("lbl", "a")}, {Metric: labels.FromStrings("lbl", "a")},
{Metric: labels.FromStrings("lbl", "a")}, {Metric: labels.FromStrings("lbl", "a")},
}, },
expected: true, expected: true,
}, },
"matrix with three series, two equal": { "matrix with three series, two equal": {
matrix: Matrix{ matrix: promql.Matrix{
{Metric: labels.FromStrings("lbl", "a")}, {Metric: labels.FromStrings("lbl", "a")},
{Metric: labels.FromStrings("lbl", "b")}, {Metric: labels.FromStrings("lbl", "b")},
{Metric: labels.FromStrings("lbl", "a")}, {Metric: labels.FromStrings("lbl", "a")},

View file

@ -30,6 +30,7 @@ import (
"github.com/prometheus/prometheus/notifier" "github.com/prometheus/prometheus/notifier"
"github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/promql/parser"
"github.com/prometheus/prometheus/promql/promqltest"
"github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/storage"
"github.com/prometheus/prometheus/util/teststorage" "github.com/prometheus/prometheus/util/teststorage"
"github.com/prometheus/prometheus/util/testutil" "github.com/prometheus/prometheus/util/testutil"
@ -148,7 +149,7 @@ func TestAlertingRuleTemplateWithHistogram(t *testing.T) {
} }
func TestAlertingRuleLabelsUpdate(t *testing.T) { func TestAlertingRuleLabelsUpdate(t *testing.T) {
storage := promql.LoadedStorage(t, ` storage := promqltest.LoadedStorage(t, `
load 1m load 1m
http_requests{job="app-server", instance="0"} 75 85 70 70 stale http_requests{job="app-server", instance="0"} 75 85 70 70 stale
`) `)
@ -252,7 +253,7 @@ func TestAlertingRuleLabelsUpdate(t *testing.T) {
} }
func TestAlertingRuleExternalLabelsInTemplate(t *testing.T) { func TestAlertingRuleExternalLabelsInTemplate(t *testing.T) {
storage := promql.LoadedStorage(t, ` storage := promqltest.LoadedStorage(t, `
load 1m load 1m
http_requests{job="app-server", instance="0"} 75 85 70 70 http_requests{job="app-server", instance="0"} 75 85 70 70
`) `)
@ -345,7 +346,7 @@ func TestAlertingRuleExternalLabelsInTemplate(t *testing.T) {
} }
func TestAlertingRuleExternalURLInTemplate(t *testing.T) { func TestAlertingRuleExternalURLInTemplate(t *testing.T) {
storage := promql.LoadedStorage(t, ` storage := promqltest.LoadedStorage(t, `
load 1m load 1m
http_requests{job="app-server", instance="0"} 75 85 70 70 http_requests{job="app-server", instance="0"} 75 85 70 70
`) `)
@ -438,7 +439,7 @@ func TestAlertingRuleExternalURLInTemplate(t *testing.T) {
} }
func TestAlertingRuleEmptyLabelFromTemplate(t *testing.T) { func TestAlertingRuleEmptyLabelFromTemplate(t *testing.T) {
storage := promql.LoadedStorage(t, ` storage := promqltest.LoadedStorage(t, `
load 1m load 1m
http_requests{job="app-server", instance="0"} 75 85 70 70 http_requests{job="app-server", instance="0"} 75 85 70 70
`) `)
@ -492,7 +493,7 @@ func TestAlertingRuleEmptyLabelFromTemplate(t *testing.T) {
} }
func TestAlertingRuleQueryInTemplate(t *testing.T) { func TestAlertingRuleQueryInTemplate(t *testing.T) {
storage := promql.LoadedStorage(t, ` storage := promqltest.LoadedStorage(t, `
load 1m load 1m
http_requests{job="app-server", instance="0"} 70 85 70 70 http_requests{job="app-server", instance="0"} 70 85 70 70
`) `)
@ -601,7 +602,7 @@ func TestAlertingRuleDuplicate(t *testing.T) {
} }
func TestAlertingRuleLimit(t *testing.T) { func TestAlertingRuleLimit(t *testing.T) {
storage := promql.LoadedStorage(t, ` storage := promqltest.LoadedStorage(t, `
load 1m load 1m
metric{label="1"} 1 metric{label="1"} 1
metric{label="2"} 1 metric{label="2"} 1
@ -783,7 +784,7 @@ func TestSendAlertsDontAffectActiveAlerts(t *testing.T) {
} }
func TestKeepFiringFor(t *testing.T) { func TestKeepFiringFor(t *testing.T) {
storage := promql.LoadedStorage(t, ` storage := promqltest.LoadedStorage(t, `
load 1m load 1m
http_requests{job="app-server", instance="0"} 75 85 70 70 10x5 http_requests{job="app-server", instance="0"} 75 85 70 70 10x5
`) `)
@ -893,7 +894,7 @@ func TestKeepFiringFor(t *testing.T) {
} }
func TestPendingAndKeepFiringFor(t *testing.T) { func TestPendingAndKeepFiringFor(t *testing.T) {
storage := promql.LoadedStorage(t, ` storage := promqltest.LoadedStorage(t, `
load 1m load 1m
http_requests{job="app-server", instance="0"} 75 10x10 http_requests{job="app-server", instance="0"} 75 10x10
`) `)

View file

@ -38,6 +38,7 @@ import (
"github.com/prometheus/prometheus/model/value" "github.com/prometheus/prometheus/model/value"
"github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/promql/parser"
"github.com/prometheus/prometheus/promql/promqltest"
"github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/storage"
"github.com/prometheus/prometheus/tsdb/chunkenc" "github.com/prometheus/prometheus/tsdb/chunkenc"
"github.com/prometheus/prometheus/tsdb/tsdbutil" "github.com/prometheus/prometheus/tsdb/tsdbutil"
@ -50,7 +51,7 @@ func TestMain(m *testing.M) {
} }
func TestAlertingRule(t *testing.T) { func TestAlertingRule(t *testing.T) {
storage := promql.LoadedStorage(t, ` storage := promqltest.LoadedStorage(t, `
load 5m load 5m
http_requests{job="app-server", instance="0", group="canary", severity="overwrite-me"} 75 85 95 105 105 95 85 http_requests{job="app-server", instance="0", group="canary", severity="overwrite-me"} 75 85 95 105 105 95 85
http_requests{job="app-server", instance="1", group="canary", severity="overwrite-me"} 80 90 100 110 120 130 140 http_requests{job="app-server", instance="1", group="canary", severity="overwrite-me"} 80 90 100 110 120 130 140
@ -190,7 +191,7 @@ func TestAlertingRule(t *testing.T) {
} }
func TestForStateAddSamples(t *testing.T) { func TestForStateAddSamples(t *testing.T) {
storage := promql.LoadedStorage(t, ` storage := promqltest.LoadedStorage(t, `
load 5m load 5m
http_requests{job="app-server", instance="0", group="canary", severity="overwrite-me"} 75 85 95 105 105 95 85 http_requests{job="app-server", instance="0", group="canary", severity="overwrite-me"} 75 85 95 105 105 95 85
http_requests{job="app-server", instance="1", group="canary", severity="overwrite-me"} 80 90 100 110 120 130 140 http_requests{job="app-server", instance="1", group="canary", severity="overwrite-me"} 80 90 100 110 120 130 140
@ -347,7 +348,7 @@ func sortAlerts(items []*Alert) {
} }
func TestForStateRestore(t *testing.T) { func TestForStateRestore(t *testing.T) {
storage := promql.LoadedStorage(t, ` storage := promqltest.LoadedStorage(t, `
load 5m load 5m
http_requests{job="app-server", instance="0", group="canary", severity="overwrite-me"} 75 85 50 0 0 25 0 0 40 0 120 http_requests{job="app-server", instance="0", group="canary", severity="overwrite-me"} 75 85 50 0 0 25 0 0 40 0 120
http_requests{job="app-server", instance="1", group="canary", severity="overwrite-me"} 125 90 60 0 0 25 0 0 40 0 130 http_requests{job="app-server", instance="1", group="canary", severity="overwrite-me"} 125 90 60 0 0 25 0 0 40 0 130
@ -1232,7 +1233,7 @@ func TestRuleHealthUpdates(t *testing.T) {
} }
func TestRuleGroupEvalIterationFunc(t *testing.T) { func TestRuleGroupEvalIterationFunc(t *testing.T) {
storage := promql.LoadedStorage(t, ` storage := promqltest.LoadedStorage(t, `
load 5m load 5m
http_requests{instance="0"} 75 85 50 0 0 25 0 0 40 0 120 http_requests{instance="0"} 75 85 50 0 0 25 0 0 40 0 120
`) `)

View file

@ -24,6 +24,7 @@ import (
"github.com/prometheus/prometheus/model/timestamp" "github.com/prometheus/prometheus/model/timestamp"
"github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/promql/parser"
"github.com/prometheus/prometheus/promql/promqltest"
"github.com/prometheus/prometheus/util/teststorage" "github.com/prometheus/prometheus/util/teststorage"
"github.com/prometheus/prometheus/util/testutil" "github.com/prometheus/prometheus/util/testutil"
) )
@ -111,7 +112,7 @@ var ruleEvalTestScenarios = []struct {
} }
func setUpRuleEvalTest(t require.TestingT) *teststorage.TestStorage { func setUpRuleEvalTest(t require.TestingT) *teststorage.TestStorage {
return promql.LoadedStorage(t, ` return promqltest.LoadedStorage(t, `
load 1m load 1m
metric{label_a="1",label_b="3"} 1 metric{label_a="1",label_b="3"} 1
metric{label_a="2",label_b="4"} 10 metric{label_a="2",label_b="4"} 10
@ -178,7 +179,7 @@ func TestRuleEvalDuplicate(t *testing.T) {
} }
func TestRecordingRuleLimit(t *testing.T) { func TestRecordingRuleLimit(t *testing.T) {
storage := promql.LoadedStorage(t, ` storage := promqltest.LoadedStorage(t, `
load 1m load 1m
metric{label="1"} 1 metric{label="1"} 1
metric{label="2"} 1 metric{label="2"} 1

View file

@ -30,14 +30,14 @@ import (
"github.com/prometheus/prometheus/config" "github.com/prometheus/prometheus/config"
"github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/prompb" "github.com/prometheus/prometheus/prompb"
"github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/promql/promqltest"
"github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/storage"
"github.com/prometheus/prometheus/tsdb/tsdbutil" "github.com/prometheus/prometheus/tsdb/tsdbutil"
"github.com/prometheus/prometheus/util/teststorage" "github.com/prometheus/prometheus/util/teststorage"
) )
func TestSampledReadEndpoint(t *testing.T) { func TestSampledReadEndpoint(t *testing.T) {
store := promql.LoadedStorage(t, ` store := promqltest.LoadedStorage(t, `
load 1m load 1m
test_metric1{foo="bar",baz="qux"} 1 test_metric1{foo="bar",baz="qux"} 1
`) `)
@ -132,7 +132,7 @@ func TestSampledReadEndpoint(t *testing.T) {
} }
func BenchmarkStreamReadEndpoint(b *testing.B) { func BenchmarkStreamReadEndpoint(b *testing.B) {
store := promql.LoadedStorage(b, ` store := promqltest.LoadedStorage(b, `
load 1m load 1m
test_metric1{foo="bar1",baz="qux"} 0+100x119 test_metric1{foo="bar1",baz="qux"} 0+100x119
test_metric1{foo="bar2",baz="qux"} 0+100x120 test_metric1{foo="bar2",baz="qux"} 0+100x120
@ -200,7 +200,7 @@ func TestStreamReadEndpoint(t *testing.T) {
// Second with 121 float samples, We expect 1 frame with 2 chunks. // Second with 121 float samples, We expect 1 frame with 2 chunks.
// Third with 241 float samples. We expect 1 frame with 2 chunks, and 1 frame with 1 chunk for the same series due to bytes limit. // Third with 241 float samples. We expect 1 frame with 2 chunks, and 1 frame with 1 chunk for the same series due to bytes limit.
// Fourth with 25 histogram samples. We expect 1 frame with 1 chunk. // Fourth with 25 histogram samples. We expect 1 frame with 1 chunk.
store := promql.LoadedStorage(t, ` store := promqltest.LoadedStorage(t, `
load 1m load 1m
test_metric1{foo="bar1",baz="qux"} 0+100x119 test_metric1{foo="bar1",baz="qux"} 0+100x119
test_metric1{foo="bar2",baz="qux"} 0+100x120 test_metric1{foo="bar2",baz="qux"} 0+100x120

41
util/almost/almost.go Normal file
View file

@ -0,0 +1,41 @@
// Copyright 2024 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package almost
import "math"
var minNormal = math.Float64frombits(0x0010000000000000) // The smallest positive normal value of type float64.
// Equal returns true if a and b differ by less than their sum
// multiplied by epsilon.
func Equal(a, b, epsilon float64) bool {
// NaN has no equality but for testing we still want to know whether both values
// are NaN.
if math.IsNaN(a) && math.IsNaN(b) {
return true
}
// Cf. http://floating-point-gui.de/errors/comparison/
if a == b {
return true
}
absSum := math.Abs(a) + math.Abs(b)
diff := math.Abs(a - b)
if a == 0 || b == 0 || absSum < minNormal {
return diff < epsilon*minNormal
}
return diff/math.Min(absSum, math.MaxFloat64) < epsilon
}

View file

@ -49,6 +49,7 @@ import (
"github.com/prometheus/prometheus/model/timestamp" "github.com/prometheus/prometheus/model/timestamp"
"github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/promql/parser"
"github.com/prometheus/prometheus/promql/promqltest"
"github.com/prometheus/prometheus/rules" "github.com/prometheus/prometheus/rules"
"github.com/prometheus/prometheus/scrape" "github.com/prometheus/prometheus/scrape"
"github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/storage"
@ -338,7 +339,7 @@ var sampleFlagMap = map[string]string{
} }
func TestEndpoints(t *testing.T) { func TestEndpoints(t *testing.T) {
storage := promql.LoadedStorage(t, ` storage := promqltest.LoadedStorage(t, `
load 1m load 1m
test_metric1{foo="bar"} 0+100x100 test_metric1{foo="bar"} 0+100x100
test_metric1{foo="boo"} 1+0x100 test_metric1{foo="boo"} 1+0x100
@ -502,7 +503,7 @@ func (b byLabels) Less(i, j int) bool { return labels.Compare(b[i], b[j]) < 0 }
func TestGetSeries(t *testing.T) { func TestGetSeries(t *testing.T) {
// TestEndpoints doesn't have enough label names to test api.labelNames // TestEndpoints doesn't have enough label names to test api.labelNames
// endpoint properly. Hence we test it separately. // endpoint properly. Hence we test it separately.
storage := promql.LoadedStorage(t, ` storage := promqltest.LoadedStorage(t, `
load 1m load 1m
test_metric1{foo1="bar", baz="abc"} 0+100x100 test_metric1{foo1="bar", baz="abc"} 0+100x100
test_metric1{foo2="boo"} 1+0x100 test_metric1{foo2="boo"} 1+0x100
@ -606,7 +607,7 @@ func TestGetSeries(t *testing.T) {
func TestQueryExemplars(t *testing.T) { func TestQueryExemplars(t *testing.T) {
start := time.Unix(0, 0) start := time.Unix(0, 0)
storage := promql.LoadedStorage(t, ` storage := promqltest.LoadedStorage(t, `
load 1m load 1m
test_metric1{foo="bar"} 0+100x100 test_metric1{foo="bar"} 0+100x100
test_metric1{foo="boo"} 1+0x100 test_metric1{foo="boo"} 1+0x100
@ -725,7 +726,7 @@ func TestQueryExemplars(t *testing.T) {
func TestLabelNames(t *testing.T) { func TestLabelNames(t *testing.T) {
// TestEndpoints doesn't have enough label names to test api.labelNames // TestEndpoints doesn't have enough label names to test api.labelNames
// endpoint properly. Hence we test it separately. // endpoint properly. Hence we test it separately.
storage := promql.LoadedStorage(t, ` storage := promqltest.LoadedStorage(t, `
load 1m load 1m
test_metric1{foo1="bar", baz="abc"} 0+100x100 test_metric1{foo1="bar", baz="abc"} 0+100x100
test_metric1{foo2="boo"} 1+0x100 test_metric1{foo2="boo"} 1+0x100
@ -3835,7 +3836,7 @@ func TestExtractQueryOpts(t *testing.T) {
// Test query timeout parameter. // Test query timeout parameter.
func TestQueryTimeout(t *testing.T) { func TestQueryTimeout(t *testing.T) {
storage := promql.LoadedStorage(t, ` storage := promqltest.LoadedStorage(t, `
load 1m load 1m
test_metric1{foo="bar"} 0+100x100 test_metric1{foo="bar"} 0+100x100
`) `)

View file

@ -34,6 +34,7 @@ import (
"github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/model/textparse" "github.com/prometheus/prometheus/model/textparse"
"github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/promql/promqltest"
"github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/storage"
"github.com/prometheus/prometheus/tsdb" "github.com/prometheus/prometheus/tsdb"
"github.com/prometheus/prometheus/util/teststorage" "github.com/prometheus/prometheus/util/teststorage"
@ -201,7 +202,7 @@ test_metric_without_labels{instance="baz"} 1001 6000000
} }
func TestFederation(t *testing.T) { func TestFederation(t *testing.T) {
storage := promql.LoadedStorage(t, ` storage := promqltest.LoadedStorage(t, `
load 1m load 1m
test_metric1{foo="bar",instance="i"} 0+100x100 test_metric1{foo="bar",instance="i"} 0+100x100
test_metric1{foo="boo",instance="i"} 1+0x100 test_metric1{foo="boo",instance="i"} 1+0x100