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

View file

@ -65,13 +65,26 @@ type MetricMapping struct {
labelKeys []string labelKeys []string
labelFormatters []*fsm.TemplateFormatter labelFormatters []*fsm.TemplateFormatter
TimerType TimerType `yaml:"timer_type"` TimerType TimerType `yaml:"timer_type"`
Buckets []float64 `yaml:"buckets"` LegacyBuckets []float64 `yaml:"buckets"`
Quantiles []metricObjective `yaml:"quantiles"` LegacyQuantiles []metricObjective `yaml:"quantiles"`
MatchType MatchType `yaml:"match_type"` MatchType MatchType `yaml:"match_type"`
HelpText string `yaml:"help"` HelpText string `yaml:"help"`
Action ActionType `yaml:"action"` Action ActionType `yaml:"action"`
MatchMetricType MetricType `yaml:"match_metric_type"` MatchMetricType MetricType `yaml:"match_metric_type"`
Ttl time.Duration `yaml:"ttl"` 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 { type metricObjective struct {
@ -172,12 +185,56 @@ func (m *MetricMapper) InitFromYAMLString(fileContents string, cacheSize int) er
currentMapping.TimerType = n.Defaults.TimerType currentMapping.TimerType = n.Defaults.TimerType
} }
if currentMapping.Buckets == nil || len(currentMapping.Buckets) == 0 { if currentMapping.LegacyQuantiles != nil &&
currentMapping.Buckets = n.Defaults.Buckets (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 { if currentMapping.LegacyBuckets != nil &&
currentMapping.Quantiles = n.Defaults.Quantiles (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 { if currentMapping.Ttl == 0 && n.Defaults.Ttl > 0 {

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) {
@ -493,6 +496,84 @@ mappings:
timer_type: wrong timer_type: wrong
name: "foo" name: "foo"
labels: {} 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, configBad: true,
}, },
@ -777,18 +858,27 @@ mappings:
} }
if len(mapping.quantiles) != 0 { if len(mapping.quantiles) != 0 {
if 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.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 { for i, quantile := range mapping.quantiles {
if quantile.Quantile != m.Quantiles[i].Quantile { if quantile.Quantile != m.SummaryOptions.Quantiles[i].Quantile {
t.Fatalf("%d.%q: Expected quantile %v, got %v", i, metric, m.Quantiles[i].Quantile, quantile.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 { if quantile.Error != m.SummaryOptions.Quantiles[i].Error {
t.Fatalf("%d.%q: Expected Error margin %v, got %v", i, metric, m.Quantiles[i].Error, quantile.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 { if vh == nil {
metricsCount.WithLabelValues("histogram").Inc() metricsCount.WithLabelValues("histogram").Inc()
buckets := r.mapper.Defaults.Buckets buckets := r.mapper.Defaults.Buckets
if mapping.Buckets != nil && len(mapping.Buckets) > 0 { if mapping.HistogramOptions != nil && len(mapping.HistogramOptions.Buckets) > 0 {
buckets = mapping.Buckets buckets = mapping.HistogramOptions.Buckets
} }
histogramVec = prometheus.NewHistogramVec(prometheus.HistogramOpts{ histogramVec = prometheus.NewHistogramVec(prometheus.HistogramOpts{
Name: metricName, Name: metricName,
@ -325,8 +325,12 @@ func (r *registry) getSummary(metricName string, labels prometheus.Labels, help
if vh == nil { if vh == nil {
metricsCount.WithLabelValues("summary").Inc() metricsCount.WithLabelValues("summary").Inc()
quantiles := r.mapper.Defaults.Quantiles quantiles := r.mapper.Defaults.Quantiles
if mapping != nil && mapping.Quantiles != nil && len(mapping.Quantiles) > 0 { if mapping != nil && mapping.SummaryOptions != nil && len(mapping.SummaryOptions.Quantiles) > 0 {
quantiles = mapping.Quantiles quantiles = mapping.SummaryOptions.Quantiles
}
summaryOptions := mapper.SummaryOptions{}
if mapping != nil && mapping.SummaryOptions != nil {
summaryOptions = *mapping.SummaryOptions
} }
objectives := make(map[float64]float64) objectives := make(map[float64]float64)
for _, q := range quantiles { for _, q := range quantiles {
@ -340,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 {