conditions.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. package alerting
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "github.com/grafana/grafana/pkg/bus"
  6. "github.com/grafana/grafana/pkg/components/simplejson"
  7. m "github.com/grafana/grafana/pkg/models"
  8. "github.com/grafana/grafana/pkg/tsdb"
  9. )
  10. type QueryCondition struct {
  11. Index int
  12. Query AlertQuery
  13. Reducer QueryReducer
  14. Evaluator AlertEvaluator
  15. HandleRequest tsdb.HandleRequestFunc
  16. }
  17. func (c *QueryCondition) Eval(context *AlertResultContext) {
  18. seriesList, err := c.executeQuery(context)
  19. if err != nil {
  20. context.Error = err
  21. return
  22. }
  23. for _, series := range seriesList {
  24. reducedValue := c.Reducer.Reduce(series)
  25. pass := c.Evaluator.Eval(series, reducedValue)
  26. if context.IsTestRun {
  27. context.Logs = append(context.Logs, &AlertResultLogEntry{
  28. Message: fmt.Sprintf("Condition[%d]: Eval: %v, Metric: %s, Value: %1.3f", c.Index, pass, series.Name, reducedValue),
  29. })
  30. }
  31. if pass {
  32. context.Events = append(context.Events, &AlertEvent{
  33. Metric: series.Name,
  34. Value: reducedValue,
  35. })
  36. context.Firing = true
  37. break
  38. }
  39. }
  40. }
  41. func (c *QueryCondition) executeQuery(context *AlertResultContext) (tsdb.TimeSeriesSlice, error) {
  42. getDsInfo := &m.GetDataSourceByIdQuery{
  43. Id: c.Query.DatasourceId,
  44. OrgId: context.Rule.OrgId,
  45. }
  46. if err := bus.Dispatch(getDsInfo); err != nil {
  47. return nil, fmt.Errorf("Could not find datasource")
  48. }
  49. req := c.getRequestForAlertRule(getDsInfo.Result)
  50. result := make(tsdb.TimeSeriesSlice, 0)
  51. resp, err := c.HandleRequest(req)
  52. if err != nil {
  53. return nil, fmt.Errorf("tsdb.HandleRequest() error %v", err)
  54. }
  55. for _, v := range resp.Results {
  56. if v.Error != nil {
  57. return nil, fmt.Errorf("tsdb.HandleRequest() response error %v", v)
  58. }
  59. result = append(result, v.Series...)
  60. if context.IsTestRun {
  61. context.Logs = append(context.Logs, &AlertResultLogEntry{
  62. Message: fmt.Sprintf("Condition[%d]: Query Result", c.Index),
  63. Data: v.Series,
  64. })
  65. }
  66. }
  67. return result, nil
  68. }
  69. func (c *QueryCondition) getRequestForAlertRule(datasource *m.DataSource) *tsdb.Request {
  70. req := &tsdb.Request{
  71. TimeRange: tsdb.TimeRange{
  72. From: c.Query.From,
  73. To: c.Query.To,
  74. },
  75. Queries: []*tsdb.Query{
  76. {
  77. RefId: "A",
  78. Query: c.Query.Model.Get("target").MustString(),
  79. DataSource: &tsdb.DataSourceInfo{
  80. Id: datasource.Id,
  81. Name: datasource.Name,
  82. PluginId: datasource.Type,
  83. Url: datasource.Url,
  84. },
  85. },
  86. },
  87. }
  88. return req
  89. }
  90. func NewQueryCondition(model *simplejson.Json, index int) (*QueryCondition, error) {
  91. condition := QueryCondition{}
  92. condition.Index = index
  93. condition.HandleRequest = tsdb.HandleRequest
  94. queryJson := model.Get("query")
  95. condition.Query.Model = queryJson.Get("model")
  96. condition.Query.From = queryJson.Get("params").MustArray()[1].(string)
  97. condition.Query.To = queryJson.Get("params").MustArray()[2].(string)
  98. condition.Query.DatasourceId = queryJson.Get("datasourceId").MustInt64()
  99. reducerJson := model.Get("reducer")
  100. condition.Reducer = NewSimpleReducer(reducerJson.Get("type").MustString())
  101. evaluatorJson := model.Get("evaluator")
  102. evaluator, err := NewDefaultAlertEvaluator(evaluatorJson)
  103. if err != nil {
  104. return nil, err
  105. }
  106. condition.Evaluator = evaluator
  107. return &condition, nil
  108. }
  109. type SimpleReducer struct {
  110. Type string
  111. }
  112. func (s *SimpleReducer) Reduce(series *tsdb.TimeSeries) float64 {
  113. var value float64 = 0
  114. switch s.Type {
  115. case "avg":
  116. for _, point := range series.Points {
  117. value += point[0]
  118. }
  119. value = value / float64(len(series.Points))
  120. }
  121. return value
  122. }
  123. func NewSimpleReducer(typ string) *SimpleReducer {
  124. return &SimpleReducer{Type: typ}
  125. }
  126. type DefaultAlertEvaluator struct {
  127. Type string
  128. Threshold float64
  129. }
  130. func (e *DefaultAlertEvaluator) Eval(series *tsdb.TimeSeries, reducedValue float64) bool {
  131. switch e.Type {
  132. case ">":
  133. return reducedValue > e.Threshold
  134. case "<":
  135. return reducedValue < e.Threshold
  136. }
  137. return false
  138. }
  139. func NewDefaultAlertEvaluator(model *simplejson.Json) (*DefaultAlertEvaluator, error) {
  140. evaluator := &DefaultAlertEvaluator{}
  141. evaluator.Type = model.Get("type").MustString()
  142. if evaluator.Type == "" {
  143. return nil, AlertValidationError{Reason: "Evaluator missing type property"}
  144. }
  145. params := model.Get("params").MustArray()
  146. if len(params) == 0 {
  147. return nil, AlertValidationError{Reason: "Evaluator missing threshold parameter"}
  148. }
  149. threshold, ok := params[0].(json.Number)
  150. if !ok {
  151. return nil, AlertValidationError{Reason: "Evaluator has invalid threshold parameter"}
  152. }
  153. evaluator.Threshold, _ = threshold.Float64()
  154. return evaluator, nil
  155. }