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

* replace math expression with predicates
This commit is contained in:
Yuri Tseretyan
2024-04-16 13:35:41 -04:00
committed by GitHub
parent 9c1ef8b16e
commit e9d6135e33
5 changed files with 414 additions and 112 deletions
+186 -72
View File
@@ -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)
})
}
})
}
}