mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
SSE: Threshold expression to use simple functions (#86062)
* replace math expression with predicates
This commit is contained in:
@@ -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: 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 = outsideRangePredicate{left: conditions[0], right: conditions[1]}
|
||||
case ThresholdIsWithinRange:
|
||||
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 = withinRangePredicate{left: conditions[0], right: conditions[1]}
|
||||
case ThresholdIsAbove:
|
||||
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 = greaterThanPredicate{value: conditions[0]}
|
||||
case ThresholdIsBelow:
|
||||
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 = 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
|
||||
}
|
||||
|
||||
88
pkg/expr/threshold_bench_test.go
Normal file
88
pkg/expr/threshold_bench_test.go
Normal file
@@ -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)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user