Skip to content

Commit

Permalink
feat: add new validator expressionUsesUnderscoresInLargeNumbers
Browse files Browse the repository at this point in the history
Signed-off-by: Martin Chodur <[email protected]>
  • Loading branch information
FUSAKLA committed Oct 3, 2024
1 parent 183f2be commit 786f91a
Show file tree
Hide file tree
Showing 10 changed files with 75 additions and 9 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Changed: Upgrade to go 1.23
- Changed: Upgrade prometheus dependencies
- Changed: Enable experimental functions in PromQL parser by default, to forbid them use a new validator `expressionDoesNotUseExperimentalFunctions`
- Added: New validator `expressionDoesNotUseExperimentalFunctions` to check if the expression does not use experimental functions
- Added: New validator `expressionUsesUnderscoresInLargeNumbers` that requires using underscores as separators in large numbers in expressions

## [3.2.0]
- Added: Build of windows and darwin and support for arm architectures in CI
Expand Down
1 change: 1 addition & 0 deletions docs/default_validation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ validationRules:
- type: rateBeforeAggregation
- type: expressionWithNoMetricName
- type: expressionDoesNotUseExperimentalFunctions
- type: expressionUsesUnderscoresInLargeNumbers
- type: expressionIsWellFormatted
params:
showExpectedForm: true
Expand Down
6 changes: 6 additions & 0 deletions docs/validations.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ All the supported validations are listed here. The validations are grouped by th
- [`expressionDoesNotUseIrate`](#expressiondoesnotuseirate)
- [`validFunctionsOnCounters`](#validfunctionsoncounters)
- [`rateBeforeAggregation`](#ratebeforeaggregation)
- [`expressionUsesUnderscoresInLargeNumbers`](#expressionusesunderscoresinlargenumbers)
- [`expressionWithNoMetricName`](#expressionwithnometricname)
- [`expressionIsWellFormatted`](#expressioniswellformatted)
- [PromQL expression validators (using live Prometheus instance)](#promql-expression-validators-using-live-prometheus-instance)
Expand Down Expand Up @@ -318,6 +319,11 @@ Fails if the expression uses a `rate` or `increase` function on a metric that do
Fails if aggregation function is used before the `rate` or `increase` functions.
> Avoid common mistake of using aggregation function before the `rate` or `increase` function.

#### `expressionUsesUnderscoresInLargeNumbers`

Fails if the query containes numbers higher than 1000 without using underscores as separators for better readability.
Ignores numbers in the `10e2` and duration format.

#### `expressionWithNoMetricName`

Fails if an expression doesn't use an explicit metric name (also if used as `__name__` label) in all its selectors(eg `up{foo="bar"}`).
Expand Down
1 change: 1 addition & 0 deletions examples/human_readable.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ <h2><a href="#check-alert-title">check-alert-title</a></h2>
<h2><a href="#check-prometheus-limitations">check-prometheus-limitations</a></h2>
<ul>
<li>All rules expression does not use any experimental PromQL functions</li>
<li>All rules expression uses underscores as separators in large numbers in PromQL expression. Example: 1_000_000</li>
<li>All rules expression does not use data older than <code>6h0m0s</code></li>
<li>All rules does not use any of the <code>cluster</code>,<code>locality</code>,<code>prometheus-type</code>,<code>replica</code> labels is in its expression</li>
</ul>
Expand Down
1 change: 1 addition & 0 deletions examples/human_readable.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Validation rules:

check-prometheus-limitations
- All rules expression does not use any experimental PromQL functions
- All rules expression uses underscores as separators in large numbers in PromQL expression. Example: 1_000_000
- All rules expression does not use data older than `6h0m0s`
- All rules does not use any of the `cluster`,`locality`,`prometheus-type`,`replica` labels is in its expression

Expand Down
14 changes: 9 additions & 5 deletions examples/rules/rules.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ groups:
labels:
foo: bar

- alert: testExperimentalPromQL
expr: |
# ignore_validations: expressionCanBeEvaluated, expressionDoesNotUseExperimentalFunctions, hasLabels, hasAnyOfAnnotations, hasAnnotations
sort_by_label(
up,
"instance"
)
# ignore_validations: labelHasAllowedValue
- name: testGroup
limit: 1000
Expand All @@ -18,11 +26,7 @@ groups:
# Comment on the same line. ignore_validations: expressionSelectorsMatchesAnything, expressionDoesNotUseOlderDataThan
# Comment after.
- alert: test
expr: |
sort_by_label(
avg_over_time(max_over_time(up{job="prometheus"}[10h] offset 10d)[10m:10m]),
"instance"
)
expr: avg_over_time(max_over_time(up{job="prometheus"}[10h] offset 10d)[10m:10m])
for: 4w
keep_firing_for: 5m
labels:
Expand Down
3 changes: 2 additions & 1 deletion examples/validation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# customDisableComment: my_disable_comment

prometheus:
url: http://demo.robustperception.io:9090
url: https://prometheus.demo.do.prometheus.io
bearerTokenFile: ./bearer.token

validationRules:
Expand Down Expand Up @@ -66,6 +66,7 @@ validationRules:
scope: All rules
validations:
- type: expressionDoesNotUseExperimentalFunctions
- type: expressionUsesUnderscoresInLargeNumbers
- type: expressionDoesNotUseOlderDataThan
params:
limit: "6h"
Expand Down
12 changes: 9 additions & 3 deletions pkg/validator/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
type validatorCreator func(params yaml.Node) (Validator, error)

var registeredUniversalRuleValidators = map[string]validatorCreator{
// Labels
"hasLabels": newHasLabels,
"doesNotHaveLabels": newDoesNotHaveLabels,
"hasAnyOfLabels": newHasAnyOfLabels,
Expand All @@ -19,6 +20,7 @@ var registeredUniversalRuleValidators = map[string]validatorCreator{
"nonEmptyLabels": newNonEmptyLabels,
"exclusiveLabels": newExclusiveLabels,

// Expressions
"expressionIsValidPromQL": newExpressionIsValidPromQL,
"validFunctionsOnCounters": newValidFunctionsOnCounters,
"rateBeforeAggregation": newRateBeforeAggregation,
Expand All @@ -33,11 +35,15 @@ var registeredUniversalRuleValidators = map[string]validatorCreator{
"expressionSelectorsMatchesAnything": newExpressionSelectorsMatchesAnything,
"expressionWithNoMetricName": newExpressionWithNoMetricName,
"expressionIsWellFormatted": newExpressionIsWellFormatted,
"expressionIsValidLogQL": newExpressionIsValidLogQL,
"expressionUsesUnderscoresInLargeNumbers": newExpressionUsesUnderscoresInLargeNumbers,
"expressionDoesNotUseExperimentalFunctions": newExpressionDoesNotUseExperimentalFunctions,
"logQlExpressionUsesRangeAggregation": newLogQLExpressionUsesRangeAggregation,
"logQlExpressionUsesFiltersFirst": newlogQlExpressionUsesFiltersFirst,

// LogQL
"expressionIsValidLogQL": newExpressionIsValidLogQL,
"logQlExpressionUsesRangeAggregation": newLogQLExpressionUsesRangeAggregation,
"logQlExpressionUsesFiltersFirst": newlogQlExpressionUsesFiltersFirst,

// Other
"hasSourceTenantsForMetrics": newHasSourceTenantsForMetrics,
}

Expand Down
38 changes: 38 additions & 0 deletions pkg/validator/promql_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -616,3 +616,41 @@ func (h expressionDoesNotUseExperimentalFunctions) Validate(_ unmarshaler.RuleGr
}
return []error{}
}

func newExpressionUsesUnderscoresInLargeNumbers(paramsConfig yaml.Node) (Validator, error) {
params := struct{}{}
if err := paramsConfig.Decode(&params); err != nil {
return nil, err
}
return &expressionUsesUnderscoresInLargeNumbers{}, nil
}

type expressionUsesUnderscoresInLargeNumbers struct{}

func (h expressionUsesUnderscoresInLargeNumbers) String() string {
return "expression uses underscores as separators in large numbers in PromQL expression. Example: 1_000_000"
}

var numberRegexp = regexp.MustCompile(`^\d+$`)

func (h expressionUsesUnderscoresInLargeNumbers) Validate(_ unmarshaler.RuleGroup, rule rulefmt.Rule, _ *prometheus.Client) []error {
promQl, err := parser.ParseExpr(rule.Expr)
if err != nil {
return []error{fmt.Errorf("failed to parse expression `%s`: %w", rule.Expr, err)}
}
invalidNumbers := []string{}
parser.Inspect(promQl, func(n parser.Node, _ []parser.Node) error {
if number, ok := n.(*parser.NumberLiteral); ok {
numberStr := rule.Expr[number.PosRange.Start:number.PosRange.End]
// Ignore numbers that use 10e2 notation and duration notation (1m, 1h, etc.) and numbers that are less than 1000 where the underscore is not needed
if numberRegexp.MatchString(numberStr) && number.Val >= 1000 && !strings.Contains(numberStr, "_") {
invalidNumbers = append(invalidNumbers, number.String())
}
}
return nil
})
if len(invalidNumbers) > 0 {
return []error{fmt.Errorf("expression should use _ in large numbers (example: 1_000_0000) for better readability in these numbers: %s", strings.Join(invalidNumbers, ", "))}
}
return []error{}
}
6 changes: 6 additions & 0 deletions pkg/validator/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,12 @@ var testCases = []struct {
// expressionDoesNotUseExperimentalFunctions
{name: "expressionDoesNotUseExperimentalFunctions_valid", validator: expressionDoesNotUseExperimentalFunctions{}, rule: rulefmt.Rule{Expr: `sort_desc(up)`}, expectedErrors: 0},
{name: "expressionDoesNotUseExperimentalFunctions_invalid", validator: expressionDoesNotUseExperimentalFunctions{}, rule: rulefmt.Rule{Expr: `sort_by_label(up, "instance")`}, expectedErrors: 1},

// expressionUsesUnderscoresInLargeNumbers
{name: "expressionUsesUnderscoresInLargeNumbers_valid", validator: expressionUsesUnderscoresInLargeNumbers{}, rule: rulefmt.Rule{Expr: `vector(time()) > 100`}, expectedErrors: 0},
{name: "expressionUsesUnderscoresInLargeNumbers_valid_duration", validator: expressionUsesUnderscoresInLargeNumbers{}, rule: rulefmt.Rule{Expr: `vector(time()) > 100h`}, expectedErrors: 0},
{name: "expressionUsesUnderscoresInLargeNumbers_valid_exp", validator: expressionUsesUnderscoresInLargeNumbers{}, rule: rulefmt.Rule{Expr: `vector(time()) > 10e12`}, expectedErrors: 0},
{name: "expressionUsesUnderscoresInLargeNumbers_invalid", validator: expressionUsesUnderscoresInLargeNumbers{}, rule: rulefmt.Rule{Expr: `time() > 1000`}, expectedErrors: 1},
}

func Test(t *testing.T) {
Expand Down

0 comments on commit 786f91a

Please sign in to comment.