package definitions import ( "container/heap" "fmt" "net/http" "sort" "strings" "time" v1 "github.com/prometheus/client_golang/api/prometheus/v1" promlabels "github.com/prometheus/prometheus/model/labels" ) // swagger:route GET /prometheus/grafana/api/v1/rules prometheus RouteGetGrafanaRuleStatuses // // gets the evaluation statuses of all rules // // Responses: // 200: RuleResponse // swagger:route GET /prometheus/{DatasourceUID}/api/v1/rules prometheus RouteGetRuleStatuses // // gets the evaluation statuses of all rules // // Responses: // 200: RuleResponse // 404: NotFound // swagger:route GET /prometheus/grafana/api/v1/alerts prometheus RouteGetGrafanaAlertStatuses // // gets the current alerts // // Responses: // 200: AlertResponse // swagger:route GET /prometheus/{DatasourceUID}/api/v1/alerts prometheus RouteGetAlertStatuses // // gets the current alerts // // Responses: // 200: AlertResponse // 404: NotFound // swagger:model type RuleResponse struct { // in: body DiscoveryBase // in: body Data RuleDiscovery `json:"data"` } // swagger:model type AlertResponse struct { // in: body DiscoveryBase // in: body Data AlertDiscovery `json:"data"` } // swagger:model type DiscoveryBase struct { // required: true Status string `json:"status"` // required: false ErrorType v1.ErrorType `json:"errorType,omitempty"` // required: false Error string `json:"error,omitempty"` } // swagger:model type RuleDiscovery struct { // required: true RuleGroups []RuleGroup `json:"groups"` NextToken string `json:"groupNextToken,omitempty"` Totals map[string]int64 `json:"totals,omitempty"` } // AlertDiscovery has info for all active alerts. // swagger:model type AlertDiscovery struct { // required: true Alerts []*Alert `json:"alerts"` } // swagger:model type RuleGroup struct { // required: true Name string `json:"name"` // required: true File string `json:"file"` // required: true FolderUID string `json:"folderUid"` // In order to preserve rule ordering, while exposing type (alerting or recording) // specific properties, both alerting and recording rules are exposed in the // same array. // required: true Rules []AlertingRule `json:"rules"` Totals map[string]int64 `json:"totals"` // required: true Interval float64 `json:"interval"` LastEvaluation time.Time `json:"lastEvaluation"` EvaluationTime float64 `json:"evaluationTime"` } // HTTPStatusCode returns the HTTP status code for a given Prometheus style error. func (d DiscoveryBase) HTTPStatusCode() int { if d.Status == "success" { return http.StatusOK } // Mapping taken from prometheus/web/api/v1/api.go // Note this is not exhaustive as our API does not return // the same spectrum of errors as Prometheus does. switch d.ErrorType { case v1.ErrBadData: return http.StatusBadRequest default: return http.StatusInternalServerError } } // RuleGroupsBy is a function that defines the ordering of Rule Groups. type RuleGroupsBy func(a1, a2 *RuleGroup) bool func (by RuleGroupsBy) Sort(groups []RuleGroup) { sort.Sort(RuleGroupsSorter{groups: groups, by: by}) } func RuleGroupsByFileAndName(a1, a2 *RuleGroup) bool { if a1.File == a2.File { return a1.Name < a2.Name } return a1.File < a2.File } type RuleGroupsSorter struct { groups []RuleGroup by RuleGroupsBy } func (s RuleGroupsSorter) Len() int { return len(s.groups) } func (s RuleGroupsSorter) Swap(i, j int) { s.groups[i], s.groups[j] = s.groups[j], s.groups[i] } func (s RuleGroupsSorter) Less(i, j int) bool { return s.by(&s.groups[i], &s.groups[j]) } // adapted from cortex // swagger:model type AlertingRule struct { // State can be "pending", "firing", "inactive". // required: true State string `json:"state,omitempty"` // required: true Name string `json:"name,omitempty"` // required: true Query string `json:"query,omitempty"` Duration float64 `json:"duration,omitempty"` // required: true Annotations promlabels.Labels `json:"annotations,omitempty"` // required: true ActiveAt *time.Time `json:"activeAt,omitempty"` Alerts []Alert `json:"alerts,omitempty"` Totals map[string]int64 `json:"totals,omitempty"` TotalsFiltered map[string]int64 `json:"totalsFiltered,omitempty"` Rule } // adapted from cortex // swagger:model type Rule struct { // required: true UID string `json:"uid"` // required: true Name string `json:"name"` // required: true FolderUID string `json:"folderUid"` // required: true Query string `json:"query"` Labels promlabels.Labels `json:"labels,omitempty"` // required: true Health string `json:"health"` LastError string `json:"lastError,omitempty"` // required: true Type string `json:"type"` LastEvaluation time.Time `json:"lastEvaluation"` EvaluationTime float64 `json:"evaluationTime"` } // Alert has info for an alert. // swagger:model type Alert struct { // required: true Labels promlabels.Labels `json:"labels"` // required: true Annotations promlabels.Labels `json:"annotations"` // required: true State string `json:"state"` ActiveAt *time.Time `json:"activeAt"` // required: true Value string `json:"value"` } type StateByImportance int const ( StateAlerting = iota StatePending StateError StateNoData StateNormal ) func stateByImportanceFromString(s string) (StateByImportance, error) { switch s = strings.ToLower(s); s { case "alerting": return StateAlerting, nil case "pending": return StatePending, nil case "error": return StateError, nil case "nodata": return StateNoData, nil case "normal": return StateNormal, nil default: return -1, fmt.Errorf("unknown state: %s", s) } } // AlertsBy is a function that defines the ordering of alerts. type AlertsBy func(a1, a2 *Alert) bool func (by AlertsBy) Sort(alerts []Alert) { sort.Sort(AlertsSorter{alerts: alerts, by: by}) } // AlertsHeap extends AlertsSorter for use with container/heap functions. type AlertsHeap struct { AlertsSorter } func (h *AlertsHeap) Push(x any) { h.alerts = append(h.alerts, x.(Alert)) } func (h *AlertsHeap) Pop() any { old := h.alerts n := len(old) x := old[n-1] h.alerts = old[0 : n-1] return x } // TopK returns the highest k elements. It does not modify the input. func (by AlertsBy) TopK(alerts []Alert, k int) []Alert { // Concept is that instead of sorting the whole list and taking the number // of items we need, maintain a heap of the top k elements, and update it // for each element. This vastly reduces the number of comparisons needed, // which is important for sorting alerts, as the comparison function is // very expensive. // If k is zero or less, return nothing. if k < 1 { return []Alert{} } // The heap must be in ascending order, so that the root of the heap is // the current smallest element. byAscending := func(a1, a2 *Alert) bool { return by(a2, a1) } h := AlertsHeap{ AlertsSorter: AlertsSorter{ alerts: make([]Alert, 0, k), by: byAscending, }, } // Go version of this algorithm taken from Prometheus (promql/engine.go) heap.Init(&h) for i := 0; i < len(alerts); i++ { a := alerts[i] // We build a heap of up to k elements, with the smallest element at heap[0]. switch { case len(h.alerts) < k: heap.Push(&h, a) case h.by(&h.alerts[0], &a): // This new element is bigger than the previous smallest element - overwrite that. h.alerts[0] = a // Maintain the heap invariant. if k > 1 { heap.Fix(&h, 0) } } } // The heap keeps the lowest value on top, so reverse it. if len(h.alerts) > 1 { sort.Sort(sort.Reverse(&h)) } return h.alerts } // AlertsByImportance orders alerts by importance. An alert is more important // than another alert if its status has higher importance. For example, "alerting" // is more important than "normal". If two alerts have the same importance // then the ordering is based on their ActiveAt time and their labels. func AlertsByImportance(a1, a2 *Alert) bool { // The importance of an alert is first based on the importance of their states. // This ordering is intended to show the most important alerts first when // using pagination. importance1, _ := stateByImportanceFromString(a1.State) importance2, _ := stateByImportanceFromString(a2.State) // If both alerts have the same importance then the ordering is based on // their ActiveAt time, and if those are equal, their labels. if importance1 == importance2 { if a1.ActiveAt != nil && a2.ActiveAt == nil { // The first alert is active but not the second return true } else if a1.ActiveAt == nil && a2.ActiveAt != nil { // The second alert is active but not the first return false } else if a1.ActiveAt != nil && a2.ActiveAt != nil && a1.ActiveAt.Before(*a2.ActiveAt) { // Both alerts are active but a1 happened before a2 return true } // Both alerts are active since the same time so compare their labels return promlabels.Compare(a1.Labels, a2.Labels) < 0 } return importance1 < importance2 } type AlertsSorter struct { alerts []Alert by AlertsBy } func (s AlertsSorter) Len() int { return len(s.alerts) } func (s AlertsSorter) Swap(i, j int) { s.alerts[i], s.alerts[j] = s.alerts[j], s.alerts[i] } func (s AlertsSorter) Less(i, j int) bool { return s.by(&s.alerts[i], &s.alerts[j]) } // LabelsFromMap creates Labels from a map. Note the Labels type requires the // labels be sorted, so we make sure to do that. func LabelsFromMap(m map[string]string) promlabels.Labels { sb := promlabels.NewScratchBuilder(len(m)) for k, v := range m { sb.Add(k, v) } sb.Sort() return sb.Labels() } // swagger:parameters RouteGetGrafanaAlertStatuses type GetGrafanaAlertStatusesParams struct { // Include Grafana specific labels as part of the response. // in: query // required: false // default: false IncludeInternalLabels bool `json:"includeInternalLabels"` } // swagger:parameters RouteGetGrafanaRuleStatuses type GetGrafanaRuleStatusesParams struct { // Include Grafana specific labels as part of the response. // in: query // required: false // default: false IncludeInternalLabels bool `json:"includeInternalLabels"` // Filter the list of rules to those that belong to the specified dashboard UID. // in: query // required: false DashboardUID string // Filter the list of rules to those that belong to the specified panel ID. Dashboard UID must be specified. // in: query // required: false PanelID int64 }