Skip to content

Commit

Permalink
feat(alerting): extract logic state updates and notifications
Browse files Browse the repository at this point in the history
ref #6444
  • Loading branch information
bergquist committed Nov 7, 2016
1 parent b887911 commit f0b591b
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 23 deletions.
31 changes: 31 additions & 0 deletions pkg/services/alerting/eval_context.go
Expand Up @@ -26,6 +26,7 @@ type EvalContext struct {
ImagePublicUrl string
ImageOnDiskPath string
NoDataFound bool
PrevAlertState m.AlertStateType

Ctx context.Context
}
Expand Down Expand Up @@ -63,6 +64,36 @@ func (c *EvalContext) GetStateModel() *StateDescription {
}
}

func (c *EvalContext) ShouldUpdateAlertState() bool {
return c.Rule.State != c.PrevAlertState
}

func (c *EvalContext) ShouldSendNotification() bool {
if (c.PrevAlertState == m.AlertStatePending) && (c.Rule.State == m.AlertStateOK) {
return false
}

alertState := c.Rule.State

if c.NoDataFound {
if c.Rule.NoDataState == m.NoDataKeepState {
return false
}

alertState = c.Rule.NoDataState.ToAlertState()
}

if c.Error != nil {
if c.Rule.ExecutionErrorState == m.NoDataKeepState {
return false
}

alertState = c.Rule.ExecutionErrorState.ToAlertState()
}

return alertState != c.PrevAlertState
}

func (a *EvalContext) GetDurationMs() float64 {
return float64(a.EndTime.Nanosecond()-a.StartTime.Nanosecond()) / float64(1000000)
}
Expand Down
110 changes: 110 additions & 0 deletions pkg/services/alerting/eval_context_test.go
@@ -0,0 +1,110 @@
package alerting

import (
"context"
"fmt"
"testing"

"github.com/grafana/grafana/pkg/models"
. "github.com/smartystreets/goconvey/convey"
)

func TestAlertingEvalContext(t *testing.T) {
Convey("Eval context", t, func() {
ctx := NewEvalContext(context.TODO(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}})
err := fmt.Errorf("Dummie error!")

Convey("Should update alert state", func() {

Convey("ok -> alerting", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.State = models.AlertStateAlerting

So(ctx.ShouldUpdateAlertState(), ShouldBeTrue)
})

Convey("ok -> ok", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.State = models.AlertStateOK

So(ctx.ShouldUpdateAlertState(), ShouldBeFalse)
})
})

Convey("Should send notifications", func() {
Convey("pending -> ok", func() {
ctx.PrevAlertState = models.AlertStatePending
ctx.Rule.State = models.AlertStateOK

So(ctx.ShouldSendNotification(), ShouldBeFalse)
})

Convey("ok -> alerting", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.State = models.AlertStateAlerting

So(ctx.ShouldSendNotification(), ShouldBeTrue)
})

Convey("alerting -> ok", func() {
ctx.PrevAlertState = models.AlertStateAlerting
ctx.Rule.State = models.AlertStateOK

So(ctx.ShouldSendNotification(), ShouldBeTrue)
})

Convey("ok -> no_data(alerting)", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.NoDataState = models.NoDataSetAlerting
ctx.Rule.State = models.AlertStateAlerting

So(ctx.ShouldSendNotification(), ShouldBeTrue)
})

Convey("ok -> no_data(ok)", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.NoDataState = models.NoDataSetOK
ctx.NoDataFound = true
ctx.Rule.State = models.AlertStateNoData

So(ctx.ShouldSendNotification(), ShouldBeFalse)
})

Convey("ok -> no_data(keep_last)", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.NoDataState = models.NoDataKeepState
ctx.Rule.State = models.AlertStateNoData
ctx.NoDataFound = true

So(ctx.ShouldSendNotification(), ShouldBeFalse)
})

Convey("ok -> execution_error(alerting)", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.State = models.AlertStateExecError
ctx.Rule.ExecutionErrorState = models.NoDataSetAlerting
ctx.Error = err

So(ctx.ShouldSendNotification(), ShouldBeTrue)
})

Convey("ok -> execution_error(ok)", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.State = models.AlertStateExecError
ctx.Rule.ExecutionErrorState = models.NoDataSetOK
ctx.Error = err

So(ctx.ShouldSendNotification(), ShouldBeFalse)
})

Convey("ok -> execution_error(keep_last)", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.State = models.AlertStateExecError
ctx.Rule.ExecutionErrorState = models.NoDataKeepState
ctx.Error = err

So(ctx.ShouldSendNotification(), ShouldBeFalse)
})
})
})
}
19 changes: 7 additions & 12 deletions pkg/services/alerting/result_handler.go
Expand Up @@ -28,7 +28,7 @@ func NewResultHandler() *DefaultResultHandler {
}

func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error {
oldState := evalContext.Rule.State
evalContext.PrevAlertState = evalContext.Rule.State

executionError := ""
annotationData := simplejson.New()
Expand All @@ -51,8 +51,8 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error {
}

countStateResult(evalContext.Rule.State)
if handler.shouldUpdateAlertState(evalContext, oldState) {
handler.log.Info("New state change", "alertId", evalContext.Rule.Id, "newState", evalContext.Rule.State, "oldState", oldState)
if evalContext.ShouldUpdateAlertState() {
handler.log.Info("New state change", "alertId", evalContext.Rule.Id, "newState", evalContext.Rule.State, "prev state", evalContext.PrevAlertState)

cmd := &m.SetAlertStateCommand{
AlertId: evalContext.Rule.Id,
Expand All @@ -76,7 +76,7 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error {
Title: evalContext.Rule.Name,
Text: evalContext.GetStateModel().Text,
NewState: string(evalContext.Rule.State),
PrevState: string(oldState),
PrevState: string(evalContext.PrevAlertState),
Epoch: time.Now().Unix(),
Data: annotationData,
}
Expand All @@ -86,21 +86,16 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error {
handler.log.Error("Failed to save annotation for new alert state", "error", err)
}

if (oldState == m.AlertStatePending) && (evalContext.Rule.State == m.AlertStateOK) {
handler.log.Info("Notfication not sent", "oldState", oldState, "newState", evalContext.Rule.State)
} else {
if evalContext.ShouldSendNotification() {
handler.notifier.Notify(evalContext)
} else {
handler.log.Info("Notfication not sent", "prev state", evalContext.PrevAlertState, "new state", evalContext.Rule.State)
}

}

return nil
}

func (handler *DefaultResultHandler) shouldUpdateAlertState(evalContext *EvalContext, oldState m.AlertStateType) bool {
return evalContext.Rule.State != oldState
}

func countStateResult(state m.AlertStateType) {
switch state {
case m.AlertStatePending:
Expand Down
23 changes: 12 additions & 11 deletions pkg/services/alerting/rule.go
Expand Up @@ -11,17 +11,18 @@ import (
)

type Rule struct {
Id int64
OrgId int64
DashboardId int64
PanelId int64
Frequency int64
Name string
Message string
NoDataState m.NoDataOption
State m.AlertStateType
Conditions []Condition
Notifications []int64
Id int64
OrgId int64
DashboardId int64
PanelId int64
Frequency int64
Name string
Message string
NoDataState m.NoDataOption
ExecutionErrorState m.NoDataOption
State m.AlertStateType
Conditions []Condition
Notifications []int64
}

type ValidationError struct {
Expand Down

0 comments on commit f0b591b

Please sign in to comment.