Merge pull request #136 from westphahl/match-metric-type

Allow mapping match on metric type
This commit is contained in:
Matthias Rampke 2018-07-02 15:22:54 +00:00 committed by GitHub
commit 09e16e7aa9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 103 additions and 12 deletions

View file

@ -251,6 +251,21 @@ mappings:
You can drop any metric using the normal match syntax. You can drop any metric using the normal match syntax.
The default action is "map" which does the normal metrics mapping. The default action is "map" which does the normal metrics mapping.
StatsD allows emitting of different metric types under the same metric name,
but the Prometheus client library can't merge those. For this use-case the
mapping definition allows you to specify which metric type to match:
```
mappings:
- match: test.foo.*
name: "test_foo"
match_metric_type: counter
labels:
provider: "$1"
```
Possible values for `match_metric_type` are `gauge`, `counter` and `timer`.
## Using Docker ## Using Docker
You can deploy this exporter using the [prom/statsd-exporter](https://registry.hub.docker.com/u/prom/statsd-exporter/) Docker image. You can deploy this exporter using the [prom/statsd-exporter](https://registry.hub.docker.com/u/prom/statsd-exporter/) Docker image.

View file

@ -182,6 +182,7 @@ type Event interface {
MetricName() string MetricName() string
Value() float64 Value() float64
Labels() map[string]string Labels() map[string]string
MetricType() metricType
} }
type CounterEvent struct { type CounterEvent struct {
@ -193,6 +194,7 @@ type CounterEvent struct {
func (c *CounterEvent) MetricName() string { return c.metricName } func (c *CounterEvent) MetricName() string { return c.metricName }
func (c *CounterEvent) Value() float64 { return c.value } func (c *CounterEvent) Value() float64 { return c.value }
func (c *CounterEvent) Labels() map[string]string { return c.labels } func (c *CounterEvent) Labels() map[string]string { return c.labels }
func (c *CounterEvent) MetricType() metricType { return metricTypeCounter }
type GaugeEvent struct { type GaugeEvent struct {
metricName string metricName string
@ -204,6 +206,7 @@ type GaugeEvent struct {
func (g *GaugeEvent) MetricName() string { return g.metricName } func (g *GaugeEvent) MetricName() string { return g.metricName }
func (g *GaugeEvent) Value() float64 { return g.value } func (g *GaugeEvent) Value() float64 { return g.value }
func (c *GaugeEvent) Labels() map[string]string { return c.labels } func (c *GaugeEvent) Labels() map[string]string { return c.labels }
func (c *GaugeEvent) MetricType() metricType { return metricTypeGauge }
type TimerEvent struct { type TimerEvent struct {
metricName string metricName string
@ -214,6 +217,7 @@ type TimerEvent struct {
func (t *TimerEvent) MetricName() string { return t.metricName } func (t *TimerEvent) MetricName() string { return t.metricName }
func (t *TimerEvent) Value() float64 { return t.value } func (t *TimerEvent) Value() float64 { return t.value }
func (c *TimerEvent) Labels() map[string]string { return c.labels } func (c *TimerEvent) Labels() map[string]string { return c.labels }
func (c *TimerEvent) MetricType() metricType { return metricTypeTimer }
type Events []Event type Events []Event
@ -248,7 +252,7 @@ func (b *Exporter) Listen(e <-chan Events) {
metricName := "" metricName := ""
prometheusLabels := event.Labels() prometheusLabels := event.Labels()
mapping, labels, present := b.mapper.getMapping(event.MetricName()) mapping, labels, present := b.mapper.getMapping(event.MetricName(), event.MetricType())
if mapping == nil { if mapping == nil {
mapping = &metricMapping{} mapping = &metricMapping{}
} }

View file

@ -45,16 +45,19 @@ type metricMapper struct {
mutex sync.Mutex mutex sync.Mutex
} }
type matchMetricType string
type metricMapping struct { type metricMapping struct {
Match string `yaml:"match"` Match string `yaml:"match"`
Name string `yaml:"name"` Name string `yaml:"name"`
regex *regexp.Regexp regex *regexp.Regexp
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"`
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"`
} }
func (m *metricMapper) initFromYAMLString(fileContents string) error { func (m *metricMapper) initFromYAMLString(fileContents string) error {
@ -148,7 +151,7 @@ func (m *metricMapper) initFromFile(fileName string) error {
return m.initFromYAMLString(string(mappingStr)) return m.initFromYAMLString(string(mappingStr))
} }
func (m *metricMapper) getMapping(statsdMetric string) (*metricMapping, prometheus.Labels, bool) { func (m *metricMapper) getMapping(statsdMetric string, statsdMetricType metricType) (*metricMapping, prometheus.Labels, bool) {
m.mutex.Lock() m.mutex.Lock()
defer m.mutex.Unlock() defer m.mutex.Unlock()
@ -165,6 +168,10 @@ func (m *metricMapper) getMapping(statsdMetric string) (*metricMapping, promethe
matches, matches,
)) ))
if mt := mapping.MatchMetricType; mt != "" && mt != statsdMetricType {
continue
}
labels := prometheus.Labels{} labels := prometheus.Labels{}
for label, valueExpr := range mapping.Labels { for label, valueExpr := range mapping.Labels {
value := mapping.regex.ExpandString([]byte{}, valueExpr, statsdMetric, matches) value := mapping.regex.ExpandString([]byte{}, valueExpr, statsdMetric, matches)

View file

@ -302,6 +302,27 @@ mappings:
- match: test.*.* - match: test.*.*
timer_type: wrong timer_type: wrong
name: "foo" name: "foo"
labels: {}
`,
configBad: true,
},
// Config with good metric type.
{
config: `---
mappings:
- match: test.*.*
match_metric_type: counter
name: "foo"
labels: {}
`,
},
// Config with bad metric type matcher.
{
config: `---
mappings:
- match: test.*.*
match_metric_type: wrong
name: "foo"
labels: {} labels: {}
`, `,
configBad: true, configBad: true,
@ -432,8 +453,9 @@ mappings:
t.Fatalf("%d. Expected bad config, but loaded ok: %s", i, scenario.config) t.Fatalf("%d. Expected bad config, but loaded ok: %s", i, scenario.config)
} }
var dummyMetricType metricType = ""
for metric, mapping := range scenario.mappings { for metric, mapping := range scenario.mappings {
m, labels, present := mapper.getMapping(metric) m, labels, present := mapper.getMapping(metric, dummyMetricType)
if present && mapping.name != "" && m.Name != mapping.name { if present && mapping.name != "" && m.Name != mapping.name {
t.Fatalf("%d.%q: Expected name %v, got %v", i, metric, m.Name, mapping.name) t.Fatalf("%d.%q: Expected name %v, got %v", i, metric, m.Name, mapping.name)
} }

43
metric_type.go Normal file
View file

@ -0,0 +1,43 @@
// Copyright 2018 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import "fmt"
type metricType string
const (
metricTypeCounter metricType = "counter"
metricTypeGauge metricType = "gauge"
metricTypeTimer metricType = "timer"
)
func (m *metricType) UnmarshalYAML(unmarshal func(interface{}) error) error {
var v string
if err := unmarshal(&v); err != nil {
return err
}
switch metricType(v) {
case metricTypeCounter:
*m = metricTypeCounter
case metricTypeGauge:
*m = metricTypeGauge
case metricTypeTimer:
*m = metricTypeTimer
default:
return fmt.Errorf("invalid metric type '%s'", v)
}
return nil
}