eval_context.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. package alerting
  2. import (
  3. "context"
  4. "fmt"
  5. "time"
  6. "github.com/grafana/grafana/pkg/bus"
  7. "github.com/grafana/grafana/pkg/infra/log"
  8. "github.com/grafana/grafana/pkg/models"
  9. "github.com/grafana/grafana/pkg/setting"
  10. )
  11. // EvalContext is the context object for an alert evaluation.
  12. type EvalContext struct {
  13. Firing bool
  14. IsTestRun bool
  15. EvalMatches []*EvalMatch
  16. Logs []*ResultLogEntry
  17. Error error
  18. ConditionEvals string
  19. StartTime time.Time
  20. EndTime time.Time
  21. Rule *Rule
  22. log log.Logger
  23. dashboardRef *models.DashboardRef
  24. ImagePublicUrl string
  25. ImageOnDiskPath string
  26. NoDataFound bool
  27. PrevAlertState models.AlertStateType
  28. Ctx context.Context
  29. }
  30. // NewEvalContext is the EvalContext constructor.
  31. func NewEvalContext(alertCtx context.Context, rule *Rule) *EvalContext {
  32. return &EvalContext{
  33. Ctx: alertCtx,
  34. StartTime: time.Now(),
  35. Rule: rule,
  36. Logs: make([]*ResultLogEntry, 0),
  37. EvalMatches: make([]*EvalMatch, 0),
  38. log: log.New("alerting.evalContext"),
  39. PrevAlertState: rule.State,
  40. }
  41. }
  42. // StateDescription contains visual information about the alert state.
  43. type StateDescription struct {
  44. Color string
  45. Text string
  46. Data string
  47. }
  48. // GetStateModel returns the `StateDescription` based on current state.
  49. func (c *EvalContext) GetStateModel() *StateDescription {
  50. switch c.Rule.State {
  51. case models.AlertStateOK:
  52. return &StateDescription{
  53. Color: "#36a64f",
  54. Text: "OK",
  55. }
  56. case models.AlertStateNoData:
  57. return &StateDescription{
  58. Color: "#888888",
  59. Text: "No Data",
  60. }
  61. case models.AlertStateAlerting:
  62. return &StateDescription{
  63. Color: "#D63232",
  64. Text: "Alerting",
  65. }
  66. case models.AlertStateUnknown:
  67. return &StateDescription{
  68. Color: "#888888",
  69. Text: "Unknown",
  70. }
  71. default:
  72. panic("Unknown rule state for alert " + c.Rule.State)
  73. }
  74. }
  75. func (c *EvalContext) shouldUpdateAlertState() bool {
  76. return c.Rule.State != c.PrevAlertState
  77. }
  78. // GetDurationMs returns the duration of the alert evaluation.
  79. func (c *EvalContext) GetDurationMs() float64 {
  80. return float64(c.EndTime.Nanosecond()-c.StartTime.Nanosecond()) / float64(1000000)
  81. }
  82. // GetNotificationTitle returns the title of the alert rule including alert state.
  83. func (c *EvalContext) GetNotificationTitle() string {
  84. return "[" + c.GetStateModel().Text + "] " + c.Rule.Name
  85. }
  86. // GetDashboardUID returns the dashboard uid for the alert rule.
  87. func (c *EvalContext) GetDashboardUID() (*models.DashboardRef, error) {
  88. if c.dashboardRef != nil {
  89. return c.dashboardRef, nil
  90. }
  91. uidQuery := &models.GetDashboardRefByIdQuery{Id: c.Rule.DashboardId}
  92. if err := bus.Dispatch(uidQuery); err != nil {
  93. return nil, err
  94. }
  95. c.dashboardRef = uidQuery.Result
  96. return c.dashboardRef, nil
  97. }
  98. const urlFormat = "%s?fullscreen&edit&tab=alert&panelId=%d&orgId=%d"
  99. // GetRuleUrl returns the url to the dashboard containing the alert.
  100. func (c *EvalContext) GetRuleUrl() (string, error) {
  101. if c.IsTestRun {
  102. return setting.AppUrl, nil
  103. }
  104. ref, err := c.GetDashboardUID()
  105. if err != nil {
  106. return "", err
  107. }
  108. return fmt.Sprintf(urlFormat, models.GetFullDashboardUrl(ref.Uid, ref.Slug), c.Rule.PanelId, c.Rule.OrgId), nil
  109. }
  110. // GetNewState returns the new state from the alert rule evaluation.
  111. func (c *EvalContext) GetNewState() models.AlertStateType {
  112. ns := getNewStateInternal(c)
  113. if ns != models.AlertStateAlerting || c.Rule.For == 0 {
  114. return ns
  115. }
  116. since := time.Since(c.Rule.LastStateChange)
  117. if c.PrevAlertState == models.AlertStatePending && since > c.Rule.For {
  118. return models.AlertStateAlerting
  119. }
  120. if c.PrevAlertState == models.AlertStateAlerting {
  121. return models.AlertStateAlerting
  122. }
  123. return models.AlertStatePending
  124. }
  125. func getNewStateInternal(c *EvalContext) models.AlertStateType {
  126. if c.Error != nil {
  127. c.log.Error("Alert Rule Result Error",
  128. "ruleId", c.Rule.Id,
  129. "name", c.Rule.Name,
  130. "error", c.Error,
  131. "changing state to", c.Rule.ExecutionErrorState.ToAlertState())
  132. if c.Rule.ExecutionErrorState == models.ExecutionErrorKeepState {
  133. return c.PrevAlertState
  134. }
  135. return c.Rule.ExecutionErrorState.ToAlertState()
  136. }
  137. if c.Firing {
  138. return models.AlertStateAlerting
  139. }
  140. if c.NoDataFound {
  141. c.log.Info("Alert Rule returned no data",
  142. "ruleId", c.Rule.Id,
  143. "name", c.Rule.Name,
  144. "changing state to", c.Rule.NoDataState.ToAlertState())
  145. if c.Rule.NoDataState == models.NoDataKeepState {
  146. return c.PrevAlertState
  147. }
  148. return c.Rule.NoDataState.ToAlertState()
  149. }
  150. return models.AlertStateOK
  151. }