Alerting: Add multiple threshold operators (#99516)

The following operators are being added:
- Equal
- Not Equal
- Greater or Equal
- Less or Equal
- Within Range Inclusive
- Outside Range Inclusive
This commit is contained in:
Paulo Dias
2025-02-21 19:11:16 +02:00
committed by GitHub
parent 6ebde0481e
commit 2d2e595555
18 changed files with 781 additions and 128 deletions
+14 -2
View File
@@ -48,9 +48,9 @@ func (rangedEvaluator) Kind() EvaluatorKind {
// an AlertEvaluator depending on evaluation operator.
func newAlertEvaluator(model ConditionEvalJSON) (evaluator, error) {
switch model.Type {
case "gt", "lt":
case "gt", "lt", "eq", "ne", "gte", "lte":
return newThresholdEvaluator(model)
case "within_range", "outside_range":
case "within_range", "outside_range", "within_range_included", "outside_range_included":
return newRangedEvaluator(model)
case "no_value":
return &noValueEvaluator{}, nil
@@ -70,6 +70,14 @@ func (e *thresholdEvaluator) Eval(reducedValue mathexp.Number) bool {
return *fv > e.Threshold
case "lt":
return *fv < e.Threshold
case "eq":
return *fv == e.Threshold
case "ne":
return *fv != e.Threshold
case "gte":
return *fv >= e.Threshold
case "lte":
return *fv <= e.Threshold
}
return false
@@ -113,6 +121,10 @@ func (e *rangedEvaluator) Eval(reducedValue mathexp.Number) bool {
return (e.Lower < *fv && e.Upper > *fv) || (e.Upper < *fv && e.Lower > *fv)
case "outside_range":
return (e.Upper < *fv && e.Lower < *fv) || (e.Upper > *fv && e.Lower > *fv)
case "within_range_included":
return (e.Lower <= *fv && e.Upper >= *fv) || (e.Upper <= *fv && e.Lower >= *fv)
case "outside_range_included":
return (e.Upper <= *fv && e.Lower <= *fv) || (e.Upper >= *fv && e.Lower >= *fv)
}
return false
+42
View File
@@ -40,6 +40,48 @@ func TestThresholdEvaluator(t *testing.T) {
inputNumber: newNumber(util.Pointer(1.0)),
expected: true,
},
{
name: "value 1 is eq 1: false",
evaluator: &thresholdEvaluator{"eq", 1},
inputNumber: newNumber(util.Pointer(1.0)),
expected: true,
},
{
name: "value 0 is eq 0: false",
evaluator: &thresholdEvaluator{"eq", 0},
inputNumber: newNumber(util.Pointer(0.0)),
expected: true,
},
{
name: "value 1 is eq 0: false",
evaluator: &thresholdEvaluator{"eq", 0},
inputNumber: newNumber(util.Pointer(1.0)),
expected: false,
},
{
name: "value 0 is eq 1: false",
evaluator: &thresholdEvaluator{"eq", 1},
inputNumber: newNumber(util.Pointer(0.0)),
expected: false,
},
{
name: "value 1 is ne 1: false",
evaluator: &thresholdEvaluator{"ne", 1},
inputNumber: newNumber(util.Pointer(1.0)),
expected: false,
},
{
name: "value 3 is gte 3: false",
evaluator: &thresholdEvaluator{"gte", 3},
inputNumber: newNumber(util.Pointer(3.0)),
expected: true,
},
{
name: "value 5 is lte 4: false",
evaluator: &thresholdEvaluator{"lte", 4},
inputNumber: newNumber(util.Pointer(5.0)),
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+15 -3
View File
@@ -712,8 +712,14 @@
"enum": [
"gt",
"lt",
"eq",
"ne",
"gte",
"lte",
"within_range",
"outside_range"
"outside_range",
"within_range_included",
"outside_range_included"
],
"x-enum-description": {}
}
@@ -744,8 +750,14 @@
"enum": [
"gt",
"lt",
"eq",
"ne",
"gte",
"lte",
"within_range",
"outside_range"
"outside_range",
"within_range_included",
"outside_range_included"
],
"x-enum-description": {}
}
@@ -1013,4 +1025,4 @@
},
"additionalProperties": true,
"$schema": "https://json-schema.org/draft-04/schema#"
}
}
+15 -3
View File
@@ -754,8 +754,14 @@
"enum": [
"gt",
"lt",
"eq",
"ne",
"gte",
"lte",
"within_range",
"outside_range"
"outside_range",
"within_range_included",
"outside_range_included"
],
"x-enum-description": {}
}
@@ -786,8 +792,14 @@
"enum": [
"gt",
"lt",
"eq",
"ne",
"gte",
"lte",
"within_range",
"outside_range"
"outside_range",
"within_range_included",
"outside_range_included"
],
"x-enum-description": {}
}
@@ -1071,4 +1083,4 @@
},
"additionalProperties": false,
"$schema": "https://json-schema.org/draft-04/schema#"
}
}
+15 -3
View File
@@ -395,8 +395,14 @@
"enum": [
"gt",
"lt",
"eq",
"ne",
"gte",
"lte",
"within_range",
"outside_range"
"outside_range",
"within_range_included",
"outside_range_included"
],
"type": "string",
"x-enum-description": {}
@@ -427,8 +433,14 @@
"enum": [
"gt",
"lt",
"eq",
"ne",
"gte",
"lte",
"within_range",
"outside_range"
"outside_range",
"within_range_included",
"outside_range_included"
],
"type": "string",
"x-enum-description": {}
@@ -579,4 +591,4 @@
}
}
]
}
}
+96 -4
View File
@@ -32,18 +32,30 @@ type ThresholdCommand struct {
type ThresholdType string
const (
ThresholdIsAbove ThresholdType = "gt"
ThresholdIsBelow ThresholdType = "lt"
ThresholdIsWithinRange ThresholdType = "within_range"
ThresholdIsOutsideRange ThresholdType = "outside_range"
ThresholdIsAbove ThresholdType = "gt"
ThresholdIsBelow ThresholdType = "lt"
ThresholdIsEqual ThresholdType = "eq"
ThresholdIsNotEqual ThresholdType = "ne"
ThresholdIsGreaterThanEqual ThresholdType = "gte"
ThresholdIsLessThanEqual ThresholdType = "lte"
ThresholdIsWithinRange ThresholdType = "within_range"
ThresholdIsOutsideRange ThresholdType = "outside_range"
ThresholdIsWithinRangeIncluded ThresholdType = "within_range_included"
ThresholdIsOutsideRangeIncluded ThresholdType = "outside_range_included"
)
var (
supportedThresholdFuncs = []string{
string(ThresholdIsAbove),
string(ThresholdIsBelow),
string(ThresholdIsEqual),
string(ThresholdIsNotEqual),
string(ThresholdIsGreaterThanEqual),
string(ThresholdIsLessThanEqual),
string(ThresholdIsWithinRange),
string(ThresholdIsOutsideRange),
string(ThresholdIsWithinRangeIncluded),
string(ThresholdIsOutsideRangeIncluded),
}
)
@@ -60,6 +72,16 @@ func NewThresholdCommand(refID, referenceVar string, thresholdFunc ThresholdType
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 ThresholdIsWithinRangeIncluded:
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 = withinRangeIncludedPredicate{left: conditions[0], right: conditions[1]}
case ThresholdIsOutsideRangeIncluded:
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 = outsideRangeIncludedPredicate{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))
@@ -70,6 +92,26 @@ func NewThresholdCommand(refID, referenceVar string, thresholdFunc ThresholdType
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]}
case ThresholdIsEqual:
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 = equalPredicate{value: conditions[0]}
case ThresholdIsNotEqual:
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 = notEqualPredicate{value: conditions[0]}
case ThresholdIsGreaterThanEqual:
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 = greaterThanEqualPredicate{value: conditions[0]}
case ThresholdIsLessThanEqual:
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 = lessThanEqualPredicate{value: conditions[0]}
default:
return nil, fmt.Errorf("expected threshold function to be one of [%s], got %s", strings.Join(supportedThresholdFuncs, ", "), thresholdFunc)
}
@@ -279,6 +321,24 @@ func (r outsideRangePredicate) Eval(f float64) bool {
return f < r.left || f > r.right
}
type withinRangeIncludedPredicate struct {
left float64
right float64
}
func (r withinRangeIncludedPredicate) Eval(f float64) bool {
return f >= r.left && f <= r.right
}
type outsideRangeIncludedPredicate struct {
left float64
right float64
}
func (r outsideRangeIncludedPredicate) Eval(f float64) bool {
return f <= r.left || f >= r.right
}
type lessThanPredicate struct {
value float64
}
@@ -294,3 +354,35 @@ type greaterThanPredicate struct {
func (r greaterThanPredicate) Eval(f float64) bool {
return f > r.value
}
type equalPredicate struct {
value float64
}
func (r equalPredicate) Eval(f float64) bool {
return f == r.value
}
type notEqualPredicate struct {
value float64
}
func (r notEqualPredicate) Eval(f float64) bool {
return f != r.value
}
type greaterThanEqualPredicate struct {
value float64
}
func (r greaterThanEqualPredicate) Eval(f float64) bool {
return f >= r.value
}
type lessThanEqualPredicate struct {
value float64
}
func (r lessThanEqualPredicate) Eval(f float64) bool {
return f <= r.value
}
+90
View File
@@ -38,6 +38,26 @@ func TestNewThresholdCommand(t *testing.T) {
args: []float64{0},
shouldError: false,
},
{
fn: "eq",
args: []float64{0},
shouldError: false,
},
{
fn: "ne",
args: []float64{0},
shouldError: false,
},
{
fn: "gte",
args: []float64{0},
shouldError: false,
},
{
fn: "lte",
args: []float64{0},
shouldError: false,
},
{
fn: "within_range",
args: []float64{0, 1},
@@ -48,6 +68,16 @@ func TestNewThresholdCommand(t *testing.T) {
args: []float64{0, 1},
shouldError: false,
},
{
fn: "within_range_included",
args: []float64{0, 1},
shouldError: false,
},
{
fn: "outside_range_included",
args: []float64{0, 1},
shouldError: false,
},
{
fn: "gt",
args: []float64{},
@@ -60,6 +90,30 @@ func TestNewThresholdCommand(t *testing.T) {
shouldError: true,
expectedError: "incorrect number of arguments",
},
{
fn: "eq",
args: []float64{},
shouldError: true,
expectedError: "incorrect number of arguments",
},
{
fn: "ne",
args: []float64{},
shouldError: true,
expectedError: "incorrect number of arguments",
},
{
fn: "gte",
args: []float64{},
shouldError: true,
expectedError: "incorrect number of arguments",
},
{
fn: "lte",
args: []float64{},
shouldError: true,
expectedError: "incorrect number of arguments",
},
{
fn: "within_range",
args: []float64{0},
@@ -72,6 +126,18 @@ func TestNewThresholdCommand(t *testing.T) {
shouldError: true,
expectedError: "incorrect number of arguments",
},
{
fn: "within_range_included",
args: []float64{0},
shouldError: true,
expectedError: "incorrect number of arguments",
},
{
fn: "outside_range_included",
args: []float64{0},
shouldError: true,
expectedError: "incorrect number of arguments",
},
}
for _, tc := range cases {
@@ -249,6 +315,22 @@ func TestIsSupportedThresholdFunc(t *testing.T) {
function: ThresholdIsBelow,
supported: true,
},
{
function: ThresholdIsEqual,
supported: true,
},
{
function: ThresholdIsNotEqual,
supported: true,
},
{
function: ThresholdIsGreaterThanEqual,
supported: true,
},
{
function: ThresholdIsLessThanEqual,
supported: true,
},
{
function: ThresholdIsWithinRange,
supported: true,
@@ -257,6 +339,14 @@ func TestIsSupportedThresholdFunc(t *testing.T) {
function: ThresholdIsOutsideRange,
supported: true,
},
{
function: ThresholdIsWithinRangeIncluded,
supported: true,
},
{
function: ThresholdIsOutsideRangeIncluded,
supported: true,
},
{
function: "foo",
supported: false,