query.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. package conditions
  2. import (
  3. "fmt"
  4. "strings"
  5. "time"
  6. gocontext "context"
  7. "github.com/grafana/grafana/pkg/bus"
  8. "github.com/grafana/grafana/pkg/components/null"
  9. "github.com/grafana/grafana/pkg/components/simplejson"
  10. m "github.com/grafana/grafana/pkg/models"
  11. "github.com/grafana/grafana/pkg/services/alerting"
  12. "github.com/grafana/grafana/pkg/tsdb"
  13. )
  14. func init() {
  15. alerting.RegisterCondition("query", func(model *simplejson.Json, index int) (alerting.Condition, error) {
  16. return NewQueryCondition(model, index)
  17. })
  18. }
  19. type QueryCondition struct {
  20. Index int
  21. Query AlertQuery
  22. Reducer QueryReducer
  23. Evaluator AlertEvaluator
  24. Operator string
  25. HandleRequest tsdb.HandleRequestFunc
  26. }
  27. type AlertQuery struct {
  28. Model *simplejson.Json
  29. DatasourceId int64
  30. From string
  31. To string
  32. }
  33. func (c *QueryCondition) Eval(context *alerting.EvalContext) (*alerting.ConditionResult, error) {
  34. timeRange := tsdb.NewTimeRange(c.Query.From, c.Query.To)
  35. seriesList, err := c.executeQuery(context, timeRange)
  36. if err != nil {
  37. return nil, err
  38. }
  39. emptySerieCount := 0
  40. evalMatchCount := 0
  41. var matches []*alerting.EvalMatch
  42. for _, series := range seriesList {
  43. reducedValue := c.Reducer.Reduce(series)
  44. evalMatch := c.Evaluator.Eval(reducedValue)
  45. if !reducedValue.Valid {
  46. emptySerieCount++
  47. }
  48. if context.IsTestRun {
  49. context.Logs = append(context.Logs, &alerting.ResultLogEntry{
  50. Message: fmt.Sprintf("Condition[%d]: Eval: %v, Metric: %s, Value: %s", c.Index, evalMatch, series.Name, reducedValue),
  51. })
  52. }
  53. if evalMatch {
  54. evalMatchCount++
  55. matches = append(matches, &alerting.EvalMatch{
  56. Metric: series.Name,
  57. Value: reducedValue,
  58. Tags: series.Tags,
  59. })
  60. }
  61. }
  62. // handle no series special case
  63. if len(seriesList) == 0 {
  64. // eval condition for null value
  65. evalMatch := c.Evaluator.Eval(null.FloatFromPtr(nil))
  66. if context.IsTestRun {
  67. context.Logs = append(context.Logs, &alerting.ResultLogEntry{
  68. Message: fmt.Sprintf("Condition: Eval: %v, Query Returned No Series (reduced to null/no value)", evalMatch),
  69. })
  70. }
  71. if evalMatch {
  72. evalMatchCount++
  73. matches = append(matches, &alerting.EvalMatch{Metric: "NoData", Value: null.FloatFromPtr(nil)})
  74. }
  75. }
  76. return &alerting.ConditionResult{
  77. Firing: evalMatchCount > 0,
  78. NoDataFound: emptySerieCount == len(seriesList),
  79. Operator: c.Operator,
  80. EvalMatches: matches,
  81. }, nil
  82. }
  83. func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *tsdb.TimeRange) (tsdb.TimeSeriesSlice, error) {
  84. getDsInfo := &m.GetDataSourceByIdQuery{
  85. Id: c.Query.DatasourceId,
  86. OrgId: context.Rule.OrgId,
  87. }
  88. if err := bus.Dispatch(getDsInfo); err != nil {
  89. return nil, fmt.Errorf("Could not find datasource %v", err)
  90. }
  91. req := c.getRequestForAlertRule(getDsInfo.Result, timeRange)
  92. result := make(tsdb.TimeSeriesSlice, 0)
  93. resp, err := c.HandleRequest(context.Ctx, getDsInfo.Result, req)
  94. if err != nil {
  95. if err == gocontext.DeadlineExceeded {
  96. return nil, fmt.Errorf("Alert execution exceeded the timeout")
  97. }
  98. return nil, fmt.Errorf("tsdb.HandleRequest() error %v", err)
  99. }
  100. for _, v := range resp.Results {
  101. if v.Error != nil {
  102. return nil, fmt.Errorf("tsdb.HandleRequest() response error %v", v)
  103. }
  104. result = append(result, v.Series...)
  105. if context.IsTestRun {
  106. context.Logs = append(context.Logs, &alerting.ResultLogEntry{
  107. Message: fmt.Sprintf("Condition[%d]: Query Result", c.Index),
  108. Data: v.Series,
  109. })
  110. }
  111. }
  112. return result, nil
  113. }
  114. func (c *QueryCondition) getRequestForAlertRule(datasource *m.DataSource, timeRange *tsdb.TimeRange) *tsdb.TsdbQuery {
  115. req := &tsdb.TsdbQuery{
  116. TimeRange: timeRange,
  117. Queries: []*tsdb.Query{
  118. {
  119. RefId: "A",
  120. Model: c.Query.Model,
  121. DataSource: datasource,
  122. },
  123. },
  124. }
  125. return req
  126. }
  127. func NewQueryCondition(model *simplejson.Json, index int) (*QueryCondition, error) {
  128. condition := QueryCondition{}
  129. condition.Index = index
  130. condition.HandleRequest = tsdb.HandleRequest
  131. queryJson := model.Get("query")
  132. condition.Query.Model = queryJson.Get("model")
  133. condition.Query.From = queryJson.Get("params").MustArray()[1].(string)
  134. condition.Query.To = queryJson.Get("params").MustArray()[2].(string)
  135. if err := validateFromValue(condition.Query.From); err != nil {
  136. return nil, err
  137. }
  138. if err := validateToValue(condition.Query.To); err != nil {
  139. return nil, err
  140. }
  141. condition.Query.DatasourceId = queryJson.Get("datasourceId").MustInt64()
  142. reducerJson := model.Get("reducer")
  143. condition.Reducer = NewSimpleReducer(reducerJson.Get("type").MustString())
  144. evaluatorJson := model.Get("evaluator")
  145. evaluator, err := NewAlertEvaluator(evaluatorJson)
  146. if err != nil {
  147. return nil, err
  148. }
  149. condition.Evaluator = evaluator
  150. operatorJson := model.Get("operator")
  151. operator := operatorJson.Get("type").MustString("and")
  152. condition.Operator = operator
  153. return &condition, nil
  154. }
  155. func validateFromValue(from string) error {
  156. fromRaw := strings.Replace(from, "now-", "", 1)
  157. _, err := time.ParseDuration("-" + fromRaw)
  158. return err
  159. }
  160. func validateToValue(to string) error {
  161. if to == "now" {
  162. return nil
  163. } else if strings.HasPrefix(to, "now-") {
  164. withoutNow := strings.Replace(to, "now-", "", 1)
  165. _, err := time.ParseDuration("-" + withoutNow)
  166. if err == nil {
  167. return nil
  168. }
  169. }
  170. _, err := time.ParseDuration(to)
  171. return err
  172. }