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"
```
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
default is "summary" as in the plain text configuration format. For example,
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
"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.
```yaml

View file

@ -117,23 +117,34 @@ func (c *GaugeContainer) Get(metricName string, labels prometheus.Labels, help s
type SummaryContainer struct {
Elements map[uint64]prometheus.Summary
mapper *metricMapper
}
func NewSummaryContainer() *SummaryContainer {
func NewSummaryContainer(mapper *metricMapper) *SummaryContainer {
return &SummaryContainer{
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)
summary, ok := c.Elements[hash]
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(
prometheus.SummaryOpts{
Name: metricName,
Help: help,
ConstLabels: labels,
Objectives: objectives,
})
if err := prometheus.Register(summary); err != nil {
return nil, err
@ -350,6 +361,7 @@ func (b *Exporter) Listen(e <-chan Events) {
metricName,
prometheusLabels,
help,
mapping,
)
if err == nil {
summary.Observe(event.Value())
@ -375,7 +387,7 @@ func NewExporter(mapper *metricMapper) *Exporter {
return &Exporter{
Counters: NewCounterContainer(),
Gauges: NewGaugeContainer(),
Summaries: NewSummaryContainer(),
Summaries: NewSummaryContainer(mapper),
Histograms: NewHistogramContainer(mapper),
mapper: mapper,
}

View file

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

View file

@ -20,6 +20,7 @@ import (
type mappings map[string]struct {
name string
labels map[string]string
quantiles []metricObjective
notPresent bool
}
@ -287,11 +288,40 @@ mappings:
timer_type: summary
name: "foo"
labels: {}
quantiles:
- quantile: 0.42
error: 0.04
- quantile: 0.7
error: 0.002
`,
mappings: mappings{
"test.*.*": {
name: "foo",
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)
}
}
}
}
}
}