diff --git a/prometheus/prometheus.go b/prometheus/prometheus.go index d8221fb..5b2eeb4 100644 --- a/prometheus/prometheus.go +++ b/prometheus/prometheus.go @@ -6,7 +6,6 @@ package prometheus import ( "fmt" "log" - "regexp" "strings" "sync" "time" @@ -248,15 +247,15 @@ func initCounters(m *sync.Map, counters []CounterDefinition, help map[string]str return } -var forbiddenChars = regexp.MustCompile("[ .=\\-/]") +var forbiddenCharsReplacer = strings.NewReplacer(" ", "_", ".", "_", "=", "_", "-", "_", "/", "_") func flattenKey(parts []string, labels []metrics.Label) (string, string) { key := strings.Join(parts, "_") - key = forbiddenChars.ReplaceAllString(key, "_") + key = forbiddenCharsReplacer.Replace(key) hash := key for _, label := range labels { - hash += fmt.Sprintf(";%s=%s", label.Name, label.Value) + hash += ";" + label.Name + "=" + label.Value } return key, hash diff --git a/prometheus/prometheus_test.go b/prometheus/prometheus_test.go index 321a7b9..f857367 100644 --- a/prometheus/prometheus_test.go +++ b/prometheus/prometheus_test.go @@ -367,3 +367,86 @@ func TestMetricSinkInterface(t *testing.T) { var pps *PrometheusPushSink _ = metrics.MetricSink(pps) } + +func Test_flattenKey(t *testing.T) { + testCases := []struct { + name string + inputParts []string + inputLabels []metrics.Label + expectedOutputKey string + expectedOutputHash string + }{ + { + name: "no replacement needed", + inputParts: []string{"my", "example", "metric"}, + inputLabels: []metrics.Label{ + {Name: "foo", Value: "bar"}, + {Name: "baz", Value: "buz"}, + }, + expectedOutputKey: "my_example_metric", + expectedOutputHash: "my_example_metric;foo=bar;baz=buz", + }, + { + name: "key with whitespace", + inputParts: []string{" my ", " example ", " metric "}, + inputLabels: []metrics.Label{ + {Name: "foo", Value: "bar"}, + {Name: "baz", Value: "buz"}, + }, + expectedOutputKey: "_my___example___metric_", + expectedOutputHash: "_my___example___metric_;foo=bar;baz=buz", + }, + { + name: "key with dot", + inputParts: []string{".my.", ".example.", ".metric."}, + inputLabels: []metrics.Label{ + {Name: "foo", Value: "bar"}, + {Name: "baz", Value: "buz"}, + }, + expectedOutputKey: "_my___example___metric_", + expectedOutputHash: "_my___example___metric_;foo=bar;baz=buz", + }, + { + name: "key with dash", + inputParts: []string{"-my-", "-example-", "-metric-"}, + inputLabels: []metrics.Label{ + {Name: "foo", Value: "bar"}, + {Name: "baz", Value: "buz"}, + }, + expectedOutputKey: "_my___example___metric_", + expectedOutputHash: "_my___example___metric_;foo=bar;baz=buz", + }, + { + name: "key with forward slash", + inputParts: []string{"/my/", "/example/", "/metric/"}, + inputLabels: []metrics.Label{ + {Name: "foo", Value: "bar"}, + {Name: "baz", Value: "buz"}, + }, + expectedOutputKey: "_my___example___metric_", + expectedOutputHash: "_my___example___metric_;foo=bar;baz=buz", + }, + { + name: "key with all restricted", + inputParts: []string{"/my-", ".example ", "metric"}, + inputLabels: []metrics.Label{ + {Name: "foo", Value: "bar"}, + {Name: "baz", Value: "buz"}, + }, + expectedOutputKey: "_my___example__metric", + expectedOutputHash: "_my___example__metric;foo=bar;baz=buz", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(b *testing.T) { + actualKey, actualHash := flattenKey(tc.inputParts, tc.inputLabels) + if actualKey != tc.expectedOutputKey { + t.Fatalf("expected key %s, got %s", tc.expectedOutputKey, actualKey) + } + if actualHash != tc.expectedOutputHash { + t.Fatalf("expected hash %s, got %s", tc.expectedOutputHash, actualHash) + } + }) + } +}