diff --git a/pkg/services/alerting/conditions_test.go b/pkg/services/alerting/conditions_test.go new file mode 100644 index 00000000000..90645270881 --- /dev/null +++ b/pkg/services/alerting/conditions_test.go @@ -0,0 +1,40 @@ +package alerting + +import ( + "testing" + + "github.com/grafana/grafana/pkg/components/simplejson" + . "github.com/smartystreets/goconvey/convey" +) + +func TestQueryCondition(t *testing.T) { + + Convey("when evaluating query condition", t, func() { + + Convey("Given avg() and > 100", func() { + + jsonModel, err := simplejson.NewJson([]byte(`{ + "type": "query", + "query": { + "params": ["A", "5m", "now"], + "datasourceId": 1, + "model": {"target": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"} + }, + "reducer": {"type": "avg", "params": []}, + "evaluator": {"type": ">", "params": [100]} + }`)) + So(err, ShouldBeNil) + + condition, err := NewQueryCondition(jsonModel) + So(err, ShouldBeNil) + + Convey("Should set result to triggered when avg is above 100", func() { + context := &AlertResultContext{} + condition.Eval(context) + + So(context.Triggered, ShouldBeTrue) + }) + }) + + }) +} diff --git a/pkg/services/alerting/engine.go b/pkg/services/alerting/engine.go index b6c31e132e4..799f6cb2b58 100644 --- a/pkg/services/alerting/engine.go +++ b/pkg/services/alerting/engine.go @@ -14,7 +14,7 @@ type Engine struct { clock clock.Clock ticker *Ticker scheduler Scheduler - handler AlertingHandler + handler AlertHandler ruleReader RuleReader log log.Logger responseHandler ResultHandler diff --git a/pkg/services/alerting/extractor.go b/pkg/services/alerting/extractor.go index 7783db75f06..69c7c05c003 100644 --- a/pkg/services/alerting/extractor.go +++ b/pkg/services/alerting/extractor.go @@ -90,6 +90,7 @@ func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) { Handler: jsonAlert.Get("handler").MustInt64(), Enabled: jsonAlert.Get("enabled").MustBool(), Description: jsonAlert.Get("description").MustString(), + Severity: jsonAlert.Get("severity").MustString(), Frequency: getTimeDurationStringToSeconds(jsonAlert.Get("frequency").MustString()), } diff --git a/pkg/services/alerting/handler.go b/pkg/services/alerting/handler.go index 48bb9e0851d..75a584fda81 100644 --- a/pkg/services/alerting/handler.go +++ b/pkg/services/alerting/handler.go @@ -31,6 +31,16 @@ func (e *HandlerImpl) eval(rule *AlertRule) *AlertResultContext { for _, condition := range rule.Conditions { condition.Eval(result) + + // break if condition could not be evaluated + if result.Error != nil { + break + } + + // break if result has not triggered yet + if result.Triggered == false { + break + } } result.EndTime = time.Now() diff --git a/pkg/services/alerting/handler_test.go b/pkg/services/alerting/handler_test.go index b9196e461af..68389891431 100644 --- a/pkg/services/alerting/handler_test.go +++ b/pkg/services/alerting/handler_test.go @@ -3,171 +3,162 @@ package alerting import ( "testing" - "github.com/grafana/grafana/pkg/components/simplejson" - "github.com/grafana/grafana/pkg/models" . "github.com/smartystreets/goconvey/convey" ) +type conditionStub struct { + triggered bool +} + +func (c *conditionStub) Eval(context *AlertResultContext) { + context.Triggered = c.triggered +} + func TestAlertingExecutor(t *testing.T) { Convey("Test alert execution", t, func() { handler := NewHandler() - Convey("single time serie", func() { - Convey("Show return ok since avg is above 2", func() { - json := ` - { - "name": "name2", - "description": "desc2", - "handler": 0, - "enabled": true, - "frequency": "60s", - "conditions": [ - { - "type": "query", - "query": { - "params": ["A", "5m", "now"], - "datasourceId": 1, - "model": {"target": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"} - }, - "reducer": {"type": "avg", "params": []}, - "evaluator": {"type": ">", "params": [100]} - } - ] + Convey("Show return triggered with single passing condition", func() { + rule := &AlertRule{ + Conditions: []AlertCondition{&conditionStub{ + triggered: true, + }}, } - ` - alertJSON, jsonErr := simplejson.NewJson([]byte(json)) - So(jsonErr, ShouldBeNil) - - alert := &models.Alert{Settings: alertJSON} - rule, _ := NewAlertRuleFromDBModel(alert) - - // timeSeries := []*tsdb.TimeSeries{ - // tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}), - // } - - result := handler.eval(rule) - So(result.Triggered, ShouldEqual, true) - }) - - // Convey("Show return critical since below 2", func() { - // rule := &AlertRule{ - // Critical: Level{Value: 10, Operator: "<"}, - // Transformer: transformers.NewAggregationTransformer("avg"), - // } - // - // timeSeries := []*tsdb.TimeSeries{ - // tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}), - // } - // - // result := executor.evaluateRule(rule, timeSeries) - // So(result.State, ShouldEqual, alertstates.Critical) - // }) - // - // Convey("Show return critical since sum is above 10", func() { - // rule := &AlertRule{ - // Critical: Level{Value: 10, Operator: ">"}, - // Transformer: transformers.NewAggregationTransformer("sum"), - // } - // - // timeSeries := []*tsdb.TimeSeries{ - // tsdb.NewTimeSeries("test1", [][2]float64{{9, 0}, {9, 0}}), - // } - // - // result := executor.evaluateRule(rule, timeSeries) - // So(result.State, ShouldEqual, alertstates.Critical) - // }) - // - // Convey("Show return ok since avg is below 10", func() { - // rule := &AlertRule{ - // Critical: Level{Value: 10, Operator: ">"}, - // Transformer: transformers.NewAggregationTransformer("avg"), - // } - // - // timeSeries := []*tsdb.TimeSeries{ - // tsdb.NewTimeSeries("test1", [][2]float64{{9, 0}, {9, 0}}), - // } - // - // result := executor.evaluateRule(rule, timeSeries) - // So(result.State, ShouldEqual, alertstates.Ok) - // }) - // - // Convey("Show return ok since min is below 10", func() { - // rule := &AlertRule{ - // Critical: Level{Value: 10, Operator: ">"}, - // Transformer: transformers.NewAggregationTransformer("avg"), - // } - // - // timeSeries := []*tsdb.TimeSeries{ - // tsdb.NewTimeSeries("test1", [][2]float64{{11, 0}, {9, 0}}), - // } - // - // result := executor.evaluateRule(rule, timeSeries) - // So(result.State, ShouldEqual, alertstates.Ok) - // }) - // - // Convey("Show return ok since max is above 10", func() { - // rule := &AlertRule{ - // Critical: Level{Value: 10, Operator: ">"}, - // Transformer: transformers.NewAggregationTransformer("max"), - // } - // - // timeSeries := []*tsdb.TimeSeries{ - // tsdb.NewTimeSeries("test1", [][2]float64{{6, 0}, {11, 0}}), - // } - // - // result := executor.evaluateRule(rule, timeSeries) - // So(result.State, ShouldEqual, alertstates.Critical) - // }) - // - // }) - // - // Convey("muliple time series", func() { - // Convey("both are ok", func() { - // rule := &AlertRule{ - // Critical: Level{Value: 10, Operator: ">"}, - // Transformer: transformers.NewAggregationTransformer("avg"), - // } - // - // timeSeries := []*tsdb.TimeSeries{ - // tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}), - // tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}), - // } - // - // result := executor.evaluateRule(rule, timeSeries) - // So(result.State, ShouldEqual, alertstates.Ok) - // }) - // - // Convey("first serie is good, second is critical", func() { - // rule := &AlertRule{ - // Critical: Level{Value: 10, Operator: ">"}, - // Transformer: transformers.NewAggregationTransformer("avg"), - // } - // - // timeSeries := []*tsdb.TimeSeries{ - // tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}), - // tsdb.NewTimeSeries("test1", [][2]float64{{11, 0}}), - // } - // - // result := executor.evaluateRule(rule, timeSeries) - // So(result.State, ShouldEqual, alertstates.Critical) - // }) - // - // Convey("first serie is warn, second is critical", func() { - // rule := &AlertRule{ - // Critical: Level{Value: 10, Operator: ">"}, - // Warning: Level{Value: 5, Operator: ">"}, - // Transformer: transformers.NewAggregationTransformer("avg"), - // } - // - // timeSeries := []*tsdb.TimeSeries{ - // tsdb.NewTimeSeries("test1", [][2]float64{{6, 0}}), - // tsdb.NewTimeSeries("test1", [][2]float64{{11, 0}}), - // } - // - // result := executor.evaluateRule(rule, timeSeries) - // So(result.State, ShouldEqual, alertstates.Critical) - // }) + result := handler.eval(rule) + So(result.Triggered, ShouldEqual, true) }) + + Convey("Show return false with not passing condition", func() { + rule := &AlertRule{ + Conditions: []AlertCondition{ + &conditionStub{triggered: true}, + &conditionStub{triggered: false}, + }, + } + + result := handler.eval(rule) + So(result.Triggered, ShouldEqual, false) + }) + + // Convey("Show return critical since below 2", func() { + // rule := &AlertRule{ + // Critical: Level{Value: 10, Operator: "<"}, + // Transformer: transformers.NewAggregationTransformer("avg"), + // } + // + // timeSeries := []*tsdb.TimeSeries{ + // tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}), + // } + // + // result := executor.evaluateRule(rule, timeSeries) + // So(result.State, ShouldEqual, alertstates.Critical) + // }) + // + // Convey("Show return critical since sum is above 10", func() { + // rule := &AlertRule{ + // Critical: Level{Value: 10, Operator: ">"}, + // Transformer: transformers.NewAggregationTransformer("sum"), + // } + // + // timeSeries := []*tsdb.TimeSeries{ + // tsdb.NewTimeSeries("test1", [][2]float64{{9, 0}, {9, 0}}), + // } + // + // result := executor.evaluateRule(rule, timeSeries) + // So(result.State, ShouldEqual, alertstates.Critical) + // }) + // + // Convey("Show return ok since avg is below 10", func() { + // rule := &AlertRule{ + // Critical: Level{Value: 10, Operator: ">"}, + // Transformer: transformers.NewAggregationTransformer("avg"), + // } + // + // timeSeries := []*tsdb.TimeSeries{ + // tsdb.NewTimeSeries("test1", [][2]float64{{9, 0}, {9, 0}}), + // } + // + // result := executor.evaluateRule(rule, timeSeries) + // So(result.State, ShouldEqual, alertstates.Ok) + // }) + // + // Convey("Show return ok since min is below 10", func() { + // rule := &AlertRule{ + // Critical: Level{Value: 10, Operator: ">"}, + // Transformer: transformers.NewAggregationTransformer("avg"), + // } + // + // timeSeries := []*tsdb.TimeSeries{ + // tsdb.NewTimeSeries("test1", [][2]float64{{11, 0}, {9, 0}}), + // } + // + // result := executor.evaluateRule(rule, timeSeries) + // So(result.State, ShouldEqual, alertstates.Ok) + // }) + // + // Convey("Show return ok since max is above 10", func() { + // rule := &AlertRule{ + // Critical: Level{Value: 10, Operator: ">"}, + // Transformer: transformers.NewAggregationTransformer("max"), + // } + // + // timeSeries := []*tsdb.TimeSeries{ + // tsdb.NewTimeSeries("test1", [][2]float64{{6, 0}, {11, 0}}), + // } + // + // result := executor.evaluateRule(rule, timeSeries) + // So(result.State, ShouldEqual, alertstates.Critical) + // }) + // + // }) + // + // Convey("muliple time series", func() { + // Convey("both are ok", func() { + // rule := &AlertRule{ + // Critical: Level{Value: 10, Operator: ">"}, + // Transformer: transformers.NewAggregationTransformer("avg"), + // } + // + // timeSeries := []*tsdb.TimeSeries{ + // tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}), + // tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}), + // } + // + // result := executor.evaluateRule(rule, timeSeries) + // So(result.State, ShouldEqual, alertstates.Ok) + // }) + // + // Convey("first serie is good, second is critical", func() { + // rule := &AlertRule{ + // Critical: Level{Value: 10, Operator: ">"}, + // Transformer: transformers.NewAggregationTransformer("avg"), + // } + // + // timeSeries := []*tsdb.TimeSeries{ + // tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}), + // tsdb.NewTimeSeries("test1", [][2]float64{{11, 0}}), + // } + // + // result := executor.evaluateRule(rule, timeSeries) + // So(result.State, ShouldEqual, alertstates.Critical) + // }) + // + // Convey("first serie is warn, second is critical", func() { + // rule := &AlertRule{ + // Critical: Level{Value: 10, Operator: ">"}, + // Warning: Level{Value: 5, Operator: ">"}, + // Transformer: transformers.NewAggregationTransformer("avg"), + // } + // + // timeSeries := []*tsdb.TimeSeries{ + // tsdb.NewTimeSeries("test1", [][2]float64{{6, 0}}), + // tsdb.NewTimeSeries("test1", [][2]float64{{11, 0}}), + // } + // + // result := executor.evaluateRule(rule, timeSeries) + // So(result.State, ShouldEqual, alertstates.Critical) + // }) + // }) }) } diff --git a/pkg/services/alerting/interfaces.go b/pkg/services/alerting/interfaces.go index 1a6d210fcf2..9fd374675e8 100644 --- a/pkg/services/alerting/interfaces.go +++ b/pkg/services/alerting/interfaces.go @@ -2,7 +2,7 @@ package alerting import "time" -type AlertingHandler interface { +type AlertHandler interface { Execute(rule *AlertRule, resultChan chan *AlertResultContext) }