cloudflare/pint

Public

mirrored from https://github.com/cloudflare/pintAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.61.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

internal/checks/alerts_comparison.go

151lines · modecode

1package checks
2
3import (
4 "context"
5
6 "github.com/cloudflare/pint/internal/discovery"
7 "github.com/cloudflare/pint/internal/parser"
8 "github.com/cloudflare/pint/internal/parser/utils"
9
10 promParser "github.com/prometheus/prometheus/promql/parser"
11)
12
13const (
14 ComparisonCheckName = "alerts/comparison"
15 ComparisonCheckDetails = `Prometheus alerting rules will trigger an alert for each query that returns *any* result.
16Unless you do want an alert to always fire you should write your query in a way that returns results only when some condition is met.
17In most cases this can be achieved by having some condition in the query expression.
18For example ` + "`" + `up == 0` + "`" + " or " + "`" + "rate(error_total[2m]) > 0" + "`." + `
19Be careful as some PromQL operations will cause the query to always return the results, for example using the [bool modifier](https://prometheus.io/docs/prometheus/latest/querying/operators/#comparison-binary-operators).`
20)
21
22func NewComparisonCheck() ComparisonCheck {
23 return ComparisonCheck{}
24}
25
26type ComparisonCheck struct{}
27
28func (c ComparisonCheck) Meta() CheckMeta {
29 return CheckMeta{
30 States: []discovery.ChangeType{
31 discovery.Noop,
32 discovery.Added,
33 discovery.Modified,
34 discovery.Moved,
35 },
36 IsOnline: false,
37 }
38}
39
40func (c ComparisonCheck) String() string {
41 return ComparisonCheckName
42}
43
44func (c ComparisonCheck) Reporter() string {
45 return ComparisonCheckName
46}
47
48func (c ComparisonCheck) Check(_ context.Context, _ discovery.Path, rule parser.Rule, _ []discovery.Entry) (problems []Problem) {
49 if rule.AlertingRule == nil {
50 return problems
51 }
52
53 if rule.AlertingRule.Expr.SyntaxError != nil {
54 return problems
55 }
56
57 expr := rule.Expr().Query
58
59 if n := utils.HasOuterBinaryExpr(expr); n != nil && n.Op == promParser.LOR {
60 if (hasComparision(n.LHS) == nil || hasComparision(n.RHS) == nil) && !isAbsent(n.LHS) && !isAbsent(n.RHS) {
61 problems = append(problems, Problem{
62 Lines: rule.AlertingRule.Expr.Value.Lines,
63 Reporter: c.Reporter(),
64 Text: "Alert query uses `or` operator with one side of the query that will always return a result, this alert will always fire.",
65 Details: ComparisonCheckDetails,
66 Severity: rewriteSeverity(Warning, n.LHS, n.RHS),
67 })
68 }
69 }
70
71 if n := hasComparision(expr.Expr); n != nil {
72 if n.ReturnBool && hasComparision(n.LHS) == nil && hasComparision(n.RHS) == nil {
73 problems = append(problems, Problem{
74 Lines: rule.AlertingRule.Expr.Value.Lines,
75 Reporter: c.Reporter(),
76 Text: "Alert query uses `bool` modifier for comparison, this means it will always return a result and the alert will always fire.",
77 Details: ComparisonCheckDetails,
78 Severity: Bug,
79 })
80 }
81 return problems
82 }
83
84 if hasAbsent(expr) {
85 return problems
86 }
87
88 problems = append(problems, Problem{
89 Lines: rule.AlertingRule.Expr.Value.Lines,
90 Reporter: c.Reporter(),
91 Text: "Alert query doesn't have any condition, it will always fire if the metric exists.",
92 Details: ComparisonCheckDetails,
93 Severity: Warning,
94 })
95
96 return problems
97}
98
99func hasComparision(n promParser.Node) *promParser.BinaryExpr {
100 if node, ok := n.(*promParser.BinaryExpr); ok {
101 if node.Op.IsComparisonOperator() {
102 return node
103 }
104 if node.Op == promParser.LUNLESS {
105 return node
106 }
107 }
108
109 for _, child := range promParser.Children(n) {
110 if node := hasComparision(child); node != nil {
111 return node
112 }
113 }
114
115 return nil
116}
117
118func isAbsent(node promParser.Node) bool {
119 if node, ok := node.(*promParser.Call); ok && (node.Func.Name == "absent") {
120 return true
121 }
122
123 for _, child := range promParser.Children(node) {
124 if isAbsent(child) {
125 return true
126 }
127 }
128
129 return false
130}
131
132func hasAbsent(n *parser.PromQLNode) bool {
133 if isAbsent(n.Expr) {
134 return true
135 }
136 for _, child := range n.Children {
137 if hasAbsent(child) {
138 return true
139 }
140 }
141 return false
142}
143
144func rewriteSeverity(s Severity, nodes ...promParser.Node) Severity {
145 for _, node := range nodes {
146 if n, ok := node.(*promParser.Call); ok && n.Func.Name == "vector" {
147 return Bug
148 }
149 }
150 return s
151}
152