github.com/hashicorp/go-metrics@v0.5.3/statsd.go (about) 1 package metrics 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "net" 8 "net/url" 9 "strings" 10 "time" 11 ) 12 13 const ( 14 // statsdMaxLen is the maximum size of a packet 15 // to send to statsd 16 statsdMaxLen = 1400 17 ) 18 19 // StatsdSink provides a MetricSink that can be used 20 // with a statsite or statsd metrics server. It uses 21 // only UDP packets, while StatsiteSink uses TCP. 22 type StatsdSink struct { 23 addr string 24 metricQueue chan string 25 } 26 27 // NewStatsdSinkFromURL creates an StatsdSink from a URL. It is used 28 // (and tested) from NewMetricSinkFromURL. 29 func NewStatsdSinkFromURL(u *url.URL) (MetricSink, error) { 30 return NewStatsdSink(u.Host) 31 } 32 33 // NewStatsdSink is used to create a new StatsdSink 34 func NewStatsdSink(addr string) (*StatsdSink, error) { 35 s := &StatsdSink{ 36 addr: addr, 37 metricQueue: make(chan string, 4096), 38 } 39 go s.flushMetrics() 40 return s, nil 41 } 42 43 // Close is used to stop flushing to statsd 44 func (s *StatsdSink) Shutdown() { 45 close(s.metricQueue) 46 } 47 48 func (s *StatsdSink) SetGauge(key []string, val float32) { 49 flatKey := s.flattenKey(key) 50 s.pushMetric(fmt.Sprintf("%s:%f|g\n", flatKey, val)) 51 } 52 53 func (s *StatsdSink) SetGaugeWithLabels(key []string, val float32, labels []Label) { 54 flatKey := s.flattenKeyLabels(key, labels) 55 s.pushMetric(fmt.Sprintf("%s:%f|g\n", flatKey, val)) 56 } 57 58 func (s *StatsdSink) SetPrecisionGauge(key []string, val float64) { 59 flatKey := s.flattenKey(key) 60 s.pushMetric(fmt.Sprintf("%s:%f|g\n", flatKey, val)) 61 } 62 63 func (s *StatsdSink) SetPrecisionGaugeWithLabels(key []string, val float64, labels []Label) { 64 flatKey := s.flattenKeyLabels(key, labels) 65 s.pushMetric(fmt.Sprintf("%s:%f|g\n", flatKey, val)) 66 } 67 68 func (s *StatsdSink) EmitKey(key []string, val float32) { 69 flatKey := s.flattenKey(key) 70 s.pushMetric(fmt.Sprintf("%s:%f|kv\n", flatKey, val)) 71 } 72 73 func (s *StatsdSink) IncrCounter(key []string, val float32) { 74 flatKey := s.flattenKey(key) 75 s.pushMetric(fmt.Sprintf("%s:%f|c\n", flatKey, val)) 76 } 77 78 func (s *StatsdSink) IncrCounterWithLabels(key []string, val float32, labels []Label) { 79 flatKey := s.flattenKeyLabels(key, labels) 80 s.pushMetric(fmt.Sprintf("%s:%f|c\n", flatKey, val)) 81 } 82 83 func (s *StatsdSink) AddSample(key []string, val float32) { 84 flatKey := s.flattenKey(key) 85 s.pushMetric(fmt.Sprintf("%s:%f|ms\n", flatKey, val)) 86 } 87 88 func (s *StatsdSink) AddSampleWithLabels(key []string, val float32, labels []Label) { 89 flatKey := s.flattenKeyLabels(key, labels) 90 s.pushMetric(fmt.Sprintf("%s:%f|ms\n", flatKey, val)) 91 } 92 93 // Flattens the key for formatting, removes spaces 94 func (s *StatsdSink) flattenKey(parts []string) string { 95 joined := strings.Join(parts, ".") 96 return strings.Map(func(r rune) rune { 97 switch r { 98 case ':': 99 fallthrough 100 case ' ': 101 return '_' 102 default: 103 return r 104 } 105 }, joined) 106 } 107 108 // Flattens the key along with labels for formatting, removes spaces 109 func (s *StatsdSink) flattenKeyLabels(parts []string, labels []Label) string { 110 for _, label := range labels { 111 parts = append(parts, label.Value) 112 } 113 return s.flattenKey(parts) 114 } 115 116 // Does a non-blocking push to the metrics queue 117 func (s *StatsdSink) pushMetric(m string) { 118 select { 119 case s.metricQueue <- m: 120 default: 121 } 122 } 123 124 // Flushes metrics 125 func (s *StatsdSink) flushMetrics() { 126 var sock net.Conn 127 var err error 128 var wait <-chan time.Time 129 ticker := time.NewTicker(flushInterval) 130 defer ticker.Stop() 131 132 CONNECT: 133 // Create a buffer 134 buf := bytes.NewBuffer(nil) 135 136 // Attempt to connect 137 sock, err = net.Dial("udp", s.addr) 138 if err != nil { 139 log.Printf("[ERR] Error connecting to statsd! Err: %s", err) 140 goto WAIT 141 } 142 143 for { 144 select { 145 case metric, ok := <-s.metricQueue: 146 // Get a metric from the queue 147 if !ok { 148 goto QUIT 149 } 150 151 // Check if this would overflow the packet size 152 if len(metric)+buf.Len() > statsdMaxLen { 153 _, err := sock.Write(buf.Bytes()) 154 buf.Reset() 155 if err != nil { 156 log.Printf("[ERR] Error writing to statsd! Err: %s", err) 157 goto WAIT 158 } 159 } 160 161 // Append to the buffer 162 buf.WriteString(metric) 163 164 case <-ticker.C: 165 if buf.Len() == 0 { 166 continue 167 } 168 169 _, err := sock.Write(buf.Bytes()) 170 buf.Reset() 171 if err != nil { 172 log.Printf("[ERR] Error flushing to statsd! Err: %s", err) 173 goto WAIT 174 } 175 } 176 } 177 178 WAIT: 179 // Wait for a while 180 wait = time.After(time.Duration(5) * time.Second) 181 for { 182 select { 183 // Dequeue the messages to avoid backlog 184 case _, ok := <-s.metricQueue: 185 if !ok { 186 goto QUIT 187 } 188 case <-wait: 189 goto CONNECT 190 } 191 } 192 QUIT: 193 s.metricQueue = nil 194 }