mirror of
https://github.com/prometheus/statsd_exporter.git
synced 2024-11-29 02:31:00 +00:00
Merge pull request #290 from tgummerer/tg/allow-setting-max-age
allow setting granularity for summary metrics
This commit is contained in:
commit
b4e9e95cf2
4 changed files with 206 additions and 40 deletions
34
README.md
34
README.md
|
@ -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,19 +248,29 @@ mappings:
|
||||||
provider: "$2"
|
provider: "$2"
|
||||||
outcome: "$3"
|
outcome: "$3"
|
||||||
job: "${1}_server"
|
job: "${1}_server"
|
||||||
quantiles:
|
summary_options:
|
||||||
- quantile: 0.99
|
quantiles:
|
||||||
error: 0.001
|
- quantile: 0.99
|
||||||
- quantile: 0.95
|
error: 0.001
|
||||||
error: 0.01
|
- quantile: 0.95
|
||||||
- quantile: 0.9
|
error: 0.01
|
||||||
error: 0.05
|
- quantile: 0.9
|
||||||
- quantile: 0.5
|
error: 0.05
|
||||||
error: 0.005
|
- 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 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,7 +279,8 @@ to set the timer type for a single metric:
|
||||||
mappings:
|
mappings:
|
||||||
- match: "test.timing.*.*.*"
|
- match: "test.timing.*.*.*"
|
||||||
timer_type: histogram
|
timer_type: histogram
|
||||||
buckets: [ 0.01, 0.025, 0.05, 0.1 ]
|
histogram_options:
|
||||||
|
buckets: [ 0.01, 0.025, 0.05, 0.1 ]
|
||||||
name: "my_timer"
|
name: "my_timer"
|
||||||
labels:
|
labels:
|
||||||
provider: "$2"
|
provider: "$2"
|
||||||
|
|
|
@ -57,21 +57,34 @@ type MetricMapper struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type MetricMapping struct {
|
type MetricMapping struct {
|
||||||
Match string `yaml:"match"`
|
Match string `yaml:"match"`
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
nameFormatter *fsm.TemplateFormatter
|
nameFormatter *fsm.TemplateFormatter
|
||||||
regex *regexp.Regexp
|
regex *regexp.Regexp
|
||||||
Labels prometheus.Labels `yaml:"labels"`
|
Labels prometheus.Labels `yaml:"labels"`
|
||||||
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 {
|
||||||
|
|
|
@ -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) {
|
||||||
|
@ -496,6 +499,84 @@ mappings:
|
||||||
`,
|
`,
|
||||||
configBad: true,
|
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,
|
||||||
|
},
|
||||||
// Config with good metric type.
|
// Config with good metric type.
|
||||||
{
|
{
|
||||||
config: `---
|
config: `---
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
15
registry.go
15
registry.go
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue