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
+95 -38
View File
@@ -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
}