From dae5d782a63f73f82ed650cc07b403f6ae03b3c5 Mon Sep 17 00:00:00 2001 From: Thomas Gummerer Date: Mon, 27 Jan 2020 14:50:34 +0000 Subject: [PATCH] allow setting granularity for summary metrics The Go client for prometheus aggregates summary metrics over 10 minutes by default, in 5 buckets. This is not always the behaviour we want. Allow tweaking those settings in `statsd_exporter`, so we can aggregate summary metrics over more or less time, with more or fewer buckets, and set the cap for the bucket as well. Signed-off-by: Thomas Gummerer --- README.md | 12 ++++++++++- pkg/mapper/mapper.go | 9 ++++---- pkg/mapper/mapper_test.go | 45 +++++++++++++++++++++++++++++++++++++++ registry.go | 3 +++ 4 files changed, 63 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 76aa73e..830b57c 100644 --- a/README.md +++ b/README.md @@ -236,7 +236,8 @@ mappings: By default, statsd timers are represented as a Prometheus summary with quantiles. You may optionally configure the [quantiles and acceptable -error](https://prometheus.io/docs/practices/histograms/#quantiles): +error](https://prometheus.io/docs/practices/histograms/#quantiles), as +well as adjusting how the summary metric is aggregated: ```yaml mappings: @@ -257,10 +258,19 @@ mappings: error: 0.05 - quantile: 0.5 error: 0.005 + max_summary_age: 30s + summary_age_buckets: 3 + stream_buffer_size: 1000 ``` The default quantiles are 0.99, 0.9, and 0.5. +The default summary age is 10 minutes, the default number of buckets +is 5 and the default buffer size is 500. See also the +[`golang_client` docs](https://godoc.org/github.com/prometheus/client_golang/prometheus#SummaryOpts). +The `max_summary_age` corresponds to `SummaryOptions.MaxAge`, `summary_age_buckets` +to `SummaryOptions.AgeBuckets` and `stream_buffer_size` to `SummaryOptions.BufCap`. + In the configuration, one may also set the timer type to "histogram". The default is "summary" as in the plain text configuration format. For example, to set the timer type for a single metric: diff --git a/pkg/mapper/mapper.go b/pkg/mapper/mapper.go index e1ec937..61814ad 100644 --- a/pkg/mapper/mapper.go +++ b/pkg/mapper/mapper.go @@ -77,7 +77,10 @@ type MetricMapping struct { } type SummaryOptions struct { - Quantiles []metricObjective `yaml:"quantiles"` + Quantiles []metricObjective `yaml:"quantiles"` + MaxAge time.Duration `yaml:"max_age"` + AgeBuckets uint32 `yaml:"age_buckets"` + BufCap uint32 `yaml:"buf_cap"` } type HistogramOptions struct { @@ -226,10 +229,6 @@ func (m *MetricMapper) InitFromYAMLString(fileContents string, cacheSize int) er if currentMapping.SummaryOptions == nil { currentMapping.SummaryOptions = &SummaryOptions{} } - } - - if currentMapping.LegacyQuantiles == nil || len(currentMapping.LegacyQuantiles) == 0 { - currentMapping.LegacyQuantiles = n.Defaults.Quantiles if currentMapping.LegacyQuantiles != nil && len(currentMapping.LegacyQuantiles) != 0 { currentMapping.SummaryOptions.Quantiles = currentMapping.LegacyQuantiles } diff --git a/pkg/mapper/mapper_test.go b/pkg/mapper/mapper_test.go index fbdeb41..dc8a45d 100644 --- a/pkg/mapper/mapper_test.go +++ b/pkg/mapper/mapper_test.go @@ -26,6 +26,9 @@ type mappings []struct { notPresent bool ttl time.Duration metricType MetricType + maxAge time.Duration + ageBuckets uint32 + bufCap uint32 } func TestMetricMapperYAML(t *testing.T) { @@ -523,6 +526,39 @@ mappings: }, }, }, + // Config with summary configuration. + { + config: `--- +mappings: +- match: test.*.* + timer_type: summary + name: "foo" + labels: {} + summary_options: + quantiles: + - quantile: 0.42 + error: 0.04 + - quantile: 0.7 + error: 0.002 + max_age: 5m + age_buckets: 2 + buf_cap: 1000 + `, + mappings: mappings{ + { + statsdMetric: "test.*.*", + name: "foo", + labels: map[string]string{}, + quantiles: []metricObjective{ + {Quantile: 0.42, Error: 0.04}, + {Quantile: 0.7, Error: 0.002}, + }, + maxAge: 5 * time.Minute, + ageBuckets: 2, + bufCap: 1000, + }, + }, + }, // duplicate quantiles are bad { config: `--- @@ -834,6 +870,15 @@ mappings: } } } + if mapping.maxAge != 0 && mapping.maxAge != m.SummaryOptions.MaxAge { + t.Fatalf("%d.%q: Expected max age %v, got %v", i, metric, mapping.maxAge, m.SummaryOptions.MaxAge) + } + if mapping.ageBuckets != 0 && mapping.ageBuckets != m.SummaryOptions.AgeBuckets { + t.Fatalf("%d.%q: Expected max age %v, got %v", i, metric, mapping.ageBuckets, m.SummaryOptions.AgeBuckets) + } + if mapping.bufCap != 0 && mapping.bufCap != m.SummaryOptions.BufCap { + t.Fatalf("%d.%q: Expected max age %v, got %v", i, metric, mapping.bufCap, m.SummaryOptions.BufCap) + } } } } diff --git a/registry.go b/registry.go index fb47d25..d29dbce 100644 --- a/registry.go +++ b/registry.go @@ -344,6 +344,9 @@ func (r *registry) getSummary(metricName string, labels prometheus.Labels, help Name: metricName, Help: help, Objectives: objectives, + MaxAge: summaryOptions.MaxAge, + AgeBuckets: summaryOptions.AgeBuckets, + BufCap: summaryOptions.BufCap, }, labelNames) if err := prometheus.Register(uncheckedCollector{summaryVec}); err != nil {