mirror of
https://github.com/prometheus/statsd_exporter.git
synced 2024-12-23 14:00:30 +00:00
Merge pull request #135 from hbagdi/feat/summary-quantiles
feat: configurable quantiles for Summaries
This commit is contained in:
commit
b7657e8399
4 changed files with 109 additions and 7 deletions
28
README.md
28
README.md
|
@ -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
|
||||
|
|
18
exporter.go
18
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,
|
||||
}
|
||||
|
|
27
mapper.go
27
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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue