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) +}