forked from mirrors/statsd_exporter
Merge pull request #136 from westphahl/match-metric-type
Allow mapping match on metric type
This commit is contained in:
commit
09e16e7aa9
5 changed files with 103 additions and 12 deletions
15
README.md
15
README.md
|
@ -251,6 +251,21 @@ mappings:
|
|||
You can drop any metric using the normal match syntax.
|
||||
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
|
||||
|
||||
You can deploy this exporter using the [prom/statsd-exporter](https://registry.hub.docker.com/u/prom/statsd-exporter/) Docker image.
|
||||
|
|
|
@ -182,6 +182,7 @@ type Event interface {
|
|||
MetricName() string
|
||||
Value() float64
|
||||
Labels() map[string]string
|
||||
MetricType() metricType
|
||||
}
|
||||
|
||||
type CounterEvent struct {
|
||||
|
@ -193,6 +194,7 @@ type CounterEvent struct {
|
|||
func (c *CounterEvent) MetricName() string { return c.metricName }
|
||||
func (c *CounterEvent) Value() float64 { return c.value }
|
||||
func (c *CounterEvent) Labels() map[string]string { return c.labels }
|
||||
func (c *CounterEvent) MetricType() metricType { return metricTypeCounter }
|
||||
|
||||
type GaugeEvent struct {
|
||||
metricName string
|
||||
|
@ -204,6 +206,7 @@ type GaugeEvent struct {
|
|||
func (g *GaugeEvent) MetricName() string { return g.metricName }
|
||||
func (g *GaugeEvent) Value() float64 { return g.value }
|
||||
func (c *GaugeEvent) Labels() map[string]string { return c.labels }
|
||||
func (c *GaugeEvent) MetricType() metricType { return metricTypeGauge }
|
||||
|
||||
type TimerEvent struct {
|
||||
metricName string
|
||||
|
@ -214,6 +217,7 @@ type TimerEvent struct {
|
|||
func (t *TimerEvent) MetricName() string { return t.metricName }
|
||||
func (t *TimerEvent) Value() float64 { return t.value }
|
||||
func (c *TimerEvent) Labels() map[string]string { return c.labels }
|
||||
func (c *TimerEvent) MetricType() metricType { return metricTypeTimer }
|
||||
|
||||
type Events []Event
|
||||
|
||||
|
@ -248,7 +252,7 @@ func (b *Exporter) Listen(e <-chan Events) {
|
|||
metricName := ""
|
||||
prometheusLabels := event.Labels()
|
||||
|
||||
mapping, labels, present := b.mapper.getMapping(event.MetricName())
|
||||
mapping, labels, present := b.mapper.getMapping(event.MetricName(), event.MetricType())
|
||||
if mapping == nil {
|
||||
mapping = &metricMapping{}
|
||||
}
|
||||
|
|
27
mapper.go
27
mapper.go
|
@ -45,16 +45,19 @@ type metricMapper struct {
|
|||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
type matchMetricType string
|
||||
|
||||
type metricMapping struct {
|
||||
Match string `yaml:"match"`
|
||||
Name string `yaml:"name"`
|
||||
regex *regexp.Regexp
|
||||
Labels prometheus.Labels `yaml:"labels"`
|
||||
TimerType timerType `yaml:"timer_type"`
|
||||
Buckets []float64 `yaml:"buckets"`
|
||||
MatchType matchType `yaml:"match_type"`
|
||||
HelpText string `yaml:"help"`
|
||||
Action actionType `yaml:"action"`
|
||||
Match string `yaml:"match"`
|
||||
Name string `yaml:"name"`
|
||||
regex *regexp.Regexp
|
||||
Labels prometheus.Labels `yaml:"labels"`
|
||||
TimerType timerType `yaml:"timer_type"`
|
||||
Buckets []float64 `yaml:"buckets"`
|
||||
MatchType matchType `yaml:"match_type"`
|
||||
HelpText string `yaml:"help"`
|
||||
Action actionType `yaml:"action"`
|
||||
MatchMetricType metricType `yaml:"match_metric_type"`
|
||||
}
|
||||
|
||||
func (m *metricMapper) initFromYAMLString(fileContents string) error {
|
||||
|
@ -148,7 +151,7 @@ func (m *metricMapper) initFromFile(fileName string) error {
|
|||
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()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
|
@ -165,6 +168,10 @@ func (m *metricMapper) getMapping(statsdMetric string) (*metricMapping, promethe
|
|||
matches,
|
||||
))
|
||||
|
||||
if mt := mapping.MatchMetricType; mt != "" && mt != statsdMetricType {
|
||||
continue
|
||||
}
|
||||
|
||||
labels := prometheus.Labels{}
|
||||
for label, valueExpr := range mapping.Labels {
|
||||
value := mapping.regex.ExpandString([]byte{}, valueExpr, statsdMetric, matches)
|
||||
|
|
|
@ -302,6 +302,27 @@ mappings:
|
|||
- match: test.*.*
|
||||
timer_type: wrong
|
||||
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: {}
|
||||
`,
|
||||
configBad: true,
|
||||
|
@ -432,8 +453,9 @@ mappings:
|
|||
t.Fatalf("%d. Expected bad config, but loaded ok: %s", i, scenario.config)
|
||||
}
|
||||
|
||||
var dummyMetricType metricType = ""
|
||||
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 {
|
||||
t.Fatalf("%d.%q: Expected name %v, got %v", i, metric, m.Name, mapping.name)
|
||||
}
|
||||
|
|
43
metric_type.go
Normal file
43
metric_type.go
Normal 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
|
||||
}
|
Loading…
Reference in a new issue