From b2db2b26dd88ceae51f986782323265858ac5231 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Sat, 12 Nov 2016 13:16:46 -0800 Subject: [PATCH 1/7] Added OR to alert_tab --- public/app/features/alerting/alert_def.ts | 6 ++++++ public/app/features/alerting/alert_tab_ctrl.ts | 4 ++++ public/app/features/alerting/partials/alert_tab.html | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/public/app/features/alerting/alert_def.ts b/public/app/features/alerting/alert_def.ts index 8ee1981f3d8..52089560296 100644 --- a/public/app/features/alerting/alert_def.ts +++ b/public/app/features/alerting/alert_def.ts @@ -28,6 +28,11 @@ var evalFunctions = [ {text: 'HAS NO VALUE' , value: 'no_value'} ]; +var evalOperators = [ + {text: 'OR', value: 'or'}, + {text: 'AND', value: 'and'}, +]; + var reducerTypes = [ {text: 'avg()', value: 'avg'}, {text: 'min()', value: 'min'}, @@ -116,6 +121,7 @@ export default { getStateDisplayModel: getStateDisplayModel, conditionTypes: conditionTypes, evalFunctions: evalFunctions, + evalOperators: evalOperators, noDataModes: noDataModes, executionErrorModes: executionErrorModes, reducerTypes: reducerTypes, diff --git a/public/app/features/alerting/alert_tab_ctrl.ts b/public/app/features/alerting/alert_tab_ctrl.ts index c882a6ac3bf..6757cfc0d91 100644 --- a/public/app/features/alerting/alert_tab_ctrl.ts +++ b/public/app/features/alerting/alert_tab_ctrl.ts @@ -18,6 +18,7 @@ export class AlertTabCtrl { alert: any; conditionModels: any; evalFunctions: any; + evalOperators: any; noDataModes: any; executionErrorModes: any; addNotificationSegment; @@ -41,6 +42,7 @@ export class AlertTabCtrl { this.$scope.ctrl = this; this.subTabIndex = 0; this.evalFunctions = alertDef.evalFunctions; + this.evalOperators = alertDef.evalOperators; this.conditionTypes = alertDef.conditionTypes; this.noDataModes = alertDef.noDataModes; this.executionErrorModes = alertDef.executionErrorModes; @@ -194,6 +196,7 @@ export class AlertTabCtrl { query: {params: ['A', '5m', 'now']}, reducer: {type: 'avg', params: []}, evaluator: {type: 'gt', params: [null]}, + operator: {type: 'and'}, }; } @@ -250,6 +253,7 @@ export class AlertTabCtrl { cm.queryPart = new QueryPart(source.query, alertDef.alertQueryDef); cm.reducerPart = alertDef.createReducerPart(source.reducer); cm.evaluator = source.evaluator; + cm.operator = source.operator; return cm; } diff --git a/public/app/features/alerting/partials/alert_tab.html b/public/app/features/alerting/partials/alert_tab.html index 5b85ec105ae..2bc687aee19 100644 --- a/public/app/features/alerting/partials/alert_tab.html +++ b/public/app/features/alerting/partials/alert_tab.html @@ -38,7 +38,7 @@
Conditions
- AND + WHEN
From dfb1b1918c293c052ee286eefc020dc148172e73 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Tue, 15 Nov 2016 06:35:25 -0800 Subject: [PATCH 2/7] Implemented operator based firiing in backend --- pkg/services/alerting/conditions/query.go | 8 +++++++- pkg/services/alerting/eval_handler.go | 9 +++++---- pkg/services/alerting/interfaces.go | 1 + 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/pkg/services/alerting/conditions/query.go b/pkg/services/alerting/conditions/query.go index b73db9d590e..394c1557490 100644 --- a/pkg/services/alerting/conditions/query.go +++ b/pkg/services/alerting/conditions/query.go @@ -23,6 +23,7 @@ type QueryCondition struct { Query AlertQuery Reducer QueryReducer Evaluator AlertEvaluator + Operator string HandleRequest tsdb.HandleRequestFunc } @@ -72,6 +73,7 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) (*alerting.Conditio return &alerting.ConditionResult{ Firing: evalMatchCount > 0, NoDataFound: emptySerieCount == len(seriesList), + Operator: c.Operator, EvalMatches: matches, }, nil } @@ -168,8 +170,12 @@ func NewQueryCondition(model *simplejson.Json, index int) (*QueryCondition, erro if err != nil { return nil, err } - condition.Evaluator = evaluator + + operatorJson := model.Get("operator") + operator := operatorJson.Get("type").MustString() + condition.Operator = operator + return &condition, nil } diff --git a/pkg/services/alerting/eval_handler.go b/pkg/services/alerting/eval_handler.go index 538c639abb8..7079eac5f38 100644 --- a/pkg/services/alerting/eval_handler.go +++ b/pkg/services/alerting/eval_handler.go @@ -32,10 +32,11 @@ func (e *DefaultEvalHandler) Eval(context *EvalContext) { break } - // break if result has not triggered yet - if cr.Firing == false { - firing = false - break + // calculating Firing based on operator + if cr.Operator == "or" { + firing = firing || cr.Firing + } else { + firing = firing && cr.Firing } context.EvalMatches = append(context.EvalMatches, cr.EvalMatches...) diff --git a/pkg/services/alerting/interfaces.go b/pkg/services/alerting/interfaces.go index cc2561473e3..566fbdb2898 100644 --- a/pkg/services/alerting/interfaces.go +++ b/pkg/services/alerting/interfaces.go @@ -24,6 +24,7 @@ type Notifier interface { type ConditionResult struct { Firing bool NoDataFound bool + Operator string EvalMatches []*EvalMatch } From 8d0bcd23f872429b0c2f0e841ee4e47ee56218b2 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Tue, 15 Nov 2016 07:54:09 -0800 Subject: [PATCH 3/7] Added tests for checking nested operators --- pkg/services/alerting/eval_handler_test.go | 83 +++++++++++++++++++++- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/pkg/services/alerting/eval_handler_test.go b/pkg/services/alerting/eval_handler_test.go index 4c2ec24b506..4ef79504e78 100644 --- a/pkg/services/alerting/eval_handler_test.go +++ b/pkg/services/alerting/eval_handler_test.go @@ -8,12 +8,13 @@ import ( ) type conditionStub struct { - firing bool - matches []*EvalMatch + firing bool + operator string + matches []*EvalMatch } func (c *conditionStub) Eval(context *EvalContext) (*ConditionResult, error) { - return &ConditionResult{Firing: c.firing, EvalMatches: c.matches}, nil + return &ConditionResult{Firing: c.firing, EvalMatches: c.matches, Operator: c.operator}, nil } func TestAlertingExecutor(t *testing.T) { @@ -42,5 +43,81 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, false) }) + + Convey("Show return true if any of the condition is passing with OR operator", func() { + context := NewEvalContext(context.TODO(), &Rule{ + Conditions: []Condition{ + &conditionStub{firing: true, operator: "and"}, + &conditionStub{firing: false, operator: "or"}, + }, + }) + + handler.Eval(context) + So(context.Firing, ShouldEqual, true) + }) + + Convey("Show return false if any of the condition is failing with AND operator", func() { + context := NewEvalContext(context.TODO(), &Rule{ + Conditions: []Condition{ + &conditionStub{firing: true, operator: "and"}, + &conditionStub{firing: false, operator: "and"}, + }, + }) + + handler.Eval(context) + So(context.Firing, ShouldEqual, false) + }) + + Convey("Show return true if one condition is failing with nested OR operator", func() { + context := NewEvalContext(context.TODO(), &Rule{ + Conditions: []Condition{ + &conditionStub{firing: true, operator: "and"}, + &conditionStub{firing: true, operator: "and"}, + &conditionStub{firing: false, operator: "or"}, + }, + }) + + handler.Eval(context) + So(context.Firing, ShouldEqual, true) + }) + + Convey("Show return false if one condition is passing with nested OR operator", func() { + context := NewEvalContext(context.TODO(), &Rule{ + Conditions: []Condition{ + &conditionStub{firing: true, operator: "and"}, + &conditionStub{firing: false, operator: "and"}, + &conditionStub{firing: false, operator: "or"}, + }, + }) + + handler.Eval(context) + So(context.Firing, ShouldEqual, false) + }) + + Convey("Show return false if a condition is failing with nested AND operator", func() { + context := NewEvalContext(context.TODO(), &Rule{ + Conditions: []Condition{ + &conditionStub{firing: true, operator: "and"}, + &conditionStub{firing: false, operator: "and"}, + &conditionStub{firing: true, operator: "and"}, + }, + }) + + handler.Eval(context) + So(context.Firing, ShouldEqual, false) + }) + + Convey("Show return true if a condition is passing with nested OR operator", func() { + context := NewEvalContext(context.TODO(), &Rule{ + Conditions: []Condition{ + &conditionStub{firing: true, operator: "and"}, + &conditionStub{firing: false, operator: "or"}, + &conditionStub{firing: true, operator: "or"}, + }, + }) + + handler.Eval(context) + So(context.Firing, ShouldEqual, true) + }) }) } From f46c4c88caacf71149460225a3991afc9e2604e1 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Tue, 15 Nov 2016 22:43:09 -0800 Subject: [PATCH 4/7] Added OR condition in docs --- docs/sources/alerting/rules.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/sources/alerting/rules.md b/docs/sources/alerting/rules.md index ecab88f48d2..af3cfecfdb8 100644 --- a/docs/sources/alerting/rules.md +++ b/docs/sources/alerting/rules.md @@ -55,7 +55,10 @@ Currently the only condition type that exists is a `Query` condition that allows specify a query letter, time range and an aggregation function. The letter refers to a query you already have added in the **Metrics** tab. The result from the query and the aggregation function is a single value that is then used in the threshold check. The query used in an alert rule cannot -contain any template variables. Currently we only support `AND` operator between conditions. +contain any template variables. Currently we only support `AND` and `OR` operators between conditions and they are executed serially. +For example, we have 3 conditions in the following order: +`condition:A(evaluates to: TRUE) OR condition:B(evaluates to: FALSE) AND condition:C(evaluates to: TRUE)` +so the result will be calculated as ((TRUE OR FALSE) AND TRUE) = TRUE. We plan to add other condition types in the future, like `Other Alert`, where you can include the state of another alert in your conditions, and `Time Of Day`. From 690868c837ef93ab7d25f82446fcf02b75680cf0 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Thu, 17 Nov 2016 01:28:17 -0800 Subject: [PATCH 5/7] Added firingEvalution to Rule test --- pkg/api/alerting.go | 3 ++- pkg/api/dtos/alerting.go | 1 + pkg/services/alerting/eval_context.go | 1 + pkg/services/alerting/eval_handler.go | 14 +++++++++++++- 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index ab9f4b7c80b..5ba5ea277bf 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -119,7 +119,8 @@ func AlertTest(c *middleware.Context, dto dtos.AlertTestCommand) Response { res := backendCmd.Result dtoRes := &dtos.AlertTestResult{ - Firing: res.Firing, + Firing: res.Firing, + FiringEval: res.FiringEval, } if res.Error != nil { diff --git a/pkg/api/dtos/alerting.go b/pkg/api/dtos/alerting.go index bf4d7f4353e..fb3130ce636 100644 --- a/pkg/api/dtos/alerting.go +++ b/pkg/api/dtos/alerting.go @@ -36,6 +36,7 @@ type AlertTestCommand struct { type AlertTestResult struct { Firing bool `json:"firing"` + FiringEval string `json:"firingEvaluation"` TimeMs string `json:"timeMs"` Error string `json:"error,omitempty"` EvalMatches []*EvalMatch `json:"matches,omitempty"` diff --git a/pkg/services/alerting/eval_context.go b/pkg/services/alerting/eval_context.go index 711416970c5..a19312c0768 100644 --- a/pkg/services/alerting/eval_context.go +++ b/pkg/services/alerting/eval_context.go @@ -18,6 +18,7 @@ type EvalContext struct { Logs []*ResultLogEntry Error error Description string + FiringEval string StartTime time.Time EndTime time.Time Rule *Rule diff --git a/pkg/services/alerting/eval_handler.go b/pkg/services/alerting/eval_handler.go index 7079eac5f38..e0ccea24ade 100644 --- a/pkg/services/alerting/eval_handler.go +++ b/pkg/services/alerting/eval_handler.go @@ -1,6 +1,7 @@ package alerting import ( + "strconv" "time" "github.com/grafana/grafana/pkg/log" @@ -21,7 +22,9 @@ func NewEvalHandler() *DefaultEvalHandler { func (e *DefaultEvalHandler) Eval(context *EvalContext) { firing := true - for _, condition := range context.Rule.Conditions { + firingEval := "" + for i := 0; i < len(context.Rule.Conditions); i++ { + condition := context.Rule.Conditions[i] cr, err := condition.Eval(context) if err != nil { context.Error = err @@ -33,15 +36,24 @@ func (e *DefaultEvalHandler) Eval(context *EvalContext) { } // calculating Firing based on operator + operator := "AND" if cr.Operator == "or" { firing = firing || cr.Firing + operator = "OR" } else { firing = firing && cr.Firing } + if i > 0 { + firingEval = "[" + firingEval + " " + operator + " " + strconv.FormatBool(cr.Firing) + "]" + } else { + firingEval = strconv.FormatBool(firing) + } + context.EvalMatches = append(context.EvalMatches, cr.EvalMatches...) } + context.FiringEval = firingEval + " = " + strconv.FormatBool(firing) context.Firing = firing context.EndTime = time.Now() elapsedTime := context.EndTime.Sub(context.StartTime) / time.Millisecond From fc82dac86815cfc8a93e767f3750b4c3752d4329 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Thu, 17 Nov 2016 01:30:50 -0800 Subject: [PATCH 6/7] Added braces to single condition firingEvaluation string --- pkg/services/alerting/eval_handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/services/alerting/eval_handler.go b/pkg/services/alerting/eval_handler.go index e0ccea24ade..620f8c9a121 100644 --- a/pkg/services/alerting/eval_handler.go +++ b/pkg/services/alerting/eval_handler.go @@ -47,7 +47,7 @@ func (e *DefaultEvalHandler) Eval(context *EvalContext) { if i > 0 { firingEval = "[" + firingEval + " " + operator + " " + strconv.FormatBool(cr.Firing) + "]" } else { - firingEval = strconv.FormatBool(firing) + firingEval = "[" + strconv.FormatBool(firing) + "]" } context.EvalMatches = append(context.EvalMatches, cr.EvalMatches...) From aae33b36ddd9c92d5b4904a6593eedb0a16694d4 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Thu, 17 Nov 2016 01:41:23 -0800 Subject: [PATCH 7/7] Added tests for firingEvaluation string --- pkg/services/alerting/eval_handler.go | 2 +- pkg/services/alerting/eval_handler_test.go | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/services/alerting/eval_handler.go b/pkg/services/alerting/eval_handler.go index 620f8c9a121..e0ccea24ade 100644 --- a/pkg/services/alerting/eval_handler.go +++ b/pkg/services/alerting/eval_handler.go @@ -47,7 +47,7 @@ func (e *DefaultEvalHandler) Eval(context *EvalContext) { if i > 0 { firingEval = "[" + firingEval + " " + operator + " " + strconv.FormatBool(cr.Firing) + "]" } else { - firingEval = "[" + strconv.FormatBool(firing) + "]" + firingEval = strconv.FormatBool(firing) } context.EvalMatches = append(context.EvalMatches, cr.EvalMatches...) diff --git a/pkg/services/alerting/eval_handler_test.go b/pkg/services/alerting/eval_handler_test.go index 4ef79504e78..1bda0c7c609 100644 --- a/pkg/services/alerting/eval_handler_test.go +++ b/pkg/services/alerting/eval_handler_test.go @@ -30,6 +30,7 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, true) + So(context.FiringEval, ShouldEqual, "true = true") }) Convey("Show return false with not passing asdf", func() { @@ -42,6 +43,7 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, false) + So(context.FiringEval, ShouldEqual, "[true AND false] = false") }) Convey("Show return true if any of the condition is passing with OR operator", func() { @@ -54,6 +56,7 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, true) + So(context.FiringEval, ShouldEqual, "[true OR false] = true") }) Convey("Show return false if any of the condition is failing with AND operator", func() { @@ -66,6 +69,7 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, false) + So(context.FiringEval, ShouldEqual, "[true AND false] = false") }) Convey("Show return true if one condition is failing with nested OR operator", func() { @@ -79,6 +83,7 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, true) + So(context.FiringEval, ShouldEqual, "[[true AND true] OR false] = true") }) Convey("Show return false if one condition is passing with nested OR operator", func() { @@ -92,6 +97,7 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, false) + So(context.FiringEval, ShouldEqual, "[[true AND false] OR false] = false") }) Convey("Show return false if a condition is failing with nested AND operator", func() { @@ -105,6 +111,7 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, false) + So(context.FiringEval, ShouldEqual, "[[true AND false] AND true] = false") }) Convey("Show return true if a condition is passing with nested OR operator", func() { @@ -118,6 +125,7 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, true) + So(context.FiringEval, ShouldEqual, "[[true OR false] OR true] = true") }) }) }