github.com/hashicorp/go-metrics@v0.5.3/datadog/dogstatsd.go (about) 1 package datadog 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/DataDog/datadog-go/statsd" 8 "github.com/hashicorp/go-metrics" 9 ) 10 11 // DogStatsdSink provides a MetricSink that can be used 12 // with a dogstatsd server. It utilizes the Dogstatsd client at github.com/DataDog/datadog-go/statsd 13 type DogStatsdSink struct { 14 client *statsd.Client 15 hostName string 16 propagateHostname bool 17 } 18 19 // NewDogStatsdSink is used to create a new DogStatsdSink with sane defaults 20 func NewDogStatsdSink(addr string, hostName string) (*DogStatsdSink, error) { 21 client, err := statsd.New(addr) 22 if err != nil { 23 return nil, err 24 } 25 sink := &DogStatsdSink{ 26 client: client, 27 hostName: hostName, 28 propagateHostname: false, 29 } 30 return sink, nil 31 } 32 33 // SetTags sets common tags on the Dogstatsd Client that will be sent 34 // along with all dogstatsd packets. 35 // Ref: http://docs.datadoghq.com/guides/dogstatsd/#tags 36 func (s *DogStatsdSink) SetTags(tags []string) { 37 s.client.Tags = tags 38 } 39 40 // EnableHostnamePropagation forces a Dogstatsd `host` tag with the value specified by `s.HostName` 41 // Since the go-metrics package has its own mechanism for attaching a hostname to metrics, 42 // setting the `propagateHostname` flag ensures that `s.HostName` overrides the host tag naively set by the DogStatsd server 43 func (s *DogStatsdSink) EnableHostNamePropagation() { 44 s.propagateHostname = true 45 } 46 47 func (s *DogStatsdSink) flattenKey(parts []string) string { 48 joined := strings.Join(parts, ".") 49 return strings.Map(sanitize, joined) 50 } 51 52 func sanitize(r rune) rune { 53 switch r { 54 case ':': 55 fallthrough 56 case ' ': 57 return '_' 58 default: 59 return r 60 } 61 } 62 63 func (s *DogStatsdSink) parseKey(key []string) ([]string, []metrics.Label) { 64 // 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 65 // The `host` tag is either forced here, or set downstream by the DogStatsd server 66 67 var labels []metrics.Label 68 hostName := s.hostName 69 70 // Splice the hostname out of the key 71 for i, el := range key { 72 if el == hostName { 73 // We need an intermediate key to prevent clobbering the 74 // original backing array that other sinks might be consuming. 75 tempKey := append([]string{}, key[:i]...) 76 key = append(tempKey, key[i+1:]...) 77 break 78 } 79 } 80 81 if s.propagateHostname { 82 labels = append(labels, metrics.Label{"host", hostName}) 83 } 84 return key, labels 85 } 86 87 // Implementation of methods in the MetricSink interface 88 89 func (s *DogStatsdSink) SetGauge(key []string, val float32) { 90 s.SetGaugeWithLabels(key, val, nil) 91 } 92 93 func (s *DogStatsdSink) SetPrecisionGauge(key []string, val float64) { 94 s.SetPrecisionGaugeWithLabels(key, val, nil) 95 } 96 97 func (s *DogStatsdSink) IncrCounter(key []string, val float32) { 98 s.IncrCounterWithLabels(key, val, nil) 99 } 100 101 // EmitKey is not implemented since DogStatsd does not provide a metric type that holds an 102 // arbitrary number of values 103 func (s *DogStatsdSink) EmitKey(key []string, val float32) { 104 } 105 106 func (s *DogStatsdSink) AddSample(key []string, val float32) { 107 s.AddSampleWithLabels(key, val, nil) 108 } 109 110 // The following ...WithLabels methods correspond to Datadog's Tag extension to Statsd. 111 // http://docs.datadoghq.com/guides/dogstatsd/#tags 112 func (s *DogStatsdSink) SetGaugeWithLabels(key []string, val float32, labels []metrics.Label) { 113 flatKey, tags := s.getFlatkeyAndCombinedLabels(key, labels) 114 rate := 1.0 115 s.client.Gauge(flatKey, float64(val), tags, rate) 116 } 117 118 // The following ...WithLabels methods correspond to Datadog's Tag extension to Statsd. 119 // http://docs.datadoghq.com/guides/dogstatsd/#tags 120 func (s *DogStatsdSink) SetPrecisionGaugeWithLabels(key []string, val float64, labels []metrics.Label) { 121 flatKey, tags := s.getFlatkeyAndCombinedLabels(key, labels) 122 rate := 1.0 123 s.client.Gauge(flatKey, val, tags, rate) 124 } 125 126 func (s *DogStatsdSink) IncrCounterWithLabels(key []string, val float32, labels []metrics.Label) { 127 flatKey, tags := s.getFlatkeyAndCombinedLabels(key, labels) 128 rate := 1.0 129 s.client.Count(flatKey, int64(val), tags, rate) 130 } 131 132 func (s *DogStatsdSink) AddSampleWithLabels(key []string, val float32, labels []metrics.Label) { 133 flatKey, tags := s.getFlatkeyAndCombinedLabels(key, labels) 134 rate := 1.0 135 s.client.TimeInMilliseconds(flatKey, float64(val), tags, rate) 136 } 137 138 // Shutdown disables further metric collection, blocks to flush data, and tears down the sink. 139 func (s *DogStatsdSink) Shutdown() { 140 s.client.Close() 141 } 142 143 func (s *DogStatsdSink) getFlatkeyAndCombinedLabels(key []string, labels []metrics.Label) (string, []string) { 144 key, parsedLabels := s.parseKey(key) 145 flatKey := s.flattenKey(key) 146 labels = append(labels, parsedLabels...) 147 148 var tags []string 149 for _, label := range labels { 150 label.Name = strings.Map(sanitize, label.Name) 151 label.Value = strings.Map(sanitize, label.Value) 152 if label.Value != "" { 153 tags = append(tags, fmt.Sprintf("%s:%s", label.Name, label.Value)) 154 } else { 155 tags = append(tags, label.Name) 156 } 157 } 158 159 return flatKey, tags 160 }