From 1faf33acacd28161ef8019e6beac68a10e524bfb Mon Sep 17 00:00:00 2001 From: Matt Layher Date: Sat, 15 Apr 2017 17:38:01 -0400 Subject: [PATCH] Add promlint check for histogram/summary reserved names (#2626) --- util/promlint/promlint.go | 43 +++++++++ util/promlint/promlint_test.go | 163 +++++++++++++++++++++++++++++++++ 2 files changed, 206 insertions(+) diff --git a/util/promlint/promlint.go b/util/promlint/promlint.go index 458fe6d4c..8ac1ae876 100644 --- a/util/promlint/promlint.go +++ b/util/promlint/promlint.go @@ -100,6 +100,7 @@ func lint(mf dto.MetricFamily) []Problem { lintHelp, lintMetricUnits, lintCounter, + lintHistogramSummaryReserved, } var problems []Problem @@ -162,6 +163,48 @@ func lintCounter(mf dto.MetricFamily) []Problem { return problems } +// 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 { + // These rules do not apply to untyped metrics. + t := mf.GetType() + if t == dto.MetricType_UNTYPED { + return nil + } + + var problems problems + + isHistogram := t == dto.MetricType_HISTOGRAM + isSummary := t == dto.MetricType_SUMMARY + + n := mf.GetName() + + if !isHistogram && strings.HasSuffix(n, "_bucket") { + problems.Add(mf, `non-histogram metrics should not have "_bucket" suffix`) + } + if !isHistogram && !isSummary && strings.HasSuffix(n, "_count") { + problems.Add(mf, `non-histogram and non-summary metrics should not have "_count" suffix`) + } + if !isHistogram && !isSummary && strings.HasSuffix(n, "_sum") { + problems.Add(mf, `non-histogram and non-summary metrics should not have "_sum" suffix`) + } + + for _, m := range mf.GetMetric() { + for _, l := range m.GetLabel() { + ln := l.GetName() + + if !isHistogram && ln == "le" { + problems.Add(mf, `non-histogram metrics should not have "le" label`) + } + if !isSummary && ln == "quantile" { + problems.Add(mf, `non-summary metrics should not have "quantile" label`) + } + } + } + + return problems +} + // metricUnits attempts to detect known unit types used as part of a metric name, // e.g. "foo_bytes_total" or "bar_baz_milligrams". func metricUnits(m string) (unit string, base string, ok bool) { diff --git a/util/promlint/promlint_test.go b/util/promlint/promlint_test.go index 19358d69e..dec3ed613 100644 --- a/util/promlint/promlint_test.go +++ b/util/promlint/promlint_test.go @@ -332,3 +332,166 @@ x_bytes 10 }) } } + +func TestLintHistogramSummaryReserved(t *testing.T) { + tests := []struct { + name string + in string + problems []promlint.Problem + }{ + { + name: "gauge with _bucket suffix", + in: ` +# HELP x_bytes_bucket Test metric. +# TYPE x_bytes_bucket gauge +x_bytes_bucket 10 +`, + problems: []promlint.Problem{{ + Metric: "x_bytes_bucket", + Text: `non-histogram metrics should not have "_bucket" suffix`, + }}, + }, + { + name: "gauge with _count suffix", + in: ` +# HELP x_bytes_count Test metric. +# TYPE x_bytes_count gauge +x_bytes_count 10 +`, + problems: []promlint.Problem{{ + Metric: "x_bytes_count", + Text: `non-histogram and non-summary metrics should not have "_count" suffix`, + }}, + }, + { + name: "gauge with _sum suffix", + in: ` +# HELP x_bytes_sum Test metric. +# TYPE x_bytes_sum gauge +x_bytes_sum 10 +`, + problems: []promlint.Problem{{ + Metric: "x_bytes_sum", + Text: `non-histogram and non-summary metrics should not have "_sum" suffix`, + }}, + }, + { + name: "gauge with le label", + in: ` +# HELP x_bytes Test metric. +# TYPE x_bytes gauge +x_bytes{le="1"} 10 +`, + problems: []promlint.Problem{{ + Metric: "x_bytes", + Text: `non-histogram metrics should not have "le" label`, + }}, + }, + { + name: "gauge with quantile label", + in: ` +# HELP x_bytes Test metric. +# TYPE x_bytes gauge +x_bytes{quantile="1"} 10 +`, + problems: []promlint.Problem{{ + Metric: "x_bytes", + Text: `non-summary metrics should not have "quantile" label`, + }}, + }, + { + name: "histogram with quantile label", + in: ` +# HELP tsdb_compaction_duration Duration of compaction runs. +# TYPE tsdb_compaction_duration histogram +tsdb_compaction_duration_bucket{le="0.005",quantile="0.01"} 0 +tsdb_compaction_duration_bucket{le="0.01",quantile="0.01"} 0 +tsdb_compaction_duration_bucket{le="0.025",quantile="0.01"} 0 +tsdb_compaction_duration_bucket{le="0.05",quantile="0.01"} 0 +tsdb_compaction_duration_bucket{le="0.1",quantile="0.01"} 0 +tsdb_compaction_duration_bucket{le="0.25",quantile="0.01"} 0 +tsdb_compaction_duration_bucket{le="0.5",quantile="0.01"} 57 +tsdb_compaction_duration_bucket{le="1",quantile="0.01"} 68 +tsdb_compaction_duration_bucket{le="2.5",quantile="0.01"} 69 +tsdb_compaction_duration_bucket{le="5",quantile="0.01"} 69 +tsdb_compaction_duration_bucket{le="10",quantile="0.01"} 69 +tsdb_compaction_duration_bucket{le="+Inf",quantile="0.01"} 69 +tsdb_compaction_duration_sum 28.740810936000006 +tsdb_compaction_duration_count 69 +`, + problems: []promlint.Problem{{ + Metric: "tsdb_compaction_duration", + Text: `non-summary metrics should not have "quantile" label`, + }}, + }, + { + name: "summary with le label", + in: ` +# HELP go_gc_duration_seconds A summary of the GC invocation durations. +# TYPE go_gc_duration_seconds summary +go_gc_duration_seconds{quantile="0",le="0.01"} 4.2365e-05 +go_gc_duration_seconds{quantile="0.25",le="0.01"} 8.1492e-05 +go_gc_duration_seconds{quantile="0.5",le="0.01"} 0.000100656 +go_gc_duration_seconds{quantile="0.75",le="0.01"} 0.000113913 +go_gc_duration_seconds{quantile="1",le="0.01"} 0.021754305 +go_gc_duration_seconds_sum 1.769429004 +go_gc_duration_seconds_count 5962 +`, + problems: []promlint.Problem{{ + Metric: "go_gc_duration_seconds", + Text: `non-histogram metrics should not have "le" label`, + }}, + }, + { + name: "histogram OK", + in: ` +# HELP tsdb_compaction_duration Duration of compaction runs. +# TYPE tsdb_compaction_duration histogram +tsdb_compaction_duration_bucket{le="0.005"} 0 +tsdb_compaction_duration_bucket{le="0.01"} 0 +tsdb_compaction_duration_bucket{le="0.025"} 0 +tsdb_compaction_duration_bucket{le="0.05"} 0 +tsdb_compaction_duration_bucket{le="0.1"} 0 +tsdb_compaction_duration_bucket{le="0.25"} 0 +tsdb_compaction_duration_bucket{le="0.5"} 57 +tsdb_compaction_duration_bucket{le="1"} 68 +tsdb_compaction_duration_bucket{le="2.5"} 69 +tsdb_compaction_duration_bucket{le="5"} 69 +tsdb_compaction_duration_bucket{le="10"} 69 +tsdb_compaction_duration_bucket{le="+Inf"} 69 +tsdb_compaction_duration_sum 28.740810936000006 +tsdb_compaction_duration_count 69 +`, + }, + { + name: "summary OK", + in: ` +# HELP go_gc_duration_seconds A summary of the GC invocation durations. +# TYPE go_gc_duration_seconds summary +go_gc_duration_seconds{quantile="0"} 4.2365e-05 +go_gc_duration_seconds{quantile="0.25"} 8.1492e-05 +go_gc_duration_seconds{quantile="0.5"} 0.000100656 +go_gc_duration_seconds{quantile="0.75"} 0.000113913 +go_gc_duration_seconds{quantile="1"} 0.021754305 +go_gc_duration_seconds_sum 1.769429004 +go_gc_duration_seconds_count 5962 +`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := promlint.New(strings.NewReader(tt.in)) + + problems, err := l.Lint() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if want, got := tt.problems, problems; !reflect.DeepEqual(want, got) { + t.Fatalf("unexpected problems:\n- want: %v\n- got: %v", + want, got) + } + }) + } +}