eval_context.go 4.5 KB

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