From dd96ad54880f1e8aaa27af0fc975dfd4f2d682ad Mon Sep 17 00:00:00 2001 From: Arthur Silva Sens Date: Mon, 21 Oct 2024 16:37:07 -0300 Subject: [PATCH] [pkg/translator/prometheus] Add option to keep UTF-8 characters Signed-off-by: Arthur Silva Sens --- .chloggen/allowutf8-prom-translator.yaml | 27 +++ .../googlemanagedprometheusexporter/config.go | 2 +- exporter/prometheusexporter/collector.go | 8 +- .../prometheusremotewriteexporter/exporter.go | 2 +- pkg/translator/loki/logs_to_loki.go | 2 +- pkg/translator/prometheus/go.mod | 8 +- pkg/translator/prometheus/go.sum | 16 +- .../prometheus/helpers_from_stdlib.go | 96 ++++++++++ pkg/translator/prometheus/normalize_label.go | 18 +- .../prometheus/normalize_label_test.go | 63 +++++-- pkg/translator/prometheus/normalize_name.go | 38 ++-- .../prometheus/normalize_name_test.go | 173 ++++++++++-------- .../prometheusremotewrite/helper.go | 4 +- .../prometheusremotewrite/histograms_test.go | 2 +- .../prometheusremotewrite/metrics_to_prw.go | 2 +- .../metrics_to_prw_v2.go | 2 +- .../otlp_to_openmetrics_metadata.go | 2 +- .../otlp_to_openmetrics_metadata_test.go | 12 +- 18 files changed, 330 insertions(+), 147 deletions(-) create mode 100644 .chloggen/allowutf8-prom-translator.yaml create mode 100644 pkg/translator/prometheus/helpers_from_stdlib.go diff --git a/.chloggen/allowutf8-prom-translator.yaml b/.chloggen/allowutf8-prom-translator.yaml new file mode 100644 index 000000000000..2399a5710c01 --- /dev/null +++ b/.chloggen/allowutf8-prom-translator.yaml @@ -0,0 +1,27 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) +component: pkg/translator/prometheus + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Optionally allow UTF-8 characters in Prometheus metric and label names. + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [35459] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: + +# If your change doesn't affect end users or the exported elements of any package, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [api] diff --git a/exporter/googlemanagedprometheusexporter/config.go b/exporter/googlemanagedprometheusexporter/config.go index 739057629540..30808ac854ce 100644 --- a/exporter/googlemanagedprometheusexporter/config.go +++ b/exporter/googlemanagedprometheusexporter/config.go @@ -51,7 +51,7 @@ func (c *GMPConfig) toCollectorConfig() collector.Config { cfg.MetricConfig.ServiceResourceLabels = false // Update metric naming to match GMP conventions cfg.MetricConfig.GetMetricName = func(baseName string, metric pmetric.Metric) (string, error) { - compliantName := prometheus.BuildCompliantName(metric, "", c.MetricConfig.Config.AddMetricSuffixes) + compliantName := prometheus.BuildCompliantName(metric, "", c.MetricConfig.Config.AddMetricSuffixes, false) return googlemanagedprometheus.GetMetricName(baseName, compliantName, metric) } // Map to the prometheus_target monitored resource diff --git a/exporter/prometheusexporter/collector.go b/exporter/prometheusexporter/collector.go index f6065307eb34..f5a32acf50d5 100644 --- a/exporter/prometheusexporter/collector.go +++ b/exporter/prometheusexporter/collector.go @@ -36,7 +36,7 @@ func newCollector(config *Config, logger *zap.Logger) *collector { return &collector{ accumulator: newAccumulator(logger, config.MetricExpiration), logger: logger, - namespace: prometheustranslator.CleanUpString(config.Namespace), + namespace: prometheustranslator.CleanUpString(config.Namespace, false), sendTimestamps: config.SendTimestamps, constLabels: config.ConstLabels, addMetricSuffixes: config.AddMetricSuffixes, @@ -110,7 +110,7 @@ func (c *collector) getMetricMetadata(metric pmetric.Metric, attributes pcommon. values := make([]string, 0, attributes.Len()+2) attributes.Range(func(k string, v pcommon.Value) bool { - keys = append(keys, prometheustranslator.NormalizeLabel(k)) + keys = append(keys, prometheustranslator.NormalizeLabel(k, false)) values = append(values, v.AsString()) return true }) @@ -125,7 +125,7 @@ func (c *collector) getMetricMetadata(metric pmetric.Metric, attributes pcommon. } return prometheus.NewDesc( - prometheustranslator.BuildCompliantName(metric, c.namespace, c.addMetricSuffixes), + prometheustranslator.BuildCompliantName(metric, c.namespace, c.addMetricSuffixes, false), metric.Description(), keys, c.constLabels, @@ -327,7 +327,7 @@ func (c *collector) createTargetInfoMetrics(resourceAttrs []pcommon.Map) ([]prom }) attributes.Range(func(k string, v pcommon.Value) bool { - finalKey := prometheustranslator.NormalizeLabel(k) + finalKey := prometheustranslator.NormalizeLabel(k, false) if existingVal, ok := labels[finalKey]; ok { labels[finalKey] = existingVal + ";" + v.AsString() } else { diff --git a/exporter/prometheusremotewriteexporter/exporter.go b/exporter/prometheusremotewriteexporter/exporter.go index aa4a58082416..3346b550698e 100644 --- a/exporter/prometheusremotewriteexporter/exporter.go +++ b/exporter/prometheusremotewriteexporter/exporter.go @@ -197,7 +197,7 @@ func validateAndSanitizeExternalLabels(cfg *Config) (map[string]string, error) { if key == "" || value == "" { return nil, fmt.Errorf("prometheus remote write: external labels configuration contains an empty key or value") } - sanitizedLabels[prometheustranslator.NormalizeLabel(key)] = value + sanitizedLabels[prometheustranslator.NormalizeLabel(key, false)] = value } return sanitizedLabels, nil diff --git a/pkg/translator/loki/logs_to_loki.go b/pkg/translator/loki/logs_to_loki.go index 9a5ddc11b071..5c5755bc0ebc 100644 --- a/pkg/translator/loki/logs_to_loki.go +++ b/pkg/translator/loki/logs_to_loki.go @@ -151,7 +151,7 @@ func LogToLokiEntry(lr plog.LogRecord, rl pcommon.Resource, scope pcommon.Instru for label := range mergedLabels { // Loki doesn't support dots in label names // labelName is normalized label name to follow Prometheus label names standard - labelName := prometheustranslator.NormalizeLabel(string(label)) + labelName := prometheustranslator.NormalizeLabel(string(label), false) labels[model.LabelName(labelName)] = mergedLabels[label] } diff --git a/pkg/translator/prometheus/go.mod b/pkg/translator/prometheus/go.mod index 243faeb3b3bf..6b6ec4dda212 100644 --- a/pkg/translator/prometheus/go.mod +++ b/pkg/translator/prometheus/go.mod @@ -4,6 +4,7 @@ go 1.22.0 require ( github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.112.0 + github.com/prometheus/common v0.60.0 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/collector/featuregate v1.18.0 go.opentelemetry.io/collector/pdata v1.18.0 @@ -19,10 +20,11 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/grpc v1.67.1 // indirect google.golang.org/protobuf v1.35.1 // indirect diff --git a/pkg/translator/prometheus/go.sum b/pkg/translator/prometheus/go.sum index efee54d6057e..b1b313bfdd63 100644 --- a/pkg/translator/prometheus/go.sum +++ b/pkg/translator/prometheus/go.sum @@ -24,6 +24,10 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA= +github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -49,20 +53,20 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= diff --git a/pkg/translator/prometheus/helpers_from_stdlib.go b/pkg/translator/prometheus/helpers_from_stdlib.go new file mode 100644 index 000000000000..9d16629469c9 --- /dev/null +++ b/pkg/translator/prometheus/helpers_from_stdlib.go @@ -0,0 +1,96 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +// Provenance-includes-location: https://github.com/golang/go/blob/f2d118fd5f7e872804a5825ce29797f81a28b0fa/src/strings/strings.go +// Provenance-includes-license: BSD-3-Clause +// Provenance-includes-copyright: Copyright The Go Authors. + +package prometheus + +import "strings" + +// fieldsFunc is a copy of strings.FieldsFunc from the Go standard library, +// but it also returns the separators as part of the result. +func fieldsFunc(s string, f func(rune) bool) ([]string, []string) { + // A span is used to record a slice of s of the form s[start:end]. + // The start index is inclusive and the end index is exclusive. + type span struct { + start int + end int + } + spans := make([]span, 0, 32) + separators := make([]string, 0, 32) + + // Find the field start and end indices. + // Doing this in a separate pass (rather than slicing the string s + // and collecting the result substrings right away) is significantly + // more efficient, possibly due to cache effects. + start := -1 // valid span start if >= 0 + for end, rune := range s { + if f(rune) { + if start >= 0 { + spans = append(spans, span{start, end}) + // Set start to a negative value. + // Note: using -1 here consistently and reproducibly + // slows down this code by a several percent on amd64. + start = ^start + separators = append(separators, string(s[end])) + } + } else { + if start < 0 { + start = end + } + } + } + + // Last field might end at EOF. + if start >= 0 { + spans = append(spans, span{start, len(s)}) + } + + // Create strings from recorded field indices. + a := make([]string, len(spans)) + for i, span := range spans { + a[i] = s[span.start:span.end] + } + + return a, separators +} + +// join is a copy of strings.Join from the Go standard library, +// but it also accepts a slice of separators to join the elements with. +// If the slice of separators is shorter than the slice of elements, use a default value. +// We also don't check for integer overflow. +func join(elems []string, separators []string, def string) string { + switch len(elems) { + case 0: + return "" + case 1: + return elems[0] + } + + var n int + var sep string + sepLen := len(separators) + for i, elem := range elems { + if i >= sepLen { + sep = def + } else { + sep = separators[i] + } + n += len(sep) + len(elem) + } + + var b strings.Builder + b.Grow(n) + b.WriteString(elems[0]) + for i, s := range elems[1:] { + if i >= sepLen { + sep = def + } else { + sep = separators[i] + } + b.WriteString(sep) + b.WriteString(s) + } + return b.String() +} \ No newline at end of file diff --git a/pkg/translator/prometheus/normalize_label.go b/pkg/translator/prometheus/normalize_label.go index af0960e86237..92edfa679dbb 100644 --- a/pkg/translator/prometheus/normalize_label.go +++ b/pkg/translator/prometheus/normalize_label.go @@ -7,6 +7,7 @@ import ( "strings" "unicode" + "github.com/prometheus/common/model" "go.opentelemetry.io/collector/featuregate" ) @@ -24,15 +25,16 @@ var dropSanitizationGate = featuregate.GlobalRegistry().MustRegister( // Labels that start with non-letter rune will be prefixed with "key_" // // Exception is made for double-underscores which are allowed -func NormalizeLabel(label string) string { +func NormalizeLabel(label string, allowUTF8 bool) string { // Trivial case if len(label) == 0 { return label } - // Replace all non-alphanumeric runes with underscores - label = strings.Map(sanitizeRune, label) + if allowUTF8 { + return label + } // If label starts with a number, prepend with "key_" if unicode.IsDigit(rune(label[0])) { @@ -41,13 +43,5 @@ func NormalizeLabel(label string) string { label = "key" + label } - return label -} - -// Return '_' for anything non-alphanumeric -func sanitizeRune(r rune) rune { - if unicode.IsLetter(r) || unicode.IsDigit(r) { - return r - } - return '_' + return model.EscapeName(label, model.UnderscoreEscaping) } diff --git a/pkg/translator/prometheus/normalize_label_test.go b/pkg/translator/prometheus/normalize_label_test.go index f00183909735..342221c2143b 100644 --- a/pkg/translator/prometheus/normalize_label_test.go +++ b/pkg/translator/prometheus/normalize_label_test.go @@ -12,24 +12,57 @@ import ( ) func TestSanitize(t *testing.T) { + oldSanitization := dropSanitizationGate.IsEnabled() + defer func() { + testutil.SetFeatureGateForTest(t, dropSanitizationGate, oldSanitization) + }() - defer testutil.SetFeatureGateForTest(t, dropSanitizationGate, false)() + for _, dropSanitization := range []bool{true, false} { + testutil.SetFeatureGateForTest(t, dropSanitizationGate, dropSanitization) - require.Equal(t, "", NormalizeLabel(""), "") - require.Equal(t, "key_test", NormalizeLabel("_test")) - require.Equal(t, "key_0test", NormalizeLabel("0test")) - require.Equal(t, "test", NormalizeLabel("test")) - require.Equal(t, "test__", NormalizeLabel("test_/")) - require.Equal(t, "__test", NormalizeLabel("__test")) -} + // Drop sanitization gate is only relevant if UTF8 isn't allowed. We use `false` for all cases. + if dropSanitization { + require.Equal(t, "", NormalizeLabel("", false), "") + require.Equal(t, "_test", NormalizeLabel("_test", false)) + require.Equal(t, "key_0test", NormalizeLabel("0test", false)) // Even dropping santiization, we still add "key_" to metrics starting with digits + require.Equal(t, "test", NormalizeLabel("test", false)) + require.Equal(t, "test__", NormalizeLabel("test_/", false)) + require.Equal(t, "__test", NormalizeLabel("__test", false)) + } else { + require.Equal(t, "", NormalizeLabel("", false), "") + require.Equal(t, "key_test", NormalizeLabel("_test", false)) + require.Equal(t, "key_0test", NormalizeLabel("0test", false)) + require.Equal(t, "test", NormalizeLabel("test", false)) + require.Equal(t, "test__", NormalizeLabel("test_/", false)) + require.Equal(t, "__test", NormalizeLabel("__test", false)) + } + } -func TestSanitizeDropSanitization(t *testing.T) { +} - defer testutil.SetFeatureGateForTest(t, dropSanitizationGate, true)() +func TestNormalizeLabel(t *testing.T) { + tests := []struct { + label string + allowUTF8 bool + expected string + }{ + {"", false, ""}, + {"", true, ""}, + {"label_with_special_chars!", false, "label_with_special_chars_"}, + {"label_with_special_chars!", true, "label_with_special_chars!"}, + {"label_with_foreign_characteres_字符", false, "label_with_foreign_characteres___"}, + {"label_with_foreign_characteres_字符", true, "label_with_foreign_characteres_字符"}, + {"label.with.dots", false, "label_with_dots"}, + {"label.with.dots", true, "label.with.dots"}, + {"123label", false, "key_123label"}, + {"123label", true, "123label"}, // UTF-8 allows numbers at the beginning + {"_label", false, "key_label"}, + {"_label", true, "_label"}, // UTF-8 allows single underscores at the beginning + {"__label", false, "__label"}, + } - require.Equal(t, "", NormalizeLabel("")) - require.Equal(t, "_test", NormalizeLabel("_test")) - require.Equal(t, "key_0test", NormalizeLabel("0test")) - require.Equal(t, "test", NormalizeLabel("test")) - require.Equal(t, "__test", NormalizeLabel("__test")) + for _, test := range tests { + result := NormalizeLabel(test.label, test.allowUTF8) + require.Equal(t, test.expected, result) + } } diff --git a/pkg/translator/prometheus/normalize_name.go b/pkg/translator/prometheus/normalize_name.go index 72fc04cea220..720533efa3ce 100644 --- a/pkg/translator/prometheus/normalize_name.go +++ b/pkg/translator/prometheus/normalize_name.go @@ -80,16 +80,20 @@ var normalizeNameGate = featuregate.GlobalRegistry().MustRegister( // // See rules at https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels // and https://prometheus.io/docs/practices/naming/#metric-and-label-naming -func BuildCompliantName(metric pmetric.Metric, namespace string, addMetricSuffixes bool) string { +func BuildCompliantName(metric pmetric.Metric, namespace string, addMetricSuffixes, allowUTF8 bool) string { var metricName string // Full normalization following standard Prometheus naming conventions if addMetricSuffixes && normalizeNameGate.IsEnabled() { - return normalizeName(metric, namespace) + return normalizeName(metric, namespace, allowUTF8) } - // Simple case (no full normalization, no units, etc.), we simply trim out forbidden chars - metricName = RemovePromForbiddenRunes(metric.Name()) + if !allowUTF8 { + // Simple case (no full normalization, no units, etc.), we simply trim out forbidden chars + metricName = RemovePromForbiddenRunes(metric.Name()) + } else { + metricName = metric.Name() + } // Namespace? if namespace != "" { @@ -105,10 +109,9 @@ func BuildCompliantName(metric pmetric.Metric, namespace string, addMetricSuffix } // Build a normalized name for the specified metric -func normalizeName(metric pmetric.Metric, namespace string) string { - - // Split metric name in "tokens" (remove all non-alphanumeric) - nameTokens := strings.FieldsFunc( +func normalizeName(metric pmetric.Metric, namespace string, allowUTF8 bool) string { + // Split metric name into "tokens" + nameTokens, separators := fieldsFunc( metric.Name(), func(r rune) bool { return !unicode.IsLetter(r) && !unicode.IsDigit(r) }, ) @@ -121,7 +124,7 @@ func normalizeName(metric pmetric.Metric, namespace string) string { if len(unitTokens) > 0 { mainUnitOtel := strings.TrimSpace(unitTokens[0]) if mainUnitOtel != "" && !strings.ContainsAny(mainUnitOtel, "{}") { - mainUnitProm := CleanUpString(unitMapGetOrDefault(mainUnitOtel)) + mainUnitProm := CleanUpString(unitMapGetOrDefault(mainUnitOtel), allowUTF8) if mainUnitProm != "" && !contains(nameTokens, mainUnitProm) { nameTokens = append(nameTokens, mainUnitProm) } @@ -132,7 +135,7 @@ func normalizeName(metric pmetric.Metric, namespace string) string { if len(unitTokens) > 1 && unitTokens[1] != "" { perUnitOtel := strings.TrimSpace(unitTokens[1]) if perUnitOtel != "" && !strings.ContainsAny(perUnitOtel, "{}") { - perUnitProm := CleanUpString(perUnitMapGetOrDefault(perUnitOtel)) + perUnitProm := CleanUpString(perUnitMapGetOrDefault(perUnitOtel), allowUTF8) if perUnitProm != "" && !contains(nameTokens, perUnitProm) { nameTokens = append(append(nameTokens, "per"), perUnitProm) } @@ -160,8 +163,12 @@ func normalizeName(metric pmetric.Metric, namespace string) string { nameTokens = append([]string{namespace}, nameTokens...) } - // Build the string from the tokens, separated with underscores - normalizedName := strings.Join(nameTokens, "_") + // Build the string from the tokens + separators. + // If UTF-8 isn't allowed, we'll use underscores as separators. + if !allowUTF8 { + separators = []string{} + } + normalizedName := join(nameTokens, separators, "_") // Metric name cannot start with a digit, so prefix it with "_" in this case if normalizedName != "" && unicode.IsDigit(rune(normalizedName[0])) { @@ -232,8 +239,11 @@ func removeSuffix(tokens []string, suffix string) []string { } // Clean up specified string so it's Prometheus compliant -func CleanUpString(s string) string { - return strings.Join(strings.FieldsFunc(s, func(r rune) bool { return !unicode.IsLetter(r) && !unicode.IsDigit(r) }), "_") +func CleanUpString(s string, allowUTF8 bool) string { + if !allowUTF8 { + return strings.Join(strings.FieldsFunc(s, func(r rune) bool { return !unicode.IsLetter(r) && !unicode.IsDigit(r) }), "_") + } + return s } func RemovePromForbiddenRunes(s string) string { diff --git a/pkg/translator/prometheus/normalize_name_test.go b/pkg/translator/prometheus/normalize_name_test.go index 6470bfe3d571..3d1e659cef93 100644 --- a/pkg/translator/prometheus/normalize_name_test.go +++ b/pkg/translator/prometheus/normalize_name_test.go @@ -15,118 +15,124 @@ import ( func TestByte(t *testing.T) { - require.Equal(t, "system_filesystem_usage_bytes", normalizeName(createGauge("system.filesystem.usage", "By"), "")) + require.Equal(t, "system_filesystem_usage_bytes", normalizeName(createGauge("system.filesystem.usage", "By"), "", false)) } func TestByteCounter(t *testing.T) { - require.Equal(t, "system_io_bytes_total", normalizeName(createCounter("system.io", "By"), "")) - require.Equal(t, "network_transmitted_bytes_total", normalizeName(createCounter("network_transmitted_bytes_total", "By"), "")) + require.Equal(t, "system_io_bytes_total", normalizeName(createCounter("system.io", "By"), "", false)) + require.Equal(t, "network_transmitted_bytes_total", normalizeName(createCounter("network_transmitted_bytes_total", "By"), "", false)) } func TestWhiteSpaces(t *testing.T) { - require.Equal(t, "system_filesystem_usage_bytes", normalizeName(createGauge("\t system.filesystem.usage ", " By\t"), "")) + require.Equal(t, "system_filesystem_usage_bytes", normalizeName(createGauge("\t system.filesystem.usage ", " By\t"), "", false)) } func TestNonStandardUnit(t *testing.T) { - require.Equal(t, "system_network_dropped", normalizeName(createGauge("system.network.dropped", "{packets}"), "")) + require.Equal(t, "system_network_dropped", normalizeName(createGauge("system.network.dropped", "{packets}"), "", false)) } func TestNonStandardUnitCounter(t *testing.T) { - require.Equal(t, "system_network_dropped_total", normalizeName(createCounter("system.network.dropped", "{packets}"), "")) + require.Equal(t, "system_network_dropped_total", normalizeName(createCounter("system.network.dropped", "{packets}"), "", false)) } func TestBrokenUnit(t *testing.T) { - require.Equal(t, "system_network_dropped_packets", normalizeName(createGauge("system.network.dropped", "packets"), "")) - require.Equal(t, "system_network_packets_dropped", normalizeName(createGauge("system.network.packets.dropped", "packets"), "")) - require.Equal(t, "system_network_packets", normalizeName(createGauge("system.network.packets", "packets"), "")) + require.Equal(t, "system_network_dropped_packets", normalizeName(createGauge("system.network.dropped", "packets"), "", false)) + require.Equal(t, "system_network_packets_dropped", normalizeName(createGauge("system.network.packets.dropped", "packets"), "", false)) + require.Equal(t, "system_network_packets", normalizeName(createGauge("system.network.packets", "packets"), "", false)) } func TestBrokenUnitCounter(t *testing.T) { - require.Equal(t, "system_network_dropped_packets_total", normalizeName(createCounter("system.network.dropped", "packets"), "")) - require.Equal(t, "system_network_packets_dropped_total", normalizeName(createCounter("system.network.packets.dropped", "packets"), "")) - require.Equal(t, "system_network_packets_total", normalizeName(createCounter("system.network.packets", "packets"), "")) + require.Equal(t, "system_network_dropped_packets_total", normalizeName(createCounter("system.network.dropped", "packets"), "", false)) + require.Equal(t, "system_network_packets_dropped_total", normalizeName(createCounter("system.network.packets.dropped", "packets"), "", false)) + require.Equal(t, "system_network_packets_total", normalizeName(createCounter("system.network.packets", "packets"), "", false)) } func TestRatio(t *testing.T) { - require.Equal(t, "hw_gpu_memory_utilization_ratio", normalizeName(createGauge("hw.gpu.memory.utilization", "1"), "")) - require.Equal(t, "hw_fan_speed_ratio", normalizeName(createGauge("hw.fan.speed_ratio", "1"), "")) - require.Equal(t, "objects_total", normalizeName(createCounter("objects", "1"), "")) + require.Equal(t, "hw_gpu_memory_utilization_ratio", normalizeName(createGauge("hw.gpu.memory.utilization", "1"), "", false)) + require.Equal(t, "hw_fan_speed_ratio", normalizeName(createGauge("hw.fan.speed_ratio", "1"), "", false)) + require.Equal(t, "objects_total", normalizeName(createCounter("objects", "1"), "", false)) } func TestHertz(t *testing.T) { - require.Equal(t, "hw_cpu_speed_limit_hertz", normalizeName(createGauge("hw.cpu.speed_limit", "Hz"), "")) + require.Equal(t, "hw_cpu_speed_limit_hertz", normalizeName(createGauge("hw.cpu.speed_limit", "Hz"), "", false)) } func TestPer(t *testing.T) { - require.Equal(t, "broken_metric_speed_km_per_hour", normalizeName(createGauge("broken.metric.speed", "km/h"), "")) - require.Equal(t, "astro_light_speed_limit_meters_per_second", normalizeName(createGauge("astro.light.speed_limit", "m/s"), "")) + require.Equal(t, "broken_metric_speed_km_per_hour", normalizeName(createGauge("broken.metric.speed", "km/h"), "", false)) + require.Equal(t, "astro_light_speed_limit_meters_per_second", normalizeName(createGauge("astro.light.speed_limit", "m/s"), "", false)) } func TestPercent(t *testing.T) { - require.Equal(t, "broken_metric_success_ratio_percent", normalizeName(createGauge("broken.metric.success_ratio", "%"), "")) - require.Equal(t, "broken_metric_success_percent", normalizeName(createGauge("broken.metric.success_percent", "%"), "")) + require.Equal(t, "broken_metric_success_ratio_percent", normalizeName(createGauge("broken.metric.success_ratio", "%"), "", false)) + require.Equal(t, "broken_metric_success_percent", normalizeName(createGauge("broken.metric.success_percent", "%"), "", false)) } func TestEmpty(t *testing.T) { - require.Equal(t, "test_metric_no_unit", normalizeName(createGauge("test.metric.no_unit", ""), "")) - require.Equal(t, "test_metric_spaces", normalizeName(createGauge("test.metric.spaces", " \t "), "")) + require.Equal(t, "test_metric_no_unit", normalizeName(createGauge("test.metric.no_unit", ""), "", false)) + require.Equal(t, "test_metric_spaces", normalizeName(createGauge("test.metric.spaces", " \t "), "", false)) } -func TestUnsupportedRunes(t *testing.T) { - - require.Equal(t, "unsupported_metric_temperature_F", normalizeName(createGauge("unsupported.metric.temperature", "°F"), "")) - require.Equal(t, "unsupported_metric_weird", normalizeName(createGauge("unsupported.metric.weird", "+=.:,!* & #"), "")) - require.Equal(t, "unsupported_metric_redundant_test_per_C", normalizeName(createGauge("unsupported.metric.redundant", "__test $/°C"), "")) - +func TestAllowUTF8(t *testing.T) { + for _, allowUTF8 := range []bool{true, false} { + if allowUTF8 { + require.Equal(t, "unsupported.metric.temperature_°F", normalizeName(createGauge("unsupported.metric.temperature", "°F"), "", allowUTF8)) + require.Equal(t, "unsupported.metric.weird_+=.:,!* & #", normalizeName(createGauge("unsupported.metric.weird", "+=.:,!* & #"), "", allowUTF8)) + require.Equal(t, "unsupported.metric.redundant___test $_per_°C", normalizeName(createGauge("unsupported.metric.redundant", "__test $/°C"), "", allowUTF8)) + } else { + require.Equal(t, "unsupported_metric_temperature_F", normalizeName(createGauge("unsupported.metric.temperature", "°F"), "", allowUTF8)) + require.Equal(t, "unsupported_metric_weird", normalizeName(createGauge("unsupported.metric.weird", "+=.:,!* & #"), "", allowUTF8)) + require.Equal(t, "unsupported_metric_redundant_test_per_C", normalizeName(createGauge("unsupported.metric.redundant", "__test $/°C"), "", allowUTF8)) + } + } } func TestOtelReceivers(t *testing.T) { - require.Equal(t, "active_directory_ds_replication_network_io_bytes_total", normalizeName(createCounter("active_directory.ds.replication.network.io", "By"), "")) - require.Equal(t, "active_directory_ds_replication_sync_object_pending_total", normalizeName(createCounter("active_directory.ds.replication.sync.object.pending", "{objects}"), "")) - require.Equal(t, "active_directory_ds_replication_object_rate_per_second", normalizeName(createGauge("active_directory.ds.replication.object.rate", "{objects}/s"), "")) - require.Equal(t, "active_directory_ds_name_cache_hit_rate_percent", normalizeName(createGauge("active_directory.ds.name_cache.hit_rate", "%"), "")) - require.Equal(t, "active_directory_ds_ldap_bind_last_successful_time_milliseconds", normalizeName(createGauge("active_directory.ds.ldap.bind.last_successful.time", "ms"), "")) - require.Equal(t, "apache_current_connections", normalizeName(createGauge("apache.current_connections", "connections"), "")) - require.Equal(t, "apache_workers_connections", normalizeName(createGauge("apache.workers", "connections"), "")) - require.Equal(t, "apache_requests_total", normalizeName(createCounter("apache.requests", "1"), "")) - require.Equal(t, "bigip_virtual_server_request_count_total", normalizeName(createCounter("bigip.virtual_server.request.count", "{requests}"), "")) - require.Equal(t, "system_cpu_utilization_ratio", normalizeName(createGauge("system.cpu.utilization", "1"), "")) - require.Equal(t, "system_disk_operation_time_seconds_total", normalizeName(createCounter("system.disk.operation_time", "s"), "")) - require.Equal(t, "system_cpu_load_average_15m_ratio", normalizeName(createGauge("system.cpu.load_average.15m", "1"), "")) - require.Equal(t, "memcached_operation_hit_ratio_percent", normalizeName(createGauge("memcached.operation_hit_ratio", "%"), "")) - require.Equal(t, "mongodbatlas_process_asserts_per_second", normalizeName(createGauge("mongodbatlas.process.asserts", "{assertions}/s"), "")) - require.Equal(t, "mongodbatlas_process_journaling_data_files_mebibytes", normalizeName(createGauge("mongodbatlas.process.journaling.data_files", "MiBy"), "")) - require.Equal(t, "mongodbatlas_process_network_io_bytes_per_second", normalizeName(createGauge("mongodbatlas.process.network.io", "By/s"), "")) - require.Equal(t, "mongodbatlas_process_oplog_rate_gibibytes_per_hour", normalizeName(createGauge("mongodbatlas.process.oplog.rate", "GiBy/h"), "")) - require.Equal(t, "mongodbatlas_process_db_query_targeting_scanned_per_returned", normalizeName(createGauge("mongodbatlas.process.db.query_targeting.scanned_per_returned", "{scanned}/{returned}"), "")) - require.Equal(t, "nginx_requests", normalizeName(createGauge("nginx.requests", "requests"), "")) - require.Equal(t, "nginx_connections_accepted", normalizeName(createGauge("nginx.connections_accepted", "connections"), "")) - require.Equal(t, "nsxt_node_memory_usage_kilobytes", normalizeName(createGauge("nsxt.node.memory.usage", "KBy"), "")) - require.Equal(t, "redis_latest_fork_microseconds", normalizeName(createGauge("redis.latest_fork", "us"), "")) + require.Equal(t, "active_directory_ds_replication_network_io_bytes_total", normalizeName(createCounter("active_directory.ds.replication.network.io", "By"), "", false)) + require.Equal(t, "active_directory_ds_replication_sync_object_pending_total", normalizeName(createCounter("active_directory.ds.replication.sync.object.pending", "{objects}"), "", false)) + require.Equal(t, "active_directory_ds_replication_object_rate_per_second", normalizeName(createGauge("active_directory.ds.replication.object.rate", "{objects}/s"), "", false)) + require.Equal(t, "active_directory_ds_name_cache_hit_rate_percent", normalizeName(createGauge("active_directory.ds.name_cache.hit_rate", "%"), "", false)) + require.Equal(t, "active_directory_ds_ldap_bind_last_successful_time_milliseconds", normalizeName(createGauge("active_directory.ds.ldap.bind.last_successful.time", "ms"), "", false)) + require.Equal(t, "apache_current_connections", normalizeName(createGauge("apache.current_connections", "connections"), "", false)) + require.Equal(t, "apache_workers_connections", normalizeName(createGauge("apache.workers", "connections"), "", false)) + require.Equal(t, "apache_requests_total", normalizeName(createCounter("apache.requests", "1"), "", false)) + require.Equal(t, "bigip_virtual_server_request_count_total", normalizeName(createCounter("bigip.virtual_server.request.count", "{requests}"), "", false)) + require.Equal(t, "system_cpu_utilization_ratio", normalizeName(createGauge("system.cpu.utilization", "1"), "", false)) + require.Equal(t, "system_disk_operation_time_seconds_total", normalizeName(createCounter("system.disk.operation_time", "s"), "", false)) + require.Equal(t, "system_cpu_load_average_15m_ratio", normalizeName(createGauge("system.cpu.load_average.15m", "1"), "", false)) + require.Equal(t, "memcached_operation_hit_ratio_percent", normalizeName(createGauge("memcached.operation_hit_ratio", "%"), "", false)) + require.Equal(t, "mongodbatlas_process_asserts_per_second", normalizeName(createGauge("mongodbatlas.process.asserts", "{assertions}/s"), "", false)) + require.Equal(t, "mongodbatlas_process_journaling_data_files_mebibytes", normalizeName(createGauge("mongodbatlas.process.journaling.data_files", "MiBy"), "", false)) + require.Equal(t, "mongodbatlas_process_network_io_bytes_per_second", normalizeName(createGauge("mongodbatlas.process.network.io", "By/s"), "", false)) + require.Equal(t, "mongodbatlas_process_oplog_rate_gibibytes_per_hour", normalizeName(createGauge("mongodbatlas.process.oplog.rate", "GiBy/h"), "", false)) + require.Equal(t, "mongodbatlas_process_db_query_targeting_scanned_per_returned", normalizeName(createGauge("mongodbatlas.process.db.query_targeting.scanned_per_returned", "{scanned}/{returned}"), "", false)) + require.Equal(t, "nginx_requests", normalizeName(createGauge("nginx.requests", "requests"), "", false)) + require.Equal(t, "nginx_connections_accepted", normalizeName(createGauge("nginx.connections_accepted", "connections"), "", false)) + require.Equal(t, "nsxt_node_memory_usage_kilobytes", normalizeName(createGauge("nsxt.node.memory.usage", "KBy"), "", false)) + require.Equal(t, "redis_latest_fork_microseconds", normalizeName(createGauge("redis.latest_fork", "us"), "", false)) } @@ -162,17 +168,28 @@ func TestTrimPromSuffixes(t *testing.T) { } func TestNamespace(t *testing.T) { - require.Equal(t, "space_test", normalizeName(createGauge("test", ""), "space")) - require.Equal(t, "space_test", normalizeName(createGauge("#test", ""), "space")) + require.Equal(t, "space_test", normalizeName(createGauge("test", ""), "space", false)) + require.Equal(t, "space_test", normalizeName(createGauge("#test", ""), "space", false)) } func TestCleanUpString(t *testing.T) { - require.Equal(t, "", CleanUpString("")) - require.Equal(t, "a_b", CleanUpString("a b")) - require.Equal(t, "hello_world", CleanUpString("hello, world!")) - require.Equal(t, "hello_you_2", CleanUpString("hello you 2")) - require.Equal(t, "1000", CleanUpString("$1000")) - require.Equal(t, "", CleanUpString("*+$^=)")) + for _, allowUTF8 := range []bool{true, false} { + if allowUTF8 { + require.Equal(t, "", CleanUpString("", allowUTF8)) + require.Equal(t, "a b", CleanUpString("a b", allowUTF8)) + require.Equal(t, "hello, world!", CleanUpString("hello, world!", allowUTF8)) + require.Equal(t, "hello you 2", CleanUpString("hello you 2", allowUTF8)) + require.Equal(t, "$1000", CleanUpString("$1000", allowUTF8)) + require.Equal(t, "*+$^=)", CleanUpString("*+$^=)", allowUTF8)) + } else { + require.Equal(t, "", CleanUpString("", allowUTF8)) + require.Equal(t, "a_b", CleanUpString("a b", allowUTF8)) + require.Equal(t, "hello_world", CleanUpString("hello, world!", allowUTF8)) + require.Equal(t, "hello_you_2", CleanUpString("hello you 2", allowUTF8)) + require.Equal(t, "1000", CleanUpString("$1000", allowUTF8)) + require.Equal(t, "", CleanUpString("*+$^=)", allowUTF8)) + } + } } func TestUnitMapGetOrDefault(t *testing.T) { @@ -207,12 +224,12 @@ func TestBuildCompliantNameWithNormalize(t *testing.T) { defer testutil.SetFeatureGateForTest(t, normalizeNameGate, true)() addUnitAndTypeSuffixes := true - require.Equal(t, "system_io_bytes_total", BuildCompliantName(createCounter("system.io", "By"), "", addUnitAndTypeSuffixes)) - require.Equal(t, "system_network_io_bytes_total", BuildCompliantName(createCounter("network.io", "By"), "system", addUnitAndTypeSuffixes)) - require.Equal(t, "_3_14_digits", BuildCompliantName(createGauge("3.14 digits", ""), "", addUnitAndTypeSuffixes)) - require.Equal(t, "envoy_rule_engine_zlib_buf_error", BuildCompliantName(createGauge("envoy__rule_engine_zlib_buf_error", ""), "", addUnitAndTypeSuffixes)) - require.Equal(t, "foo_bar", BuildCompliantName(createGauge(":foo::bar", ""), "", addUnitAndTypeSuffixes)) - require.Equal(t, "foo_bar_total", BuildCompliantName(createCounter(":foo::bar", ""), "", addUnitAndTypeSuffixes)) + require.Equal(t, "system_io_bytes_total", BuildCompliantName(createCounter("system.io", "By"), "", addUnitAndTypeSuffixes, false)) + require.Equal(t, "system_network_io_bytes_total", BuildCompliantName(createCounter("network.io", "By"), "system", addUnitAndTypeSuffixes, false)) + require.Equal(t, "_3_14_digits", BuildCompliantName(createGauge("3.14 digits", ""), "", addUnitAndTypeSuffixes, false)) + require.Equal(t, "envoy_rule_engine_zlib_buf_error", BuildCompliantName(createGauge("envoy__rule_engine_zlib_buf_error", ""), "", addUnitAndTypeSuffixes, false)) + require.Equal(t, "foo_bar", BuildCompliantName(createGauge(":foo::bar", ""), "", addUnitAndTypeSuffixes, false)) + require.Equal(t, "foo_bar_total", BuildCompliantName(createCounter(":foo::bar", ""), "", addUnitAndTypeSuffixes, false)) } @@ -220,13 +237,13 @@ func TestBuildCompliantNameWithSuffixesFeatureGateDisabled(t *testing.T) { defer testutil.SetFeatureGateForTest(t, normalizeNameGate, false)() addUnitAndTypeSuffixes := true - require.Equal(t, "system_io", BuildCompliantName(createCounter("system.io", "By"), "", addUnitAndTypeSuffixes)) - require.Equal(t, "system_network_io", BuildCompliantName(createCounter("network.io", "By"), "system", addUnitAndTypeSuffixes)) - require.Equal(t, "system_network_I_O", BuildCompliantName(createCounter("network (I/O)", "By"), "system", addUnitAndTypeSuffixes)) - require.Equal(t, "_3_14_digits", BuildCompliantName(createGauge("3.14 digits", "By"), "", addUnitAndTypeSuffixes)) - require.Equal(t, "envoy__rule_engine_zlib_buf_error", BuildCompliantName(createGauge("envoy__rule_engine_zlib_buf_error", ""), "", addUnitAndTypeSuffixes)) - require.Equal(t, ":foo::bar", BuildCompliantName(createGauge(":foo::bar", ""), "", addUnitAndTypeSuffixes)) - require.Equal(t, ":foo::bar", BuildCompliantName(createCounter(":foo::bar", ""), "", addUnitAndTypeSuffixes)) + require.Equal(t, "system_io", BuildCompliantName(createCounter("system.io", "By"), "", addUnitAndTypeSuffixes, false)) + require.Equal(t, "system_network_io", BuildCompliantName(createCounter("network.io", "By"), "system", addUnitAndTypeSuffixes, false)) + require.Equal(t, "system_network_I_O", BuildCompliantName(createCounter("network (I/O)", "By"), "system", addUnitAndTypeSuffixes, false)) + require.Equal(t, "_3_14_digits", BuildCompliantName(createGauge("3.14 digits", "By"), "", addUnitAndTypeSuffixes, false)) + require.Equal(t, "envoy__rule_engine_zlib_buf_error", BuildCompliantName(createGauge("envoy__rule_engine_zlib_buf_error", ""), "", addUnitAndTypeSuffixes, false)) + require.Equal(t, ":foo::bar", BuildCompliantName(createGauge(":foo::bar", ""), "", addUnitAndTypeSuffixes, false)) + require.Equal(t, ":foo::bar", BuildCompliantName(createCounter(":foo::bar", ""), "", addUnitAndTypeSuffixes, false)) } @@ -234,12 +251,12 @@ func TestBuildCompliantNameWithoutSuffixes(t *testing.T) { defer testutil.SetFeatureGateForTest(t, normalizeNameGate, false)() addUnitAndTypeSuffixes := false - require.Equal(t, "system_io", BuildCompliantName(createCounter("system.io", "By"), "", addUnitAndTypeSuffixes)) - require.Equal(t, "system_network_io", BuildCompliantName(createCounter("network.io", "By"), "system", addUnitAndTypeSuffixes)) - require.Equal(t, "system_network_I_O", BuildCompliantName(createCounter("network (I/O)", "By"), "system", addUnitAndTypeSuffixes)) - require.Equal(t, "_3_14_digits", BuildCompliantName(createGauge("3.14 digits", "By"), "", addUnitAndTypeSuffixes)) - require.Equal(t, "envoy__rule_engine_zlib_buf_error", BuildCompliantName(createGauge("envoy__rule_engine_zlib_buf_error", ""), "", addUnitAndTypeSuffixes)) - require.Equal(t, ":foo::bar", BuildCompliantName(createGauge(":foo::bar", ""), "", addUnitAndTypeSuffixes)) - require.Equal(t, ":foo::bar", BuildCompliantName(createCounter(":foo::bar", ""), "", addUnitAndTypeSuffixes)) + require.Equal(t, "system_io", BuildCompliantName(createCounter("system.io", "By"), "", addUnitAndTypeSuffixes, false)) + require.Equal(t, "system_network_io", BuildCompliantName(createCounter("network.io", "By"), "system", addUnitAndTypeSuffixes, false)) + require.Equal(t, "system_network_I_O", BuildCompliantName(createCounter("network (I/O)", "By"), "system", addUnitAndTypeSuffixes, false)) + require.Equal(t, "_3_14_digits", BuildCompliantName(createGauge("3.14 digits", "By"), "", addUnitAndTypeSuffixes, false)) + require.Equal(t, "envoy__rule_engine_zlib_buf_error", BuildCompliantName(createGauge("envoy__rule_engine_zlib_buf_error", ""), "", addUnitAndTypeSuffixes, false)) + require.Equal(t, ":foo::bar", BuildCompliantName(createGauge(":foo::bar", ""), "", addUnitAndTypeSuffixes, false)) + require.Equal(t, ":foo::bar", BuildCompliantName(createCounter(":foo::bar", ""), "", addUnitAndTypeSuffixes, false)) } diff --git a/pkg/translator/prometheusremotewrite/helper.go b/pkg/translator/prometheusremotewrite/helper.go index cb03a7c32959..8c1ed53552e2 100644 --- a/pkg/translator/prometheusremotewrite/helper.go +++ b/pkg/translator/prometheusremotewrite/helper.go @@ -130,7 +130,7 @@ func createAttributes(resource pcommon.Resource, attributes pcommon.Map, externa sort.Stable(ByLabelName(labels)) for _, label := range labels { - var finalKey = prometheustranslator.NormalizeLabel(label.Name) + var finalKey = prometheustranslator.NormalizeLabel(label.Name, false) if existingValue, alreadyExists := l[finalKey]; alreadyExists { l[finalKey] = existingValue + ";" + label.Value } else { @@ -170,7 +170,7 @@ func createAttributes(resource pcommon.Resource, attributes pcommon.Map, externa // internal labels should be maintained name := extras[i] if !(len(name) > 4 && name[:2] == "__" && name[len(name)-2:] == "__") { - name = prometheustranslator.NormalizeLabel(name) + name = prometheustranslator.NormalizeLabel(name, false) } l[name] = extras[i+1] } diff --git a/pkg/translator/prometheusremotewrite/histograms_test.go b/pkg/translator/prometheusremotewrite/histograms_test.go index d2b3cba24ae2..f584c2214fa2 100644 --- a/pkg/translator/prometheusremotewrite/histograms_test.go +++ b/pkg/translator/prometheusremotewrite/histograms_test.go @@ -743,7 +743,7 @@ func TestPrometheusConverter_addExponentialHistogramDataPoints(t *testing.T) { metric.ExponentialHistogram().DataPoints(), pcommon.NewResource(), Settings{}, - prometheustranslator.BuildCompliantName(metric, "", true), + prometheustranslator.BuildCompliantName(metric, "", true, false), )) assert.Equal(t, tt.wantSeries(), converter.unique) diff --git a/pkg/translator/prometheusremotewrite/metrics_to_prw.go b/pkg/translator/prometheusremotewrite/metrics_to_prw.go index 65def1ccfa58..fc3d483fdcfd 100644 --- a/pkg/translator/prometheusremotewrite/metrics_to_prw.go +++ b/pkg/translator/prometheusremotewrite/metrics_to_prw.go @@ -75,7 +75,7 @@ func (c *prometheusConverter) fromMetrics(md pmetric.Metrics, settings Settings) continue } - promName := prometheustranslator.BuildCompliantName(metric, settings.Namespace, settings.AddMetricSuffixes) + promName := prometheustranslator.BuildCompliantName(metric, settings.Namespace, settings.AddMetricSuffixes, false) // handle individual metrics based on type //exhaustive:enforce diff --git a/pkg/translator/prometheusremotewrite/metrics_to_prw_v2.go b/pkg/translator/prometheusremotewrite/metrics_to_prw_v2.go index 5ccd4e166e8a..4d775c9f8255 100644 --- a/pkg/translator/prometheusremotewrite/metrics_to_prw_v2.go +++ b/pkg/translator/prometheusremotewrite/metrics_to_prw_v2.go @@ -67,7 +67,7 @@ func (c *prometheusConverterV2) fromMetrics(md pmetric.Metrics, settings Setting continue } - promName := prometheustranslator.BuildCompliantName(metric, settings.Namespace, settings.AddMetricSuffixes) + promName := prometheustranslator.BuildCompliantName(metric, settings.Namespace, settings.AddMetricSuffixes, false) // handle individual metrics based on type //exhaustive:enforce diff --git a/pkg/translator/prometheusremotewrite/otlp_to_openmetrics_metadata.go b/pkg/translator/prometheusremotewrite/otlp_to_openmetrics_metadata.go index 046a713c741e..0a01785ebf35 100644 --- a/pkg/translator/prometheusremotewrite/otlp_to_openmetrics_metadata.go +++ b/pkg/translator/prometheusremotewrite/otlp_to_openmetrics_metadata.go @@ -64,7 +64,7 @@ func OtelMetricsToMetadata(md pmetric.Metrics, addMetricSuffixes bool) []*prompb metric := scopeMetrics.Metrics().At(k) entry := prompb.MetricMetadata{ Type: otelMetricTypeToPromMetricType(metric), - MetricFamilyName: prometheustranslator.BuildCompliantName(metric, "", addMetricSuffixes), + MetricFamilyName: prometheustranslator.BuildCompliantName(metric, "", addMetricSuffixes, false), Help: metric.Description(), } metadata = append(metadata, &entry) diff --git a/pkg/translator/prometheusremotewrite/otlp_to_openmetrics_metadata_test.go b/pkg/translator/prometheusremotewrite/otlp_to_openmetrics_metadata_test.go index 2127ffad06ab..f8e91180e53e 100644 --- a/pkg/translator/prometheusremotewrite/otlp_to_openmetrics_metadata_test.go +++ b/pkg/translator/prometheusremotewrite/otlp_to_openmetrics_metadata_test.go @@ -166,7 +166,7 @@ func TestOtelMetricsToMetadata(t *testing.T) { testdata.TestGaugeDoubleMetricName, pcommon.NewMap(), 1, ts, - ), "", false), + ), "", false, false), Help: "gauge description", }, { @@ -175,7 +175,7 @@ func TestOtelMetricsToMetadata(t *testing.T) { testdata.TestGaugeIntMetricName, pcommon.NewMap(), 1, ts, - ), "", false), + ), "", false, false), Help: "gauge description", }, { @@ -184,7 +184,7 @@ func TestOtelMetricsToMetadata(t *testing.T) { testdata.TestSumDoubleMetricName, pcommon.NewMap(), 1, ts, - ), "", false), + ), "", false, false), Help: "sum description", }, { @@ -193,7 +193,7 @@ func TestOtelMetricsToMetadata(t *testing.T) { testdata.TestSumIntMetricName, pcommon.NewMap(), 1, ts, - ), "", false), + ), "", false, false), Help: "sum description", }, { @@ -202,7 +202,7 @@ func TestOtelMetricsToMetadata(t *testing.T) { testdata.TestDoubleHistogramMetricName, pcommon.NewMap(), 1, ts, - ), "", false), + ), "", false, false), Help: "histogram description", }, { @@ -211,7 +211,7 @@ func TestOtelMetricsToMetadata(t *testing.T) { testdata.TestDoubleSummaryMetricName, pcommon.NewMap(), 1, ts, - ), "", false), + ), "", false, false), Help: "summary description", }, },