SSE: Threshold expression to use simple functions (#86062)

* replace math expression with predicates
pull/86389/head
Yuri Tseretyan 1 year ago committed by GitHub
parent 9c1ef8b16e
commit e9d6135e33
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      pkg/expr/hysteresis_test.go
  2. 43
      pkg/expr/testing.go
  3. 133
      pkg/expr/threshold.go
  4. 88
      pkg/expr/threshold_bench_test.go
  5. 258
      pkg/expr/threshold_test.go

@ -95,13 +95,13 @@ func TestHysteresisExecute(t *testing.T) {
ReferenceVar: "A",
RefID: "B",
ThresholdFunc: ThresholdIsAbove,
Conditions: []float64{loadThreshold},
predicate: greaterThanPredicate{loadThreshold},
},
UnloadingThresholdFunc: ThresholdCommand{
ReferenceVar: "A",
RefID: "B",
ThresholdFunc: ThresholdIsAbove,
Conditions: []float64{unloadThreshold},
predicate: greaterThanPredicate{unloadThreshold},
},
LoadedDimensions: tc.loadedDimensions,
}

@ -2,9 +2,12 @@ package expr
import (
"context"
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/expr/mathexp"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/datasources"
)
@ -82,3 +85,43 @@ func (f *recordingCallResourceHandler) CallResource(_ context.Context, req *back
f.recordings = append(f.recordings, req)
return sender.Send(f.response)
}
func newScalar(value *float64) mathexp.Scalar {
n := mathexp.NewScalar("", value)
return n
}
func newNumber(labels data.Labels, value *float64) mathexp.Number {
n := mathexp.NewNumber("", labels)
n.SetValue(value)
return n
}
func newSeries(points ...float64) mathexp.Series {
series := mathexp.NewSeries("", nil, len(points))
for idx, point := range points {
p := point
series.SetPoint(idx, time.Unix(int64(idx), 0), &p)
}
return series
}
func newSeriesPointer(points ...*float64) mathexp.Series {
series := mathexp.NewSeries("", nil, len(points))
for idx, point := range points {
series.SetPoint(idx, time.Unix(int64(idx), 0), point)
}
return series
}
func newSeriesWithLabels(labels data.Labels, values ...*float64) mathexp.Series {
series := mathexp.NewSeries("", labels, len(values))
for idx, value := range values {
series.SetPoint(idx, time.Unix(int64(idx), 0), value)
}
return series
}
func newResults(values ...mathexp.Value) mathexp.Results {
return mathexp.Results{Values: values}
}

