From 8bfc2e11591b01caa50680ca6fd635a81c7515f7 Mon Sep 17 00:00:00 2001 From: Lukasz Mierzwa Date: Thu, 8 Aug 2024 14:28:03 +0100 Subject: [PATCH 1/2] Update go version --- go.ver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.ver b/go.ver index da9594fd..013173af 100644 --- a/go.ver +++ b/go.ver @@ -1 +1 @@ -1.22.5 +1.22.6 From e8bf8e7b8141658b81695fe8b4b2a877e78de4cd Mon Sep 17 00:00:00 2001 From: Lukasz Mierzwa Date: Thu, 8 Aug 2024 15:39:44 +0100 Subject: [PATCH 2/2] Add ignoreLabelsValue setting --- docs/changelog.md | 6 + docs/checks/promql/series.md | 25 +++- internal/checks/promql_series.go | 23 +++- internal/checks/promql_series_test.go | 178 +++++++++++++++++++++++++- 4 files changed, 223 insertions(+), 9 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index fe00b7bc..2d7f7244 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,11 @@ # Changelog +## v0.64.0 + +### Added + +- Added `ignoreLabelsValue` to [promql/series](checks/promql/series.md) check settings. + ## v0.63.0 ### Added diff --git a/docs/checks/promql/series.md b/docs/checks/promql/series.md index 671ceae5..c4caac16 100644 --- a/docs/checks/promql/series.md +++ b/docs/checks/promql/series.md @@ -141,7 +141,8 @@ Syntax: ```js check "promql/series" { - ignoreMetrics = [ "(.*)", ... ] + ignoreMetrics = [ "(.*)", ... ] + ignoreLabelsValue = { "...": [ "...", ... ] } } ``` @@ -159,6 +160,10 @@ check "promql/series" { - `ignoreMetrics` - list of regexp matchers, if a metric is missing from Prometheus but the name matches any of provided regexp matchers then pint will only report a warning, instead of a bug level report. +- `ignoreLabelsValue` - allows to configure a global list of label **names** for which pint + should **NOT** report problems if there's a query that uses a **value** that does not exist. + This can be also set per rule using `# pint rule/set promql/series ignore/label-value $labelName` + comments, see below. Example: @@ -175,6 +180,19 @@ check "promql/series" { } ``` +If you might have a query with `http_requests_total{code="401"}` selector but `http_requests_total` +will only have time series with `code="401"` label if there were requests that resulted in responses +with HTTP code `401`, which would result in a pint reports. This example would disable all such pint +reports for all Prometheus rules: + +```js +check "promql/series" { + ignoreLabelsValue = { + "http_requests_total" = [ "code" ] + } +} +``` + ### min-age But default this check will report a problem if a metric was present @@ -232,10 +250,13 @@ there's at least one HTTP response that generated this code. You can relax pint checks so it doesn't validate if label values for specific labels are present on any time series. +This can be also set globally for all rules using `ignoreLabelsValue` config option, +see above. + Syntax: ```yaml -# pint rule/set promql/series ignore/label-value $labelName` +# pint rule/set promql/series ignore/label-value $labelName ``` Example: diff --git a/internal/checks/promql_series.go b/internal/checks/promql_series.go index fe9f17d4..a832a387 100644 --- a/internal/checks/promql_series.go +++ b/internal/checks/promql_series.go @@ -24,9 +24,10 @@ import ( ) type PromqlSeriesSettings struct { - LookbackRange string `hcl:"lookbackRange,optional" json:"lookbackRange,omitempty"` - LookbackStep string `hcl:"lookbackStep,optional" json:"lookbackStep,omitempty"` - IgnoreMetrics []string `hcl:"ignoreMetrics,optional" json:"ignoreMetrics,omitempty"` + IgnoreLabelsValue map[string][]string `hcl:"ignoreLabelsValue,optional" json:"ignoreLabelsValue,omitempty"` + LookbackRange string `hcl:"lookbackRange,optional" json:"lookbackRange,omitempty"` + LookbackStep string `hcl:"lookbackStep,optional" json:"lookbackStep,omitempty"` + IgnoreMetrics []string `hcl:"ignoreMetrics,optional" json:"ignoreMetrics,omitempty"` ignoreMetricsRe []*regexp.Regexp lookbackRangeDuration time.Duration lookbackStepDuration time.Duration @@ -389,8 +390,7 @@ func (c SeriesCheck) Check(ctx context.Context, _ discovery.Path, rule parser.Ru if lm.Type != labels.MatchEqual && lm.Type != labels.MatchRegexp { continue } - if c.isLabelValueIgnored(rule, selector, lm.Name) { - slog.Debug("Label check disabled by comment", slog.String("selector", (&selector).String()), slog.String("label", lm.Name)) + if c.isLabelValueIgnored(settings, rule, selector, lm.Name) { continue } labelSelector := promParser.VectorSelector{ @@ -674,7 +674,17 @@ func (c SeriesCheck) getMinAge(rule parser.Rule, selector promParser.VectorSelec return minAge, problems } -func (c SeriesCheck) isLabelValueIgnored(rule parser.Rule, selector promParser.VectorSelector, labelName string) bool { +func (c SeriesCheck) isLabelValueIgnored(settings *PromqlSeriesSettings, rule parser.Rule, selector promParser.VectorSelector, labelName string) bool { + for matcher, names := range settings.IgnoreLabelsValue { + isMatch, _ := matchSelectorToMetric(selector, matcher) + if !isMatch { + continue + } + if slices.Contains(names, labelName) { + slog.Debug("Label check disabled globally via config", slog.String("label", labelName)) + return true + } + } for _, ruleSet := range comments.Only[comments.RuleSet](rule.Comments, comments.RuleSetType) { matcher, key, value := parseRuleSet(ruleSet.Value) if key != "ignore/label-value" { @@ -687,6 +697,7 @@ func (c SeriesCheck) isLabelValueIgnored(rule parser.Rule, selector promParser.V } } if labelName == value { + slog.Debug("Label check disabled by comment", slog.String("selector", (&selector).String()), slog.String("label", labelName)) return true } } diff --git a/internal/checks/promql_series_test.go b/internal/checks/promql_series_test.go index d15082d5..24ac3dc1 100644 --- a/internal/checks/promql_series_test.go +++ b/internal/checks/promql_series_test.go @@ -2294,7 +2294,7 @@ func TestSeriesCheck(t *testing.T) { }, }, { - description: "#5 ignored label value", + description: "#5 ignored label value via comment", content: ` - record: foo # pint rule/set promql/series ignore/label-value error @@ -2334,6 +2334,182 @@ func TestSeriesCheck(t *testing.T) { }, }, }, + { + description: "#5 ignored label value globally / match name", + content: ` +- record: foo + expr: sum(foo{error="notfound"}) +`, + checker: newSeriesCheck, + prometheus: newSimpleProm, + ctx: func(_ string) context.Context { + s := checks.PromqlSeriesSettings{ + IgnoreLabelsValue: map[string][]string{ + "foo": {"error"}, + }, + } + if err := s.Validate(); err != nil { + t.Error(err) + t.FailNow() + } + return context.WithValue(context.Background(), checks.SettingsKey(checks.SeriesCheckName), &s) + }, + problems: noProblems, + mocks: []*prometheusMock{ + { + conds: []requestCondition{ + requireQueryPath, + formCond{key: "query", value: `count(foo{error="notfound"})`}, + }, + resp: respondWithEmptyVector(), + }, + { + conds: []requestCondition{ + requireRangeQueryPath, + formCond{key: "query", value: `count(foo)`}, + }, + resp: respondWithSingleRangeVector1W(), + }, + { + conds: []requestCondition{ + requireRangeQueryPath, + formCond{key: "query", value: `absent(foo{error=~".+"})`}, + }, + resp: respondWithEmptyMatrix(), + }, + { + conds: []requestCondition{ + requireRangeQueryPath, + formCond{key: "query", value: "count(up)"}, + }, + resp: respondWithSingleRangeVector1W(), + }, + }, + }, + { + description: "#5 ignored label value globally / match selector", + content: ` +- record: foo + expr: sum(foo{error="notfound"}) +`, + checker: newSeriesCheck, + prometheus: newSimpleProm, + ctx: func(_ string) context.Context { + s := checks.PromqlSeriesSettings{ + IgnoreLabelsValue: map[string][]string{ + "foo{}": {"error"}, + }, + } + if err := s.Validate(); err != nil { + t.Error(err) + t.FailNow() + } + return context.WithValue(context.Background(), checks.SettingsKey(checks.SeriesCheckName), &s) + }, + problems: noProblems, + mocks: []*prometheusMock{ + { + conds: []requestCondition{ + requireQueryPath, + formCond{key: "query", value: `count(foo{error="notfound"})`}, + }, + resp: respondWithEmptyVector(), + }, + { + conds: []requestCondition{ + requireRangeQueryPath, + formCond{key: "query", value: `count(foo)`}, + }, + resp: respondWithSingleRangeVector1W(), + }, + { + conds: []requestCondition{ + requireRangeQueryPath, + formCond{key: "query", value: `absent(foo{error=~".+"})`}, + }, + resp: respondWithEmptyMatrix(), + }, + { + conds: []requestCondition{ + requireRangeQueryPath, + formCond{key: "query", value: "count(up)"}, + }, + resp: respondWithSingleRangeVector1W(), + }, + }, + }, + { + description: "#5 ignored label value globally / no match", + content: ` +- record: foo + expr: sum(foo{error="notfound"}) +`, + checker: newSeriesCheck, + prometheus: newSimpleProm, + ctx: func(_ string) context.Context { + s := checks.PromqlSeriesSettings{ + IgnoreLabelsValue: map[string][]string{ + "foo{cluster=\"dev\"}": {"error"}, + }, + } + if err := s.Validate(); err != nil { + t.Error(err) + t.FailNow() + } + return context.WithValue(context.Background(), checks.SettingsKey(checks.SeriesCheckName), &s) + }, + problems: func(uri string) []checks.Problem { + return []checks.Problem{ + { + Lines: parser.LineRange{ + First: 3, + Last: 3, + }, + Reporter: checks.SeriesCheckName, + Text: noFilterMatchText("prom", uri, "foo", "error", `{error="notfound"}`, "1w"), + Details: checks.SeriesCheckCommonProblemDetails, + Severity: checks.Bug, + }, + } + }, + mocks: []*prometheusMock{ + { + conds: []requestCondition{ + requireQueryPath, + formCond{key: "query", value: `count(foo{error="notfound"})`}, + }, + resp: respondWithEmptyVector(), + }, + { + conds: []requestCondition{ + requireRangeQueryPath, + formCond{key: "query", value: `count(foo{error="notfound"})`}, + }, + resp: respondWithEmptyMatrix(), + }, + { + conds: []requestCondition{ + requireRangeQueryPath, + formCond{key: "query", value: `count(foo)`}, + }, + resp: respondWithSingleRangeVector1W(), + }, + { + conds: []requestCondition{ + requireRangeQueryPath, + formCond{key: "query", value: `absent(foo{error=~".+"})`}, + }, + resp: respondWithEmptyMatrix(), + }, + { + conds: []requestCondition{ + requireRangeQueryPath, + formCond{key: "query", value: "count(up)"}, + }, + resp: respondWithSingleRangeVector1W(), + }, + }, + }, { description: "#5 ignored label value / selector match", content: `