diff --git a/mapper.go b/mapper.go index af861c4..2bcd017 100644 --- a/mapper.go +++ b/mapper.go @@ -143,6 +143,21 @@ func (m *metricMapper) initFromYAMLString(fileContents string) error { return fmt.Errorf("invalid match: %s", currentMapping.Match) } + // check that label is correct + for k, v := range currentMapping.Labels { + label := fmt.Sprintf("%s=%q", k, v) + if len(labelLineRE.FindStringSubmatch(label)) != 3 { + return fmt.Errorf("invalid label: %s: %s", k, v) + } + if k == "name" && !metricNameRE.MatchString(v) { + return fmt.Errorf("metric name '%s' doesn't match regex '%s'", v, metricNameRE) + } + } + + if _, ok := currentMapping.Labels["name"]; !ok { + return fmt.Errorf("Line %d: metric mapping didn't set a metric name", i) + } + // Translate the glob-style metric match line into a proper regex that we // can use to match metrics later on. metricRe := strings.Replace(currentMapping.Match, ".", "\\.", -1) @@ -156,6 +171,7 @@ func (m *metricMapper) initFromYAMLString(fileContents string) error { if currentMapping.Buckets == nil || len(currentMapping.Buckets) == 0 { currentMapping.Buckets = n.Defaults.Buckets } + } m.mutex.Lock() diff --git a/mapper_test.go b/mapper_test.go index a891a44..560dbb1 100644 --- a/mapper_test.go +++ b/mapper_test.go @@ -28,30 +28,30 @@ func TestMetricMapper(t *testing.T) { // Config with several mapping definitions. { config: ` - # this is a comment - # this is another - test.dispatcher.*.*.* - name="dispatch_events" - processor="$1" - action="$2" - result="$3" - # here is a third - job="test_dispatcher" + # this is a comment + # this is another + test.dispatcher.*.*.* + name="dispatch_events" + processor="$1" + action="$2" + result="$3" + # here is a third + job="test_dispatcher" - test.my-dispatch-host01.name.dispatcher.*.*.* - name="host_dispatch_events" - processor="$1" - action="$2" - result="$3" - job="test_dispatcher" + test.my-dispatch-host01.name.dispatcher.*.*.* + name="host_dispatch_events" + processor="$1" + action="$2" + result="$3" + job="test_dispatcher" - *.* - name="catchall" - first="$1" - second="$2" - third="$3" - job="$1-$2-$3" - `, + *.* + name="catchall" + first="$1" + second="$2" + third="$3" + job="$1-$2-$3" + `, mappings: map[string]map[string]string{ "test.dispatcher.FooProcessor.send.succeeded": map[string]string{ "name": "dispatch_events", @@ -80,10 +80,10 @@ func TestMetricMapper(t *testing.T) { // Config with bad regex reference. { config: ` - test.* - name="name" - label="$1_foo" - `, + test.* + name="name" + label="$1_foo" + `, mappings: map[string]map[string]string{ "test.a": map[string]string{ "name": "name", @@ -94,10 +94,10 @@ func TestMetricMapper(t *testing.T) { // Config with good regex reference. { config: ` - test.* - name="name" - label="${1}_foo" - `, + test.* + name="name" + label="${1}_foo" + `, mappings: map[string]map[string]string{ "test.a": map[string]string{ "name": "name", @@ -108,53 +108,53 @@ func TestMetricMapper(t *testing.T) { // Config with bad metric line. { config: ` - bad--metric-line.*.* - name="foo" - `, + bad--metric-line.*.* + name="foo" + `, configBad: true, }, // Config with bad label line. { config: ` - test.*.* - name=foo - `, + test.*.* + name=foo + `, configBad: true, }, // Config with bad label line. { config: ` - test.*.* - name="foo-name" - `, + test.*.* + name="foo-name" + `, configBad: true, }, // Config with bad metric name. { config: ` - test.*.* - name="0foo" - `, + test.*.* + name="0foo" + `, configBad: true, }, // A single mapping config without a terminating newline. { config: ` - test.* - name="name" - label="foo"`, + test.* + name="name" + label="foo"`, configBad: true, }, // Multiple mapping configs and no terminating newline. { config: ` - test.bar - name="name_bar" - label="foo" + test.bar + name="name_bar" + label="foo" - test.foo - name="name_foo" - label="bar"`, + test.foo + name="name_foo" + label="bar"`, configBad: true, }, } @@ -185,3 +185,158 @@ func TestMetricMapper(t *testing.T) { } } } + +func TestMetricMapperYAML(t *testing.T) { + scenarios := []struct { + config string + configBad bool + mappings map[string]map[string]string + }{ + // Empty config. + {}, + // Config with several mapping definitions. + { + config: `--- +mappings: +- match: test.dispatcher.*.*.* + labels: + name: "dispatch_events" + processor: "$1" + action: "$2" + result: "$3" + job: "test_dispatcher" +- match: test.my-dispatch-host01.name.dispatcher.*.*.* + labels: + name: "host_dispatch_events" + processor: "$1" + action: "$2" + result: "$3" + job: "test_dispatcher" +- match: "*.*" + labels: + name: "catchall" + first: "$1" + second: "$2" + third: "$3" + job: "$1-$2-$3" + `, + mappings: map[string]map[string]string{ + "test.dispatcher.FooProcessor.send.succeeded": map[string]string{ + "name": "dispatch_events", + "processor": "FooProcessor", + "action": "send", + "result": "succeeded", + "job": "test_dispatcher", + }, + "test.my-dispatch-host01.name.dispatcher.FooProcessor.send.succeeded": map[string]string{ + "name": "host_dispatch_events", + "processor": "FooProcessor", + "action": "send", + "result": "succeeded", + "job": "test_dispatcher", + }, + "foo.bar": map[string]string{ + "name": "catchall", + "first": "foo", + "second": "bar", + "third": "", + "job": "foo-bar-", + }, + "foo.bar.baz": map[string]string{}, + }, + }, + // Config with bad regex reference. + { + config: `--- +mappings: +- match: test.* + labels: + name: "name" + label: "$1_foo" + `, + mappings: map[string]map[string]string{ + "test.a": map[string]string{ + "name": "name", + "label": "", + }, + }, + }, + // Config with good regex reference. + { + config: ` +mappings: +- match: test.* + labels: + name: "name" + label: "${1}_foo" + `, + mappings: map[string]map[string]string{ + "test.a": map[string]string{ + "name": "name", + "label": "a_foo", + }, + }, + }, + // Config with bad metric line. + { + config: `--- +mappings: +- match: bad--metric-line.*.* + labels: + name: "foo" + `, + configBad: true, + }, + // Config with bad metric name. + { + config: `--- +mappings: +- match: test.*.* + labels: + name: "0foo" + `, + configBad: true, + }, + // Config with no metric name. + { + config: `--- +mappings: +- match: test.*.* + labels: + this: "$1" + `, + configBad: true, + }, + // Config with no mappings. + { + config: ``, + mappings: map[string]map[string]string{}, + }, + } + + mapper := metricMapper{} + for i, scenario := range scenarios { + err := mapper.initFromYAMLString(scenario.config) + 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) + } + + for metric, mapping := range scenario.mappings { + _, labels, present := mapper.getMapping(metric) + if len(labels) == 0 && present { + t.Fatalf("%d.%q: Expected metric to not be present", i, metric) + } + if len(labels) != len(mapping) { + t.Fatalf("%d.%q: Expected %d labels, got %d", i, metric, len(mapping), len(labels)) + } + for label, value := range labels { + if mapping[label] != value { + t.Fatalf("%d.%q: Expected labels %v, got %v", i, metric, mapping, labels) + } + } + } + } +}