diff --git a/Makefile b/Makefile index 0dd35121..10174e54 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,9 @@ test: race: GO15VENDOREXPERIMENT=1 go build -race $(shell GO15VENDOREXPERIMENT=1 go list ./... | grep -v /vendor/) +bench: + GO15VENDOREXPERIMENT=1 go test -bench=. $(shell GO15VENDOREXPERIMENT=1 go list ./... | grep -v /vendor/) + cover: ./cover.sh diff --git a/ROADMAP.md b/ROADMAP.md index 6a6f68b6..76360988 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -11,7 +11,7 @@ Pri 1 * [x] Add more timers aggregations e.g. mean, standard deviation, etc. * [ ] Add tests * [x] Add load testing -* [ ] Add benchmarks +* [x] Add benchmarks * [x] Review reset of counters, gauges, timers, etc. using last flush time and an expiration window * [x] Add support for global metrics namespace * [x] Add support for default tags diff --git a/statsd/aggregator_test.go b/statsd/aggregator_test.go index 44a87403..005bd932 100644 --- a/statsd/aggregator_test.go +++ b/statsd/aggregator_test.go @@ -27,6 +27,7 @@ func newFakeMetricAggregator() (backend.MetricSender, *MetricAggregator) { time.Duration(10)*time.Second, time.Duration(5)*time.Minute, 2, + []string{}, ) } @@ -118,6 +119,34 @@ func TestFlush(t *testing.T) { assert.Equal(expected.Sets, actual.Sets) } +func BenchmarkFlush(b *testing.B) { + _, ma := newFakeMetricAggregator() + ma.Counters["some"] = make(map[string]types.Counter) + ma.Counters["some"][""] = types.Counter{Value: 50} + ma.Counters["some"]["thing"] = types.Counter{Value: 100} + ma.Counters["some"]["other:thing"] = types.Counter{Value: 150} + + ma.Timers["some"] = make(map[string]types.Timer) + ma.Timers["some"]["thing"] = types.Timer{Values: []float64{2, 4, 12}} + ma.Timers["some"]["empty"] = types.Timer{Values: []float64{}} + + ma.Gauges["some"] = make(map[string]types.Gauge) + ma.Gauges["some"][""] = types.Gauge{Value: 50} + ma.Gauges["some"]["thing"] = types.Gauge{Value: 100} + ma.Gauges["some"]["other:thing"] = types.Gauge{Value: 150} + + ma.Sets["some"] = make(map[string]types.Set) + unique := make(map[string]int64) + unique["user"] = 1 + ma.Sets["some"]["thing"] = types.Set{Values: unique} + + b.ResetTimer() + + for n := 0; n < b.N; n++ { + ma.flush() + } +} + func TestReset(t *testing.T) { assert := assert.New(t) now := time.Now() @@ -298,3 +327,46 @@ func TestReceiveMetric(t *testing.T) { expectedSets["uniq.usr"]["baz,foo:bar"] = types.Set{Values: sets2, Interval: interval} assert.Equal(expectedSets, ma.Sets) } + +func benchmarkReceiveMetric(metric types.Metric, b *testing.B) { + _, ma := newFakeMetricAggregator() + now := time.Now() + b.ResetTimer() + + for n := 0; n < b.N; n++ { + ma.receiveMetric(metric, now) + } +} + +func BenchmarkReceiveMetricCounter(b *testing.B) { benchmarkReceiveMetric(types.Metric{Name: "foo.bar.baz", Value: 2, Type: types.COUNTER}, b) } +func BenchmarkReceiveMetricGauge(b *testing.B) { benchmarkReceiveMetric(types.Metric{Name: "abc.def.g", Value: 3, Type: types.GAUGE}, b) } +func BenchmarkReceiveMetricTimer(b *testing.B) { benchmarkReceiveMetric(types.Metric{Name: "def.g", Value: 10, Type: types.TIMER}, b) } +func BenchmarkReceiveMetricSet(b *testing.B) { benchmarkReceiveMetric(types.Metric{Name: "uniq.usr", StringValue: "joe", Type: types.SET}, b) } + +func BenchmarkReceiveMetrics(b *testing.B) { + _, ma := newFakeMetricAggregator() + now := time.Now() + + tests := []types.Metric{ + {Name: "foo.bar.baz", Value: 2, Type: types.COUNTER}, + {Name: "abc.def.g", Value: 3, Type: types.GAUGE}, + {Name: "abc.def.g", Value: 8, Type: types.GAUGE, Tags: types.Tags{"foo:bar", "baz"}}, + {Name: "def.g", Value: 10, Type: types.TIMER}, + {Name: "def.g", Value: 1, Type: types.TIMER, Tags: types.Tags{"foo:bar", "baz"}}, + {Name: "smp.rte", Value: 50, Type: types.COUNTER}, + {Name: "smp.rte", Value: 50, Type: types.COUNTER, Tags: types.Tags{"foo:bar", "baz"}}, + {Name: "smp.rte", Value: 5, Type: types.COUNTER, Tags: types.Tags{"foo:bar", "baz"}}, + {Name: "uniq.usr", StringValue: "joe", Type: types.SET}, + {Name: "uniq.usr", StringValue: "joe", Type: types.SET}, + {Name: "uniq.usr", StringValue: "bob", Type: types.SET}, + {Name: "uniq.usr", StringValue: "john", Type: types.SET}, + {Name: "uniq.usr", StringValue: "john", Type: types.SET, Tags: types.Tags{"foo:bar", "baz"}}, + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + for _, metric := range tests { + ma.receiveMetric(metric, now) + } + } +} \ No newline at end of file diff --git a/statsd/receiver_test.go b/statsd/receiver_test.go index 8b447351..d0aa5c0c 100644 --- a/statsd/receiver_test.go +++ b/statsd/receiver_test.go @@ -68,6 +68,7 @@ func TestParseLine(t *testing.T) { "abc.def.g:3|g": {Name: "abc.def.g", Value: 3, Type: types.GAUGE, Tags: types.Tags{"env:foo"}}, "def.g:10|ms": {Name: "def.g", Value: 10, Type: types.TIMER, Tags: types.Tags{"env:foo"}}, "uniq.usr:joe|s": {Name: "uniq.usr", StringValue: "joe", Type: types.SET, Tags: types.Tags{"env:foo"}}, + "uniq.usr:joe|s|#foo:bar": {Name: "uniq.usr", StringValue: "joe", Type: types.SET, Tags: types.Tags{"env:foo", "foo:bar"}}, } mr = &MetricReceiver{Tags: []string{"env:foo"}} @@ -85,6 +86,21 @@ func TestParseLine(t *testing.T) { } } +func benchmarkParseLine(mr *MetricReceiver, input string, b *testing.B) { + for n := 0; n < b.N; n++ { + mr.parseLine([]byte(input)) + } +} + +func BenchmarkParseLineCounter(b *testing.B) { benchmarkParseLine(&MetricReceiver{}, "foo.bar.baz:2|c", b) } +func BenchmarkParseLineCounterWithTagsAndSampleRate(b *testing.B) { benchmarkParseLine(&MetricReceiver{}, "smp.rte:5|c|@0.1|#foo:bar,baz", b) } +func BenchmarkParseLineGauge(b *testing.B) { benchmarkParseLine(&MetricReceiver{}, "abc.def.g:3|g", b) } +func BenchmarkParseLineTimer(b *testing.B) { benchmarkParseLine(&MetricReceiver{}, "def.g:10|ms", b) } +func BenchmarkParseLineSet(b *testing.B) { benchmarkParseLine(&MetricReceiver{}, "uniq.usr:joe|s", b) } +func BenchmarkParseLineCounterWithDefaultTags(b *testing.B) { benchmarkParseLine(&MetricReceiver{Tags: []string{"env:foo"}}, "foo.bar.baz:2|c", b) } +func BenchmarkParseLineCounterWithDefaultTagsAndTags(b *testing.B) { benchmarkParseLine(&MetricReceiver{Tags: []string{"env:foo"}}, "foo.bar.baz:2|c|#foo:bar,baz", b) } +func BenchmarkParseLineCounterWithDefaultTagsAndTagsAndNameSpace(b *testing.B) { benchmarkParseLine(&MetricReceiver{Namespace: "stats", Tags: []string{"env:foo"}}, "foo.bar.baz:2|c|#foo:bar,baz", b) } + func TestParseTags(t *testing.T) { mr := &MetricReceiver{} _, err := mr.parseTags("%foo:bar") @@ -101,3 +117,11 @@ func TestParseTags(t *testing.T) { t.Errorf("test #foo:bar,bar,baz:foo: expected %s, got %s", expected, result) } } + +func BenchmarkParseTags(b *testing.B) { + mr := &MetricReceiver{} + b.ResetTimer() + for n := 0; n < b.N; n++ { + mr.parseTags("#foo:bar,bar,baz:foo") + } +} \ No newline at end of file diff --git a/types/metrics_test.go b/types/metrics_test.go index 5874259d..a34f7542 100644 --- a/types/metrics_test.go +++ b/types/metrics_test.go @@ -22,6 +22,14 @@ func TestIndexOfKey(t *testing.T) { assert.Equal("", value) } +func BenchmarkIndexOfKey(b *testing.B) { + tags := Tags{"foo", "bar:baz", "baz"} + b.ResetTimer() + for n := 0; n < b.N; n++ { + tags.IndexOfKey("bar") + } +} + func TestExtractSourceFromTags(t *testing.T) { assert := assert.New(t) @@ -38,6 +46,14 @@ func TestExtractSourceFromTags(t *testing.T) { assert.Equal(Tags{"foo", "source_id:1.2.3.4", "baz"}, tags) } +func BenchmarkExtractSourceFromTags(b *testing.B) { + stags := "foo,statsd_source_id:1.2.3.4,baz" + b.ResetTimer() + for n := 0; n < b.N; n++ { + ExtractSourceFromTags(stags) + } +} + func TestNormalise(t *testing.T) { assert := assert.New(t)