Allow colons in the prometheus metric name (#6751)

This commit is contained in:
Daniel Nelson 2019-12-03 13:34:00 -08:00 committed by GitHub
parent 09f9b70354
commit 1f7b68a2b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 124 additions and 28 deletions

View File

@ -113,7 +113,7 @@ func (c *Collection) createLabels(metric telegraf.Metric) []LabelPair {
} }
} }
name, ok := SanitizeName(tag.Key) name, ok := SanitizeLabelName(tag.Key)
if !ok { if !ok {
continue continue
} }
@ -132,7 +132,7 @@ func (c *Collection) createLabels(metric telegraf.Metric) []LabelPair {
continue continue
} }
name, ok := SanitizeName(field.Key) name, ok := SanitizeLabelName(field.Key)
if !ok { if !ok {
continue continue
} }
@ -161,7 +161,7 @@ func (c *Collection) Add(metric telegraf.Metric) {
labels := c.createLabels(metric) labels := c.createLabels(metric)
for _, field := range metric.FieldList() { for _, field := range metric.FieldList() {
metricName := MetricName(metric.Name(), field.Key, metric.Type()) metricName := MetricName(metric.Name(), field.Key, metric.Type())
metricName, ok := SanitizeName(metricName) metricName, ok := SanitizeMetricName(metricName)
if !ok { if !ok {
continue continue
} }

View File

@ -8,16 +8,42 @@ import (
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
) )
var FirstTable = &unicode.RangeTable{ type Table struct {
First *unicode.RangeTable
Rest *unicode.RangeTable
}
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,
},
}
var LabelNameTable = Table{
First: &unicode.RangeTable{
R16: []unicode.Range16{ R16: []unicode.Range16{
{0x0041, 0x005A, 1}, // A-Z {0x0041, 0x005A, 1}, // A-Z
{0x005F, 0x005F, 1}, // _ {0x005F, 0x005F, 1}, // _
{0x0061, 0x007A, 1}, // a-z {0x0061, 0x007A, 1}, // a-z
}, },
LatinOffset: 3, LatinOffset: 3,
} },
Rest: &unicode.RangeTable{
var RestTable = &unicode.RangeTable{
R16: []unicode.Range16{ R16: []unicode.Range16{
{0x0030, 0x0039, 1}, // 0-9 {0x0030, 0x0039, 1}, // 0-9
{0x0041, 0x005A, 1}, // A-Z {0x0041, 0x005A, 1}, // A-Z
@ -25,9 +51,10 @@ var RestTable = &unicode.RangeTable{
{0x0061, 0x007A, 1}, // a-z {0x0061, 0x007A, 1}, // a-z
}, },
LatinOffset: 4, LatinOffset: 4,
},
} }
func isValid(name string) bool { func isValid(name string, table Table) bool {
if name == "" { if name == "" {
return false return false
} }
@ -35,11 +62,11 @@ func isValid(name string) bool {
for i, r := range name { for i, r := range name {
switch { switch {
case i == 0: case i == 0:
if !unicode.In(r, FirstTable) { if !unicode.In(r, table.First) {
return false return false
} }
default: default:
if !unicode.In(r, RestTable) { if !unicode.In(r, table.Rest) {
return false return false
} }
} }
@ -48,12 +75,11 @@ func isValid(name string) bool {
return true return true
} }
// SanitizeName check if the name is a valid Prometheus metric name and label // Sanitize checks if the name is valid according to the table. If not, it
// name. If not, it attempts to replaces invalid runes with an underscore to // attempts to replaces invalid runes with an underscore to create a valid
// create a valid name. Returns the metric name and true if the name is valid // name.
// to use. func sanitize(name string, table Table) (string, bool) {
func SanitizeName(name string) (string, bool) { if isValid(name, table) {
if isValid(name) {
return name, true return name, true
} }
@ -62,11 +88,11 @@ func SanitizeName(name string) (string, bool) {
for i, r := range name { for i, r := range name {
switch { switch {
case i == 0: case i == 0:
if unicode.In(r, FirstTable) { if unicode.In(r, table.First) {
b.WriteRune(r) b.WriteRune(r)
} }
default: default:
if unicode.In(r, RestTable) { if unicode.In(r, table.Rest) {
b.WriteRune(r) b.WriteRune(r)
} else { } else {
b.WriteString("_") b.WriteString("_")
@ -82,6 +108,20 @@ func SanitizeName(name string) (string, bool) {
return name, true 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. // MetricName returns the Prometheus metric name.
func MetricName(measurement, fieldKey string, valueType telegraf.ValueType) string { func MetricName(measurement, fieldKey string, valueType telegraf.ValueType) string {
switch valueType { switch valueType {

View File

@ -409,6 +409,42 @@ rpc_duration_seconds_count 2693
# HELP cpu_time_idle Telegraf collected metric # HELP cpu_time_idle Telegraf collected metric
# TYPE cpu_time_idle untyped # TYPE cpu_time_idle untyped
cpu_time_idle 43 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 # HELP cpu_time_idle Telegraf collected metric
# TYPE cpu_time_idle untyped # TYPE cpu_time_idle untyped
cpu_time_idle{host_name="example.org"} 42 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
`), `),
}, },
{ {