SSE: Threshold expression to use simple functions (#86062)
* replace math expression with predicates
This commit is contained in:
+186
-72
@@ -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