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
This commit is contained in:
Matt T. Proud 2014-04-14 18:45:16 +02:00
parent b83e1b7cad
commit 7efd34a6f8
8 changed files with 129 additions and 45 deletions

View File

@ -28,6 +28,8 @@ export GOENV = TMPDIR=$(TMPDIR) GOROOT=$(GOROOT) GOPATH=$(GOPATH)
export GO = $(GOENV) $(GOCC) export GO = $(GOENV) $(GOCC)
export GOFMT = $(GOROOT)/bin/gofmt export GOFMT = $(GOROOT)/bin/gofmt
BENCHMARK_FILTER ?= .
FULL_GOPATH = $(GOPATH)/src/github.com/prometheus/client_golang FULL_GOPATH = $(GOPATH)/src/github.com/prometheus/client_golang
FULL_GOPATH_BASE = $(GOPATH)/src/github.com/prometheus FULL_GOPATH_BASE = $(GOPATH)/src/github.com/prometheus
@ -61,6 +63,9 @@ dependencies: source_path $(GOCC)
test: build test: build
$(GO) test ./... $(GO) test ./...
benchmark: build
$(GO) test -benchmem -test.bench="$(BENCHMARK_FILTER)" ./...
advice: test advice: test
$(MAKE) -C prometheus advice $(MAKE) -C prometheus advice
$(MAKE) -C examples advice $(MAKE) -C examples advice

View File

@ -48,3 +48,5 @@ const (
valueKey = "value" valueKey = "value"
labelsKey = "labels" labelsKey = "labels"
) )
var blankLabelsSingleton = map[string]string{}

View File

