From 1f7b68a2b293a838673460424f1faf67e10bc9d5 Mon Sep 17 00:00:00 2001 From: Daniel Nelson Date: Tue, 3 Dec 2019 13:34:00 -0800 Subject: [PATCH] Allow colons in the prometheus metric name (#6751) --- plugins/serializers/prometheus/collection.go | 6 +- plugins/serializers/prometheus/convert.go | 90 +++++++++++++------ .../serializers/prometheus/prometheus_test.go | 56 ++++++++++++ 3 files changed, 124 insertions(+), 28 deletions(-) diff --git a/plugins/serializers/prometheus/collection.go b/plugins/serializers/prometheus/collection.go index d16208622..8ca06520b 100644 --- a/plugins/serializers/prometheus/collection.go +++ b/plugins/serializers/prometheus/collection.go @@ -113,7 +113,7 @@ func (c *Collection) createLabels(metric telegraf.Metric) []LabelPair { } } - name, ok := SanitizeName(tag.Key) + name, ok := SanitizeLabelName(tag.Key) if !ok { continue } @@ -132,7 +132,7 @@ func (c *Collection) createLabels(metric telegraf.Metric) []LabelPair { continue } - name, ok := SanitizeName(field.Key) + name, ok := SanitizeLabelName(field.Key) if !ok { continue } @@ -161,7 +161,7 @@ func (c *Collection) Add(metric telegraf.Metric) { labels := c.createLabels(metric) for _, field := range metric.FieldList() { metricName := MetricName(metric.Name(), field.Key, metric.Type()) - metricName, ok := SanitizeName(metricName) + metricName, ok := SanitizeMetricName(metricName) if !ok { continue } diff --git a/plugins/serializers/prometheus/convert.go b/plugins/serializers/prometheus/convert.go index 2ef23be63..131ac31b8 100644 --- a/plugins/serializers/prometheus/convert.go +++ b/plugins/serializers/prometheus/convert.go @@ -8,26 +8,53 @@ import ( dto "github.com/prometheus/client_model/go" ) -var FirstTable = &unicode.RangeTable{ - R16: []unicode.Range16{ - {0x0041, 0x005A, 1}, // A-Z - {0x005F, 0x005F, 1}, // _ - {0x0061, 0x007A, 1}, // a-z - }, - LatinOffset: 3, +type Table struct { + First *unicode.RangeTable + Rest *unicode.RangeTable } -var RestTable = &unicode.RangeTable{ - R16: []unicode.Range16{ - {0x0030, 0x0039, 1}, // 0-9 - {0x0041, 0x005A, 1}, // A-Z - {0x005F, 0x005F, 1}, // _ - {0x0061, 0x007A, 1}, // a-z +var MetricNameTable = Table{ + First: &unicode.RangeTable{ + R16: []unicode.Range16{ + {0x003A, 0x003A, 1}, // : + {0x0041, 0x005A, 1}, // A-Z + {0x005F, 0x005F, 1}, // _ + {0x0061, 0x007A, 1}, // a-z + }, + LatinOffset: 4, + }, + Rest: &unicode.RangeTable{ + R16: []unicode.Range16{ + {0x0030, 0x003A, 1}, // 0-: + {0x0041, 0x005A, 1}, // A-Z + {0x005F, 0x005F, 1}, // _ + {0x0061, 0x007A, 1}, // a-z + }, + LatinOffset: 4, }, - LatinOffset: 4, } -func isValid(name string) bool { +var LabelNameTable = Table{ + First: &unicode.RangeTable{ + R16: []unicode.Range16{ + {0x0041, 0x005A, 1}, // A-Z + {0x005F, 0x005F, 1}, // _ + {0x0061, 0x007A, 1}, // a-z + }, + LatinOffset: 3, + }, + Rest: &unicode.RangeTable{ + R16: []unicode.Range16{ + {0x0030, 0x0039, 1}, // 0-9 + {0x0041, 0x005A, 1}, // A-Z + {0x005F, 0x005F, 1}, // _ + {0x0061, 0x007A, 1}, // a-z + }, + LatinOffset: 4, + }, +} + +func isValid(name string, table Table) bool { if name == "" { return false } @@ -35,11 +62,11 @@ func isValid(name string) bool { for i, r := range name { switch { case i == 0: - if !unicode.In(r, FirstTable) { + if !unicode.In(r, table.First) { return false } default: - if !unicode.In(r, RestTable) { + if !unicode.In(r, table.Rest) { return false } } @@ -48,12 +75,11 @@ func isValid(name string) bool { return true } -// SanitizeName check if the name is a valid Prometheus metric name and label -// name. If not, it attempts to replaces invalid runes with an underscore to -// create a valid name. Returns the metric name and true if the name is valid -// to use. -func SanitizeName(name string) (string, bool) { - if isValid(name) { +// Sanitize checks if the name is valid according to the table. If not, it +// attempts to replaces invalid runes with an underscore to create a valid +// name. +func sanitize(name string, table Table) (string, bool) { + if isValid(name, table) { return name, true } @@ -62,11 +88,11 @@ func SanitizeName(name string) (string, bool) { for i, r := range name { switch { case i == 0: - if unicode.In(r, FirstTable) { + if unicode.In(r, table.First) { b.WriteRune(r) } default: - if unicode.In(r, RestTable) { + if unicode.In(r, table.Rest) { b.WriteRune(r) } else { b.WriteString("_") @@ -82,6 +108,20 @@ func SanitizeName(name string) (string, bool) { return name, true } +// SanitizeMetricName checks if the name is a valid Prometheus metric name. If +// not, it attempts to replaces invalid runes with an underscore to create a +// valid name. +func SanitizeMetricName(name string) (string, bool) { + return sanitize(name, MetricNameTable) +} + +// SanitizeLabelName checks if the name is a valid Prometheus label name. If +// not, it attempts to replaces invalid runes with an underscore to create a +// valid name. +func SanitizeLabelName(name string) (string, bool) { + return sanitize(name, LabelNameTable) +} + // MetricName returns the Prometheus metric name. func MetricName(measurement, fieldKey string, valueType telegraf.ValueType) string { switch valueType { diff --git a/plugins/serializers/prometheus/prometheus_test.go b/plugins/serializers/prometheus/prometheus_test.go index 6195fbead..632ca148e 100644 --- a/plugins/serializers/prometheus/prometheus_test.go +++ b/plugins/serializers/prometheus/prometheus_test.go @@ -409,6 +409,42 @@ rpc_duration_seconds_count 2693 # HELP cpu_time_idle Telegraf collected metric # TYPE cpu_time_idle untyped cpu_time_idle 43 +`), + }, + { + name: "colons are not replaced in metric name from measurement", + metrics: []telegraf.Metric{ + testutil.MustMetric( + "cpu::xyzzy", + map[string]string{}, + map[string]interface{}{ + "time_idle": 42.0, + }, + time.Unix(0, 0), + ), + }, + expected: []byte(` +# HELP cpu::xyzzy_time_idle Telegraf collected metric +# TYPE cpu::xyzzy_time_idle untyped +cpu::xyzzy_time_idle 42 +`), + }, + { + name: "colons are not replaced in metric name from field", + metrics: []telegraf.Metric{ + testutil.MustMetric( + "cpu", + map[string]string{}, + map[string]interface{}{ + "time:idle": 42.0, + }, + time.Unix(0, 0), + ), + }, + expected: []byte(` +# HELP cpu_time:idle Telegraf collected metric +# TYPE cpu_time:idle untyped +cpu_time:idle 42 `), }, { @@ -429,6 +465,26 @@ cpu_time_idle 43 # HELP cpu_time_idle Telegraf collected metric # TYPE cpu_time_idle untyped cpu_time_idle{host_name="example.org"} 42 +`), + }, + { + name: "colons are replaced in label name", + metrics: []telegraf.Metric{ + testutil.MustMetric( + "cpu", + map[string]string{ + "host:name": "example.org", + }, + map[string]interface{}{ + "time_idle": 42.0, + }, + time.Unix(0, 0), + ), + }, + expected: []byte(` +# HELP cpu_time_idle Telegraf collected metric +# TYPE cpu_time_idle untyped +cpu_time_idle{host_name="example.org"} 42 `), }, {