summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/armon/go-metrics/datadog
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/armon/go-metrics/datadog')
-rw-r--r--vendor/github.com/armon/go-metrics/datadog/dogstatsd.go125
-rw-r--r--vendor/github.com/armon/go-metrics/datadog/dogstatsd_test.go147
2 files changed, 272 insertions, 0 deletions
diff --git a/vendor/github.com/armon/go-metrics/datadog/dogstatsd.go b/vendor/github.com/armon/go-metrics/datadog/dogstatsd.go
new file mode 100644
index 000000000..aaba9fe0e
--- /dev/null
+++ b/vendor/github.com/armon/go-metrics/datadog/dogstatsd.go
@@ -0,0 +1,125 @@
+package datadog
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/DataDog/datadog-go/statsd"
+)
+
+// DogStatsdSink provides a MetricSink that can be used
+// with a dogstatsd server. It utilizes the Dogstatsd client at github.com/DataDog/datadog-go/statsd
+type DogStatsdSink struct {
+ client *statsd.Client
+ hostName string
+ propagateHostname bool
+}
+
+// NewDogStatsdSink is used to create a new DogStatsdSink with sane defaults
+func NewDogStatsdSink(addr string, hostName string) (*DogStatsdSink, error) {
+ client, err := statsd.New(addr)
+ if err != nil {
+ return nil, err
+ }
+ sink := &DogStatsdSink{
+ client: client,
+ hostName: hostName,
+ propagateHostname: false,
+ }
+ return sink, nil
+}
+
+// SetTags sets common tags on the Dogstatsd Client that will be sent
+// along with all dogstatsd packets.
+// Ref: http://docs.datadoghq.com/guides/dogstatsd/#tags
+func (s *DogStatsdSink) SetTags(tags []string) {
+ s.client.Tags = tags
+}
+
+// EnableHostnamePropagation forces a Dogstatsd `host` tag with the value specified by `s.HostName`
+// Since the go-metrics package has its own mechanism for attaching a hostname to metrics,
+// setting the `propagateHostname` flag ensures that `s.HostName` overrides the host tag naively set by the DogStatsd server
+func (s *DogStatsdSink) EnableHostNamePropagation() {
+ s.propagateHostname = true
+}
+
+func (s *DogStatsdSink) flattenKey(parts []string) string {
+ joined := strings.Join(parts, ".")
+ return strings.Map(func(r rune) rune {
+ switch r {
+ case ':':
+ fallthrough
+ case ' ':
+ return '_'
+ default:
+ return r
+ }
+ }, joined)
+}
+
+func (s *DogStatsdSink) parseKey(key []string) ([]string, []string) {
+ // Since DogStatsd supports dimensionality via tags on metric keys, this sink's approach is to splice the hostname out of the key in favor of a `host` tag
+ // The `host` tag is either forced here, or set downstream by the DogStatsd server
+
+ var tags []string
+ hostName := s.hostName
+
+ //Splice the hostname out of the key
+ for i, el := range key {
+ if el == hostName {
+ key = append(key[:i], key[i+1:]...)
+ }
+ }
+
+ if s.propagateHostname {
+ tags = append(tags, fmt.Sprintf("host:%s", hostName))
+ }
+ return key, tags
+}
+
+// Implementation of methods in the MetricSink interface
+
+func (s *DogStatsdSink) SetGauge(key []string, val float32) {
+ s.SetGaugeWithTags(key, val, []string{})
+}
+
+func (s *DogStatsdSink) IncrCounter(key []string, val float32) {
+ s.IncrCounterWithTags(key, val, []string{})
+}
+
+// EmitKey is not implemented since DogStatsd does not provide a metric type that holds an
+// arbitrary number of values
+func (s *DogStatsdSink) EmitKey(key []string, val float32) {
+}
+
+func (s *DogStatsdSink) AddSample(key []string, val float32) {
+ s.AddSampleWithTags(key, val, []string{})
+}
+
+// The following ...WithTags methods correspond to Datadog's Tag extension to Statsd.
+// http://docs.datadoghq.com/guides/dogstatsd/#tags
+
+func (s *DogStatsdSink) SetGaugeWithTags(key []string, val float32, tags []string) {
+ flatKey, tags := s.getFlatkeyAndCombinedTags(key, tags)
+ rate := 1.0
+ s.client.Gauge(flatKey, float64(val), tags, rate)
+}
+
+func (s *DogStatsdSink) IncrCounterWithTags(key []string, val float32, tags []string) {
+ flatKey, tags := s.getFlatkeyAndCombinedTags(key, tags)
+ rate := 1.0
+ s.client.Count(flatKey, int64(val), tags, rate)
+}
+
+func (s *DogStatsdSink) AddSampleWithTags(key []string, val float32, tags []string) {
+ flatKey, tags := s.getFlatkeyAndCombinedTags(key, tags)
+ rate := 1.0
+ s.client.TimeInMilliseconds(flatKey, float64(val), tags, rate)
+}
+
+func (s *DogStatsdSink) getFlatkeyAndCombinedTags(key []string, tags []string) (flattenedKey string, combinedTags []string) {
+ key, hostTags := s.parseKey(key)
+ flatKey := s.flattenKey(key)
+ tags = append(tags, hostTags...)
+ return flatKey, tags
+}
diff --git a/vendor/github.com/armon/go-metrics/datadog/dogstatsd_test.go b/vendor/github.com/armon/go-metrics/datadog/dogstatsd_test.go
new file mode 100644
index 000000000..0ec51e3f1
--- /dev/null
+++ b/vendor/github.com/armon/go-metrics/datadog/dogstatsd_test.go
@@ -0,0 +1,147 @@
+package datadog
+
+import (
+ "fmt"
+ "net"
+ "reflect"
+ "testing"
+)
+
+var EmptyTags []string
+
+const (
+ DogStatsdAddr = "127.0.0.1:7254"
+ HostnameEnabled = true
+ HostnameDisabled = false
+ TestHostname = "test_hostname"
+)
+
+func MockGetHostname() string {
+ return TestHostname
+}
+
+var ParseKeyTests = []struct {
+ KeyToParse []string
+ Tags []string
+ PropagateHostname bool
+ ExpectedKey []string
+ ExpectedTags []string
+}{
+ {[]string{"a", MockGetHostname(), "b", "c"}, EmptyTags, HostnameDisabled, []string{"a", "b", "c"}, EmptyTags},
+ {[]string{"a", "b", "c"}, EmptyTags, HostnameDisabled, []string{"a", "b", "c"}, EmptyTags},
+ {[]string{"a", "b", "c"}, EmptyTags, HostnameEnabled, []string{"a", "b", "c"}, []string{fmt.Sprintf("host:%s", MockGetHostname())}},
+}
+
+var FlattenKeyTests = []struct {
+ KeyToFlatten []string
+ Expected string
+}{
+ {[]string{"a", "b", "c"}, "a.b.c"},
+ {[]string{"spaces must", "flatten", "to", "underscores"}, "spaces_must.flatten.to.underscores"},
+}
+
+var MetricSinkTests = []struct {
+ Method string
+ Metric []string
+ Value interface{}
+ Tags []string
+ PropagateHostname bool
+ Expected string
+}{
+ {"SetGauge", []string{"foo", "bar"}, float32(42), EmptyTags, HostnameDisabled, "foo.bar:42.000000|g"},
+ {"SetGauge", []string{"foo", "bar", "baz"}, float32(42), EmptyTags, HostnameDisabled, "foo.bar.baz:42.000000|g"},
+ {"AddSample", []string{"sample", "thing"}, float32(4), EmptyTags, HostnameDisabled, "sample.thing:4.000000|ms"},
+ {"IncrCounter", []string{"count", "me"}, float32(3), EmptyTags, HostnameDisabled, "count.me:3|c"},
+
+ {"SetGauge", []string{"foo", "baz"}, float32(42), []string{"my_tag:my_value"}, HostnameDisabled, "foo.baz:42.000000|g|#my_tag:my_value"},
+ {"SetGauge", []string{"foo", "bar"}, float32(42), []string{"my_tag:my_value", "other_tag:other_value"}, HostnameDisabled, "foo.bar:42.000000|g|#my_tag:my_value,other_tag:other_value"},
+ {"SetGauge", []string{"foo", "bar"}, float32(42), []string{"my_tag:my_value", "other_tag:other_value"}, HostnameEnabled, "foo.bar:42.000000|g|#my_tag:my_value,other_tag:other_value,host:test_hostname"},
+}
+
+func mockNewDogStatsdSink(addr string, tags []string, tagWithHostname bool) *DogStatsdSink {
+ dog, _ := NewDogStatsdSink(addr, MockGetHostname())
+ dog.SetTags(tags)
+ if tagWithHostname {
+ dog.EnableHostNamePropagation()
+ }
+
+ return dog
+}
+
+func setupTestServerAndBuffer(t *testing.T) (*net.UDPConn, []byte) {
+ udpAddr, err := net.ResolveUDPAddr("udp", DogStatsdAddr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ server, err := net.ListenUDP("udp", udpAddr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return server, make([]byte, 1024)
+}
+
+func TestParseKey(t *testing.T) {
+ for _, tt := range ParseKeyTests {
+ dog := mockNewDogStatsdSink(DogStatsdAddr, tt.Tags, tt.PropagateHostname)
+ key, tags := dog.parseKey(tt.KeyToParse)
+
+ if !reflect.DeepEqual(key, tt.ExpectedKey) {
+ t.Fatalf("Key Parsing failed for %v", tt.KeyToParse)
+ }
+
+ if !reflect.DeepEqual(tags, tt.ExpectedTags) {
+ t.Fatalf("Tag Parsing Failed for %v", tt.KeyToParse)
+ }
+ }
+}
+
+func TestFlattenKey(t *testing.T) {
+ dog := mockNewDogStatsdSink(DogStatsdAddr, EmptyTags, HostnameDisabled)
+ for _, tt := range FlattenKeyTests {
+ if !reflect.DeepEqual(dog.flattenKey(tt.KeyToFlatten), tt.Expected) {
+ t.Fatalf("Flattening %v failed", tt.KeyToFlatten)
+ }
+ }
+}
+
+func TestMetricSink(t *testing.T) {
+ server, buf := setupTestServerAndBuffer(t)
+ defer server.Close()
+
+ for _, tt := range MetricSinkTests {
+ dog := mockNewDogStatsdSink(DogStatsdAddr, tt.Tags, tt.PropagateHostname)
+ method := reflect.ValueOf(dog).MethodByName(tt.Method)
+ method.Call([]reflect.Value{
+ reflect.ValueOf(tt.Metric),
+ reflect.ValueOf(tt.Value)})
+ assertServerMatchesExpected(t, server, buf, tt.Expected)
+ }
+}
+
+func TestTaggableMetrics(t *testing.T) {
+ server, buf := setupTestServerAndBuffer(t)
+ defer server.Close()
+
+ dog := mockNewDogStatsdSink(DogStatsdAddr, EmptyTags, HostnameDisabled)
+
+ dog.AddSampleWithTags([]string{"sample", "thing"}, float32(4), []string{"tagkey:tagvalue"})
+ assertServerMatchesExpected(t, server, buf, "sample.thing:4.000000|ms|#tagkey:tagvalue")
+
+ dog.SetGaugeWithTags([]string{"sample", "thing"}, float32(4), []string{"tagkey:tagvalue"})
+ assertServerMatchesExpected(t, server, buf, "sample.thing:4.000000|g|#tagkey:tagvalue")
+
+ dog.IncrCounterWithTags([]string{"sample", "thing"}, float32(4), []string{"tagkey:tagvalue"})
+ assertServerMatchesExpected(t, server, buf, "sample.thing:4|c|#tagkey:tagvalue")
+
+ dog = mockNewDogStatsdSink(DogStatsdAddr, []string{"global"}, HostnameEnabled) // with hostname, global tags
+ dog.IncrCounterWithTags([]string{"sample", "thing"}, float32(4), []string{"tagkey:tagvalue"})
+ assertServerMatchesExpected(t, server, buf, "sample.thing:4|c|#global,tagkey:tagvalue,host:test_hostname")
+}
+
+func assertServerMatchesExpected(t *testing.T, server *net.UDPConn, buf []byte, expected string) {
+ n, _ := server.Read(buf)
+ msg := buf[:n]
+ if string(msg) != expected {
+ t.Fatalf("Line %s does not match expected: %s", string(msg), expected)
+ }
+}