From a144b1f9f74ce16b1a1939cb87fcf65582ea34ba Mon Sep 17 00:00:00 2001 From: Harry Bagdi Date: Wed, 8 Aug 2018 19:41:41 -0700 Subject: [PATCH] feat: configurable quantiles for Summaries * Quantile values for a Summary are now configurable. * The default quantiles (DefObjectives) in the prometheus Go client library is deprecated. This commit removes the reliance on it. Signed-off-by: Harry Bagdi --- README.md | 9 +++++++++ exporter.go | 18 +++++++++++++++--- mapper.go | 27 ++++++++++++++++++++++++--- mapper_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9eb1610..228c449 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,15 @@ mappings: provider: "$2" outcome: "$3" job: "${1}_server" + quantiles: # Optionally configure quantiles for your summaries + - quantile: 0.99 # https://prometheus.io/docs/practices/histograms/#quantiles + error: 0.001 + - quantile: 0.95 + error: 0.01 + - quantile: 0.9 + error: 0.05 + - quantile: 0.5 + error: 0.005 ``` Another capability when using YAML configuration is the ability to define matches diff --git a/exporter.go b/exporter.go index 1de131e..dab584c 100644 --- a/exporter.go +++ b/exporter.go @@ -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, } diff --git a/mapper.go b/mapper.go index 9b8f004..2a897de 100644 --- a/mapper.go +++ b/mapper.go @@ -34,9 +34,10 @@ var ( ) type mapperConfigDefaults struct { - TimerType timerType `yaml:"timer_type"` - Buckets []float64 `yaml:"buckets"` - MatchType matchType `yaml:"match_type"` + TimerType timerType `yaml:"timer_type"` + Buckets []float64 `yaml:"buckets"` + Quantiles []metricObjective `yaml:"quantiles"` + MatchType matchType `yaml:"match_type"` } type metricMapper struct { @@ -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() diff --git a/mapper_test.go b/mapper_test.go index 83c08f2..b11d5c2 100644 --- a/mapper_test.go +++ b/mapper_test.go @@ -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) + } + } + } } } }