diff --git a/README.md b/README.md index 7e26d1c..0c22871 100644 --- a/README.md +++ b/README.md @@ -341,9 +341,9 @@ mappings: error: 0.05 - quantile: 0.5 error: 0.005 - max_summary_age: 30s - summary_age_buckets: 3 - stream_buffer_size: 1000 + max_age: 30s + age_buckets: 3 + buf_cap: 1000 ``` The default quantiles are 0.99, 0.9, and 0.5. @@ -414,16 +414,34 @@ mappings: ### Global defaults -One may also set defaults for the observer type, buckets or quantiles, and match type. +One may also set defaults for the observer type, histogram options, summary options, and match type. These will be used by all mappings that do not define them. An option that can only be configured in `defaults` is `glob_disable_ordering`, which is `false` if omitted. By setting this to `true`, `glob` match type will not honor the occurance of rules in the mapping rules file and always treat `*` as lower priority than a concrete string. +Setting `buckets` or `quantiles` in the defaults is deprecated in favor of `histogram_options` and `summary_options`, which will override the deprecated values. + +If `summary_options` is present in a mapping config, it will only override the fields set in the mapping. Unset fields in the mapping will take the values from the defaults. + ```yaml defaults: observer_type: histogram - buckets: [.005, .01, .025, .05, .1, .25, .5, 1, 2.5 ] + histogram_options: + buckets: [.005, .01, .025, .05, .1, .25, .5, 1, 2.5 ] + summary_options: + 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 + max_age: 5m + age_buckets: 2 + buf_cap: 1000 match_type: glob glob_disable_ordering: false ttl: 0 # metrics do not expire @@ -435,7 +453,7 @@ mappings: provider: "$2" outcome: "$3" job: "${1}_server" -# This will be a summary timer. +# This will be a summary using the summary_options set in `defaults` - match: "other.distribution.*.*.*" observer_type: summary name: "other_distribution" @@ -542,4 +560,4 @@ We encourage re-use of these packages and welcome [issues](https://github.com/pr [travis]: https://travis-ci.org/prometheus/statsd_exporter [circleci]: https://circleci.com/gh/prometheus/statsd_exporter [quay]: https://quay.io/repository/prometheus/statsd-exporter -[hub]: https://hub.docker.com/r/prom/statsd-exporter/ +[hub]: https://hub.docker.com/r/prom/statsd-exporter/ \ No newline at end of file diff --git a/pkg/exporter/exporter.go b/pkg/exporter/exporter.go index ae73353..0ee0b36 100644 --- a/pkg/exporter/exporter.go +++ b/pkg/exporter/exporter.go @@ -20,6 +20,7 @@ import ( "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/statsd_exporter/pkg/clock" "github.com/prometheus/statsd_exporter/pkg/event" "github.com/prometheus/statsd_exporter/pkg/mapper" diff --git a/pkg/mapper/mapper.go b/pkg/mapper/mapper.go index ad92f55..680952b 100644 --- a/pkg/mapper/mapper.go +++ b/pkg/mapper/mapper.go @@ -22,8 +22,9 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/log" - "github.com/prometheus/statsd_exporter/pkg/mapper/fsm" yaml "gopkg.in/yaml.v2" + + "github.com/prometheus/statsd_exporter/pkg/mapper/fsm" ) var ( @@ -77,12 +78,12 @@ func (m *MetricMapper) InitFromYAMLString(fileContents string, cacheSize int, op return err } - if n.Defaults.Buckets == nil || len(n.Defaults.Buckets) == 0 { - n.Defaults.Buckets = prometheus.DefBuckets + if len(n.Defaults.HistogramOptions.Buckets) == 0 { + n.Defaults.HistogramOptions.Buckets = prometheus.DefBuckets } - if n.Defaults.Quantiles == nil || len(n.Defaults.Quantiles) == 0 { - n.Defaults.Quantiles = defaultQuantiles + if len(n.Defaults.SummaryOptions.Quantiles) == 0 { + n.Defaults.SummaryOptions.Quantiles = defaultQuantiles } if n.Defaults.MatchType == MatchTypeDefault { @@ -190,7 +191,7 @@ func (m *MetricMapper) InitFromYAMLString(fileContents string, cacheSize int, op currentMapping.HistogramOptions.Buckets = currentMapping.LegacyBuckets } if currentMapping.HistogramOptions.Buckets == nil || len(currentMapping.HistogramOptions.Buckets) == 0 { - currentMapping.HistogramOptions.Buckets = n.Defaults.Buckets + currentMapping.HistogramOptions.Buckets = n.Defaults.HistogramOptions.Buckets } } @@ -205,7 +206,16 @@ func (m *MetricMapper) InitFromYAMLString(fileContents string, cacheSize int, op currentMapping.SummaryOptions.Quantiles = currentMapping.LegacyQuantiles } if currentMapping.SummaryOptions.Quantiles == nil || len(currentMapping.SummaryOptions.Quantiles) == 0 { - currentMapping.SummaryOptions.Quantiles = n.Defaults.Quantiles + currentMapping.SummaryOptions.Quantiles = n.Defaults.SummaryOptions.Quantiles + } + if currentMapping.SummaryOptions.MaxAge == 0 { + currentMapping.SummaryOptions.MaxAge = n.Defaults.SummaryOptions.MaxAge + } + if currentMapping.SummaryOptions.AgeBuckets == 0 { + currentMapping.SummaryOptions.AgeBuckets = n.Defaults.SummaryOptions.AgeBuckets + } + if currentMapping.SummaryOptions.BufCap == 0 { + currentMapping.SummaryOptions.BufCap = n.Defaults.SummaryOptions.BufCap } } diff --git a/pkg/mapper/mapper_defaults.go b/pkg/mapper/mapper_defaults.go index 7fba9fe..754a677 100644 --- a/pkg/mapper/mapper_defaults.go +++ b/pkg/mapper/mapper_defaults.go @@ -16,19 +16,30 @@ package mapper import "time" type mapperConfigDefaults struct { + ObserverType ObserverType `yaml:"observer_type"` + MatchType MatchType `yaml:"match_type"` + GlobDisableOrdering bool `yaml:"glob_disable_ordering"` + Ttl time.Duration `yaml:"ttl"` + SummaryOptions SummaryOptions `yaml:"summary_options"` + HistogramOptions HistogramOptions `yaml:"histogram_options"` +} + +// mapperConfigDefaultsAlias is used to unmarshal the yaml config into mapperConfigDefaults and allows deprecated fields +type mapperConfigDefaultsAlias struct { ObserverType ObserverType `yaml:"observer_type"` - TimerType ObserverType `yaml:"timer_type,omitempty"` // DEPRECATED - field only present to preserve backwards compatibility in configs. Always empty - Buckets []float64 `yaml:"buckets"` - Quantiles []metricObjective `yaml:"quantiles"` + TimerType ObserverType `yaml:"timer_type,omitempty"` // DEPRECATED - field only present to preserve backwards compatibility in configs + Buckets []float64 `yaml:"buckets"` // DEPRECATED - field only present to preserve backwards compatibility in configs + Quantiles []metricObjective `yaml:"quantiles"` // DEPRECATED - field only present to preserve backwards compatibility in configs MatchType MatchType `yaml:"match_type"` GlobDisableOrdering bool `yaml:"glob_disable_ordering"` Ttl time.Duration `yaml:"ttl"` + SummaryOptions SummaryOptions `yaml:"summary_options"` + HistogramOptions HistogramOptions `yaml:"histogram_options"` } // UnmarshalYAML is a custom unmarshal function to allow use of deprecated config keys // observer_type will override timer_type func (d *mapperConfigDefaults) UnmarshalYAML(unmarshal func(interface{}) error) error { - type mapperConfigDefaultsAlias mapperConfigDefaults var tmp mapperConfigDefaultsAlias if err := unmarshal(&tmp); err != nil { return err @@ -36,16 +47,26 @@ func (d *mapperConfigDefaults) UnmarshalYAML(unmarshal func(interface{}) error) // Copy defaults d.ObserverType = tmp.ObserverType - d.Buckets = tmp.Buckets - d.Quantiles = tmp.Quantiles d.MatchType = tmp.MatchType d.GlobDisableOrdering = tmp.GlobDisableOrdering d.Ttl = tmp.Ttl + d.SummaryOptions = tmp.SummaryOptions + d.HistogramOptions = tmp.HistogramOptions // Use deprecated TimerType if necessary if tmp.ObserverType == "" { d.ObserverType = tmp.TimerType } + // Use deprecated quantiles if necessary + if len(tmp.SummaryOptions.Quantiles) == 0 && len(tmp.Quantiles) > 0 { + d.SummaryOptions = SummaryOptions{Quantiles: tmp.Quantiles} + } + + // Use deprecated buckets if necessary + if len(tmp.HistogramOptions.Buckets) == 0 && len(tmp.Buckets) > 0 { + d.HistogramOptions = HistogramOptions{Buckets: tmp.Buckets} + } + return nil } diff --git a/pkg/mapper/mapper_test.go b/pkg/mapper/mapper_test.go index 30a98ed..ad7e32e 100644 --- a/pkg/mapper/mapper_test.go +++ b/pkg/mapper/mapper_test.go @@ -16,6 +16,8 @@ package mapper import ( "testing" "time" + + "github.com/prometheus/client_golang/prometheus" ) type mappings []struct { @@ -29,18 +31,21 @@ type mappings []struct { maxAge time.Duration ageBuckets uint32 bufCap uint32 + buckets []float64 } func TestMetricMapperYAML(t *testing.T) { scenarios := []struct { + testName string config string configBad bool mappings mappings }{ - // Empty config. - {}, - // Config with several mapping definitions. { + testName: "Empty config", + }, + { + testName: "Config with several mapping definitions", config: `--- mappings: - match: test.dispatcher.*.*.* @@ -153,8 +158,8 @@ mappings: }, }, }, - //Config with backtracking { + testName: "Config with backtracking", config: ` defaults: glob_disable_ordering: true @@ -198,6 +203,7 @@ mappings: // This test case makes sure the captures in the non-matched later rule // doesn't affect the captures in the first matched rule. { + testName: "Config with backtracking, the non-matched rule has star(s)", config: ` defaults: glob_disable_ordering: false @@ -232,8 +238,8 @@ mappings: }, }, }, - //Config with super sets, disables ordering { + testName: "Config with super sets, disables ordering", config: ` defaults: glob_disable_ordering: true @@ -268,8 +274,8 @@ mappings: }, }, }, - //Config with super sets, keeps ordering { + testName: "Config with super sets, keeps ordering", config: ` defaults: glob_disable_ordering: false @@ -293,8 +299,8 @@ mappings: }, }, }, - // Config with bad regex reference. { + testName: "Config with bad regex reference", config: `--- mappings: - match: test.* @@ -312,8 +318,8 @@ mappings: }, }, }, - // Config with good regex reference. { + testName: "Config with good regex reference", config: ` mappings: - match: test.* @@ -331,8 +337,8 @@ mappings: }, }, }, - // Config with bad metric line. { + testName: "Config with bad metric line", config: `--- mappings: - match: bad--metric-line.*.* @@ -341,8 +347,8 @@ mappings: `, configBad: true, }, - // Config with dynamic metric name. { + testName: "Config with dynamic metric name", config: `--- mappings: - match: test1.*.* @@ -371,8 +377,8 @@ mappings: }, }, }, - // Config with bad metric name. { + testName: "Config with bad metric name", config: `--- mappings: - match: test.*.* @@ -381,8 +387,8 @@ mappings: `, configBad: true, }, - // Config with no metric name. { + testName: "Config with no metric name", config: `--- mappings: - match: test.*.* @@ -391,13 +397,13 @@ mappings: `, configBad: true, }, - // Config with no mappings. { + testName: "Config with no mappings", config: ``, mappings: mappings{}, }, - // Config without a trailing newline. { + testName: "Config without a trailing newline", config: `mappings: - match: test.* name: "name" @@ -413,8 +419,8 @@ mappings: }, }, }, - // Config with an improperly escaped *. { + testName: "Config with an improperly escaped *", config: ` mappings: - match: *.test.* @@ -423,8 +429,8 @@ mappings: label: "${1}_foo"`, configBad: true, }, - // Config with a properly escaped *. { + testName: "Config with a properly escaped *", config: ` mappings: - match: "*.test.*" @@ -441,8 +447,8 @@ mappings: }, }, }, - // Config with good observer type. { + testName: "Config with good observer type", config: `--- mappings: - match: test.*.* @@ -467,8 +473,8 @@ mappings: }, }, }, - // Config with good observer type and unused timer type { + testName: "Config with good observer type and unused timer type", config: `--- mappings: - match: test.*.* @@ -495,6 +501,7 @@ mappings: }, }, { + testName: "Config with good observertype and no defaults", config: `--- mappings: - match: test1.*.* @@ -515,8 +522,8 @@ mappings: }, }, }, - // Config with good deprecated timer type { + testName: "Config with good deprecated timer type", config: `--- mappings: - match: test1.*.* @@ -537,8 +544,8 @@ mappings: }, }, }, - // Config with bad observer type. { + testName: "Config with bad observer type", config: `--- mappings: - match: test.*.* @@ -548,8 +555,8 @@ mappings: `, configBad: true, }, - // Config with bad deprecated timer type. { + testName: "Config with bad deprecated timer type", config: `--- mappings: - match: test.*.* @@ -559,8 +566,8 @@ mappings: `, configBad: true, }, - // new style quantiles { + testName: "New style quantiles", config: `--- mappings: - match: test.*.* @@ -586,8 +593,8 @@ mappings: }, }, }, - // Config with summary configuration. { + testName: "Config with summary options", config: `--- mappings: - match: test.*.* @@ -619,8 +626,457 @@ mappings: }, }, }, - // duplicate quantiles are bad { + testName: "Config with default summary options", + config: `--- +defaults: + 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: +- match: test.*.* + observer_type: summary + name: "foo" + labels: {} +`, + 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, + }, + }, + }, + { + testName: "Config with default summary options without quantiles", + config: `--- +defaults: + summary_options: + max_age: 5m + age_buckets: 2 + buf_cap: 1000 +mappings: +- match: test.*.* + observer_type: summary + name: "foo" + labels: {} +`, + mappings: mappings{ + { + statsdMetric: "test.*.*", + name: "foo", + labels: map[string]string{}, + quantiles: defaultQuantiles, + maxAge: 5 * time.Minute, + ageBuckets: 2, + bufCap: 1000, + }, + }, + }, + { + testName: "Config with default summary options overrides quantiles", + config: `--- +defaults: + quantiles: + - quantile: 0.9 + error: 0.1 + - quantile: 0.99 + error: 0.01 + 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: +- match: test.*.* + observer_type: summary + name: "foo" + labels: {} +`, + 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, + }, + }, + }, + { + testName: "Config that overrides default summary options", + config: `--- +defaults: + summary_options: + quantiles: + - quantile: 0.042 + error: 0.4 + - quantile: 0.07 + error: 0.02 + max_age: 15m + age_buckets: 3 + buf_cap: 100 +mappings: +- match: test.*.* + observer_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, + }, + }, + }, + { + testName: "Config that overrides default summary options and a default options mapping", + config: `--- +defaults: + summary_options: + quantiles: + - quantile: 0.9 + error: 0.1 + - quantile: 0.99 + error: 0.01 + max_age: 15m + age_buckets: 3 + buf_cap: 100 +mappings: +- match: test.*.* + observer_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 +- match: test_default.*.* + observer_type: summary + name: "foo_default" + labels: {} +`, + 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, + }, + { + statsdMetric: "test_default.*.*", + name: "foo_default", + labels: map[string]string{}, + quantiles: []metricObjective{ + {Quantile: 0.9, Error: 0.1}, + {Quantile: 0.99, Error: 0.01}, + }, + maxAge: 15 * time.Minute, + ageBuckets: 3, + bufCap: 100, + }, + }, + }, + { + testName: "Config that partially overrides default summary quantiles and a default options mapping", + config: `--- +defaults: + summary_options: + quantiles: + - quantile: 0.9 + error: 0.1 + - quantile: 0.99 + error: 0.01 + max_age: 15m + age_buckets: 3 + buf_cap: 100 +mappings: +- match: test.*.* + observer_type: summary + name: "foo" + labels: {} + summary_options: + quantiles: + - quantile: 0.42 + error: 0.04 + - quantile: 0.7 + error: 0.002 +- match: test_default.*.* + observer_type: summary + name: "foo_default" + labels: {} +`, + 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: 15 * time.Minute, + ageBuckets: 3, + bufCap: 100, + }, + { + statsdMetric: "test_default.*.*", + name: "foo_default", + labels: map[string]string{}, + quantiles: []metricObjective{ + {Quantile: 0.9, Error: 0.1}, + {Quantile: 0.99, Error: 0.01}, + }, + maxAge: 15 * time.Minute, + ageBuckets: 3, + bufCap: 100, + }, + }, + }, + { + testName: "Config that partially overrides default summary max_age and a default options mapping", + config: `--- +defaults: + summary_options: + quantiles: + - quantile: 0.9 + error: 0.1 + - quantile: 0.99 + error: 0.01 + max_age: 15m + age_buckets: 3 + buf_cap: 100 +mappings: +- match: test.*.* + observer_type: summary + name: "foo" + labels: {} + summary_options: + max_age: 5m +- match: test_default.*.* + observer_type: summary + name: "foo_default" + labels: {} +`, + mappings: mappings{ + { + statsdMetric: "test.*.*", + name: "foo", + labels: map[string]string{}, + quantiles: []metricObjective{ + {Quantile: 0.9, Error: 0.1}, + {Quantile: 0.99, Error: 0.01}, + }, + maxAge: 5 * time.Minute, + ageBuckets: 3, + bufCap: 100, + }, + { + statsdMetric: "test_default.*.*", + name: "foo_default", + labels: map[string]string{}, + quantiles: []metricObjective{ + {Quantile: 0.9, Error: 0.1}, + {Quantile: 0.99, Error: 0.01}, + }, + maxAge: 15 * time.Minute, + ageBuckets: 3, + bufCap: 100, + }, + }, + }, + { + testName: "Config with histogram options", + config: `--- +mappings: +- match: test.*.* + observer_type: histogram + name: "foo" + labels: {} + histogram_options: + buckets: [0.1, 1, 10, 100, 1000] +`, + mappings: mappings{ + { + statsdMetric: "test.*.*", + name: "foo", + labels: map[string]string{}, + buckets: []float64{0.1, 1, 10, 100, 1000}, + }, + }, + }, + { + testName: "Config with default histogram options", + config: `--- +defaults: + histogram_options: + buckets: [0.1, 1, 10, 100, 1000] +mappings: +- match: test.*.* + observer_type: histogram + name: "foo" + labels: {} +`, + mappings: mappings{ + { + statsdMetric: "test.*.*", + name: "foo", + labels: map[string]string{}, + buckets: []float64{0.1, 1, 10, 100, 1000}, + }, + }, + }, + { + testName: "Config with default histogram options without buckets", + config: `--- +defaults: + histogram_options: + buckets: [] +mappings: +- match: test.*.* + observer_type: histogram + name: "foo" + labels: {} +`, + mappings: mappings{ + { + statsdMetric: "test.*.*", + name: "foo", + labels: map[string]string{}, + buckets: prometheus.DefBuckets, + }, + }, + }, + { + testName: "Config with default histogram options overrides buckets", + config: `--- +defaults: + buckets: [0.2, 2, 20, 200, 2000] + histogram_options: + buckets: [0.1, 1, 10, 100, 1000] +mappings: +- match: test.*.* + observer_type: histogram + name: "foo" + labels: {} +`, + mappings: mappings{ + { + statsdMetric: "test.*.*", + name: "foo", + labels: map[string]string{}, + buckets: []float64{0.1, 1, 10, 100, 1000}, + }, + }, + }, + { + testName: "Config that overrides default histogram configuration", + config: `--- +defaults: + histogram_options: + buckets: [0.2, 2, 20, 200, 2000] +mappings: +- match: test.*.* + observer_type: histogram + name: "foo" + labels: {} + histogram_options: + buckets: [0.1, 1, 10, 100, 1000] +`, + mappings: mappings{ + { + statsdMetric: "test.*.*", + name: "foo", + labels: map[string]string{}, + buckets: []float64{0.1, 1, 10, 100, 1000}, + }, + }, + }, + { + testName: "Config that overrides default histogram configuration and a default options mapping", + config: `--- +defaults: + histogram_options: + buckets: [0.2, 2, 20, 200] +mappings: +- match: test.*.* + observer_type: histogram + name: "foo" + labels: {} + histogram_options: + buckets: [0.1, 1, 10, 100, 1000] +- match: test_default.*.* + observer_type: histogram + name: "foo_default" + labels: {} +`, + mappings: mappings{ + { + statsdMetric: "test.*.*", + name: "foo", + labels: map[string]string{}, + buckets: []float64{0.1, 1, 10, 100, 1000}, + }, + { + statsdMetric: "test_default.*.*", + name: "foo_default", + labels: map[string]string{}, + buckets: []float64{0.2, 2, 20, 200}, + }, + }, + }, + { + testName: "Duplicate quantiles are bad", config: `--- mappings: - match: test.*.* @@ -637,8 +1093,8 @@ mappings: `, configBad: true, }, - // Config with good metric type. { + testName: "Config with good metric type", config: `--- mappings: - match: test.*.* @@ -647,8 +1103,8 @@ mappings: labels: {} `, }, - // Config with good metric type observer. { + testName: "Config with good metric type observer", config: `--- mappings: - match: test.*.* @@ -657,8 +1113,8 @@ mappings: labels: {} `, }, - // Config with good metric type timer. { + testName: "Config with good metric type timer", config: `--- mappings: - match: test.*.* @@ -667,8 +1123,8 @@ mappings: labels: {} `, }, - // Config with bad metric type matcher. { + testName: "Config with bad metric type matcher", config: `--- mappings: - match: test.*.* @@ -678,8 +1134,8 @@ mappings: `, configBad: true, }, - // Config with multiple explicit metric types { + testName: "Config with multiple explicit metric types", config: `--- mappings: - match: test.foo.* @@ -702,8 +1158,8 @@ mappings: }, }, }, - //Config with uncompilable regex. { + testName: "Config with uncompilable regex", config: `--- mappings: - match: "*\\.foo" @@ -713,8 +1169,8 @@ mappings: `, configBad: true, }, - //Config with non-matched metric. { + testName: "Config with non-matched metric", config: `--- mappings: - match: foo.*.* @@ -731,8 +1187,8 @@ mappings: }, }, }, - //Config with no name. { + testName: "Config with no name", config: `--- mappings: - match: *\.foo @@ -743,6 +1199,7 @@ mappings: configBad: true, }, { + testName: "Config with labels from glob", config: `--- mappings: - match: p.*.*.c.* @@ -765,8 +1222,8 @@ mappings: }, }, }, - // Example from the README. { + testName: "Example from the README", config: ` mappings: - match: test.dispatcher.*.*.* @@ -810,8 +1267,8 @@ mappings: }, }, }, - // Config that drops all. { + testName: "Config that drops all", config: `mappings: - match: . match_type: regex @@ -826,8 +1283,8 @@ mappings: }, }, }, - // Config that has a catch-all to drop all. { + testName: "Config that has a catch-all to drop all", config: `mappings: - match: web.* name: "web" @@ -850,8 +1307,8 @@ mappings: }, }, }, - // Config that has a ttl. { + testName: "Config that has a ttl", config: `mappings: - match: web.* name: "web" @@ -872,8 +1329,8 @@ mappings: }, }, }, - // Config that has a default ttl. { + testName: "Config that has a default ttl", config: `defaults: ttl: 1m2s mappings: @@ -895,8 +1352,8 @@ mappings: }, }, }, - // Config that override a default ttl. { + testName: "Config that override a default ttl", config: `defaults: ttl: 1m2s mappings: @@ -923,77 +1380,94 @@ mappings: mapper := MetricMapper{} for i, scenario := range scenarios { - err := mapper.InitFromYAMLString(scenario.config, 1000) - if err != nil && !scenario.configBad { - t.Fatalf("%d. Config load error: %s %s", i, scenario.config, err) + if scenario.testName == "" { + t.Fatalf("Missing testName in scenario %+v", scenario) } - if err == nil && scenario.configBad { - t.Fatalf("%d. Expected bad config, but loaded ok: %s", i, scenario.config) - } - - for metric, mapping := range scenario.mappings { - // exporter will call mapper.GetMapping with valid MetricType - // so we also pass a sane MetricType in testing if it's not specified - mapType := mapping.metricType - if mapType == "" { - mapType = MetricTypeCounter + t.Run(scenario.testName, func(t *testing.T) { + err := mapper.InitFromYAMLString(scenario.config, 1000) + if err != nil && !scenario.configBad { + t.Fatalf("%d. Config load error: %s %s", i, scenario.config, err) } - m, labels, present := mapper.GetMapping(mapping.statsdMetric, mapType) - if present && mapping.name != "" && m.Name != mapping.name { - t.Fatalf("%d.%q: Expected name %v, got %v", i, metric, m.Name, mapping.name) - } - if mapping.notPresent && present { - t.Fatalf("%d.%q: Expected metric to not be present", i, metric) - } - if len(labels) != len(mapping.labels) { - t.Fatalf("%d.%q: Expected %d labels, got %d", i, metric, len(mapping.labels), len(labels)) - } - for label, value := range labels { - if mapping.labels[label] != value { - t.Fatalf("%d.%q: Expected labels %v, got %v", i, metric, mapping, labels) - } - } - if mapping.ttl > 0 && mapping.ttl != m.Ttl { - t.Fatalf("%d.%q: Expected ttl of %s, got %s", i, metric, mapping.ttl.String(), m.Ttl.String()) - } - if mapping.metricType != "" && mapType != m.MatchMetricType { - t.Fatalf("%d.%q: Expected match metric of %s, got %s", i, metric, mapType, m.MatchMetricType) + if err == nil && scenario.configBad { + t.Fatalf("%d. Expected bad config, but loaded ok: %s", i, scenario.config) } - if len(mapping.quantiles) != 0 { - if len(mapping.quantiles) != len(m.SummaryOptions.Quantiles) { - t.Fatalf("%d.%q: Expected %d quantiles, got %d", i, metric, len(mapping.quantiles), len(m.SummaryOptions.Quantiles)) + for metric, mapping := range scenario.mappings { + // exporter will call mapper.GetMapping with valid MetricType + // so we also pass a sane MetricType in testing if it's not specified + mapType := mapping.metricType + if mapType == "" { + mapType = MetricTypeCounter } - for i, quantile := range mapping.quantiles { - if quantile.Quantile != m.SummaryOptions.Quantiles[i].Quantile { - t.Fatalf("%d.%q: Expected quantile %v, got %v", i, metric, m.SummaryOptions.Quantiles[i].Quantile, quantile.Quantile) - } - if quantile.Error != m.SummaryOptions.Quantiles[i].Error { - t.Fatalf("%d.%q: Expected Error margin %v, got %v", i, metric, m.SummaryOptions.Quantiles[i].Error, quantile.Error) + m, labels, present := mapper.GetMapping(mapping.statsdMetric, mapType) + if present && mapping.name != "" && m.Name != mapping.name { + t.Fatalf("%d.%q: Expected name %v, got %v", i, metric, m.Name, mapping.name) + } + if mapping.notPresent && present { + t.Fatalf("%d.%q: Expected metric to not be present", i, metric) + } + if len(labels) != len(mapping.labels) { + t.Fatalf("%d.%q: Expected %d labels, got %d", i, metric, len(mapping.labels), len(labels)) + } + for label, value := range labels { + if mapping.labels[label] != value { + t.Fatalf("%d.%q: Expected labels %v, got %v", i, metric, mapping, labels) } } + if mapping.ttl > 0 && mapping.ttl != m.Ttl { + t.Fatalf("%d.%q: Expected ttl of %s, got %s", i, metric, mapping.ttl.String(), m.Ttl.String()) + } + if mapping.metricType != "" && mapType != m.MatchMetricType { + t.Fatalf("%d.%q: Expected match metric of %s, got %s", i, metric, mapType, m.MatchMetricType) + } + + if len(mapping.buckets) != 0 { + if len(mapping.buckets) != len(m.HistogramOptions.Buckets) { + t.Fatalf("%d.%q: Expected %d buckets, got %d", i, metric, len(mapping.buckets), len(m.HistogramOptions.Buckets)) + } + for i, bucket := range mapping.buckets { + if bucket != m.HistogramOptions.Buckets[i] { + t.Fatalf("%d.%q: Expected bucket %v, got %v", i, metric, m.HistogramOptions.Buckets[i], bucket) + } + } + } + + if len(mapping.quantiles) != 0 { + if len(mapping.quantiles) != len(m.SummaryOptions.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 { + if quantile.Quantile != m.SummaryOptions.Quantiles[i].Quantile { + t.Fatalf("%d.%q: Expected quantile %v, got %v", i, metric, m.SummaryOptions.Quantiles[i].Quantile, quantile.Quantile) + } + if quantile.Error != m.SummaryOptions.Quantiles[i].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) + } } - 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) - } - } + }) } } func TestAction(t *testing.T) { scenarios := []struct { + testName string config string configBad bool expectedAction ActionType }{ { - // no action set + testName: "no action set", config: `--- mappings: - match: test.*.* @@ -1003,7 +1477,7 @@ mappings: expectedAction: ActionTypeMap, }, { - // map action set + testName: "map action set", config: `--- mappings: - match: test.*.* @@ -1014,7 +1488,7 @@ mappings: expectedAction: ActionTypeMap, }, { - // drop action set + testName: "drop action set", config: `--- mappings: - match: test.*.* @@ -1025,7 +1499,7 @@ mappings: expectedAction: ActionTypeDrop, }, { - // invalid action set + testName: "invalid action set", config: `--- mappings: - match: test.*.* @@ -1036,7 +1510,7 @@ mappings: expectedAction: ActionTypeDrop, }, { - // valid yaml example + testName: "valid yaml example", config: `--- mappings: - match: "test\\.(\\w+)\\.(\\w+)\\.counter" @@ -1049,7 +1523,7 @@ mappings: expectedAction: ActionTypeMap, }, { - // invalid yaml example + testName: "invalid yaml example", config: `--- mappings: - match: "test\.(\w+)\.(\w+)\.counter" @@ -1063,21 +1537,26 @@ mappings: } for i, scenario := range scenarios { - mapper := MetricMapper{} - err := mapper.InitFromYAMLString(scenario.config, 0) - if err != nil && !scenario.configBad { - t.Fatalf("%d. Config load error: %s %s", i, scenario.config, err) + if scenario.testName == "" { + t.Fatalf("Missing testName in scenario %+v", scenario) } - if err == nil && scenario.configBad { - t.Fatalf("%d. Expected bad config, but loaded ok: %s", i, scenario.config) - } - - if !scenario.configBad { - a := mapper.Mappings[0].Action - if scenario.expectedAction != a { - t.Fatalf("%d: Expected action %v, got %v", i, scenario.expectedAction, a) + t.Run(scenario.testName, func(t *testing.T) { + mapper := MetricMapper{} + err := mapper.InitFromYAMLString(scenario.config, 0) + if err != nil && !scenario.configBad { + t.Fatalf("%d. Config load error: %s %s", i, scenario.config, err) } - } + if err == nil && scenario.configBad { + t.Fatalf("%d. Expected bad config, but loaded ok: %s", i, scenario.config) + } + + if !scenario.configBad { + a := mapper.Mappings[0].Action + if scenario.expectedAction != a { + t.Fatalf("%d: Expected action %v, got %v", i, scenario.expectedAction, a) + } + } + }) } } diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go index 80a9dc1..d5c813d 100644 --- a/pkg/registry/registry.go +++ b/pkg/registry/registry.go @@ -23,6 +23,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" + "github.com/prometheus/statsd_exporter/pkg/clock" "github.com/prometheus/statsd_exporter/pkg/mapper" "github.com/prometheus/statsd_exporter/pkg/metrics" @@ -248,7 +249,7 @@ func (r *Registry) GetHistogram(metricName string, labels prometheus.Labels, hel var histogramVec *prometheus.HistogramVec if vh == nil { metricsCount.WithLabelValues("histogram").Inc() - buckets := r.Mapper.Defaults.Buckets + buckets := r.Mapper.Defaults.HistogramOptions.Buckets if mapping.HistogramOptions != nil && len(mapping.HistogramOptions.Buckets) > 0 { buckets = mapping.HistogramOptions.Buckets } @@ -295,14 +296,21 @@ func (r *Registry) GetSummary(metricName string, labels prometheus.Labels, help var summaryVec *prometheus.SummaryVec if vh == nil { metricsCount.WithLabelValues("summary").Inc() - quantiles := r.Mapper.Defaults.Quantiles + quantiles := r.Mapper.Defaults.SummaryOptions.Quantiles if mapping != nil && mapping.SummaryOptions != nil && len(mapping.SummaryOptions.Quantiles) > 0 { quantiles = mapping.SummaryOptions.Quantiles } - summaryOptions := mapper.SummaryOptions{} + + summaryOptions := mapper.SummaryOptions{ + MaxAge: r.Mapper.Defaults.SummaryOptions.MaxAge, + AgeBuckets: r.Mapper.Defaults.SummaryOptions.AgeBuckets, + BufCap: r.Mapper.Defaults.SummaryOptions.BufCap, + } + if mapping != nil && mapping.SummaryOptions != nil { summaryOptions = *mapping.SummaryOptions } + objectives := make(map[float64]float64) for _, q := range quantiles { objectives[q.Quantile] = q.Error