@ -13,14 +13,19 @@ import (
"github.com/grafana/grafana/pkg/expr/mathexp"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/util"
)
type predicate interface {
Eval(f float64) bool
}
type ThresholdCommand struct {
ReferenceVar string
RefID string
ThresholdFunc ThresholdType
Conditions []float64
Invert bool
predicate predicate
}
// +enum
@ -43,15 +48,28 @@ var (
)
func NewThresholdCommand(refID, referenceVar string, thresholdFunc ThresholdType, conditions []float64) (*ThresholdCommand, error) {
var predicate predicate
switch thresholdFunc {
case ThresholdIsOutsideRange, ThresholdIsWithinRange:
case ThresholdIsOutsideRange:
if len(conditions) < 2 {
return nil, fmt.Errorf("incorrect number of arguments for threshold function '%s': got %d but need 2", thresholdFunc, len(conditions))
}
predicate = outsideRangePredicate{left: conditions[0], right: conditions[1]}
case ThresholdIsWithinRange:
if len(conditions) < 2 {
return nil, fmt.Errorf("incorrect number of arguments: got %d but need 2", len(conditions))
return nil, fmt.Errorf("incorrect number of arguments for threshold function '%s': got %d but need 2", thresholdFunc, len(conditions))
}
case ThresholdIsAbove, ThresholdIsBelow:
predicate = withinRangePredicate{left: conditions[0], right: conditions[1]}
case ThresholdIsAbove:
if len(conditions) < 1 {
return nil, fmt.Errorf("incorrect number of arguments for threshold function '%s': got %d but need 1", thresholdFunc, len(conditions))
}
predicate = greaterThanPredicate{value: conditions[0]}
case ThresholdIsBelow:
if len(conditions) < 1 {
return nil, fmt.Errorf("incorrect number of arguments: got %d but need 1", len(conditions))
return nil, fmt.Errorf("incorrect number of arguments for threshold function '%s': got %d but need 1", thresholdFunc, len(conditions))
}
predicate = lessThanPredicate{value: conditions[0]}
default:
return nil, fmt.Errorf("expected threshold function to be one of [%s], got %s", strings.Join(supportedThresholdFuncs, ", "), thresholdFunc)
}
@ -60,7 +78,7 @@ func NewThresholdCommand(refID, referenceVar string, thresholdFunc ThresholdType
RefID: refID,
ReferenceVar: referenceVar,
ThresholdFunc: thresholdFunc,
Conditions: conditions,
predicate: predicate,
}, nil
}
@ -114,46 +132,51 @@ func (tc *ThresholdCommand) NeedsVars() []string {
return []string{tc.ReferenceVar}
}
func (tc *ThresholdCommand) Execute(ctx context.Context, now time.Time, vars mathexp.Vars, tracer tracing.Tracer) (mathexp.Results, error) {
mathExpression, err := createMathExpression(tc.ReferenceVar, tc.ThresholdFunc, tc.Conditions, tc.Invert)
if err != nil {
return mathexp.Results{}, err
func (tc *ThresholdCommand) Execute(_ context.Context, _ time.Time, vars mathexp.Vars, _ tracing.Tracer) (mathexp.Results, error) {
eval := func(maybeValue *float64) *float64 {
if maybeValue == nil {
return nil
}
result := tc.predicate.Eval(*maybeValue)
if tc.Invert {
result = !result
}
if result {
return util.Pointer(float64(1))
}
return util.Pointer(float64(0))
}
mathCommand, err := NewMathCommand(tc.RefID, mathExpression)
if err != nil {
return mathexp.Results{}, err
refVarResult := vars[tc.ReferenceVar]
newRes := mathexp.Results{Values: make(mathexp.Values, 0, len(refVarResult.Values))}
for _, val := range refVarResult.Values {
switch v := val.(type) {
case mathexp.Series:
s := mathexp.NewSeries(tc.RefID, v.GetLabels(), v.Len())
for i := 0; i < v.Len(); i++ {
t, value := v.GetPoint(i)
s.SetPoint(i, t, eval(value))
}
newRes.Values = append(newRes.Values, s)
case mathexp.Number:
copyV := mathexp.NewNumber(tc.RefID, v.GetLabels())
copyV.SetValue(eval(v.GetFloat64Value()))
newRes.Values = append(newRes.Values, copyV)
case mathexp.Scalar:
copyV := mathexp.NewScalar(tc.RefID, eval(v.GetFloat64Value()))
newRes.Values = append(newRes.Values, copyV)
case mathexp.NoData:
newRes.Values = append(newRes.Values, mathexp.NewNoData())
default:
return newRes, fmt.Errorf("unsupported format of the input data, got type %v", val.Type())
}
}
return mathCommand.Execute(ctx, now, vars, tracer)
return newRes, nil
}
func (tc *ThresholdCommand) Type() string {
return TypeThreshold.String()
}
// createMathExpression converts all the info we have about a "threshold" expression in to a Math expression
func createMathExpression(referenceVar string, thresholdFunc ThresholdType, args []float64, invert bool) (string, error) {
var exp string
switch thresholdFunc {
case ThresholdIsAbove:
exp = fmt.Sprintf("${%s} > %f", referenceVar, args[0])
case ThresholdIsBelow:
exp = fmt.Sprintf("${%s} < %f", referenceVar, args[0])
case ThresholdIsWithinRange:
exp = fmt.Sprintf("${%s} > %f && ${%s} < %f", referenceVar, args[0], referenceVar, args[1])
case ThresholdIsOutsideRange:
exp = fmt.Sprintf("${%s} < %f || ${%s} > %f", referenceVar, args[0], referenceVar, args[1])
default:
return "", fmt.Errorf("failed to evaluate threshold expression: no such threshold function %s", thresholdFunc)
}
if invert {
return fmt.Sprintf("!(%s)", exp), nil
}
return exp, nil
}
func IsSupportedThresholdFunc(name string) bool {
isSupported := false
@ -237,3 +260,37 @@ func getConditionForHysteresisCommand(query map[string]any) (map[string]any, err
}
return condition, nil
}
type withinRangePredicate struct {
left float64
right float64
}
func (r withinRangePredicate) Eval(f float64) bool {
return f > r.left && f < r.right
}
type outsideRangePredicate struct {
left float64
right float64
}
func (r outsideRangePredicate) Eval(f float64) bool {
return f < r.left || f > r.right
}
type lessThanPredicate struct {
value float64
}
func (r lessThanPredicate) Eval(f float64) bool {
return f < r.value
}
type greaterThanPredicate struct {
value float64
}
func (r greaterThanPredicate) Eval(f float64) bool {
return f > r.value
}

@ -0,0 +1,88 @@
package expr
import (
"context"
"fmt"
"testing"
"time"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/expr/mathexp"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/util"
)
func BenchmarkThreshold(b *testing.B) {
results := make(mathexp.Values, 0, 1000)
for i := 0; i < cap(results); i++ {
n := newNumber(data.Labels{"test": fmt.Sprintf("series-%d", i)}, util.Pointer(float64(i)))
results = append(results, n)
}
ctx := context.Background()
timeNow := time.Now()
vars := mathexp.Vars{
"A": newResults(results...),
}
trace := tracing.InitializeTracerForTest()
b.ResetTimer()
b.Run("greater than", func(b *testing.B) {
greater, err := NewThresholdCommand("B", "A", ThresholdIsAbove, []float64{500})
if err != nil {
b.Fatalf("error: %s", err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = greater.Execute(ctx, timeNow, vars, trace)
}
})
b.Run("less than", func(b *testing.B) {
greater, err := NewThresholdCommand("B", "A", ThresholdIsAbove, []float64{500})
if err != nil {
b.Fatalf("error: %s", err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = greater.Execute(ctx, timeNow, vars, trace)
}
})
b.Run("within range", func(b *testing.B) {
greater, err := NewThresholdCommand("B", "A", ThresholdIsWithinRange, []float64{400.0, 600.0})
if err != nil {
b.Fatalf("error: %s", err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = greater.Execute(ctx, timeNow, vars, trace)
}
})
b.Run("within range, no labels", func(b *testing.B) {
greater, err := NewThresholdCommand("B", "A", ThresholdIsWithinRange, []float64{400.0, 600.0})
if err != nil {
b.Fatalf("error: %s", err)
}
results := make(mathexp.Values, 0, 1000)
for i := 0; i < cap(results); i++ {
n := newNumber(nil, util.Pointer(float64(i)))
results = append(results, n)
}
vars := mathexp.Vars{
"A": newResults(results...),
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = greater.Execute(ctx, timeNow, vars, trace)
}
})
b.Run("outside range", func(b *testing.B) {
greater, err := NewThresholdCommand("B", "A", ThresholdIsOutsideRange, []float64{400.0, 600.0})
if err != nil {
b.Fatalf("error: %s", err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = greater.Execute(ctx, timeNow, vars, trace)
}
})
}

@ -1,15 +1,22 @@
package expr
import (
"context"
"encoding/json"
"fmt"
"math"
"slices"
"sort"
"testing"
"time"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/stretchr/testify/require"
"golang.org/x/exp/maps"
"github.com/grafana/grafana/pkg/expr/mathexp"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/util"
)
func TestNewThresholdCommand(t *testing.T) {
@ -108,7 +115,7 @@ func TestUnmarshalThresholdCommand(t *testing.T) {
cmd := command.(*ThresholdCommand)
require.Equal(t, []string{"A"}, cmd.NeedsVars())
require.Equal(t, ThresholdIsAbove, cmd.ThresholdFunc)
require.Equal(t, []float64{20.0, 80.0}, cmd.Conditions)
require.Equal(t, greaterThanPredicate{20.0}, cmd.predicate)
},
},
{
@ -173,10 +180,10 @@ func TestUnmarshalThresholdCommand(t *testing.T) {
require.Equal(t, []string{"B"}, cmd.NeedsVars())
require.Equal(t, []string{"B"}, cmd.LoadingThresholdFunc.NeedsVars())
require.Equal(t, ThresholdIsAbove, cmd.LoadingThresholdFunc.ThresholdFunc)
require.Equal(t, []float64{100.0}, cmd.LoadingThresholdFunc.Conditions)
require.Equal(t, greaterThanPredicate{100.0}, cmd.LoadingThresholdFunc.predicate)
require.Equal(t, []string{"B"}, cmd.UnloadingThresholdFunc.NeedsVars())
require.Equal(t, ThresholdIsBelow, cmd.UnloadingThresholdFunc.ThresholdFunc)
require.Equal(t, []float64{31.0}, cmd.UnloadingThresholdFunc.Conditions)
require.Equal(t, lessThanPredicate{31.0}, cmd.UnloadingThresholdFunc.predicate)
require.True(t, cmd.UnloadingThresholdFunc.Invert)
require.NotNil(t, cmd.LoadedDimensions)
actual := make([]uint64, 0, len(cmd.LoadedDimensions))
@ -227,74 +234,6 @@ func TestThresholdCommandVars(t *testing.T) {
require.Equal(t, cmd.NeedsVars(), []string{"A"})
}
func TestCreateMathExpression(t *testing.T) {
type testCase struct {
description string
expected string
ref string
function ThresholdType
params []float64
}
cases := []testCase{
{
description: "is above",
ref: "My Ref",
function: "gt",
params: []float64{0},
expected: "${My Ref} > 0.000000",
},
{
description: "is below",
ref: "A",
function: "lt",
params: []float64{0},
expected: "${A} < 0.000000",
},
{
description: "is within",
ref: "B",
function: "within_range",
params: []float64{20, 80},
expected: "${B} > 20.000000 && ${B} < 80.000000",
},
{
description: "is outside",
ref: "B",
function: "outside_range",
params: []float64{20, 80},
expected: "${B} < 20.000000 || ${B} > 80.000000",
},
}
for _, tc := range cases {
t.Run(tc.description, func(t *testing.T) {
expr, err := createMathExpression(tc.ref, tc.function, tc.params, false)
require.Nil(t, err)
require.NotNil(t, expr)
require.Equal(t, tc.expected, expr)
t.Run("inverted", func(t *testing.T) {
expr, err := createMathExpression(tc.ref, tc.function, tc.params, true)
require.Nil(t, err)
require.NotNil(t, expr)
require.Equal(t, fmt.Sprintf("!(%s)", tc.expected), expr)
})
})
}
t.Run("should error if function is unsupported", func(t *testing.T) {
expr, err := createMathExpression("A", "foo", []float64{0}, false)
require.Equal(t, expr, "")
require.NotNil(t, err)
require.Contains(t, err.Error(), "no such threshold function")
})
}
func TestIsSupportedThresholdFunc(t *testing.T) {
type testCase struct {
function ThresholdType
@ -443,3 +382,178 @@ func TestSetLoadedDimensionsToHysteresisCommand(t *testing.T) {
require.Equal(t, fingerprints, cmd.(*HysteresisCommand).LoadedDimensions)
})
}
func TestThresholdExecute(t *testing.T) {
input := map[string]mathexp.Value{
//
"no-data": mathexp.NewNoData(),
//
"series - numbers": newSeries(8, 9, 10, 11, 12),
"series - empty": newSeriesPointer(),
"series - all nils": newSeriesPointer(nil, nil, nil),
"series - with labels": newSeriesWithLabels(data.Labels{"test": "test"}, nil, util.Pointer(float64(9)), nil, util.Pointer(float64(11)), nil),
"series - with NaNs": newSeries(math.NaN(), math.NaN(), math.NaN()),
//
"scalar - nil": newScalar(nil),
"scalar - NaN": newScalar(util.Pointer(math.NaN())),
"scalar - 8": newScalar(util.Pointer(float64(8))),
"scalar - 9": newScalar(util.Pointer(float64(9))),
"scalar - 10": newScalar(util.Pointer(float64(10))),
"scalar - 11": newScalar(util.Pointer(float64(11))),
"scalar - 12": newScalar(util.Pointer(float64(12))),
//
"number - nil": newNumber(data.Labels{"number": "test"}, nil),
"number - NaN": newNumber(data.Labels{"number": "test"}, util.Pointer(math.NaN())),
"number - 8": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(8))),
"number - 9": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(9))),
"number - 10": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(10))),
"number - 11": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(11))),
"number - 12": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(12))),
}
keys := maps.Keys(input)
slices.Sort(keys)
testCases := []struct {
name string
pred predicate
expected map[string]mathexp.Value
errorMsg string
}{
{
name: "greater than 10",
pred: greaterThanPredicate{10.0},
expected: map[string]mathexp.Value{
//
"no-data": mathexp.NewNoData(),
//
"series - numbers": newSeries(0, 0, 0, 1, 1),
"series - empty": newSeriesPointer(),
"series - all nils": newSeriesPointer(nil, nil, nil),
"series - with labels": newSeriesWithLabels(data.Labels{"test": "test"}, nil, util.Pointer(float64(0)), nil, util.Pointer(float64(1)), nil),
"series - with NaNs": newSeries(0, 0, 0),
//
"scalar - nil": newScalar(nil),
"scalar - NaN": newScalar(util.Pointer(float64(0))),
"scalar - 8": newScalar(util.Pointer(float64(0))),
"scalar - 9": newScalar(util.Pointer(float64(0))),
"scalar - 10": newScalar(util.Pointer(float64(0))),
"scalar - 11": newScalar(util.Pointer(float64(1))),
"scalar - 12": newScalar(util.Pointer(float64(1))),
//
"number - nil": newNumber(data.Labels{"number": "test"}, nil),
"number - NaN": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))),
"number - 8": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))),
"number - 9": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))),
"number - 10": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))),
"number - 11": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(1))),
"number - 12": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(1))),
},
},
{
name: "less than 10",
pred: lessThanPredicate{10.0},
expected: map[string]mathexp.Value{
//
"no-data": mathexp.NewNoData(),
//
"series - numbers": newSeries(1, 1, 0, 0, 0),
"series - empty": newSeriesPointer(),
"series - all nils": newSeriesPointer(nil, nil, nil),
"series - with labels": newSeriesWithLabels(data.Labels{"test": "test"}, nil, util.Pointer(float64(1)), nil, util.Pointer(float64(0)), nil),
"series - with NaNs": newSeries(0, 0, 0),
//
"scalar - nil": newScalar(nil),
"scalar - NaN": newScalar(util.Pointer(float64(0))),
"scalar - 8": newScalar(util.Pointer(float64(1))),
"scalar - 9": newScalar(util.Pointer(float64(1))),
"scalar - 10": newScalar(util.Pointer(float64(0))),
"scalar - 11": newScalar(util.Pointer(float64(0))),
"scalar - 12": newScalar(util.Pointer(float64(0))),
//
"number - nil": newNumber(data.Labels{"number": "test"}, nil),
"number - NaN": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))),
"number - 8": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(1))),
"number - 9": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(1))),
"number - 10": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))),
"number - 11": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))),
"number - 12": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))),
},
},
{
name: "within range (8,11)",
pred: withinRangePredicate{8, 11},
expected: map[string]mathexp.Value{
//
"no-data": mathexp.NewNoData(),
//
"series - numbers": newSeries(0, 1, 1, 0, 0),
"series - empty": newSeriesPointer(),
"series - all nils": newSeriesPointer(nil, nil, nil),
"series - with labels": newSeriesWithLabels(data.Labels{"test": "test"}, nil, util.Pointer(float64(1)), nil, util.Pointer(float64(0)), nil),
"series - with NaNs": newSeries(0, 0, 0),
//
"scalar - nil": newScalar(nil),
"scalar - NaN": newScalar(util.Pointer(float64(0))),
"scalar - 8": newScalar(util.Pointer(float64(0))),
"scalar - 9": newScalar(util.Pointer(float64(1))),
"scalar - 10": newScalar(util.Pointer(float64(1))),
"scalar - 11": newScalar(util.Pointer(float64(0))),
"scalar - 12": newScalar(util.Pointer(float64(0))),
//
"number - nil": newNumber(data.Labels{"number": "test"}, nil),
"number - NaN": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))),
"number - 8": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))),
"number - 9": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(1))),
"number - 10": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(1))),
"number - 11": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))),
"number - 12": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))),
},
},
{
name: "outside range (8, 11)",
pred: outsideRangePredicate{8, 11},
expected: map[string]mathexp.Value{
//
"no-data": mathexp.NewNoData(),
//
"series - numbers": newSeries(0, 0, 0, 0, 1),
"series - empty": newSeriesPointer(),
"series - all nils": newSeriesPointer(nil, nil, nil),
"series - with labels": newSeriesWithLabels(data.Labels{"test": "test"}, nil, util.Pointer(float64(0)), nil, util.Pointer(float64(0)), nil),
"series - with NaNs": newSeries(0, 0, 0),
//
"scalar - nil": newScalar(nil),
"scalar - NaN": newScalar(util.Pointer(float64(0))),
"scalar - 8": newScalar(util.Pointer(float64(0))),
"scalar - 9": newScalar(util.Pointer(float64(0))),
"scalar - 10": newScalar(util.Pointer(float64(0))),
"scalar - 11": newScalar(util.Pointer(float64(0))),
"scalar - 12": newScalar(util.Pointer(float64(1))),
//
"number - nil": newNumber(data.Labels{"number": "test"}, nil),
"number - NaN": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))),
"number - 8": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))),
"number - 9": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))),
"number - 10": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))),
"number - 11": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))),
"number - 12": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(1))),
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
cmd := ThresholdCommand{
predicate: tc.pred,
ReferenceVar: "A",
}
for _, name := range keys {
t.Run(name, func(t *testing.T) {
result, err := cmd.Execute(context.Background(), time.Now(), mathexp.Vars{
"A": newResults(input[name]),
}, tracing.InitializeTracerForTest())
require.NoError(t, err)
require.Equal(t, newResults(tc.expected[name]), result)
})
}
})
}
}

Loading…
Cancel
Save