Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
### Fixed

- [promql/vector_matching](checks/promql/vector_matching.md) check no longer reports problems that are also reported by [promql/impossible](checks/promql/impossible.md).
- Fixed problem description in [promql/series](checks/promql/series.md) check for
disable comments that have no effect.

## v0.79.0

Expand Down
116 changes: 83 additions & 33 deletions internal/checks/promql_series.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,14 @@ func (c SeriesCheck) Check(ctx context.Context, entry *discovery.Entry, entries

params := promapi.NewRelativeRange(settings.lookbackRangeDuration, settings.lookbackStepDuration)

selectors := getNonFallbackSelectors(expr)
selectors := getSelectors(expr)

done := map[string]bool{}
for _, selector := range selectors {
for _, si := range selectors {
if si.hasFallback {
continue
}
selector := si.selector
s := selector.String()
if _, ok := done[s]; ok {
continue
Expand Down Expand Up @@ -679,6 +683,12 @@ func (c SeriesCheck) Check(ctx context.Context, entry *discovery.Entry, entries
}

for _, comment := range orphanedComments(ctx, entry.Rule, selectors) {
var msg string
if comment.fallback != "" {
msg = fmt.Sprintf("pint %s comment `%s` has no effect because the selector is not being checked due to a fallback `%s`", comment.kind, comment.match, comment.fallback)
} else {
msg = fmt.Sprintf("pint %s comment `%s` doesn't match any selector in this query", comment.kind, comment.match)
}
problems = append(problems, Problem{
Anchor: AnchorAfter,
Lines: expr.Value.Pos.Lines(),
Expand All @@ -688,7 +698,7 @@ func (c SeriesCheck) Check(ctx context.Context, entry *discovery.Entry, entries
Summary: "invalid comment",
Diagnostics: []diags.Diagnostic{
{
Message: fmt.Sprintf("pint %s comment `%s` doesn't match any selector in this query", comment.kind, comment.match),
Message: msg,
Pos: expr.Value.Pos,
FirstColumn: 1,
LastColumn: len(expr.Value.Value),
Expand Down Expand Up @@ -961,28 +971,63 @@ func joinHasFallback(src []source.Join) bool {
return false
}

func getNonFallbackSelectors(n *parser.PromQLExpr) (selectors []*promParser.VectorSelector) {
type selectorInfo struct {
selector *promParser.VectorSelector
fallback string
hasFallback bool
}

func getSelectors(n *parser.PromQLExpr) (selectors []selectorInfo) {
expr := n.Value.Value
sources := n.Source()
hasVectorFallback := sourceHasFallback(sources)
for _, ls := range sources {
if !hasVectorFallback {
if vs, ok := source.MostOuterOperation[*promParser.VectorSelector](ls); ok {
selectors = append(selectors, selectorWithoutOffset(vs))
var fallbackExpr string
if hasVectorFallback {
for _, ls := range sources {
if ls.ReturnInfo.AlwaysReturns {
fallbackExpr = source.GetQueryFragment(expr, ls.Position)
break
}
}
if !joinHasFallback(ls.Joins) {
}
joinFallback := false
for _, ls := range sources {
if vs, ok := source.MostOuterOperation[*promParser.VectorSelector](ls); ok {
selectors = append(selectors, selectorInfo{
selector: selectorWithoutOffset(vs),
hasFallback: hasVectorFallback,
fallback: fallbackExpr,
})
}
joinFallback = joinHasFallback(ls.Joins)
var joinFallbackExpr string
if joinFallback {
for _, js := range ls.Joins {
if vs, ok := source.MostOuterOperation[*promParser.VectorSelector](js.Src); ok {
selectors = append(selectors, selectorWithoutOffset(vs))
if js.Src.ReturnInfo.AlwaysReturns {
joinFallbackExpr = source.GetQueryFragment(expr, js.Src.Position)
break
}
}
}
for _, js := range ls.Joins {
if vs, ok := source.MostOuterOperation[*promParser.VectorSelector](js.Src); ok {
selectors = append(selectors, selectorInfo{
selector: selectorWithoutOffset(vs),
hasFallback: joinFallback,
fallback: joinFallbackExpr,
})
}
}
for _, us := range ls.Unless {
if !us.Src.IsConditional {
continue
}
if vs, ok := source.MostOuterOperation[*promParser.VectorSelector](us.Src); ok {
selectors = append(selectors, selectorWithoutOffset(vs))
selectors = append(selectors, selectorInfo{
selector: selectorWithoutOffset(vs),
fallback: "",
hasFallback: false,
})
}
}
}
Expand Down Expand Up @@ -1087,37 +1132,38 @@ func parseRuleSet(s string) (matcher, key, value string) {
return matcher, key, value
}

func wasCommentUsed(commentMatch string, promNames, promTags []string, selectors []*promParser.VectorSelector) bool {
func wasCommentUsed(commentMatch string, promNames, promTags []string, selectors []selectorInfo) (used bool, fallback string) {
match := strings.TrimSuffix(strings.TrimPrefix(commentMatch, SeriesCheckName+"("), ")")
// Skip matching tags.
if strings.HasPrefix(match, "+") && slices.Contains(promTags, strings.TrimPrefix(match, "+")) {
return true
return true, ""
}
// Skip matching Prometheus servers.
if slices.Contains(promNames, match) {
return true
return true, ""
}
if !strings.HasPrefix(commentMatch, SeriesCheckName+"(") || !strings.HasSuffix(commentMatch, ")") {
return true
return true, ""
}
for _, selector := range selectors {
isMatch, ok := matchSelectorToMetric(selector, match)
for _, si := range selectors {
isMatch, ok := matchSelectorToMetric(si.selector, match)
if !ok {
continue
}
if isMatch {
return true
return true, si.fallback
}
}
return false
return false, ""
}

type orphanedComment struct {
kind string
match string
kind string
match string
fallback string
}

func orphanedComments(ctx context.Context, rule parser.Rule, selectors []*promParser.VectorSelector) (orhpaned []orphanedComment) {
func orphanedComments(ctx context.Context, rule parser.Rule, selectors []selectorInfo) (orhpaned []orphanedComment) {
var promNames, promTags []string
if val := ctx.Value(promapi.AllPrometheusServers); val != nil {
for _, server := range val.([]*promapi.FailoverGroup) {
Expand All @@ -1126,31 +1172,35 @@ func orphanedComments(ctx context.Context, rule parser.Rule, selectors []*promPa
}
}
for _, disable := range comments.Only[comments.Disable](rule.Comments, comments.DisableType) {
if !wasCommentUsed(disable.Match, promNames, promTags, selectors) {
used, fallback := wasCommentUsed(disable.Match, promNames, promTags, selectors)
if !used || fallback != "" {
orhpaned = append(orhpaned, orphanedComment{
kind: comments.DisableComment,
match: disable.Match,
kind: comments.DisableComment,
match: disable.Match,
fallback: fallback,
})
}
}
for _, snooze := range comments.Only[comments.Snooze](rule.Comments, comments.SnoozeType) {
if !wasCommentUsed(snooze.Match, promNames, promTags, selectors) {
used, fallback := wasCommentUsed(snooze.Match, promNames, promTags, selectors)
if !used || fallback != "" {
orhpaned = append(orhpaned, orphanedComment{
kind: comments.SnoozeComment,
match: snooze.Match,
kind: comments.SnoozeComment,
match: snooze.Match,
fallback: fallback,
})
}
}
return orhpaned
}

func orphanedRuleSetComments(rule parser.Rule, selectors []*promParser.VectorSelector) (orhpaned []comments.RuleSet) {
func orphanedRuleSetComments(rule parser.Rule, selectors []selectorInfo) (orhpaned []comments.RuleSet) {
for _, ruleSet := range comments.Only[comments.RuleSet](rule.Comments, comments.RuleSetType) {
var wasUsed bool
matcher, key, value := parseRuleSet(ruleSet.Value)
for _, selector := range selectors {
for _, si := range selectors {
if matcher != "" {
isMatch, _ := matchSelectorToMetric(selector, matcher)
isMatch, _ := matchSelectorToMetric(si.selector, matcher)
if !isMatch {
continue
}
Expand All @@ -1159,7 +1209,7 @@ func orphanedRuleSetComments(rule parser.Rule, selectors []*promParser.VectorSel
case "min-age":
wasUsed = true
case "ignore/label-value":
for _, lm := range selector.LabelMatchers {
for _, lm := range si.selector.LabelMatchers {
if lm.Name == value {
wasUsed = true
goto NEXT
Expand Down
38 changes: 6 additions & 32 deletions internal/checks/promql_series_test.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1199,15 +1199,15 @@
8 | (rate(metric1[5m]) or vector(0)) +
9 | (rate(metric3{log_name="samplerd"}[5m]) or vector(0))
10 | > 0
^^^ pint disable comment `promql/series(metric1)` doesn't match any selector in this query
^^^ pint disable comment `promql/series(metric1)` has no effect because the selector is not being checked due to a fallback `vector(0)`
problem:
reporter: promql/series
summary: invalid comment
details: |-
One of the `# pint disable promql/series` comments used in this rule doesn't have any effect and won't disable anything. Make sure that the comment targets series that are used in the rule query and are not already ignored.
[Click here](https://cloudflare.github.io/pint/checks/promql/series.html#how-to-disable-it) to see docs about disable comment syntax.
diagnostics:
- message: pint disable comment `promql/series(metric1)` doesn't match any selector in this query
- message: pint disable comment `promql/series(metric1)` has no effect because the selector is not being checked due to a fallback `vector(0)`
firstcolumn: 1
lastcolumn: 128
kind: 0
Expand All @@ -1233,15 +1233,15 @@
8 | (rate(metric1[5m]) or vector(0)) +
9 | (rate(metric3{log_name="samplerd"}[5m]) or vector(0))
10 | > 0
^^^ pint disable comment `promql/series(metric2)` doesn't match any selector in this query
^^^ pint disable comment `promql/series(metric2)` has no effect because the selector is not being checked due to a fallback `vector(0)`
problem:
reporter: promql/series
summary: invalid comment
details: |-
One of the `# pint disable promql/series` comments used in this rule doesn't have any effect and won't disable anything. Make sure that the comment targets series that are used in the rule query and are not already ignored.
[Click here](https://cloudflare.github.io/pint/checks/promql/series.html#how-to-disable-it) to see docs about disable comment syntax.
diagnostics:
- message: pint disable comment `promql/series(metric2)` doesn't match any selector in this query
- message: pint disable comment `promql/series(metric2)` has no effect because the selector is not being checked due to a fallback `vector(0)`
firstcolumn: 1
lastcolumn: 128
kind: 0
Expand All @@ -1267,15 +1267,15 @@
8 | (rate(metric1[5m]) or vector(0)) +
9 | (rate(metric3{log_name="samplerd"}[5m]) or vector(0))
10 | > 0
^^^ pint disable comment `promql/series(metric3)` doesn't match any selector in this query
^^^ pint disable comment `promql/series(metric3)` has no effect because the selector is not being checked due to a fallback `vector(0)`
problem:
reporter: promql/series
summary: invalid comment
details: |-
One of the `# pint disable promql/series` comments used in this rule doesn't have any effect and won't disable anything. Make sure that the comment targets series that are used in the rule query and are not already ignored.
[Click here](https://cloudflare.github.io/pint/checks/promql/series.html#how-to-disable-it) to see docs about disable comment syntax.
diagnostics:
- message: pint disable comment `promql/series(metric3)` doesn't match any selector in this query
- message: pint disable comment `promql/series(metric3)` has no effect because the selector is not being checked due to a fallback `vector(0)`
firstcolumn: 1
lastcolumn: 128
kind: 0
Expand Down Expand Up @@ -2393,32 +2393,6 @@
- description: unused rule/set comment or vector
content: |4

- alert : Foo
# pint rule/set promql/series(mymetric) ignore/label-value action
# pint rule/set promql/series(mymetric) ignore/label-value type
expr: (rate(mymetric{action="failure"}[2m]) or vector(0)) > 0
output: |
5 | expr: (rate(mymetric{action="failure"}[2m]) or vector(0)) > 0
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pint rule/set comment `promql/series(mymetric) ignore/label-value action` doesn't match any label matcher in this query
problem:
reporter: promql/series
summary: invalid comment
details: |-
One of the `# pint rule/set promql/series` comments used in this rule doesn't have any effect. Make sure that the comment targets series and labels that are used in the rule query and are not already ignored.
[Click here](https://cloudflare.github.io/pint/checks/promql/series.html#ignorelabel-value) for docs about comment syntax.
diagnostics:
- message: pint rule/set comment `promql/series(mymetric) ignore/label-value action` doesn't match any label matcher in this query
firstcolumn: 1
lastcolumn: 55
kind: 0
lines:
first: 5
last: 5
severity: 1
anchor: 0
- description: unused rule/set comment or vector
content: |4

- alert : Foo
# pint rule/set promql/series(mymetric) ignore/label-value action
# pint rule/set promql/series(mymetric) ignore/label-value type
Expand Down
Loading