Merge pull request #290 from tgummerer/tg/allow-setting-max-age

allow setting granularity for summary metrics
This commit is contained in:
Matthias Rampke 2020-02-20 13:25:40 +01:00 committed by GitHub
commit b4e9e95cf2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 206 additions and 40 deletions

View file

@ -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:
@ -247,6 +248,7 @@ mappings:
provider: "$2"
outcome: "$3"
job: "${1}_server"
summary_options:
quantiles:
- quantile: 0.99
error: 0.001
@ -256,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:
@ -268,6 +279,7 @@ to set the timer type for a single metric:
mappings:
- match: "test.timing.*.*.*"
timer_type: histogram
histogram_options:
buckets: [ 0.01, 0.025, 0.05, 0.1 ]
name: "my_timer"
labels:

View file

@ -65,13 +65,26 @@ type MetricMapping struct {
labelKeys []string
labelFormatters []*fsm.TemplateFormatter
TimerType TimerType `yaml:"timer_type"`
Buckets []float64 `yaml:"buckets"`
Quantiles []metricObjective `yaml:"quantiles"`
LegacyBuckets []float64 `yaml:"buckets"`
LegacyQuantiles []metricObjective `yaml:"quantiles"`
MatchType MatchType `yaml:"match_type"`
HelpText string `yaml:"help"`
Action ActionType `yaml:"action"`
MatchMetricType MetricType `yaml:"match_metric_type"`
Ttl time.Duration `yaml:"ttl"`
SummaryOptions *SummaryOptions `yaml:"summary_options"`
HistogramOptions *HistogramOptions `yaml:"histogram_options"`
}
type SummaryOptions struct {
Quantiles []metricObjective `yaml:"quantiles"`
MaxAge time.Duration `yaml:"max_age"`
AgeBuckets uint32 `yaml:"age_buckets"`
BufCap uint32 `yaml:"buf_cap"`
}
type HistogramOptions struct {
Buckets []float64 `yaml:"buckets"`
}
type metricObjective struct {
@ -172,12 +185,56 @@ func (m *MetricMapper) InitFromYAMLString(fileContents string, cacheSize int) er
currentMapping.TimerType = n.Defaults.TimerType
}
if currentMapping.Buckets == nil || len(currentMapping.Buckets) == 0 {
currentMapping.Buckets = n.Defaults.Buckets
if currentMapping.LegacyQuantiles != nil &&
(currentMapping.SummaryOptions == nil || currentMapping.SummaryOptions.Quantiles != nil) {
log.Warn("using the top level quantiles is deprecated. Please use quantiles in the summary_options hierarchy")
}
if currentMapping.Quantiles == nil || len(currentMapping.Quantiles) == 0 {
currentMapping.Quantiles = n.Defaults.Quantiles
if currentMapping.LegacyBuckets != nil &&
(currentMapping.HistogramOptions == nil || currentMapping.HistogramOptions.Buckets != nil) {
log.Warn("using the top level buckets is deprecated. Please use buckets in the histogram_options hierarchy")
}
if currentMapping.SummaryOptions != nil &&
currentMapping.LegacyQuantiles != nil &&
currentMapping.SummaryOptions.Quantiles != nil {
return fmt.Errorf("cannot use quantiles in both the top level and summary options at the same time in %s", currentMapping.Match)
}
if currentMapping.HistogramOptions != nil &&
currentMapping.LegacyBuckets != nil &&
currentMapping.HistogramOptions.Buckets != nil {
return fmt.Errorf("cannot use buckets in both the top level and histogram options at the same time in %s", currentMapping.Match)
}
if currentMapping.TimerType == TimerTypeHistogram {
if currentMapping.SummaryOptions != nil {
return fmt.Errorf("cannot use histogram timer and summary options at the same time")
}
if currentMapping.HistogramOptions == nil {
currentMapping.HistogramOptions = &HistogramOptions{}
}
if currentMapping.LegacyBuckets != nil && len(currentMapping.LegacyBuckets) != 0 {
currentMapping.HistogramOptions.Buckets = currentMapping.LegacyBuckets
}
if currentMapping.HistogramOptions.Buckets == nil || len(currentMapping.HistogramOptions.Buckets) == 0 {
currentMapping.HistogramOptions.Buckets = n.Defaults.Buckets
}
}
if currentMapping.TimerType == TimerTypeSummary {
if currentMapping.HistogramOptions != nil {
return fmt.Errorf("cannot use summary timer and histogram options at the same time")
}
if currentMapping.SummaryOptions == nil {
currentMapping.SummaryOptions = &SummaryOptions{}
}
if currentMapping.LegacyQuantiles != nil && len(currentMapping.LegacyQuantiles) != 0 {
currentMapping.SummaryOptions.Quantiles = currentMapping.LegacyQuantiles
}
if currentMapping.SummaryOptions.Quantiles == nil || len(currentMapping.SummaryOptions.Quantiles) == 0 {
currentMapping.SummaryOptions.Quantiles = n.Defaults.Quantiles
}
}
if currentMapping.Ttl == 0 && n.Defaults.Ttl > 0 {

View file

@ -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) {
@ -493,6 +496,84 @@ mappings:
timer_type: wrong
name: "foo"
labels: {}
`,
configBad: true,
},
// new style quantiles
{
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
`,
mappings: mappings{
{
statsdMetric: "test.*.*",
name: "foo",
labels: map[string]string{},
quantiles: []metricObjective{
{Quantile: 0.42, Error: 0.04},
{Quantile: 0.7, Error: 0.002},
},
},
},
},
// 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: `---
mappings:
- match: test.*.*
timer_type: summary
name: "foo"
labels: {}
quantiles:
- quantile: 0.42
error: 0.04
summary_options:
quantiles:
- quantile: 0.42
error: 0.04
`,
configBad: true,
},
@ -777,18 +858,27 @@ mappings:
}
if len(mapping.quantiles) != 0 {
if len(mapping.quantiles) != len(m.Quantiles) {
t.Fatalf("%d.%q: Expected %d quantiles, got %d", i, metric, len(mapping.quantiles), len(m.Quantiles))
if len(mapping.quantiles) != len(m.SummaryOptions.Quantiles) {
t.Fatalf("%d.%q: Expected %d quantiles, got %d", i, metric, len(mapping.quantiles), len(m.SummaryOptions.Quantiles))
}
for i, quantile := range mapping.quantiles {
if quantile.Quantile != m.Quantiles[i].Quantile {
t.Fatalf("%d.%q: Expected quantile %v, got %v", i, metric, m.Quantiles[i].Quantile, quantile.Quantile)
if quantile.Quantile != m.SummaryOptions.Quantiles[i].Quantile {
t.Fatalf("%d.%q: Expected quantile %v, got %v", i, metric, m.SummaryOptions.Quantiles[i].Quantile, quantile.Quantile)
}
if quantile.Error != m.Quantiles[i].Error {
t.Fatalf("%d.%q: Expected Error margin %v, got %v", i, metric, m.Quantiles[i].Error, quantile.Error)
if quantile.Error != m.SummaryOptions.Quantiles[i].Error {
t.Fatalf("%d.%q: Expected Error margin %v, got %v", i, metric, m.SummaryOptions.Quantiles[i].Error, quantile.Error)
}
}
}
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

@ -278,8 +278,8 @@ func (r *registry) getHistogram(metricName string, labels prometheus.Labels, hel
if vh == nil {
metricsCount.WithLabelValues("histogram").Inc()
buckets := r.mapper.Defaults.Buckets
if mapping.Buckets != nil && len(mapping.Buckets) > 0 {
buckets = mapping.Buckets
if mapping.HistogramOptions != nil && len(mapping.HistogramOptions.Buckets) > 0 {
buckets = mapping.HistogramOptions.Buckets
}
histogramVec = prometheus.NewHistogramVec(prometheus.HistogramOpts{
Name: metricName,
@ -325,8 +325,12 @@ func (r *registry) getSummary(metricName string, labels prometheus.Labels, help
if vh == nil {
metricsCount.WithLabelValues("summary").Inc()
quantiles := r.mapper.Defaults.Quantiles
if mapping != nil && mapping.Quantiles != nil && len(mapping.Quantiles) > 0 {
quantiles = mapping.Quantiles
if mapping != nil && mapping.SummaryOptions != nil && len(mapping.SummaryOptions.Quantiles) > 0 {
quantiles = mapping.SummaryOptions.Quantiles
}
summaryOptions := mapper.SummaryOptions{}
if mapping != nil && mapping.SummaryOptions != nil {
summaryOptions = *mapping.SummaryOptions
}
objectives := make(map[float64]float64)
for _, q := range quantiles {
@ -340,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 {