Merge pull request #135 from hbagdi/feat/summary-quantiles

feat: configurable quantiles for Summaries
This commit is contained in:
Matthias Rampke 2018-08-09 11:40:26 +02:00 committed by GitHub
commit b7657e8399
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 109 additions and 7 deletions

View file

@ -162,6 +162,32 @@ mappings:
code: "$1" code: "$1"
``` ```
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):
```yaml
mappings:
- match: test.timing.*.*.*
timer_type: summary
name: "my_timer"
labels:
provider: "$2"
outcome: "$3"
job: "${1}_server"
quantiles:
- quantile: 0.99
error: 0.001
- quantile: 0.95
error: 0.01
- quantile: 0.9
error: 0.05
- quantile: 0.5
error: 0.005
```
The default quantiles are 0.99, 0.9, and 0.5.
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:
@ -204,7 +230,7 @@ automatically.
only used when the statsd metric type is a timerand the `timer_type` is set to only used when the statsd metric type is a timerand the `timer_type` is set to
"histogram." "histogram."
One may also set defaults for the timer type, buckets and match_type. These will be used One may also set defaults for the timer type, buckets or quantiles, and match_type. These will be used
by all mappings that do not define these. by all mappings that do not define these.
```yaml ```yaml

View file

@ -117,23 +117,34 @@ func (c *GaugeContainer) Get(metricName string, labels prometheus.Labels, help s
type SummaryContainer struct { type SummaryContainer struct {
Elements map[uint64]prometheus.Summary Elements map[uint64]prometheus.Summary
mapper *metricMapper
} }
func NewSummaryContainer() *SummaryContainer { func NewSummaryContainer(mapper *metricMapper) *SummaryContainer {
return &SummaryContainer{ return &SummaryContainer{
Elements: make(map[uint64]prometheus.Summary), Elements: make(map[uint64]prometheus.Summary),
mapper: mapper,
} }
} }
func (c *SummaryContainer) Get(metricName string, labels prometheus.Labels, help string) (prometheus.Summary, error) { func (c *SummaryContainer) Get(metricName string, labels prometheus.Labels, help string, mapping *metricMapping) (prometheus.Summary, error) {
hash := hashNameAndLabels(metricName, labels) hash := hashNameAndLabels(metricName, labels)
summary, ok := c.Elements[hash] summary, ok := c.Elements[hash]
if !ok { if !ok {
quantiles := c.mapper.Defaults.Quantiles
if mapping != nil && mapping.Quantiles != nil && len(mapping.Quantiles) > 0 {
quantiles = mapping.Quantiles
}
objectives := make(map[float64]float64)
for _, q := range quantiles {
objectives[q.Quantile] = q.Error
}
summary = prometheus.NewSummary( summary = prometheus.NewSummary(
prometheus.SummaryOpts{ prometheus.SummaryOpts{
Name: metricName, Name: metricName,
Help: help, Help: help,
ConstLabels: labels, ConstLabels: labels,
Objectives: objectives,
}) })
if err := prometheus.Register(summary); err != nil { if err := prometheus.Register(summary); err != nil {
return nil, err return nil, err
@ -350,6 +361,7 @@ func (b *Exporter) Listen(e <-chan Events) {
metricName, metricName,
prometheusLabels, prometheusLabels,
help, help,
mapping,
) )
if err == nil { if err == nil {
summary.Observe(event.Value()) summary.Observe(event.Value())
@ -375,7 +387,7 @@ func NewExporter(mapper *metricMapper) *Exporter {
return &Exporter{ return &Exporter{
Counters: NewCounterContainer(), Counters: NewCounterContainer(),
Gauges: NewGaugeContainer(), Gauges: NewGaugeContainer(),
Summaries: NewSummaryContainer(), Summaries: NewSummaryContainer(mapper),
Histograms: NewHistogramContainer(mapper), Histograms: NewHistogramContainer(mapper),
mapper: mapper, mapper: mapper,
} }

View file

@ -36,6 +36,7 @@ var (
type mapperConfigDefaults struct { type mapperConfigDefaults struct {
TimerType timerType `yaml:"timer_type"` TimerType timerType `yaml:"timer_type"`
Buckets []float64 `yaml:"buckets"` Buckets []float64 `yaml:"buckets"`
Quantiles []metricObjective `yaml:"quantiles"`
MatchType matchType `yaml:"match_type"` MatchType matchType `yaml:"match_type"`
} }
@ -54,12 +55,24 @@ type metricMapping struct {
Labels prometheus.Labels `yaml:"labels"` Labels prometheus.Labels `yaml:"labels"`
TimerType timerType `yaml:"timer_type"` TimerType timerType `yaml:"timer_type"`
Buckets []float64 `yaml:"buckets"` Buckets []float64 `yaml:"buckets"`
Quantiles []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"`
} }
type metricObjective struct {
Quantile float64 `yaml:"quantile"`
Error float64 `yaml:"error"`
}
var defaultQuantiles = []metricObjective{
{Quantile: 0.5, Error: 0.05},
{Quantile: 0.9, Error: 0.01},
{Quantile: 0.99, Error: 0.001},
}
func (m *metricMapper) initFromYAMLString(fileContents string) error { func (m *metricMapper) initFromYAMLString(fileContents string) error {
var n metricMapper var n metricMapper
@ -71,6 +84,10 @@ func (m *metricMapper) initFromYAMLString(fileContents string) error {
n.Defaults.Buckets = prometheus.DefBuckets n.Defaults.Buckets = prometheus.DefBuckets
} }
if n.Defaults.Quantiles == nil || len(n.Defaults.Quantiles) == 0 {
n.Defaults.Quantiles = defaultQuantiles
}
if n.Defaults.MatchType == matchTypeDefault { if n.Defaults.MatchType == matchTypeDefault {
n.Defaults.MatchType = matchTypeGlob n.Defaults.MatchType = matchTypeGlob
} }
@ -130,6 +147,10 @@ func (m *metricMapper) initFromYAMLString(fileContents string) error {
currentMapping.Buckets = n.Defaults.Buckets currentMapping.Buckets = n.Defaults.Buckets
} }
if currentMapping.Quantiles == nil || len(currentMapping.Quantiles) == 0 {
currentMapping.Quantiles = n.Defaults.Quantiles
}
} }
m.mutex.Lock() m.mutex.Lock()

View file

@ -20,6 +20,7 @@ import (
type mappings map[string]struct { type mappings map[string]struct {
name string name string
labels map[string]string labels map[string]string
quantiles []metricObjective
notPresent bool notPresent bool
} }
@ -287,11 +288,40 @@ mappings:
timer_type: summary timer_type: summary
name: "foo" name: "foo"
labels: {} labels: {}
quantiles:
- quantile: 0.42
error: 0.04
- quantile: 0.7
error: 0.002
`, `,
mappings: mappings{ mappings: mappings{
"test.*.*": { "test.*.*": {
name: "foo", name: "foo",
labels: map[string]string{}, labels: map[string]string{},
quantiles: []metricObjective{
{Quantile: 0.42, Error: 0.04},
{Quantile: 0.7, Error: 0.002},
},
},
},
},
{
config: `---
mappings:
- match: test1.*.*
timer_type: summary
name: "foo"
labels: {}
`,
mappings: mappings{
"test1.*.*": {
name: "foo",
labels: map[string]string{},
quantiles: []metricObjective{
{Quantile: 0.5, Error: 0.05},
{Quantile: 0.9, Error: 0.01},
{Quantile: 0.99, Error: 0.001},
},
}, },
}, },
}, },
@ -471,6 +501,19 @@ 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))
}
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.Error != m.Quantiles[i].Error {
t.Fatalf("%d.%q: Expected Error margin %v, got %v", i, metric, m.Quantiles[i].Error, quantile.Error)
}
}
}
} }
} }
} }