Move mapper to a package

This allows the mapping logic and configuration format to be re-used by
the graphite exporter.

Enables prometheus/graphite_exporter#37, cf. https://github.com/prometheus/graphite_exporter/pull/52#issuecomment-412324849

Signed-off-by: Matthias Rampke <mr@soundcloud.com>
This commit is contained in:
Matthias Rampke 2018-08-10 12:28:38 +00:00
parent 419e27d284
commit 124c5b88d0
No known key found for this signature in database
GPG key ID: 2CDE413A9BD0A5BC
11 changed files with 148 additions and 118 deletions

View file

@ -29,6 +29,8 @@ import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/log"
"github.com/prometheus/common/model"
"github.com/prometheus/statsd_exporter/mapper"
)
const (
@ -117,17 +119,17 @@ func (c *GaugeContainer) Get(metricName string, labels prometheus.Labels, help s
type SummaryContainer struct {
Elements map[uint64]prometheus.Summary
mapper *metricMapper
mapper *mapper.MetricMapper
}
func NewSummaryContainer(mapper *metricMapper) *SummaryContainer {
func NewSummaryContainer(mapper *mapper.MetricMapper) *SummaryContainer {
return &SummaryContainer{
Elements: make(map[uint64]prometheus.Summary),
mapper: mapper,
}
}
func (c *SummaryContainer) Get(metricName string, labels prometheus.Labels, help string, mapping *metricMapping) (prometheus.Summary, error) {
func (c *SummaryContainer) Get(metricName string, labels prometheus.Labels, help string, mapping *mapper.MetricMapping) (prometheus.Summary, error) {
hash := hashNameAndLabels(metricName, labels)
summary, ok := c.Elements[hash]
if !ok {
@ -156,17 +158,17 @@ func (c *SummaryContainer) Get(metricName string, labels prometheus.Labels, help
type HistogramContainer struct {
Elements map[uint64]prometheus.Histogram
mapper *metricMapper
mapper *mapper.MetricMapper
}
func NewHistogramContainer(mapper *metricMapper) *HistogramContainer {
func NewHistogramContainer(mapper *mapper.MetricMapper) *HistogramContainer {
return &HistogramContainer{
Elements: make(map[uint64]prometheus.Histogram),
mapper: mapper,
}
}
func (c *HistogramContainer) Get(metricName string, labels prometheus.Labels, help string, mapping *metricMapping) (prometheus.Histogram, error) {
func (c *HistogramContainer) Get(metricName string, labels prometheus.Labels, help string, mapping *mapper.MetricMapping) (prometheus.Histogram, error) {
hash := hashNameAndLabels(metricName, labels)
histogram, ok := c.Elements[hash]
if !ok {
@ -193,7 +195,7 @@ type Event interface {
MetricName() string
Value() float64
Labels() map[string]string
MetricType() metricType
MetricType() mapper.MetricType
}
type CounterEvent struct {
@ -202,10 +204,10 @@ type CounterEvent struct {
labels map[string]string
}
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 }
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() mapper.MetricType { return mapper.MetricTypeCounter }
type GaugeEvent struct {
metricName string
@ -214,10 +216,10 @@ type GaugeEvent struct {
labels map[string]string
}
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 }
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() mapper.MetricType { return mapper.MetricTypeGauge }
type TimerEvent struct {
metricName string
@ -225,10 +227,10 @@ type TimerEvent struct {
labels map[string]string
}
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 }
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() mapper.MetricType { return mapper.MetricTypeTimer }
type Events []Event
@ -237,7 +239,7 @@ type Exporter struct {
Gauges *GaugeContainer
Summaries *SummaryContainer
Histograms *HistogramContainer
mapper *metricMapper
mapper *mapper.MetricMapper
}
func escapeMetricName(metricName string) string {
@ -263,12 +265,12 @@ func (b *Exporter) Listen(e <-chan Events) {
metricName := ""
prometheusLabels := event.Labels()
mapping, labels, present := b.mapper.getMapping(event.MetricName(), event.MetricType())
mapping, labels, present := b.mapper.GetMapping(event.MetricName(), event.MetricType())
if mapping == nil {
mapping = &metricMapping{}
mapping = &mapper.MetricMapping{}
}
if mapping.Action == actionTypeDrop {
if mapping.Action == mapper.ActionTypeDrop {
continue
}
@ -332,16 +334,16 @@ func (b *Exporter) Listen(e <-chan Events) {
}
case *TimerEvent:
t := timerTypeDefault
t := mapper.TimerTypeDefault
if mapping != nil {
t = mapping.TimerType
}
if t == timerTypeDefault {
if t == mapper.TimerTypeDefault {
t = b.mapper.Defaults.TimerType
}
switch t {
case timerTypeHistogram:
case mapper.TimerTypeHistogram:
histogram, err := b.Histograms.Get(
metricName,
prometheusLabels,
@ -356,7 +358,7 @@ func (b *Exporter) Listen(e <-chan Events) {
conflictingEventStats.WithLabelValues("timer").Inc()
}
case timerTypeDefault, timerTypeSummary:
case mapper.TimerTypeDefault, mapper.TimerTypeSummary:
summary, err := b.Summaries.Get(
metricName,
prometheusLabels,
@ -383,7 +385,7 @@ func (b *Exporter) Listen(e <-chan Events) {
}
}
func NewExporter(mapper *metricMapper) *Exporter {
func NewExporter(mapper *mapper.MetricMapper) *Exporter {
return &Exporter{
Counters: NewCounterContainer(),
Gauges: NewGaugeContainer(),

View file

@ -20,6 +20,8 @@ import (
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/statsd_exporter/mapper"
)
// TestNegativeCounter validates when we send a negative
@ -44,7 +46,7 @@ func TestNegativeCounter(t *testing.T) {
},
}
events <- c
ex := NewExporter(&metricMapper{})
ex := NewExporter(&mapper.MetricMapper{})
// Close channel to signify we are done with the listener after a short period.
go func() {
@ -60,7 +62,7 @@ func TestNegativeCounter(t *testing.T) {
// It sends the same tags first with a valid value, then with an invalid one.
// The exporter should not panic, but drop the invalid event
func TestInvalidUtf8InDatadogTagValue(t *testing.T) {
ex := NewExporter(&metricMapper{})
ex := NewExporter(&mapper.MetricMapper{})
for _, l := range []statsDPacketHandler{&StatsDUDPListener{}, &mockStatsDTCPListener{}} {
events := make(chan Events, 2)
@ -96,8 +98,8 @@ func TestHistogramUnits(t *testing.T) {
},
}
events <- c
ex := NewExporter(&metricMapper{})
ex.mapper.Defaults.TimerType = timerTypeHistogram
ex := NewExporter(&mapper.MetricMapper{})
ex.mapper.Defaults.TimerType = mapper.TimerTypeHistogram
// Close channel to signify we are done with the listener after a short period.
go func() {

10
main.go
View file

@ -25,6 +25,8 @@ import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/log"
"github.com/prometheus/common/version"
"github.com/prometheus/statsd_exporter/mapper"
)
func init() {
@ -96,7 +98,7 @@ func tcpAddrFromString(addr string) *net.TCPAddr {
}
}
func watchConfig(fileName string, mapper *metricMapper) {
func watchConfig(fileName string, mapper *mapper.MetricMapper) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
@ -111,7 +113,7 @@ func watchConfig(fileName string, mapper *metricMapper) {
select {
case ev := <-watcher.Event:
log.Infof("Config file changed (%s), attempting reload", ev)
err = mapper.initFromFile(fileName)
err = mapper.InitFromFile(fileName)
if err != nil {
log.Errorln("Error reloading config:", err)
configLoads.WithLabelValues("failure").Inc()
@ -186,9 +188,9 @@ func main() {
go tl.Listen(events)
}
mapper := &metricMapper{}
mapper := &mapper.MetricMapper{}
if *mappingConfig != "" {
err := mapper.initFromFile(*mappingConfig)
err := mapper.InitFromFile(*mappingConfig)
if err != nil {
log.Fatal("Error loading config:", err)
}

View file

@ -11,30 +11,30 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package main
package mapper
import "fmt"
type actionType string
type ActionType string
const (
actionTypeMap actionType = "map"
actionTypeDrop actionType = "drop"
actionTypeDefault actionType = ""
ActionTypeMap ActionType = "map"
ActionTypeDrop ActionType = "drop"
ActionTypeDefault ActionType = ""
)
func (t *actionType) UnmarshalYAML(unmarshal func(interface{}) error) error {
func (t *ActionType) UnmarshalYAML(unmarshal func(interface{}) error) error {
var v string
if err := unmarshal(&v); err != nil {
return err
}
switch actionType(v) {
case actionTypeDrop:
*t = actionTypeDrop
case actionTypeMap, actionTypeDefault:
*t = actionTypeMap
switch ActionType(v) {
case ActionTypeDrop:
*t = ActionTypeDrop
case ActionTypeMap, ActionTypeDefault:
*t = ActionTypeMap
default:
return fmt.Errorf("invalid action type %q", v)
}

View file

@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package main
package mapper
import (
"fmt"
@ -34,32 +34,32 @@ var (
)
type mapperConfigDefaults struct {
TimerType timerType `yaml:"timer_type"`
TimerType TimerType `yaml:"timer_type"`
Buckets []float64 `yaml:"buckets"`
Quantiles []metricObjective `yaml:"quantiles"`
MatchType matchType `yaml:"match_type"`
MatchType MatchType `yaml:"match_type"`
}
type metricMapper struct {
type MetricMapper struct {
Defaults mapperConfigDefaults `yaml:"defaults"`
Mappings []metricMapping `yaml:"mappings"`
Mappings []MetricMapping `yaml:"mappings"`
mutex sync.Mutex
}
type matchMetricType string
type metricMapping struct {
type MetricMapping struct {
Match string `yaml:"match"`
Name string `yaml:"name"`
regex *regexp.Regexp
Labels prometheus.Labels `yaml:"labels"`
TimerType timerType `yaml:"timer_type"`
TimerType TimerType `yaml:"timer_type"`
Buckets []float64 `yaml:"buckets"`
Quantiles []metricObjective `yaml:"quantiles"`
MatchType matchType `yaml:"match_type"`
MatchType MatchType `yaml:"match_type"`
HelpText string `yaml:"help"`
Action actionType `yaml:"action"`
MatchMetricType metricType `yaml:"match_metric_type"`
Action ActionType `yaml:"action"`
MatchMetricType MetricType `yaml:"match_metric_type"`
}
type metricObjective struct {
@ -73,8 +73,8 @@ var defaultQuantiles = []metricObjective{
{Quantile: 0.99, Error: 0.001},
}
func (m *metricMapper) initFromYAMLString(fileContents string) error {
var n metricMapper
func (m *MetricMapper) InitFromYAMLString(fileContents string) error {
var n MetricMapper
if err := yaml.Unmarshal([]byte(fileContents), &n); err != nil {
return err
@ -88,8 +88,8 @@ func (m *metricMapper) initFromYAMLString(fileContents string) error {
n.Defaults.Quantiles = defaultQuantiles
}
if n.Defaults.MatchType == matchTypeDefault {
n.Defaults.MatchType = matchTypeGlob
if n.Defaults.MatchType == MatchTypeDefault {
n.Defaults.MatchType = MatchTypeGlob
}
for i := range n.Mappings {
@ -115,10 +115,10 @@ func (m *metricMapper) initFromYAMLString(fileContents string) error {
}
if currentMapping.Action == "" {
currentMapping.Action = actionTypeMap
currentMapping.Action = ActionTypeMap
}
if currentMapping.MatchType == matchTypeGlob {
if currentMapping.MatchType == MatchTypeGlob {
if !metricLineRE.MatchString(currentMapping.Match) {
return fmt.Errorf("invalid match: %s", currentMapping.Match)
}
@ -164,15 +164,15 @@ func (m *metricMapper) initFromYAMLString(fileContents string) error {
return nil
}
func (m *metricMapper) initFromFile(fileName string) error {
func (m *MetricMapper) InitFromFile(fileName string) error {
mappingStr, err := ioutil.ReadFile(fileName)
if err != nil {
return err
}
return m.initFromYAMLString(string(mappingStr))
return m.InitFromYAMLString(string(mappingStr))
}
func (m *metricMapper) getMapping(statsdMetric string, statsdMetricType metricType) (*metricMapping, prometheus.Labels, bool) {
func (m *MetricMapper) GetMapping(statsdMetric string, statsdMetricType MetricType) (*MetricMapping, prometheus.Labels, bool) {
m.mutex.Lock()
defer m.mutex.Unlock()

View file

@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package main
package mapper
import (
"testing"
@ -473,9 +473,9 @@ mappings:
},
}
mapper := metricMapper{}
mapper := MetricMapper{}
for i, scenario := range scenarios {
err := mapper.initFromYAMLString(scenario.config)
err := mapper.InitFromYAMLString(scenario.config)
if err != nil && !scenario.configBad {
t.Fatalf("%d. Config load error: %s %s", i, scenario.config, err)
}
@ -483,9 +483,9 @@ mappings:
t.Fatalf("%d. Expected bad config, but loaded ok: %s", i, scenario.config)
}
var dummyMetricType metricType = ""
var dummyMetricType MetricType = ""
for metric, mapping := range scenario.mappings {
m, labels, present := mapper.getMapping(metric, dummyMetricType)
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)
}
@ -522,7 +522,7 @@ func TestAction(t *testing.T) {
scenarios := []struct {
config string
configBad bool
expectedAction actionType
expectedAction ActionType
}{
{
// no action set
@ -532,7 +532,7 @@ mappings:
name: "foo"
`,
configBad: false,
expectedAction: actionTypeMap,
expectedAction: ActionTypeMap,
},
{
// map action set
@ -543,7 +543,7 @@ mappings:
action: map
`,
configBad: false,
expectedAction: actionTypeMap,
expectedAction: ActionTypeMap,
},
{
// drop action set
@ -554,7 +554,7 @@ mappings:
action: drop
`,
configBad: false,
expectedAction: actionTypeDrop,
expectedAction: ActionTypeDrop,
},
{
// invalid action set
@ -565,13 +565,13 @@ mappings:
action: xyz
`,
configBad: true,
expectedAction: actionTypeDrop,
expectedAction: ActionTypeDrop,
},
}
for i, scenario := range scenarios {
mapper := metricMapper{}
err := mapper.initFromYAMLString(scenario.config)
mapper := MetricMapper{}
err := mapper.InitFromYAMLString(scenario.config)
if err != nil && !scenario.configBad {
t.Fatalf("%d. Config load error: %s %s", i, scenario.config, err)
}

View file

@ -11,29 +11,29 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package main
package mapper
import "fmt"
type matchType string
type MatchType string
const (
matchTypeGlob matchType = "glob"
matchTypeRegex matchType = "regex"
matchTypeDefault matchType = ""
MatchTypeGlob MatchType = "glob"
MatchTypeRegex MatchType = "regex"
MatchTypeDefault MatchType = ""
)
func (t *matchType) UnmarshalYAML(unmarshal func(interface{}) error) error {
func (t *MatchType) UnmarshalYAML(unmarshal func(interface{}) error) error {
var v string
if err := unmarshal(&v); err != nil {
return err
}
switch matchType(v) {
case matchTypeRegex:
*t = matchTypeRegex
case matchTypeGlob, matchTypeDefault:
*t = matchTypeGlob
switch MatchType(v) {
case MatchTypeRegex:
*t = MatchTypeRegex
case MatchTypeGlob, MatchTypeDefault:
*t = MatchTypeGlob
default:
return fmt.Errorf("invalid match type %q", v)
}

View file

@ -11,31 +11,31 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package main
package mapper
import "fmt"
type metricType string
type MetricType string
const (
metricTypeCounter metricType = "counter"
metricTypeGauge metricType = "gauge"
metricTypeTimer metricType = "timer"
MetricTypeCounter MetricType = "counter"
MetricTypeGauge MetricType = "gauge"
MetricTypeTimer MetricType = "timer"
)
func (m *metricType) UnmarshalYAML(unmarshal func(interface{}) error) error {
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
switch MetricType(v) {
case MetricTypeCounter:
*m = MetricTypeCounter
case MetricTypeGauge:
*m = MetricTypeGauge
case MetricTypeTimer:
*m = MetricTypeTimer
default:
return fmt.Errorf("invalid metric type '%s'", v)
}

29
mapper/telemetry.go Normal file
View file

@ -0,0 +1,29 @@
// Copyright 2013 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 mapper
import (
"github.com/prometheus/client_golang/prometheus"
)
var (
mappingsCount = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "statsd_exporter_loaded_mappings",
Help: "The current number of configured metric mappings.",
})
)
func init() {
prometheus.MustRegister(mappingsCount)
}

View file

@ -11,29 +11,29 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package main
package mapper
import "fmt"
type timerType string
type TimerType string
const (
timerTypeHistogram timerType = "histogram"
timerTypeSummary timerType = "summary"
timerTypeDefault timerType = ""
TimerTypeHistogram TimerType = "histogram"
TimerTypeSummary TimerType = "summary"
TimerTypeDefault TimerType = ""
)
func (t *timerType) UnmarshalYAML(unmarshal func(interface{}) error) error {
func (t *TimerType) UnmarshalYAML(unmarshal func(interface{}) error) error {
var v string
if err := unmarshal(&v); err != nil {
return err
}
switch timerType(v) {
case timerTypeHistogram:
*t = timerTypeHistogram
case timerTypeSummary, timerTypeDefault:
*t = timerTypeSummary
switch TimerType(v) {
case TimerTypeHistogram:
*t = TimerTypeHistogram
case TimerTypeSummary, TimerTypeDefault:
*t = TimerTypeSummary
default:
return fmt.Errorf("invalid timer type '%s'", v)
}

View file

@ -91,10 +91,6 @@ var (
},
[]string{"outcome"},
)
mappingsCount = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "statsd_exporter_loaded_mappings",
Help: "The current number of configured metric mappings.",
})
conflictingEventStats = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "statsd_exporter_events_conflict_total",
@ -116,6 +112,5 @@ func init() {
prometheus.MustRegister(tagsReceived)
prometheus.MustRegister(tagErrors)
prometheus.MustRegister(configLoads)
prometheus.MustRegister(mappingsCount)
prometheus.MustRegister(conflictingEventStats)
}