query.go 5.1 KB

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