From d103ed6ca97ca5a2669f6cf5fe4b3d2a9c945f26 Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Wed, 17 May 2017 16:51:25 -0400 Subject: Upgrading server dependancies (#6431) --- .../armon/go-metrics/datadog/dogstatsd.go | 125 ++++++++++++++++++ .../armon/go-metrics/datadog/dogstatsd_test.go | 147 +++++++++++++++++++++ 2 files changed, 272 insertions(+) create mode 100644 vendor/github.com/armon/go-metrics/datadog/dogstatsd.go create mode 100644 vendor/github.com/armon/go-metrics/datadog/dogstatsd_test.go (limited to 'vendor/github.com/armon/go-metrics/datadog') 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) + } +} -- cgit v1.2.3-1-g7c22