github.com/hashicorp/go-metrics@v0.5.3/statsite.go (about) 1 package metrics 2 3 import ( 4 "bufio" 5 "fmt" 6 "log" 7 "net" 8 "net/url" 9 "strings" 10 "time" 11 ) 12 13 const ( 14 // We force flush the statsite metrics after this period of 15 // inactivity. Prevents stats from getting stuck in a buffer 16 // forever. 17 flushInterval = 100 * time.Millisecond 18 ) 19 20 // NewStatsiteSinkFromURL creates an StatsiteSink from a URL. It is used 21 // (and tested) from NewMetricSinkFromURL. 22 func NewStatsiteSinkFromURL(u *url.URL) (MetricSink, error) { 23 return NewStatsiteSink(u.Host) 24 } 25 26 // StatsiteSink provides a MetricSink that can be used with a 27 // statsite metrics server 28 type StatsiteSink struct { 29 addr string 30 metricQueue chan string 31 } 32 33 // NewStatsiteSink is used to create a new StatsiteSink 34 func NewStatsiteSink(addr string) (*StatsiteSink, error) { 35 s := &StatsiteSink{ 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 statsite 44 func (s *StatsiteSink) Shutdown() { 45 close(s.metricQueue) 46 } 47 48 func (s *StatsiteSink) 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 *StatsiteSink) 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 *StatsiteSink) 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 *StatsiteSink) 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 *StatsiteSink) 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 *StatsiteSink) 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 *StatsiteSink) 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 *StatsiteSink) 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 *StatsiteSink) 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 *StatsiteSink) 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 *StatsiteSink) 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 *StatsiteSink) pushMetric(m string) { 118 select { 119 case s.metricQueue <- m: 120 default: 121 } 122 } 123 124 // Flushes metrics 125 func (s *StatsiteSink) flushMetrics() { 126 var sock net.Conn 127 var err error 128 var wait <-chan time.Time 129 var buffered *bufio.Writer 130 ticker := time.NewTicker(flushInterval) 131 defer ticker.Stop() 132 133 CONNECT: 134 // Attempt to connect 135 sock, err = net.Dial("tcp", s.addr) 136 if err != nil { 137 log.Printf("[ERR] Error connecting to statsite! Err: %s", err) 138 goto WAIT 139 } 140 141 // Create a buffered writer 142 buffered = bufio.NewWriter(sock) 143 144 for { 145 select { 146 case metric, ok := <-s.metricQueue: 147 // Get a metric from the queue 148 if !ok { 149 goto QUIT 150 } 151 152 // Try to send to statsite 153 _, err := buffered.Write([]byte(metric)) 154 if err != nil { 155 log.Printf("[ERR] Error writing to statsite! Err: %s", err) 156 goto WAIT 157 } 158 case <-ticker.C: 159 if err := buffered.Flush(); err != nil { 160 log.Printf("[ERR] Error flushing to statsite! Err: %s", err) 161 goto WAIT 162 } 163 } 164 } 165 166 WAIT: 167 // Wait for a while 168 wait = time.After(time.Duration(5) * time.Second) 169 for { 170 select { 171 // Dequeue the messages to avoid backlog 172 case _, ok := <-s.metricQueue: 173 if !ok { 174 goto QUIT 175 } 176 case <-wait: 177 goto CONNECT 178 } 179 } 180 QUIT: 181 s.metricQueue = nil 182 }