2016-07-19 09:15:26 -05:00
|
|
|
package alerting
|
|
|
|
|
2016-07-19 10:45:37 -05:00
|
|
|
import (
|
|
|
|
"encoding/json"
|
2016-07-20 07:28:02 -05:00
|
|
|
"fmt"
|
2016-07-19 09:15:26 -05:00
|
|
|
|
2016-07-20 07:28:02 -05:00
|
|
|
"github.com/grafana/grafana/pkg/bus"
|
2016-07-19 10:45:37 -05:00
|
|
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
2016-07-20 07:28:02 -05:00
|
|
|
m "github.com/grafana/grafana/pkg/models"
|
|
|
|
"github.com/grafana/grafana/pkg/tsdb"
|
2016-07-19 10:45:37 -05:00
|
|
|
)
|
2016-07-19 09:15:26 -05:00
|
|
|
|
|
|
|
type QueryCondition struct {
|
2016-07-21 14:54:12 -05:00
|
|
|
Index int
|
2016-07-20 07:28:02 -05:00
|
|
|
Query AlertQuery
|
|
|
|
Reducer QueryReducer
|
|
|
|
Evaluator AlertEvaluator
|
|
|
|
HandleRequest tsdb.HandleRequestFunc
|
2016-07-19 09:15:26 -05:00
|
|
|
}
|
|
|
|
|
2016-07-19 15:36:59 -05:00
|
|
|
func (c *QueryCondition) Eval(context *AlertResultContext) {
|
2016-07-20 07:28:02 -05:00
|
|
|
seriesList, err := c.executeQuery(context)
|
|
|
|
if err != nil {
|
|
|
|
context.Error = err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, series := range seriesList {
|
|
|
|
reducedValue := c.Reducer.Reduce(series)
|
|
|
|
pass := c.Evaluator.Eval(series, reducedValue)
|
2016-07-21 14:54:12 -05:00
|
|
|
|
|
|
|
if context.IsTestRun {
|
|
|
|
context.Logs = append(context.Logs, &AlertResultLogEntry{
|
|
|
|
Message: fmt.Sprintf("Condition[%d]: Eval: %v, Metric: %s, Value: %1.3f", c.Index, pass, series.Name, reducedValue),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2016-07-20 07:28:02 -05:00
|
|
|
if pass {
|
2016-07-21 14:54:12 -05:00
|
|
|
context.Events = append(context.Events, &AlertEvent{
|
|
|
|
Metric: series.Name,
|
|
|
|
Value: reducedValue,
|
|
|
|
})
|
2016-07-22 06:14:09 -05:00
|
|
|
context.Firing = true
|
2016-07-20 07:28:02 -05:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *QueryCondition) executeQuery(context *AlertResultContext) (tsdb.TimeSeriesSlice, error) {
|
|
|
|
getDsInfo := &m.GetDataSourceByIdQuery{
|
|
|
|
Id: c.Query.DatasourceId,
|
|
|
|
OrgId: context.Rule.OrgId,
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := bus.Dispatch(getDsInfo); err != nil {
|
|
|
|
return nil, fmt.Errorf("Could not find datasource")
|
|
|
|
}
|
|
|
|
|
|
|
|
req := c.getRequestForAlertRule(getDsInfo.Result)
|
|
|
|
result := make(tsdb.TimeSeriesSlice, 0)
|
|
|
|
|
|
|
|
resp, err := c.HandleRequest(req)
|
|
|
|
if err != nil {
|
2016-07-21 06:09:12 -05:00
|
|
|
return nil, fmt.Errorf("tsdb.HandleRequest() error %v", err)
|
2016-07-20 07:28:02 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, v := range resp.Results {
|
|
|
|
if v.Error != nil {
|
2016-07-21 06:09:12 -05:00
|
|
|
return nil, fmt.Errorf("tsdb.HandleRequest() response error %v", v)
|
2016-07-20 07:28:02 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
result = append(result, v.Series...)
|
2016-07-21 06:09:12 -05:00
|
|
|
|
|
|
|
if context.IsTestRun {
|
|
|
|
context.Logs = append(context.Logs, &AlertResultLogEntry{
|
2016-07-21 14:54:12 -05:00
|
|
|
Message: fmt.Sprintf("Condition[%d]: Query Result", c.Index),
|
2016-07-21 06:09:12 -05:00
|
|
|
Data: v.Series,
|
|
|
|
})
|
|
|
|
}
|
2016-07-20 07:28:02 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *QueryCondition) getRequestForAlertRule(datasource *m.DataSource) *tsdb.Request {
|
|
|
|
req := &tsdb.Request{
|
|
|
|
TimeRange: tsdb.TimeRange{
|
|
|
|
From: c.Query.From,
|
|
|
|
To: c.Query.To,
|
|
|
|
},
|
|
|
|
Queries: []*tsdb.Query{
|
|
|
|
{
|
|
|
|
RefId: "A",
|
|
|
|
Query: c.Query.Model.Get("target").MustString(),
|
|
|
|
DataSource: &tsdb.DataSourceInfo{
|
|
|
|
Id: datasource.Id,
|
|
|
|
Name: datasource.Name,
|
|
|
|
PluginId: datasource.Type,
|
|
|
|
Url: datasource.Url,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
return req
|
2016-07-19 09:15:26 -05:00
|
|
|
}
|
|
|
|
|
2016-07-21 14:54:12 -05:00
|
|
|
func NewQueryCondition(model *simplejson.Json, index int) (*QueryCondition, error) {
|
2016-07-19 10:45:37 -05:00
|
|
|
condition := QueryCondition{}
|
2016-07-21 14:54:12 -05:00
|
|
|
condition.Index = index
|
2016-07-20 07:28:02 -05:00
|
|
|
condition.HandleRequest = tsdb.HandleRequest
|
2016-07-19 10:45:37 -05:00
|
|
|
|
|
|
|
queryJson := model.Get("query")
|
|
|
|
|
2016-07-20 07:28:02 -05:00
|
|
|
condition.Query.Model = queryJson.Get("model")
|
2016-07-19 10:45:37 -05:00
|
|
|
condition.Query.From = queryJson.Get("params").MustArray()[1].(string)
|
|
|
|
condition.Query.To = queryJson.Get("params").MustArray()[2].(string)
|
|
|
|
condition.Query.DatasourceId = queryJson.Get("datasourceId").MustInt64()
|
|
|
|
|
|
|
|
reducerJson := model.Get("reducer")
|
|
|
|
condition.Reducer = NewSimpleReducer(reducerJson.Get("type").MustString())
|
|
|
|
|
|
|
|
evaluatorJson := model.Get("evaluator")
|
|
|
|
evaluator, err := NewDefaultAlertEvaluator(evaluatorJson)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
condition.Evaluator = evaluator
|
|
|
|
return &condition, nil
|
2016-07-19 09:15:26 -05:00
|
|
|
}
|
|
|
|
|
2016-07-19 10:45:37 -05:00
|
|
|
type SimpleReducer struct {
|
|
|
|
Type string
|
2016-07-19 09:15:26 -05:00
|
|
|
}
|
|
|
|
|
2016-07-20 07:28:02 -05:00
|
|
|
func (s *SimpleReducer) Reduce(series *tsdb.TimeSeries) float64 {
|
|
|
|
var value float64 = 0
|
|
|
|
|
|
|
|
switch s.Type {
|
|
|
|
case "avg":
|
|
|
|
for _, point := range series.Points {
|
|
|
|
value += point[0]
|
|
|
|
}
|
|
|
|
value = value / float64(len(series.Points))
|
|
|
|
}
|
|
|
|
|
|
|
|
return value
|
2016-07-19 10:45:37 -05:00
|
|
|
}
|
2016-07-19 09:15:26 -05:00
|
|
|
|
2016-07-19 10:45:37 -05:00
|
|
|
func NewSimpleReducer(typ string) *SimpleReducer {
|
|
|
|
return &SimpleReducer{Type: typ}
|
|
|
|
}
|
|
|
|
|
|
|
|
type DefaultAlertEvaluator struct {
|
|
|
|
Type string
|
|
|
|
Threshold float64
|
|
|
|
}
|
|
|
|
|
2016-07-20 07:28:02 -05:00
|
|
|
func (e *DefaultAlertEvaluator) Eval(series *tsdb.TimeSeries, reducedValue float64) bool {
|
|
|
|
switch e.Type {
|
|
|
|
case ">":
|
|
|
|
return reducedValue > e.Threshold
|
|
|
|
case "<":
|
|
|
|
return reducedValue < e.Threshold
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
2016-07-19 10:45:37 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewDefaultAlertEvaluator(model *simplejson.Json) (*DefaultAlertEvaluator, error) {
|
|
|
|
evaluator := &DefaultAlertEvaluator{}
|
|
|
|
|
|
|
|
evaluator.Type = model.Get("type").MustString()
|
|
|
|
if evaluator.Type == "" {
|
2016-07-21 06:09:12 -05:00
|
|
|
return nil, AlertValidationError{Reason: "Evaluator missing type property"}
|
2016-07-19 10:45:37 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
params := model.Get("params").MustArray()
|
|
|
|
if len(params) == 0 {
|
2016-07-21 06:09:12 -05:00
|
|
|
return nil, AlertValidationError{Reason: "Evaluator missing threshold parameter"}
|
2016-07-19 10:45:37 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
threshold, ok := params[0].(json.Number)
|
|
|
|
if !ok {
|
2016-07-21 06:09:12 -05:00
|
|
|
return nil, AlertValidationError{Reason: "Evaluator has invalid threshold parameter"}
|
2016-07-19 10:45:37 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
evaluator.Threshold, _ = threshold.Float64()
|
|
|
|
return evaluator, nil
|
2016-07-19 09:15:26 -05:00
|
|
|
}
|