Merge remote-tracking branch 'origin/master' into getting-started-panel-css2
This commit is contained in:
@@ -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("and")
|
||||
condition.Operator = operator
|
||||
|
||||
return &condition, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ type EvalContext struct {
|
||||
EvalMatches []*EvalMatch
|
||||
Logs []*ResultLogEntry
|
||||
Error error
|
||||
Description string
|
||||
ConditionEvals string
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
Rule *Rule
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
@@ -21,7 +23,10 @@ func NewEvalHandler() *DefaultEvalHandler {
|
||||
|
||||
func (e *DefaultEvalHandler) Eval(context *EvalContext) {
|
||||
firing := true
|
||||
for _, condition := range context.Rule.Conditions {
|
||||
conditionEvals := ""
|
||||
|
||||
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
|
||||
@@ -32,15 +37,23 @@ 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
|
||||
}
|
||||
|
||||
if i > 0 {
|
||||
conditionEvals = "[" + conditionEvals + " " + strings.ToUpper(cr.Operator) + " " + strconv.FormatBool(cr.Firing) + "]"
|
||||
} else {
|
||||
conditionEvals = strconv.FormatBool(firing)
|
||||
}
|
||||
|
||||
context.EvalMatches = append(context.EvalMatches, cr.EvalMatches...)
|
||||
}
|
||||
|
||||
context.ConditionEvals = conditionEvals + " = " + strconv.FormatBool(firing)
|
||||
context.Firing = firing
|
||||
context.EndTime = time.Now()
|
||||
elapsedTime := context.EndTime.Sub(context.StartTime) / time.Millisecond
|
||||
|
||||
@@ -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) {
|
||||
@@ -29,18 +30,102 @@ func TestAlertingExecutor(t *testing.T) {
|
||||
|
||||
handler.Eval(context)
|
||||
So(context.Firing, ShouldEqual, true)
|
||||
So(context.ConditionEvals, ShouldEqual, "true = true")
|
||||
})
|
||||
|
||||
Convey("Show return false with not passing asdf", func() {
|
||||
context := NewEvalContext(context.TODO(), &Rule{
|
||||
Conditions: []Condition{
|
||||
&conditionStub{firing: true, matches: []*EvalMatch{&EvalMatch{}, &EvalMatch{}}},
|
||||
&conditionStub{firing: false},
|
||||
&conditionStub{firing: true, operator: "and", matches: []*EvalMatch{&EvalMatch{}, &EvalMatch{}}},
|
||||
&conditionStub{firing: false, operator: "and"},
|
||||
},
|
||||
})
|
||||
|
||||
handler.Eval(context)
|
||||
So(context.Firing, ShouldEqual, false)
|
||||
So(context.ConditionEvals, ShouldEqual, "[true AND false] = 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)
|
||||
So(context.ConditionEvals, ShouldEqual, "[true OR false] = 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)
|
||||
So(context.ConditionEvals, ShouldEqual, "[true AND false] = 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)
|
||||
So(context.ConditionEvals, ShouldEqual, "[[true AND true] OR false] = 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)
|
||||
So(context.ConditionEvals, ShouldEqual, "[[true AND false] OR false] = 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)
|
||||
So(context.ConditionEvals, ShouldEqual, "[[true AND false] AND true] = 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)
|
||||
So(context.ConditionEvals, ShouldEqual, "[[true OR false] OR true] = true")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ type Notifier interface {
|
||||
type ConditionResult struct {
|
||||
Firing bool
|
||||
NoDataFound bool
|
||||
Operator string
|
||||
EvalMatches []*EvalMatch
|
||||
}
|
||||
|
||||
|
||||
@@ -26,11 +26,32 @@ type Rule struct {
|
||||
}
|
||||
|
||||
type ValidationError struct {
|
||||
Reason string
|
||||
Reason string
|
||||
Err error
|
||||
Alertid int64
|
||||
DashboardId int64
|
||||
PanelId int64
|
||||
}
|
||||
|
||||
func (e ValidationError) Error() string {
|
||||
return e.Reason
|
||||
extraInfo := ""
|
||||
if e.Alertid != 0 {
|
||||
extraInfo = fmt.Sprintf("%s AlertId: %v", extraInfo, e.Alertid)
|
||||
}
|
||||
|
||||
if e.PanelId != 0 {
|
||||
extraInfo = fmt.Sprintf("%s PanelId: %v ", extraInfo, e.PanelId)
|
||||
}
|
||||
|
||||
if e.DashboardId != 0 {
|
||||
extraInfo = fmt.Sprintf("%s DashboardId: %v", extraInfo, e.DashboardId)
|
||||
}
|
||||
|
||||
if e.Err != nil {
|
||||
return fmt.Sprintf("%s %s%s", e.Err.Error(), e.Reason, extraInfo)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Failed to extract alert.Reason: %s %s", e.Reason, extraInfo)
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -83,7 +104,7 @@ func NewRuleFromDBAlert(ruleDef *m.Alert) (*Rule, error) {
|
||||
for _, v := range ruleDef.Settings.Get("notifications").MustArray() {
|
||||
jsonModel := simplejson.NewFromAny(v)
|
||||
if id, err := jsonModel.Get("id").Int64(); err != nil {
|
||||
return nil, ValidationError{Reason: "Invalid notification schema"}
|
||||
return nil, ValidationError{Reason: "Invalid notification schema", DashboardId: model.DashboardId, Alertid: model.Id, PanelId: model.PanelId}
|
||||
} else {
|
||||
model.Notifications = append(model.Notifications, id)
|
||||
}
|
||||
@@ -93,10 +114,10 @@ func NewRuleFromDBAlert(ruleDef *m.Alert) (*Rule, error) {
|
||||
conditionModel := simplejson.NewFromAny(condition)
|
||||
conditionType := conditionModel.Get("type").MustString()
|
||||
if factory, exist := conditionFactories[conditionType]; !exist {
|
||||
return nil, ValidationError{Reason: "Unknown alert condition: " + conditionType}
|
||||
return nil, ValidationError{Reason: "Unknown alert condition: " + conditionType, DashboardId: model.DashboardId, Alertid: model.Id, PanelId: model.PanelId}
|
||||
} else {
|
||||
if queryCondition, err := factory(conditionModel, index); err != nil {
|
||||
return nil, err
|
||||
return nil, ValidationError{Err: err, DashboardId: model.DashboardId, Alertid: model.Id, PanelId: model.PanelId}
|
||||
} else {
|
||||
model.Conditions = append(model.Conditions, queryCondition)
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@ func (s *SchedulerImpl) Update(rules []*Rule) {
|
||||
|
||||
offset := ((rule.Frequency * 1000) / int64(len(rules))) * int64(i)
|
||||
job.Offset = int64(math.Floor(float64(offset) / 1000))
|
||||
if job.Offset == 0 { //zero offset causes division with 0 panics.
|
||||
job.Offset = 1
|
||||
}
|
||||
jobs[rule.Id] = job
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,11 @@ func GetSystemStats(query *m.GetSystemStatsQuery) error {
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM ` + dialect.Quote("playlist") + `
|
||||
) AS playlist_count
|
||||
) AS playlist_count,
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM ` + dialect.Quote("alert") + `
|
||||
) AS alert_count
|
||||
`
|
||||
|
||||
var stats m.SystemStats
|
||||
@@ -85,7 +89,11 @@ func GetAdminStats(query *m.GetAdminStatsQuery) error {
|
||||
(
|
||||
SELECT COUNT(DISTINCT ` + dialect.Quote("dashboard_id") + ` )
|
||||
FROM ` + dialect.Quote("star") + `
|
||||
) AS starred_db_count
|
||||
) AS starred_db_count,
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM ` + dialect.Quote("alert") + `
|
||||
) AS alert_count
|
||||
`
|
||||
|
||||
var stats m.AdminStats
|
||||
|
||||
Reference in New Issue
Block a user