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 {
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
}

View File

@ -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 {

View File

@ -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
`),
},
{