@ -45,14 +45,15 @@ type counter struct {
} }
func (metric *counter) Set(labels map[string]string, value float64) float64 { func (metric *counter) Set(labels map[string]string, value float64) float64 {
if labels == nil {
labels = blankLabelsSingleton
}
signature := labelValuesToSignature(labels)
metric.mutex.Lock() metric.mutex.Lock()
defer metric.mutex.Unlock() defer metric.mutex.Unlock()
if labels == nil {
labels = map[string]string{}
}
signature := labelsToSignature(labels)
if original, ok := metric.values[signature]; ok { if original, ok := metric.values[signature]; ok {
original.Value = value original.Value = value
} else { } else {
@ -66,10 +67,10 @@ func (metric *counter) Set(labels map[string]string, value float64) float64 {
} }
func (metric *counter) Reset(labels map[string]string) { func (metric *counter) Reset(labels map[string]string) {
signature := labelValuesToSignature(labels)
metric.mutex.Lock() metric.mutex.Lock()
defer metric.mutex.Unlock() defer metric.mutex.Unlock()
signature := labelsToSignature(labels)
delete(metric.values, signature) delete(metric.values, signature)
} }
@ -95,14 +96,15 @@ func (metric *counter) String() string {
} }
func (metric *counter) IncrementBy(labels map[string]string, value float64) float64 { func (metric *counter) IncrementBy(labels map[string]string, value float64) float64 {
if labels == nil {
labels = blankLabelsSingleton
}
signature := labelValuesToSignature(labels)
metric.mutex.Lock() metric.mutex.Lock()
defer metric.mutex.Unlock() defer metric.mutex.Unlock()
if labels == nil {
labels = map[string]string{}
}
signature := labelsToSignature(labels)
if original, ok := metric.values[signature]; ok { if original, ok := metric.values[signature]; ok {
original.Value += value original.Value += value
} else { } 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 { func (metric *counter) DecrementBy(labels map[string]string, value float64) float64 {
if labels == nil {
labels = blankLabelsSingleton
}
signature := labelValuesToSignature(labels)
metric.mutex.Lock() metric.mutex.Lock()
defer metric.mutex.Unlock() defer metric.mutex.Unlock()
if labels == nil {
labels = map[string]string{}
}
signature := labelsToSignature(labels)
if original, ok := metric.values[signature]; ok { if original, ok := metric.values[signature]; ok {
original.Value -= value original.Value -= value
} else { } else {

View File

@ -51,14 +51,14 @@ func (metric *gauge) String() string {
} }
func (metric *gauge) Set(labels map[string]string, value float64) float64 { func (metric *gauge) Set(labels map[string]string, value float64) float64 {
metric.mutex.Lock()
defer metric.mutex.Unlock()
if labels == nil { 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 { if original, ok := metric.values[signature]; ok {
original.Value = value 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) { func (metric *gauge) Reset(labels map[string]string) {
signature := labelValuesToSignature(labels)
metric.mutex.Lock() metric.mutex.Lock()
defer metric.mutex.Unlock() defer metric.mutex.Unlock()
signature := labelsToSignature(labels)
delete(metric.values, signature) delete(metric.values, signature)
} }

View File

@ -97,15 +97,16 @@ type histogramVector struct {
} }
func (h *histogram) Add(labels map[string]string, value float64) { 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() h.mutex.Lock()
defer h.mutex.Unlock() 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 { if original, ok := h.values[signature]; ok {
histogram = original histogram = original
} else { } else {
@ -297,10 +298,11 @@ func (h *histogram) Purge() {
} }
func (h *histogram) Reset(labels map[string]string) { func (h *histogram) Reset(labels map[string]string) {
h.mutex.Lock() signature := labelValuesToSignature(labels)
defer h.mutex.Unlock()
h.mutex.RLock()
defer h.mutex.RUnlock()
signature := labelsToSignature(labels)
value, ok := h.values[signature] value, ok := h.values[signature]
if !ok { if !ok {

View File

@ -214,18 +214,18 @@ func testHandler(t tester) {
varintBuf := make([]byte, binary.MaxVarintLen32) varintBuf := make([]byte, binary.MaxVarintLen32)
externalMetricFamily := []*dto.MetricFamily{ externalMetricFamily := []*dto.MetricFamily{
&dto.MetricFamily{ {
Name: proto.String("externalname"), Name: proto.String("externalname"),
Help: proto.String("externaldocstring"), Help: proto.String("externaldocstring"),
Type: dto.MetricType_COUNTER.Enum(), Type: dto.MetricType_COUNTER.Enum(),
Metric: []*dto.Metric{ Metric: []*dto.Metric{
&dto.Metric{ {
Label: []*dto.LabelPair{ Label: []*dto.LabelPair{
&dto.LabelPair{ {
Name: proto.String("externallabelname"), Name: proto.String("externallabelname"),
Value: proto.String("externalval1"), Value: proto.String("externalval1"),
}, },
&dto.LabelPair{ {
Name: proto.String("externalbasename"), Name: proto.String("externalbasename"),
Value: proto.String("externalbasevalue"), Value: proto.String("externalbasevalue"),
}, },
@ -258,13 +258,13 @@ func testHandler(t tester) {
Help: proto.String("docstring"), Help: proto.String("docstring"),
Type: dto.MetricType_COUNTER.Enum(), Type: dto.MetricType_COUNTER.Enum(),
Metric: []*dto.Metric{ Metric: []*dto.Metric{
&dto.Metric{ {
Label: []*dto.LabelPair{ Label: []*dto.LabelPair{
&dto.LabelPair{ {
Name: proto.String("labelname"), Name: proto.String("labelname"),
Value: proto.String("val1"), Value: proto.String("val1"),
}, },
&dto.LabelPair{ {
Name: proto.String("basename"), Name: proto.String("basename"),
Value: proto.String("basevalue"), Value: proto.String("basevalue"),
}, },
@ -273,13 +273,13 @@ func testHandler(t tester) {
Value: proto.Float64(1), Value: proto.Float64(1),
}, },
}, },
&dto.Metric{ {
Label: []*dto.LabelPair{ Label: []*dto.LabelPair{
&dto.LabelPair{ {
Name: proto.String("labelname"), Name: proto.String("labelname"),
Value: proto.String("val2"), Value: proto.String("val2"),
}, },
&dto.LabelPair{ {
Name: proto.String("basename"), Name: proto.String("basename"),
Value: proto.String("basevalue"), Value: proto.String("basevalue"),
}, },

View File

@ -7,7 +7,6 @@
package prometheus package prometheus
import ( import (
"fmt"
"hash/fnv" "hash/fnv"
"sort" "sort"
@ -34,7 +33,31 @@ func labelsToSignature(labels map[string]string) uint64 {
hasher := fnv.New64a() hasher := fnv.New64a()
for _, name := range names { 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() return hasher.Sum64()

View File

@ -22,7 +22,7 @@ func testLabelsToSignature(t tester) {
}, },
{ {
in: map[string]string{"name": "garland, briggs", "fear": "love is not enough"}, 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) 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)
}