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 <t.gummerer@gmail.com>
This commit is contained in:
Thomas Gummerer 2020-01-27 14:50:34 +00:00
parent 80b77513a6
commit dae5d782a6
4 changed files with 63 additions and 6 deletions

View file

@ -236,7 +236,8 @@ mappings:
By default, statsd timers are represented as a Prometheus summary with By default, statsd timers are represented as a Prometheus summary with
quantiles. You may optionally configure the [quantiles and acceptable 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 ```yaml
mappings: mappings:
@ -257,10 +258,19 @@ mappings:
error: 0.05 error: 0.05
- quantile: 0.5 - quantile: 0.5
error: 0.005 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 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 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, default is "summary" as in the plain text configuration format. For example,
to set the timer type for a single metric: to set the timer type for a single metric:

View file

@ -78,6 +78,9 @@ type MetricMapping struct {
type SummaryOptions 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 { type HistogramOptions struct {
@ -226,10 +229,6 @@ func (m *MetricMapper) InitFromYAMLString(fileContents string, cacheSize int) er
if currentMapping.SummaryOptions == nil { if currentMapping.SummaryOptions == nil {
currentMapping.SummaryOptions = &SummaryOptions{} currentMapping.SummaryOptions = &SummaryOptions{}
} }
}
if currentMapping.LegacyQuantiles == nil || len(currentMapping.LegacyQuantiles) == 0 {
currentMapping.LegacyQuantiles = n.Defaults.Quantiles
if currentMapping.LegacyQuantiles != nil && len(currentMapping.LegacyQuantiles) != 0 { if currentMapping.LegacyQuantiles != nil && len(currentMapping.LegacyQuantiles) != 0 {
currentMapping.SummaryOptions.Quantiles = currentMapping.LegacyQuantiles currentMapping.SummaryOptions.Quantiles = currentMapping.LegacyQuantiles
} }

View file

@ -26,6 +26,9 @@ type mappings []struct {
notPresent bool notPresent bool
ttl time.Duration ttl time.Duration
metricType MetricType metricType MetricType
maxAge time.Duration
ageBuckets uint32
bufCap uint32
} }
func TestMetricMapperYAML(t *testing.T) { 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 // duplicate quantiles are bad
{ {
config: `--- 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)
}
} }
} }
} }

View file

@ -344,6 +344,9 @@ func (r *registry) getSummary(metricName string, labels prometheus.Labels, help
Name: metricName, Name: metricName,
Help: help, Help: help,
Objectives: objectives, Objectives: objectives,
MaxAge: summaryOptions.MaxAge,
AgeBuckets: summaryOptions.AgeBuckets,
BufCap: summaryOptions.BufCap,
}, labelNames) }, labelNames)
if err := prometheus.Register(uncheckedCollector{summaryVec}); err != nil { if err := prometheus.Register(uncheckedCollector{summaryVec}); err != nil {