diff --git a/CHANGELOG.md b/CHANGELOG.md index 5494734..b9155b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] - Put unreleased items here. +## [2.0.0] - 2020-05-28 + +- Go module support +- Go 1.12-1.14 support +- Upgrade to datadog-go 3.x +- Adds support for DataDog Distributions: https://docs.datadoghq.com/metrics/distributions/ +- Adds Close() to Client interface to support intentional flushes when applications are shutting down +- Optimize tag handling + ## [1.4.0] - 2019-08-26 - Updated `datadog-go` to version `2.2.0` diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000..cf42eb1 --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,56 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + digest = "1:d0e487cb47a2c3ab0a831a946cf5fc9b845bdbc8441620f56a49f1ca75505c81" + name = "github.com/DataDog/datadog-go" + packages = ["statsd"] + pruneopts = "UT" + revision = "ee4b28bb65ba11a2cbbe6813fa89281626b4b463" + version = "v3.7.1" + +[[projects]] + digest = "1:0109cf4321a15313ec895f42e723e1f76121c6975ea006abfa20012272ec0937" + name = "github.com/mattn/go-colorable" + packages = ["."] + pruneopts = "UT" + revision = "68e95eba382c972aafde02ead2cd2426a8a92480" + version = "v0.1.6" + +[[projects]] + digest = "1:0c58d31abe2a2ccb429c559b6292e7df89dcda675456fecc282fa90aa08273eb" + name = "github.com/mattn/go-isatty" + packages = ["."] + pruneopts = "UT" + revision = "7b513a986450394f7bbf1476909911b3aa3a55ce" + version = "v0.0.12" + +[[projects]] + branch = "master" + digest = "1:2b32af4d2a529083275afc192d1067d8126b578c7a9613b26600e4df9c735155" + name = "github.com/mgutz/ansi" + packages = ["."] + pruneopts = "UT" + revision = "9520e82c474b0a04dd04f8a40959027271bab992" + +[[projects]] + branch = "master" + digest = "1:020620a097c2bfd056c8db7d31a69ea2cfed874ce985763dcd9ae00f9fa5f74b" + name = "golang.org/x/sys" + packages = [ + "internal/unsafeheader", + "unix", + ] + pruneopts = "UT" + revision = "05986578812163b26672dabd9b425240ae2bb0ad" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + input-imports = [ + "github.com/DataDog/datadog-go/statsd", + "github.com/mattn/go-isatty", + "github.com/mgutz/ansi", + ] + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..419db56 --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,42 @@ +# Gopkg.toml example +# +# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + name = "github.com/DataDog/datadog-go" + version = "3.7.1" + +[[constraint]] + name = "github.com/mattn/go-isatty" + version = "0.0.12" + +[[constraint]] + branch = "master" + name = "github.com/mgutz/ansi" + +[prune] + go-tests = true + unused-packages = true diff --git a/README.md b/README.md index 55c50de..8e300c6 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ if os.Getenv("env") == "prod" { // Log to standard out instead of sending production metrics. client = metrics.NewLoggerClient(nil) } +defer client.Close() // Simple incrementing counter client.Incr("requests.count") diff --git a/go.mod b/go.mod index e7919ff..f41a410 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,11 @@ module github.com/istreamlabs/go-metrics go 1.12 require ( - github.com/DataDog/datadog-go v3.4.1+incompatible + github.com/DataDog/datadog-go v3.7.1+incompatible github.com/mattn/go-colorable v0.1.6 // indirect github.com/mattn/go-isatty v0.0.12 github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b github.com/stretchr/testify v1.5.1 // indirect golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 // indirect + gopkg.in/go-playground/assert.v1 v1.2.1 ) diff --git a/go.sum b/go.sum index 384d4a6..f660d5f 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/DataDog/datadog-go v3.4.1+incompatible h1:hRUopimy+td4Lc3QDvP/hsbQKI3n5xsmGJTRghwaA7U= github.com/DataDog/datadog-go v3.4.1+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/datadog-go v3.7.1+incompatible h1:HmA9qHVrHIAqpSvoCYJ+c6qst0lgqEhNW6/KwfkHbS8= +github.com/DataDog/datadog-go v3.7.1+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= @@ -20,5 +22,7 @@ golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0 golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/metrics/datadog.go b/metrics/datadog.go index 64e054e..97cfef7 100644 --- a/metrics/datadog.go +++ b/metrics/datadog.go @@ -11,7 +11,7 @@ import ( type DataDogClient struct { client *statsd.Client rate float64 - tagMap map[string]string + tags []string } // NewDataDogClient creates a new dogstatsd client pointing to `address` with @@ -39,7 +39,7 @@ func (c *DataDogClient) WithRate(rate float64) Client { return &DataDogClient{ client: c.client, rate: rate, - tagMap: combine(c.tagMap, map[string]string{}), + tags: c.tags, // clone isn't necessary since original slice is immutable } } @@ -49,14 +49,10 @@ func (c *DataDogClient) WithTags(tags map[string]string) Client { return &DataDogClient{ client: c.client, rate: c.rate, - tagMap: combine(c.tagMap, tags), + tags: cloneTagsWithMap(c.tags, tags), } } -func (c *DataDogClient) tagsList() []string { - return mapToStrings(c.tagMap) -} - // Close closes all client connections and flushes any buffered data. func (c *DataDogClient) Close() error { return c.client.Close() @@ -64,7 +60,7 @@ func (c *DataDogClient) Close() error { // Count adds some integer value to a metric. func (c *DataDogClient) Count(name string, value int64) { - c.client.Count(name, value, c.tagsList(), c.rate) + c.client.Count(name, value, c.tags, c.rate) } // Incr adds one to a metric. @@ -79,13 +75,13 @@ func (c *DataDogClient) Decr(name string) { // Gauge sets a numeric value. func (c *DataDogClient) Gauge(name string, value float64) { - c.client.Gauge(name, value, c.tagsList(), c.rate) + c.client.Gauge(name, value, c.tags, c.rate) } // Event tracks an event that may be relevant to other metrics. func (c *DataDogClient) Event(e *statsd.Event) { - if len(c.tagMap) > 0 { - e.Tags = append(e.Tags, c.tagsList()...) + if len(c.tags) > 0 { + e.Tags = append(e.Tags, c.tags...) } c.client.Event(e) @@ -93,15 +89,15 @@ func (c *DataDogClient) Event(e *statsd.Event) { // Timing tracks a duration. func (c *DataDogClient) Timing(name string, value time.Duration) { - c.client.Timing(name, value, c.tagsList(), c.rate) + c.client.Timing(name, value, c.tags, c.rate) } // Histogram sets a numeric value while tracking min/max/avg/p95/etc. func (c *DataDogClient) Histogram(name string, value float64) { - c.client.Histogram(name, value, c.tagsList(), c.rate) + c.client.Histogram(name, value, c.tags, c.rate) } // Distribution tracks the statistical distribution of a set of values. func (c *DataDogClient) Distribution(name string, value float64) { - c.client.Distribution(name, value, c.tagsList(), c.rate) + c.client.Distribution(name, value, c.tags, c.rate) } diff --git a/metrics/datadog_test.go b/metrics/datadog_test.go index 03e0522..798e97c 100644 --- a/metrics/datadog_test.go +++ b/metrics/datadog_test.go @@ -1,6 +1,7 @@ package metrics_test import ( + "fmt" "reflect" "testing" "time" @@ -51,15 +52,18 @@ func TestDataDogClient(t *testing.T) { // Test that tag overrides work. override := datadog.WithTags(map[string]string{ "tag1": "value1", + "tag2": "value2", }).WithTags(map[string]string{ "tag1": "override", - "tag2": "value2", + "tag3": "value3", }) - actual := override.(*metrics.DataDogClient).TagMap() - expected := map[string]string{ - "tag1": "override", - "tag2": "value2", + actual := override.(*metrics.DataDogClient).Tags() + expected := []string{ + "tag1:override", + "tag1:value1", + "tag2:value2", + "tag3:value3", } if !reflect.DeepEqual(actual, expected) { t.Fatalf("Expected %v to equal %v", actual, expected) @@ -80,3 +84,55 @@ func TestDataDogClient(t *testing.T) { datadog.Close() } + +func Benchmark_0Tags_100Emits(b *testing.B) { + benchmarkClient(b, 0, 100, false) +} + +func BenchmarkTags_5Tags_100Emits(b *testing.B) { + benchmarkClient(b, 5, 100, false) +} + +func BenchmarkTags_5Tags_100Emits_WithInline(b *testing.B) { + benchmarkClient(b, 5, 100, true) +} + +func BenchmarkTags_10Tags_1000Emits(b *testing.B) { + benchmarkClient(b, 10, 1000, false) +} + +func BenchmarkTags_10Tags_1000Emits_WithInline(b *testing.B) { + benchmarkClient(b, 10, 1000, true) +} + +func BenchmarkTags_15Tags_100Emits(b *testing.B) { + benchmarkClient(b, 15, 100, false) +} + +func BenchmarkTags_15Tags_100Emits_WithInline(b *testing.B) { + benchmarkClient(b, 15, 100, true) +} + +func benchmarkClient(b *testing.B, numTags, numMetrics int, inlineTags bool) { + var datadog metrics.Client + datadog = metrics.NewDataDogClient("127.0.0.1:8126", "testing") + defer datadog.Close() + + tags := map[string]string{} + for i := 0; i < numTags; i++ { + tags[fmt.Sprintf("tag-%v", i)] = fmt.Sprintf("value-%v", i) + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + cli := datadog.WithTags(tags) + for m := 0; m < numMetrics; m++ { + if inlineTags { + cli.WithTags(map[string]string{"a": "b"}).Histogram("histo", 123) + } else { + cli.Histogram("histo", 123) + } + } + } +} diff --git a/metrics/export_test.go b/metrics/export_test.go index 3c3f526..a50784f 100644 --- a/metrics/export_test.go +++ b/metrics/export_test.go @@ -1,6 +1,9 @@ package metrics -// TagMap returns the internal tag map from a DataDog client instance. -func (c *DataDogClient) TagMap() map[string]string { - return c.tagMap +import "sort" + +// Tags returns the internal tag list from a DataDog client instance. +func (c *DataDogClient) Tags() []string { + sort.Strings(c.tags) + return c.tags } diff --git a/metrics/logger.go b/metrics/logger.go index 8eb5905..0ec29d3 100644 --- a/metrics/logger.go +++ b/metrics/logger.go @@ -72,7 +72,7 @@ func (c *LoggerClient) Colorized() *LoggerClient { logger: c.logger, rate: c.rate, colors: true, - tagMap: combine(map[string]string{}, c.tagMap), + tagMap: c.tagMap, } } @@ -94,7 +94,7 @@ func (c *LoggerClient) WithRate(rate float64) Client { logger: c.logger, rate: rate, colors: c.colors, - tagMap: combine(map[string]string{}, c.tagMap), + tagMap: c.tagMap, } } diff --git a/metrics/recorder.go b/metrics/recorder.go index c0bd779..8284614 100644 --- a/metrics/recorder.go +++ b/metrics/recorder.go @@ -168,7 +168,7 @@ func (c *RecorderClient) WithRate(rate float64) Client { callInfo: c.callInfo, test: c.test, rate: rate, - tagMap: combine(map[string]string{}, c.tagMap), + tagMap: c.tagMap, } } diff --git a/metrics/recorder_test.go b/metrics/recorder_test.go index 0c94780..04b8448 100644 --- a/metrics/recorder_test.go +++ b/metrics/recorder_test.go @@ -13,6 +13,7 @@ import ( // ExpectEqual compares two values and fails if they are not deeply equal. func ExpectEqual(t *testing.T, expected, actual interface{}) { + t.Helper() if !reflect.DeepEqual(actual, expected) { t.Fatalf("Expected '%s' to be '%s'", actual, expected) } diff --git a/metrics/util.go b/metrics/util.go index f3b5139..dc0a98a 100644 --- a/metrics/util.go +++ b/metrics/util.go @@ -25,6 +25,20 @@ func combine(original, override map[string]string) map[string]string { return combined } +// cloneTagsWithMap clones the original string slice and appends the new tags in the map +func cloneTagsWithMap(original []string, newTags map[string]string) []string { + combined := make([]string, len(original)+len(newTags)) + copy(combined, original) + + i := len(original) + for k, v := range newTags { + combined[i] = fmt.Sprintf("%s:%s", k, v) + i++ + } + + return combined +} + // Converts a map to an array of strings like `key:value`. func mapToStrings(tagMap map[string]string) []string { tags := make([]string, 0, len(tagMap))