Skip to content

Commit

Permalink
Get rid of rulefmt.Parse
Browse files Browse the repository at this point in the history
rulefmt.Parse checks if a rule file will pass Prometheus checks but it's very expensive.
On top of that it does a lot of what we do later on anyway, but without giving us same level of detail.
  • Loading branch information
prymitive committed Jul 4, 2024
1 parent 0e6bbd7 commit fdcb74e
Show file tree
Hide file tree
Showing 34 changed files with 1,460 additions and 240 deletions.
1 change: 1 addition & 0 deletions .github/spellcheck/wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ promql
PromQL
PRs
prymitive
rulefmt
samber
SNI
symlink
Expand Down
50 changes: 11 additions & 39 deletions cmd/pint/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,9 @@ import (
"errors"
"fmt"
"log/slog"
"regexp"
"strconv"
"sync"
"time"

"github.com/prometheus/prometheus/model/rulefmt"
"go.uber.org/atomic"

"github.com/cloudflare/pint/internal/checks"
Expand All @@ -22,41 +19,14 @@ import (
"github.com/cloudflare/pint/internal/reporter"
)

var (
yamlErrRe = regexp.MustCompile("^yaml: line (.+): (.+)")
yamlUnmarshalErrRe = regexp.MustCompile("^yaml: unmarshal errors:\n line (.+): (.+)")
rulefmtGroupRe = regexp.MustCompile("^([0-9]+):[0-9]+: group \".+\", rule [0-9]+, (.+)")
rulefmtGroupnameRe = regexp.MustCompile("^([0-9]+):[0-9]+: (groupname: .+)")
)

const (
yamlParseReporter = "yaml/parse"
ignoreFileReporter = "ignore/file"
pintCommentReporter = "pint/comment"
)

func tryDecodingYamlError(err error) (l int, s string) {
s = err.Error()

werr := &rulefmt.WrappedError{}
if errors.As(err, &werr) {
if uerr := werr.Unwrap(); uerr != nil {
s = uerr.Error()
}
}

for _, re := range []*regexp.Regexp{yamlErrRe, yamlUnmarshalErrRe, rulefmtGroupRe, rulefmtGroupnameRe} {
parts := re.FindStringSubmatch(err.Error())
if len(parts) > 2 {
line, err2 := strconv.Atoi(parts[1])
if err2 != nil || line <= 0 {
return 1, s
}
return line, parts[2]
}
}
return 1, s
}
yamlDetails = `This Prometheus rule is not valid.
This usually means that it's missing some required fields.`
)

