hashicorp / go-metrics

A Golang library for exporting performance and runtime metrics to external metrics systems (i.e. statsite, statsd)
MIT License
1.46k stars 177 forks source link

prometheus: improve flatten key performance. #138

Closed jrasell closed 2 years ago

jrasell commented 2 years ago

The flattenKey function is called whenever a gauge, counter, or sample is set when using the Prometheus sink. The change most notably uses a replacer rather than a regex. The hash formatting also now uses + for concatenation rather than fmt.Sprintf for an addition perf improvement.

The Nomad team recently looked into some Nomad metrics which indicated a fair amount of CPU time spent within the Prometheus flattenKey function. While this behaviour was likely exacerbated by other cluster stresses, a small investigation found a couple of potential improvements to the function to improve its performance.

Benchmark Code ```go package go_metrics import ( "fmt" "regexp" "strings" "testing" "github.com/armon/go-metrics" ) func BenchmarkPrometheus_flattenKey(b *testing.B) { testCases := []struct { name string inputParts []string inputLabels []metrics.Label }{ { name: "3_parts_1_label_", inputParts: []string{"nomad", "client", "allocs"}, inputLabels: []metrics.Label{ {Name: "alloc_id", Value: "88ea10d6-bf55-2883-b263-1b0ec007d254"}, }, }, { name: "4_parts_5_labels_", inputParts: []string{"client", "host", "memory", "total"}, inputLabels: []metrics.Label{ {Name: "node_id", Value: "88ea10d6-bf55-2883-b263-1b0ec007d254"}, {Name: "datacenter", Value: "my-datacenter"}, {Name: "node_class", Value: "none"}, {Name: "node_status", Value: "ready"}, {Name: "node_scheduling_eligibility", Value: "eligible"}, }, }, { name: "forbidden_chars_", inputParts: []string{"client", ".host", ".memory", ".total"}, inputLabels: []metrics.Label{ {Name: "node_id", Value: "88ea10d6-bf55-2883-b263-1b0ec007d254"}, {Name: "datacenter", Value: "my-datacenter"}, {Name: "node_class", Value: "none"}, {Name: "node_status", Value: "ready"}, {Name: "node_scheduling_eligibility", Value: "eligible"}, }, }, } for _, tc := range testCases { b.Run(tc.name+"current", func(b *testing.B) { for i := 0; i < b.N; i++ { _, _ = flattenKey(tc.inputParts, tc.inputLabels) } }) b.Run(tc.name+"skip-regex", func(b *testing.B) { for i := 0; i < b.N; i++ { _, _ = flattenKeySkipRegex(tc.inputParts, tc.inputLabels) } }) b.Run(tc.name+"replacer", func(b *testing.B) { for i := 0; i < b.N; i++ { _, _ = flattenKeyReplacer(tc.inputParts, tc.inputLabels) } }) } } var forbiddenChars = regexp.MustCompile("[ .=\\-/]") func flattenKey(parts []string, labels []metrics.Label) (string, string) { key := strings.Join(parts, "_") key = forbiddenChars.ReplaceAllString(key, "_") hash := key for _, label := range labels { hash += fmt.Sprintf(";%s=%s", label.Name, label.Value) } return key, hash } func flattenKeySkipRegex(parts []string, labels []metrics.Label) (string, string) { key := strings.Join(parts, "_") if strings.ContainsAny(key, " .=-/") { key = forbiddenChars.ReplaceAllString(key, "_") } hash := key for _, label := range labels { hash += ";" + label.Name + "=" + label.Value } return key, hash } var forbiddenCharsReplacer = strings.NewReplacer(" ", "_", ".", "_", "=", "_", "-", "_", "/", "_") func flattenKeyReplacer(parts []string, labels []metrics.Label) (string, string) { key := strings.Join(parts, "_") key = forbiddenCharsReplacer.Replace(key) hash := key for _, label := range labels { hash += ";" + label.Name + "=" + label.Value } return key, hash } ```

Benchmark Results:

  $ go test -bench=. -benchmem
goos: darwin
goarch: amd64
pkg: github.com/jrasell/dev-mess/go/benchmark/go-metrics
cpu: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
BenchmarkPrometheus_flattenKey/3_parts_1_label_current-16            1499564           893.0 ns/op       250 B/op          8 allocs/op
BenchmarkPrometheus_flattenKey/3_parts_1_label_skip-regex-16         7859928           129.4 ns/op       104 B/op          2 allocs/op
BenchmarkPrometheus_flattenKey/3_parts_1_label_replacer-16          10091814           127.3 ns/op       104 B/op          2 allocs/op
BenchmarkPrometheus_flattenKey/4_parts_5_labels_current-16            689094          1736 ns/op        1017 B/op         24 allocs/op
BenchmarkPrometheus_flattenKey/4_parts_5_labels_skip-regex-16        2907072           408.4 ns/op       616 B/op          6 allocs/op
BenchmarkPrometheus_flattenKey/4_parts_5_labels_replacer-16          3008982           399.4 ns/op       616 B/op          6 allocs/op
BenchmarkPrometheus_flattenKey/forbidden_chars_current-16             503493          2264 ns/op        1115 B/op         26 allocs/op
BenchmarkPrometheus_flattenKey/forbidden_chars_skip-regex-16          771224          1590 ns/op         784 B/op         11 allocs/op
BenchmarkPrometheus_flattenKey/forbidden_chars_replacer-16           2389686           497.4 ns/op       736 B/op          8 allocs/op
PASS
ok      github.com/jrasell/dev-mess/go/benchmark/go-metrics 14.985s