From 6433bcf819e24b6d1841cfc3a05e688fe6083480 Mon Sep 17 00:00:00 2001 From: beorn7 Date: Fri, 24 Apr 2020 23:42:49 +0200 Subject: [PATCH] Add the capability to lint MetricFamilies directly Also, change all the `dto.MetricFamily` arguments to pointers to be more consistent with what we do in client_golang in general. Signed-off-by: beorn7 --- prometheus/testutil/promlint/promlint.go | 63 ++++++++++++++---------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/prometheus/testutil/promlint/promlint.go b/prometheus/testutil/promlint/promlint.go index 2e00822..18ca942 100644 --- a/prometheus/testutil/promlint/promlint.go +++ b/prometheus/testutil/promlint/promlint.go @@ -29,7 +29,8 @@ import ( // A Linter is a Prometheus metrics linter. It identifies issues with metric // names, types, and metadata, and reports them to the caller. type Linter struct { - r io.Reader + r io.Reader + mfs []*dto.MetricFamily } // A Problem is an issue detected by a Linter. @@ -42,40 +43,52 @@ type Problem struct { } // newProblem is helper function to create a Problem. -func newProblem(mf dto.MetricFamily, text string) Problem { +func newProblem(mf *dto.MetricFamily, text string) Problem { return Problem{ Metric: mf.GetName(), Text: text, } } -// New creates a new Linter that reads an input stream of Prometheus metrics. -// Only the Prometheus text exposition format is supported. +// New creates a new Linter that reads an input stream of Prometheus metrics in +// the Prometheus text exposition format. func New(r io.Reader) *Linter { return &Linter{ r: r, } } +// NewWithMetricFamilies creates a new Linter that reads from a slice of +// MetricFamily protobuf messages. +func NewWithMetricFamilies(mfs []*dto.MetricFamily) *Linter { + return &Linter{ + mfs: mfs, + } +} + // Lint performs a linting pass, returning a slice of Problems indicating any -// issues found in the metrics stream. The slice is sorted by metric name +// issues found in the metrics stream. The slice is sorted by metric name // and issue description. func (l *Linter) Lint() ([]Problem, error) { - // TODO(mdlayher): support for protobuf exposition format? - d := expfmt.NewDecoder(l.r, expfmt.FmtText) - var problems []Problem - var mf dto.MetricFamily - for { - if err := d.Decode(&mf); err != nil { - if err == io.EOF { - break + if l.r != nil { + d := expfmt.NewDecoder(l.r, expfmt.FmtText) + + mf := &dto.MetricFamily{} + for { + if err := d.Decode(mf); err != nil { + if err == io.EOF { + break + } + + return nil, err } - return nil, err + problems = append(problems, lint(mf)...) } - + } + for _, mf := range l.mfs { problems = append(problems, lint(mf)...) } @@ -91,8 +104,8 @@ func (l *Linter) Lint() ([]Problem, error) { } // lint is the entry point for linting a single metric. -func lint(mf dto.MetricFamily) []Problem { - fns := []func(mf dto.MetricFamily) []Problem{ +func lint(mf *dto.MetricFamily) []Problem { + fns := []func(mf *dto.MetricFamily) []Problem{ lintHelp, lintMetricUnits, lintCounter, @@ -113,7 +126,7 @@ func lint(mf dto.MetricFamily) []Problem { } // lintHelp detects issues related to the help text for a metric. -func lintHelp(mf dto.MetricFamily) []Problem { +func lintHelp(mf *dto.MetricFamily) []Problem { var problems []Problem // Expect all metrics to have help text available. @@ -125,7 +138,7 @@ func lintHelp(mf dto.MetricFamily) []Problem { } // lintMetricUnits detects issues with metric unit names. -func lintMetricUnits(mf dto.MetricFamily) []Problem { +func lintMetricUnits(mf *dto.MetricFamily) []Problem { var problems []Problem unit, base, ok := metricUnits(*mf.Name) @@ -146,7 +159,7 @@ func lintMetricUnits(mf dto.MetricFamily) []Problem { // lintCounter detects issues specific to counters, as well as patterns that should // only be used with counters. -func lintCounter(mf dto.MetricFamily) []Problem { +func lintCounter(mf *dto.MetricFamily) []Problem { var problems []Problem isCounter := mf.GetType() == dto.MetricType_COUNTER @@ -165,7 +178,7 @@ func lintCounter(mf dto.MetricFamily) []Problem { // lintHistogramSummaryReserved detects when other types of metrics use names or labels // reserved for use by histograms and/or summaries. -func lintHistogramSummaryReserved(mf dto.MetricFamily) []Problem { +func lintHistogramSummaryReserved(mf *dto.MetricFamily) []Problem { // These rules do not apply to untyped metrics. t := mf.GetType() if t == dto.MetricType_UNTYPED { @@ -206,7 +219,7 @@ func lintHistogramSummaryReserved(mf dto.MetricFamily) []Problem { } // lintMetricTypeInName detects when metric types are included in the metric name. -func lintMetricTypeInName(mf dto.MetricFamily) []Problem { +func lintMetricTypeInName(mf *dto.MetricFamily) []Problem { var problems []Problem n := strings.ToLower(mf.GetName()) @@ -224,7 +237,7 @@ func lintMetricTypeInName(mf dto.MetricFamily) []Problem { } // lintReservedChars detects colons in metric names. -func lintReservedChars(mf dto.MetricFamily) []Problem { +func lintReservedChars(mf *dto.MetricFamily) []Problem { var problems []Problem if strings.Contains(mf.GetName(), ":") { problems = append(problems, newProblem(mf, "metric names should not contain ':'")) @@ -235,7 +248,7 @@ func lintReservedChars(mf dto.MetricFamily) []Problem { var camelCase = regexp.MustCompile(`[a-z][A-Z]`) // lintCamelCase detects metric names and label names written in camelCase. -func lintCamelCase(mf dto.MetricFamily) []Problem { +func lintCamelCase(mf *dto.MetricFamily) []Problem { var problems []Problem if camelCase.FindString(mf.GetName()) != "" { problems = append(problems, newProblem(mf, "metric names should be written in 'snake_case' not 'camelCase'")) @@ -252,7 +265,7 @@ func lintCamelCase(mf dto.MetricFamily) []Problem { } // lintUnitAbbreviations detects abbreviated units in the metric name. -func lintUnitAbbreviations(mf dto.MetricFamily) []Problem { +func lintUnitAbbreviations(mf *dto.MetricFamily) []Problem { var problems []Problem n := strings.ToLower(mf.GetName()) for _, s := range unitAbbreviations {