func checkRules(ctx context.Context, workers int, isOffline bool, gen *config.PrometheusGenerator, cfg config.Config, entries []discovery.Entry) (summary reporter.Summary, err error) {
if isOffline {
Expand Down Expand Up @@ -224,7 +194,6 @@ func scanWorker(ctx context.Context, jobs <-chan scanJob, results chan<- reporte
Owner: job.entry.Owner,
}
case job.entry.PathError != nil:
line, e := tryDecodingYamlError(job.entry.PathError)
results <- reporter.Report{
Path: discovery.Path{
Name: job.entry.Path.Name,
Expand All @@ -233,11 +202,11 @@ func scanWorker(ctx context.Context, jobs <-chan scanJob, results chan<- reporte
ModifiedLines: job.entry.ModifiedLines,
Problem: checks.Problem{
Lines: parser.LineRange{
First: line,
Last: line,
First: 1,
Last: 1,
},
Reporter: yamlParseReporter,
Text: fmt.Sprintf("YAML parser returned an error when reading this file: `%s`.", e),
Text: fmt.Sprintf("YAML parser returned an error when reading this file: `%s`.", job.entry.PathError),
Details: `pint cannot read this file because YAML parser returned an error.
This usually means that you have an indention error or the file doesn't have the YAML structure required by Prometheus for [recording](https://prometheus.io/docs/prometheus/latest/configuration/recording_rules/) and [alerting](https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/) rules.
If this file is a template that will be rendered into valid YAML then you can instruct pint to ignore some lines using comments, see [pint docs](https://cloudflare.github.io/pint/ignoring.html).
Expand All @@ -247,6 +216,10 @@ If this file is a template that will be rendered into valid YAML then you can in
Owner: job.entry.Owner,
}
case job.entry.Rule.Error.Err != nil:
details := yamlDetails
if job.entry.Rule.Error.Details != "" {
details = job.entry.Rule.Error.Details
}
results <- reporter.Report{
Path: discovery.Path{
Name: job.entry.Path.Name,
Expand All @@ -261,8 +234,7 @@ If this file is a template that will be rendered into valid YAML then you can in
},
Reporter: yamlParseReporter,
Text: fmt.Sprintf("This rule is not a valid Prometheus rule: `%s`.", job.entry.Rule.Error.Err.Error()),
Details: `This Prometheus rule is not valid.
This usually means that it's missing some required fields.`,
Details: details,
Severity: checks.Fatal,
},
Owner: job.entry.Owner,
Expand Down
6 changes: 3 additions & 3 deletions cmd/pint/tests/0004_fail_invalid_yaml.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ cmp stderr stderr.txt
-- stderr.txt --
level=INFO msg="Loading configuration file" path=.pint.hcl
level=INFO msg="Finding all rules to check" paths=["rules"]
level=ERROR msg="Failed to parse file content" err="yaml: line 4: did not find expected key" path=rules/bad.yaml lines=1-7
rules/bad.yaml:4 Fatal: YAML parser returned an error when reading this file: `did not find expected key`. (yaml/parse)
4 |
level=WARN msg="Failed to parse file content" err="error at line 4: did not find expected key" path=rules/bad.yaml lines=1-7
rules/bad.yaml:1 Fatal: YAML parser returned an error when reading this file: `error at line 4: did not find expected key`. (yaml/parse)
1 | xxx:

rules/ok.yml:5 Fatal: Prometheus failed to parse the query with this PromQL error: unclosed left bracket. (promql/syntax)
5 | expr: sum(foo[5m)
Expand Down
6 changes: 3 additions & 3 deletions cmd/pint/tests/0010_syntax_check.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ cmp stderr stderr.txt
-- stderr.txt --
level=INFO msg="Loading configuration file" path=.pint.hcl
level=INFO msg="Finding all rules to check" paths=["rules"]
level=ERROR msg="Failed to parse file content" err="yaml: line 6: did not find expected '-' indicator" path=rules/1.yaml lines=1-12
rules/1.yaml:6 Fatal: YAML parser returned an error when reading this file: `did not find expected '-' indicator`. (yaml/parse)
6 |
level=WARN msg="Failed to parse file content" err="error at line 6: did not find expected '-' indicator" path=rules/1.yaml lines=1-12
rules/1.yaml:1 Fatal: YAML parser returned an error when reading this file: `error at line 6: did not find expected '-' indicator`. (yaml/parse)
1 | - alert: Good

level=INFO msg="Problems found" Fatal=1
level=ERROR msg="Fatal error" err="found 1 problem(s) with severity Bug or higher"
Expand Down
5 changes: 3 additions & 2 deletions cmd/pint/tests/0067_relaxed.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ cmp stderr stderr.txt
-- stderr.txt --
level=INFO msg="Loading configuration file" path=.pint.hcl
level=INFO msg="Finding all rules to check" paths=["rules"]
rules/strict.yml:2 Fatal: YAML parser returned an error when reading this file: `cannot unmarshal !!seq into rulefmt.RuleGroups`. (yaml/parse)
2 | - alert: No Owner
level=WARN msg="Failed to parse file content" err="error at line 2: top level field must be a groups key, got list" path=rules/strict.yml lines=1-4
rules/strict.yml:1 Fatal: YAML parser returned an error when reading this file: `error at line 2: top level field must be a groups key, got list`. (yaml/parse)
1 | {%- raw %} # pint ignore/line

level=INFO msg="Problems found" Fatal=1
level=ERROR msg="Fatal error" err="found 1 problem(s) with severity Bug or higher"
Expand Down
1 change: 1 addition & 0 deletions cmd/pint/tests/0071_ci_owner.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ groups:

-- src/.pint.hcl --
ci {
include = [".+.yml"]
baseBranch = "main"
}
repository {
Expand Down
5 changes: 3 additions & 2 deletions cmd/pint/tests/0074_strict_error.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ cmp stderr stderr.txt

-- stderr.txt --
level=INFO msg="Finding all rules to check" paths=["rules"]
rules/strict.yml:2 Fatal: YAML parser returned an error when reading this file: `field alert not found in type rulefmt.RuleGroup`. (yaml/parse)
2 | - alert: Conntrack_Table_Almost_Full
level=WARN msg="Failed to parse file content" err="error at line 2: invalid group key alert" path=rules/strict.yml lines=1-9
rules/strict.yml:1 Fatal: YAML parser returned an error when reading this file: `error at line 2: invalid group key alert`. (yaml/parse)
1 | groups:

level=INFO msg="Problems found" Fatal=1
level=ERROR msg="Fatal error" err="found 1 problem(s) with severity Bug or higher"
Expand Down
1 change: 1 addition & 0 deletions cmd/pint/tests/0076_ci_group_errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ groups:
-- src/.pint.hcl --
ci {
baseBranch = "main"
include = [".+.yml"]
}
repository {
bitbucket {
Expand Down
5 changes: 3 additions & 2 deletions cmd/pint/tests/0078_repeated_group.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ cmp stderr stderr.txt

-- stderr.txt --
level=INFO msg="Finding all rules to check" paths=["rules"]
rules/strict.yml:4 Fatal: YAML parser returned an error when reading this file: `groupname: "foo" is repeated in the same file`. (yaml/parse)
4 | - name: foo
level=WARN msg="Failed to parse file content" err="error at line 4: duplicated group name" path=rules/strict.yml lines=1-5
rules/strict.yml:1 Fatal: YAML parser returned an error when reading this file: `error at line 4: duplicated group name`. (yaml/parse)
1 | groups:

level=INFO msg="Problems found" Fatal=1
level=ERROR msg="Fatal error" err="found 1 problem(s) with severity Bug or higher"
Expand Down
1 change: 1 addition & 0 deletions cmd/pint/tests/0123_ci_owner_allowed.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ owners {
}
ci {
baseBranch = "main"
include = [".+.yml"]
}
repository {
bitbucket {
Expand Down
1 change: 1 addition & 0 deletions cmd/pint/tests/0160_ci_comment_edit.txt
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ groups:
-- src/.pint.hcl --
ci {
baseBranch = "main"
include = [".+.yml"]
}
prometheus "prom" {
uri = "http://127.0.0.1:7160"
Expand Down
1 change: 1 addition & 0 deletions cmd/pint/tests/0161_ci_move_files.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ groups:
-- src/.pint.hcl --
ci {
baseBranch = "main"
include = [".+.yml"]
}
prometheus "prom1" {
uri = "http://127.0.0.1:7161/1"
Expand Down
1 change: 1 addition & 0 deletions cmd/pint/tests/0162_ci_deleted_dependency.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ groups:
-- src/.pint.hcl --
ci {
baseBranch = "main"
include = [".+.yml"]
}
prometheus "prom" {
uri = "http://127.0.0.1:7162"
Expand Down
1 change: 1 addition & 0 deletions cmd/pint/tests/0167_rule_duplicate_symlink.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ cmp stderr ../stderrV1.txt
-- stderrV1.txt --
level=INFO msg="Loading configuration file" path=.pint.hcl
level=INFO msg="Finding all rules to check on current git branch" base=main
level=WARN msg="Failed to parse file content" err="error at line 1: top level field must be a groups key, got string" path=.pint.hcl lines=1-20
level=INFO msg="Configured new Prometheus server" name=prom1 uris=1 uptime=up tags=[] include=["^rules.yml$"] exclude=[]
level=INFO msg="Configured new Prometheus server" name=prom2 uris=1 uptime=up tags=[] include=["^symlink.yml$"] exclude=[]
-- stderrV2.txt --
Expand Down
1 change: 1 addition & 0 deletions cmd/pint/tests/0173_rule_duplicate_move.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ cmp stderr ../stderr.txt
-- stderr.txt --
level=INFO msg="Loading configuration file" path=.pint.hcl
level=INFO msg="Finding all rules to check on current git branch" base=main
level=WARN msg="Failed to parse file content" err="error at line 1: top level field must be a groups key, got string" path=.pint.hcl lines=1-24
level=INFO msg="Configured new Prometheus server" name=prom1 uris=1 uptime=up tags=[] include=["^rules/alert.*$"] exclude=[]
level=INFO msg="Configured new Prometheus server" name=prom2a uris=1 uptime=up tags=[] include=["^rules/record.*$"] exclude=[]
level=INFO msg="Configured new Prometheus server" name=prom2b uris=1 uptime=up tags=[] include=["^rules/record.*$"] exclude=[]
Expand Down
4 changes: 4 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
- Don't suggest using `humanize` when alert template is already using printf on format the `$value`.
- Fixed git history parsing when running `pint ci` on a branch that include merge commits.

### Changed

- Refactored YAML syntax checks to avoid using [rulefmt.Parse](https://pkg.go.dev/github.com/prometheus/prometheus@v0.53.0/model/rulefmt#Parse) and effectively parsing rules twice. Some error messages will have different formatting.

## v0.60.0

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion internal/checks/base_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func runTests(t *testing.T, testCases []checkTest) {
}

func parseContent(content string) (entries []discovery.Entry, err error) {
p := parser.NewParser()
p := parser.NewParser(false)
rules, err := p.Parse([]byte(content))
if err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion internal/checks/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func TestTemplatedRegexpExpand(t *testing.T) {
}

func newMustRule(content string) parser.Rule {
p := parser.NewParser()
p := parser.NewParser(false)
rules, err := p.Parse([]byte(content))
if err != nil {
panic(err)
Expand Down
2 changes: 1 addition & 1 deletion internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func TestSetDisabledChecks(t *testing.T) {
}

func newRule(t *testing.T, content string) parser.Rule {
p := parser.NewParser()
p := parser.NewParser(false)
rules, err := p.Parse([]byte(content))
if err != nil {
t.Error(err)
Expand Down
55 changes: 2 additions & 53 deletions internal/discovery/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ import (
"fmt"
"io"
"log/slog"
"strings"
"time"

"github.com/prometheus/prometheus/model/rulefmt"
"golang.org/x/exp/slices"

"github.com/cloudflare/pint/internal/comments"
Expand All @@ -23,17 +21,6 @@ const (
RuleOwnerComment = "rule/owner"
)

var ignoredErrors = []string{
"one of 'record' or 'alert' must be set",
"field 'expr' must be set in rule",
"could not parse expression: ",
"cannot unmarshal !!seq into rulefmt.ruleGroups",
": template: __",
"invalid label name: ",
"invalid annotation name: ",
"invalid recording rule name: ",
}

type FileIgnoreError struct {
Err error
Line int
Expand All @@ -43,16 +30,6 @@ func (fe FileIgnoreError) Error() string {
return fe.Err.Error()
}

func isStrictIgnored(err error) bool {
s := err.Error()
for _, ign := range ignoredErrors {
if strings.Contains(s, ign) {
return true
}
}
return false
}

type ChangeType uint8

func (c ChangeType) String() string {
Expand Down Expand Up @@ -113,8 +90,6 @@ type Entry struct {
}

func readRules(reportedPath, sourcePath string, r io.Reader, isStrict bool) (entries []Entry, err error) {
p := parser.NewParser()

content, fileComments, err := parser.ReadContent(r)
if err != nil {
return nil, err
Expand Down Expand Up @@ -182,36 +157,10 @@ func readRules(reportedPath, sourcePath string, r io.Reader, isStrict bool) (ent
return entries, nil
}

if isStrict {
if _, errs := rulefmt.Parse(content.Body); len(errs) > 0 {
seen := map[string]struct{}{}
for _, err := range errs {
if isStrictIgnored(err) {
continue
}
if _, ok := seen[err.Error()]; ok {
continue
}
seen[err.Error()] = struct{}{}
entries = append(entries, Entry{
Path: Path{
Name: sourcePath,
SymlinkTarget: reportedPath,
},
PathError: err,
Owner: fileOwner,
ModifiedLines: contentLines.Expand(),
})
}
if len(entries) > 0 {
return entries, nil
}
}
}

p := parser.NewParser(isStrict)
rules, err := p.Parse(content.Body)
if err != nil {
slog.Error(
slog.Warn(
"Failed to parse file content",
slog.Any("err", err),
slog.String("path", sourcePath),
Expand Down
2 changes: 1 addition & 1 deletion internal/discovery/discovery_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func (r failingReader) Read(_ []byte) (int, error) {

func TestReadRules(t *testing.T) {
mustParse := func(offset int, s string) parser.Rule {
p := parser.NewParser()
p := parser.NewParser(false)
r, err := p.Parse([]byte(strings.Repeat("\n", offset) + s))
if err != nil {
panic(fmt.Sprintf("failed to parse rule:\n---\n%s\n---\nerror: %s", s, err))
Expand Down
Loading

0 comments on commit fdcb74e

Please sign in to comment.