From 7efd34a6f8061333458e3105936d10f5c24493b7 Mon Sep 17 00:00:00 2001 From: "Matt T. Proud" Date: Mon, 14 Apr 2014 18:45:16 +0200 Subject: [PATCH] Optimize fingerprinting and metric locks. These are all simple changes we should have caught a long time ago: 1. The hashing mechanism for fingerprint label sets should have not allocated new objects for the actual hashing---at least not egregiously. This simplifies the hash writing by just byte- dumping the string stream into the hasher. 2. The hashing mechanism within the scope of a metric does not care about the value of the label keys themselves but only of the label values. The keys can be dropped from the calculation. 3. The locking mechanism for the metrics should not block on hash computation but rather solely on the actual mutation or critical section reads. 4. For scalar metrics (i.e., ones with niladic label signatures), we should rely on a preallocated map versus requesting a new one ad hoc. This is tested with Go 1.1, so the results may yield other values for us elsewhere: BEFORE BenchmarkLabelValuesToSignatureScalar 500000000 3.97 ns/op 0 B/op 0 allocs/op BenchmarkLabelValuesToSignatureSingle 5000000 714 ns/op 74 B/op 4 allocs/op BenchmarkLabelValuesToSignatureDouble 1000000 1153 ns/op 107 B/op 5 allocs/op BenchmarkLabelValuesToSignatureTriple 1000000 1588 ns/op 138 B/op 6 allocs/op BenchmarkLabelToSignatureScalar 500000000 3.91 ns/op 0 B/op 0 allocs/op BenchmarkLabelToSignatureSingle 2000000 874 ns/op 92 B/op 5 allocs/op BenchmarkLabelToSignatureDouble 1000000 1528 ns/op 139 B/op 7 allocs/op BenchmarkLabelToSignatureTriple 1000000 2172 ns/op 186 B/op 9 allocs/op AFTER BenchmarkLabelValuesToSignatureScalar 500000000 4.36 ns/op 0 B/op 0 allocs/op BenchmarkLabelValuesToSignatureSingle 5000000 378 ns/op 89 B/op 4 allocs/op BenchmarkLabelValuesToSignatureDouble 5000000 574 ns/op 142 B/op 5 allocs/op BenchmarkLabelValuesToSignatureTriple 5000000 758 ns/op 186 B/op 6 allocs/op BenchmarkLabelToSignatureScalar 500000000 4.06 ns/op 0 B/op 0 allocs/op BenchmarkLabelToSignatureSingle 5000000 472 ns/op 106 B/op 5 allocs/op BenchmarkLabelToSignatureDouble 2000000 746 ns/op 174 B/op 7 allocs/op BenchmarkLabelToSignatureTriple 1000000 1061 ns/op 235 B/op 9 allocs/op In effect, a single metric mutation operation's lookup overhead will move from Before::iBenchmarkLabelToSignature to After::BenchmarkLabelValuesToSignature. This MINIMALLY reduces 1/2 the overhead. I would be hesitant in reading the memory allocation statistics, for this was run with the GC still on and thusly inaccurate per Go benchmarking documentation. Before::BenchmarkLabelValuesToSignature never existed, so it is not of any intrinsic value in itself. That said, the cases that still rely on LabelToSignature experience consistently a 1/2 drop in time. Change-Id: Ifc9e69f718af65a59f5be8117473518233258159 --- Makefile | 5 ++++ prometheus/constants.go | 2 ++ prometheus/counter.go | 37 ++++++++++++++------------ prometheus/gauge.go | 13 +++++----- prometheus/histogram.go | 20 ++++++++------- prometheus/registry_test.go | 20 +++++++-------- prometheus/signature.go | 27 +++++++++++++++++-- prometheus/signature_test.go | 50 +++++++++++++++++++++++++++++++++++- 8 files changed, 129 insertions(+), 45 deletions(-) diff --git a/Makefile b/Makefile index 8ba9db0..cd9e2cc 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,8 @@ export GOENV = TMPDIR=$(TMPDIR) GOROOT=$(GOROOT) GOPATH=$(GOPATH) export GO = $(GOENV) $(GOCC) export GOFMT = $(GOROOT)/bin/gofmt +BENCHMARK_FILTER ?= . + FULL_GOPATH = $(GOPATH)/src/github.com/prometheus/client_golang FULL_GOPATH_BASE = $(GOPATH)/src/github.com/prometheus @@ -61,6 +63,9 @@ dependencies: source_path $(GOCC) test: build $(GO) test ./... +benchmark: build + $(GO) test -benchmem -test.bench="$(BENCHMARK_FILTER)" ./... + advice: test $(MAKE) -C prometheus advice $(MAKE) -C examples advice diff --git a/prometheus/constants.go b/prometheus/constants.go index 5b97966..09b5634 100644 --- a/prometheus/constants.go +++ b/prometheus/constants.go @@ -48,3 +48,5 @@ const ( valueKey = "value" labelsKey = "labels" ) + +var blankLabelsSingleton = map[string]string{} diff --git a/prometheus/counter.go b/prometheus/counter.go index 1ed3e82..180ad78 100644 --- a/prometheus/counter.go +++ b/prometheus/counter.go @@ -45,14 +45,15 @@ type counter struct { } func (metric *counter) Set(labels map[string]string, value float64) float64 { + if labels == nil { + labels = blankLabelsSingleton + } + + signature := labelValuesToSignature(labels) + metric.mutex.Lock() defer metric.mutex.Unlock() - if labels == nil { - labels = map[string]string{} - } - - signature := labelsToSignature(labels) if original, ok := metric.values[signature]; ok { original.Value = value } else { @@ -66,10 +67,10 @@ func (metric *counter) Set(labels map[string]string, value float64) float64 { } func (metric *counter) Reset(labels map[string]string) { + signature := labelValuesToSignature(labels) + metric.mutex.Lock() defer metric.mutex.Unlock() - - signature := labelsToSignature(labels) delete(metric.values, signature) } @@ -95,14 +96,15 @@ func (metric *counter) String() string { } func (metric *counter) IncrementBy(labels map[string]string, value float64) float64 { + if labels == nil { + labels = blankLabelsSingleton + } + + signature := labelValuesToSignature(labels) + metric.mutex.Lock() defer metric.mutex.Unlock() - if labels == nil { - labels = map[string]string{} - } - - signature := labelsToSignature(labels) if original, ok := metric.values[signature]; ok { original.Value += value } else { @@ -120,14 +122,15 @@ func (metric *counter) Increment(labels map[string]string) float64 { } func (metric *counter) DecrementBy(labels map[string]string, value float64) float64 { + if labels == nil { + labels = blankLabelsSingleton + } + + signature := labelValuesToSignature(labels) + metric.mutex.Lock() defer metric.mutex.Unlock() - if labels == nil { - labels = map[string]string{} - } - - signature := labelsToSignature(labels) if original, ok := metric.values[signature]; ok { original.Value -= value } else { diff --git a/prometheus/gauge.go b/prometheus/gauge.go index 5a2c782..0a5000f 100644 --- a/prometheus/gauge.go +++ b/prometheus/gauge.go @@ -51,14 +51,14 @@ func (metric *gauge) String() string { } func (metric *gauge) Set(labels map[string]string, value float64) float64 { - metric.mutex.Lock() - defer metric.mutex.Unlock() - if labels == nil { - labels = map[string]string{} + labels = blankLabelsSingleton } - signature := labelsToSignature(labels) + signature := labelValuesToSignature(labels) + + metric.mutex.Lock() + defer metric.mutex.Unlock() if original, ok := metric.values[signature]; ok { original.Value = value @@ -73,10 +73,11 @@ func (metric *gauge) Set(labels map[string]string, value float64) float64 { } func (metric *gauge) Reset(labels map[string]string) { + signature := labelValuesToSignature(labels) + metric.mutex.Lock() defer metric.mutex.Unlock() - signature := labelsToSignature(labels) delete(metric.values, signature) } diff --git a/prometheus/histogram.go b/prometheus/histogram.go index 18a8db8..b7d39c7 100644 --- a/prometheus/histogram.go +++ b/prometheus/histogram.go @@ -97,15 +97,16 @@ type histogramVector struct { } func (h *histogram) Add(labels map[string]string, value float64) { + if labels == nil { + labels = blankLabelsSingleton + } + + signature := labelValuesToSignature(labels) + var histogram *histogramVector = nil + h.mutex.Lock() defer h.mutex.Unlock() - if labels == nil { - labels = map[string]string{} - } - - signature := labelsToSignature(labels) - var histogram *histogramVector = nil if original, ok := h.values[signature]; ok { histogram = original } else { @@ -297,10 +298,11 @@ func (h *histogram) Purge() { } func (h *histogram) Reset(labels map[string]string) { - h.mutex.Lock() - defer h.mutex.Unlock() + signature := labelValuesToSignature(labels) + + h.mutex.RLock() + defer h.mutex.RUnlock() - signature := labelsToSignature(labels) value, ok := h.values[signature] if !ok { diff --git a/prometheus/registry_test.go b/prometheus/registry_test.go index ec58927..b153822 100644 --- a/prometheus/registry_test.go +++ b/prometheus/registry_test.go @@ -214,18 +214,18 @@ func testHandler(t tester) { varintBuf := make([]byte, binary.MaxVarintLen32) externalMetricFamily := []*dto.MetricFamily{ - &dto.MetricFamily{ + { Name: proto.String("externalname"), Help: proto.String("externaldocstring"), Type: dto.MetricType_COUNTER.Enum(), Metric: []*dto.Metric{ - &dto.Metric{ + { Label: []*dto.LabelPair{ - &dto.LabelPair{ + { Name: proto.String("externallabelname"), Value: proto.String("externalval1"), }, - &dto.LabelPair{ + { Name: proto.String("externalbasename"), Value: proto.String("externalbasevalue"), }, @@ -258,13 +258,13 @@ func testHandler(t tester) { Help: proto.String("docstring"), Type: dto.MetricType_COUNTER.Enum(), Metric: []*dto.Metric{ - &dto.Metric{ + { Label: []*dto.LabelPair{ - &dto.LabelPair{ + { Name: proto.String("labelname"), Value: proto.String("val1"), }, - &dto.LabelPair{ + { Name: proto.String("basename"), Value: proto.String("basevalue"), }, @@ -273,13 +273,13 @@ func testHandler(t tester) { Value: proto.Float64(1), }, }, - &dto.Metric{ + { Label: []*dto.LabelPair{ - &dto.LabelPair{ + { Name: proto.String("labelname"), Value: proto.String("val2"), }, - &dto.LabelPair{ + { Name: proto.String("basename"), Value: proto.String("basevalue"), }, diff --git a/prometheus/signature.go b/prometheus/signature.go index 505e51a..2cc158c 100644 --- a/prometheus/signature.go +++ b/prometheus/signature.go @@ -7,7 +7,6 @@ package prometheus import ( - "fmt" "hash/fnv" "sort" @@ -34,7 +33,31 @@ func labelsToSignature(labels map[string]string) uint64 { hasher := fnv.New64a() for _, name := range names { - fmt.Fprintf(hasher, string(name), labels[string(name)]) + hasher.Write([]byte(name)) + hasher.Write([]byte(labels[string(name)])) + } + + return hasher.Sum64() +} + +// LabelValuesToSignature provides a way of building a unique signature +// (i.e., fingerprint) for a given set of label's values. +func labelValuesToSignature(labels map[string]string) uint64 { + if len(labels) == 0 { + return emptyLabelSignature + } + + names := make(model.LabelNames, 0, len(labels)) + for name := range labels { + names = append(names, model.LabelName(name)) + } + + sort.Sort(names) + + hasher := fnv.New64a() + + for _, name := range names { + hasher.Write([]byte(labels[string(name)])) } return hasher.Sum64() diff --git a/prometheus/signature_test.go b/prometheus/signature_test.go index 9b0035b..e113901 100644 --- a/prometheus/signature_test.go +++ b/prometheus/signature_test.go @@ -22,7 +22,7 @@ func testLabelsToSignature(t tester) { }, { in: map[string]string{"name": "garland, briggs", "fear": "love is not enough"}, - out: 15753083015552662396, + out: 12256296522964301276, }, } @@ -63,3 +63,51 @@ func BenchmarkLabelToSignature(b *testing.B) { testLabelsToSignature(b) } } + +func benchmarkLabelValuesToSignature(b *testing.B, l map[string]string, e uint64) { + for i := 0; i < b.N; i++ { + if a := labelValuesToSignature(l); a != e { + b.Fatalf("expected signature of %d for %s, got %d", e, l, a) + } + } +} + +func BenchmarkLabelValuesToSignatureScalar(b *testing.B) { + benchmarkLabelValuesToSignature(b, nil, 14695981039346656037) +} + +func BenchmarkLabelValuesToSignatureSingle(b *testing.B) { + benchmarkLabelValuesToSignature(b, map[string]string{"first-label": "first-label-value"}, 2653746141194979650) +} + +func BenchmarkLabelValuesToSignatureDouble(b *testing.B) { + benchmarkLabelValuesToSignature(b, map[string]string{"first-label": "first-label-value", "second-label": "second-label-value"}, 5670080368112985613) +} + +func BenchmarkLabelValuesToSignatureTriple(b *testing.B) { + benchmarkLabelValuesToSignature(b, map[string]string{"first-label": "first-label-value", "second-label": "second-label-value", "third-label": "third-label-value"}, 2503588453955211397) +} + +func benchmarkLabelToSignature(b *testing.B, l map[string]string, e uint64) { + for i := 0; i < b.N; i++ { + if a := labelsToSignature(l); a != e { + b.Fatalf("expected signature of %d for %s, got %d", e, l, a) + } + } +} + +func BenchmarkLabelToSignatureScalar(b *testing.B) { + benchmarkLabelToSignature(b, nil, 14695981039346656037) +} + +func BenchmarkLabelToSignatureSingle(b *testing.B) { + benchmarkLabelToSignature(b, map[string]string{"first-label": "first-label-value"}, 2231159900647003583) +} + +func BenchmarkLabelToSignatureDouble(b *testing.B) { + benchmarkLabelToSignature(b, map[string]string{"first-label": "first-label-value", "second-label": "second-label-value"}, 14091549261072856487) +} + +func BenchmarkLabelToSignatureTriple(b *testing.B) { + benchmarkLabelToSignature(b, map[string]string{"first-label": "first-label-value", "second-label": "second-label-value", "third-label": "third-label-value"}, 9120920685107702735) +}