From 8df19b80bbe9535410e91fb5e871e33b24953ca2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20K=C5=82obuszewski?= Date: Thu, 15 Apr 2021 16:23:28 +0200 Subject: [PATCH] Optimize dpgSignature function in Prometheus receiver This function is invoked very often, which leads to high CPU usage when collecting Prometheus metrics. Benchmark results on my machine show 3x less CPU and almost 4x less memory used. Since I'm also changing the format of returned values (dropping superfluous characters), it will reduce overall memory usage of the receiver, because they are used as map keys. Benchmark result before this change: Benchmark_dpgSignature-6 1284531 969.6 ns/op 120 B/op 7 allocs/op Benchmark result after this change: Benchmark_dpgSignature-6 3515179 313.9 ns/op 32 B/op 2 allocs/op --- .../internal/metricsbuilder.go | 18 ++++++++++++--- .../internal/metricsbuilder_test.go | 22 ++++++++++++++----- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/receiver/prometheusreceiver/internal/metricsbuilder.go b/receiver/prometheusreceiver/internal/metricsbuilder.go index fd18f8f900c..ccaed19ed19 100644 --- a/receiver/prometheusreceiver/internal/metricsbuilder.go +++ b/receiver/prometheusreceiver/internal/metricsbuilder.go @@ -179,15 +179,27 @@ func isUsefulLabel(mType metricspb.MetricDescriptor_Type, labelKey string) bool // dpgSignature is used to create a key for data complexValue belong to a same group of a metric family func dpgSignature(orderedKnownLabelKeys []string, ls labels.Labels) string { - sign := make([]string, 0, len(orderedKnownLabelKeys)) + size := 0 for _, k := range orderedKnownLabelKeys { v := ls.Get(k) if v == "" { continue } - sign = append(sign, k+"="+v) + // 2 enclosing quotes + 1 equality sign = 3 extra chars. + // Note: if any character in the label value requires escaping, + // we'll need more space than that, which will lead to some + // extra allocation. + size += 3 + len(k) + len(v) } - return fmt.Sprintf("%#v", sign) + sign := make([]byte, 0, size) + for _, k := range orderedKnownLabelKeys { + v := ls.Get(k) + if v == "" { + continue + } + sign = strconv.AppendQuote(sign, k+"="+v) + } + return string(sign) } func normalizeMetricName(name string) string { diff --git a/receiver/prometheusreceiver/internal/metricsbuilder_test.go b/receiver/prometheusreceiver/internal/metricsbuilder_test.go index 7a64bbf1635..63cbc0379b7 100644 --- a/receiver/prometheusreceiver/internal/metricsbuilder_test.go +++ b/receiver/prometheusreceiver/internal/metricsbuilder_test.go @@ -16,6 +16,7 @@ package internal import ( "reflect" + "runtime" "testing" metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" @@ -1205,6 +1206,15 @@ func Test_isUsefulLabel(t *testing.T) { } } +func Benchmark_dpgSignature(b *testing.B) { + knownLabelKeys := []string{"a", "b"} + labels := labels.FromStrings("a", "va", "b", "vb", "x", "xa") + b.ReportAllocs() + for i := 0; i < b.N; i++ { + runtime.KeepAlive(dpgSignature(knownLabelKeys, labels)) + } +} + func Test_dpgSignature(t *testing.T) { knownLabelKeys := []string{"a", "b"} @@ -1213,16 +1223,16 @@ func Test_dpgSignature(t *testing.T) { ls labels.Labels want string }{ - {"1st label", labels.FromStrings("a", "va"), `[]string{"a=va"}`}, - {"2nd label", labels.FromStrings("b", "vb"), `[]string{"b=vb"}`}, - {"two labels", labels.FromStrings("a", "va", "b", "vb"), `[]string{"a=va", "b=vb"}`}, - {"extra label", labels.FromStrings("a", "va", "b", "vb", "x", "xa"), `[]string{"a=va", "b=vb"}`}, - {"different order", labels.FromStrings("b", "vb", "a", "va"), `[]string{"a=va", "b=vb"}`}, + {"1st label", labels.FromStrings("a", "va"), `"a=va"`}, + {"2nd label", labels.FromStrings("b", "vb"), `"b=vb"`}, + {"two labels", labels.FromStrings("a", "va", "b", "vb"), `"a=va""b=vb"`}, + {"extra label", labels.FromStrings("a", "va", "b", "vb", "x", "xa"), `"a=va""b=vb"`}, + {"different order", labels.FromStrings("b", "vb", "a", "va"), `"a=va""b=vb"`}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := dpgSignature(knownLabelKeys, tt.ls); got != tt.want { - t.Errorf("dpgSignature() = %v, want %v", got, tt.want) + t.Errorf("dpgSignature() = %q, want %q", got, tt.want) } }) }