github.com/wfusion/gofusion@v1.1.14/common/infra/metrics/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 "github.com/wfusion/gofusion/common/utils" 13 ) 14 15 const ( 16 // We force flush the statsite metrics after this period of 17 // inactivity. Prevents stats from getting stuck in a buffer 18 // forever. 19 flushInterval = 100 * time.Millisecond 20 ) 21 22 // NewStatsiteSinkFromURL creates an StatsiteSink from a URL. It is used 23 // (and tested) from NewMetricSinkFromURL. 24 func NewStatsiteSinkFromURL(u *url.URL) (MetricSink, error) { 25 return NewStatsiteSink(u.Host) 26 } 27 28 // StatsiteSink provides a MetricSink that can be used with a 29 // statsite metrics server 30 type StatsiteSink struct { 31 addr string 32 metricQueue chan string 33 } 34 35 // NewStatsiteSink is used to create a new StatsiteSink 36 func NewStatsiteSink(addr string) (*StatsiteSink, error) { 37 s := &StatsiteSink{ 38 addr: addr, 39 metricQueue: make(chan string, 4096), 40 } 41 go s.flushMetrics() 42 return s, nil 43 } 44 45 // Close is used to stop flushing to statsite 46 func (s *StatsiteSink) Shutdown() { 47 close(s.metricQueue) 48 } 49 50 func (s *StatsiteSink) SetGauge(key []string, val float32, opts ...utils.OptionExtender) { 51 flatKey := s.flattenKey(key) 52 s.pushMetric(fmt.Sprintf("%s:%f|g\n", flatKey, val)) 53 } 54 55 func (s *StatsiteSink) SetGaugeWithLabels(key []string, val float32, labels []Label, opts ...utils.OptionExtender) { 56 flatKey := s.flattenKeyLabels(key, labels) 57 s.pushMetric(fmt.Sprintf("%s:%f|g\n", flatKey, val)) 58 } 59 60 func (s *StatsiteSink) SetPrecisionGauge(key []string, val float64, opts ...utils.OptionExtender) { 61 flatKey := s.flattenKey(key) 62 s.pushMetric(fmt.Sprintf("%s:%f|g\n", flatKey, val)) 63 } 64 65 func (s *StatsiteSink) SetPrecisionGaugeWithLabels(key []string, val float64, labels []Label, 66 opts ...utils.OptionExtender) { 67 flatKey := s.flattenKeyLabels(key, labels) 68 s.pushMetric(fmt.Sprintf("%s:%f|g\n", flatKey, val)) 69 } 70 71 func (s *StatsiteSink) EmitKey(key []string, val float32, opts ...utils.OptionExtender) { 72 flatKey := s.flattenKey(key) 73 s.pushMetric(fmt.Sprintf("%s:%f|kv\n", flatKey, val)) 74 } 75 76 func (s *StatsiteSink) IncrCounter(key []string, val float32, opts ...utils.OptionExtender) { 77 flatKey := s.flattenKey(key) 78 s.pushMetric(fmt.Sprintf("%s:%f|c\n", flatKey, val)) 79 } 80 81 func (s *StatsiteSink) IncrCounterWithLabels(key []string, val float32, labels []Label, opts ...utils.OptionExtender) { 82 flatKey := s.flattenKeyLabels(key, labels) 83 s.pushMetric(fmt.Sprintf("%s:%f|c\n", flatKey, val)) 84 } 85 86 func (s *StatsiteSink) AddSample(key []string, val float32, opts ...utils.OptionExtender) { 87 flatKey := s.flattenKey(key) 88 s.pushMetric(fmt.Sprintf("%s:%f|ms\n", flatKey, val)) 89 } 90 91 func (s *StatsiteSink) AddSampleWithLabels(key []string, val float32, labels []Label, opts ...utils.OptionExtender) { 92 flatKey := s.flattenKeyLabels(key, labels) 93 s.pushMetric(fmt.Sprintf("%s:%f|ms\n", flatKey, val)) 94 } 95 96 // Flattens the key for formatting, removes spaces 97 func (s *StatsiteSink) flattenKey(parts []string) string { 98 joined := strings.Join(parts, ".") 99 return strings.Map(func(r rune) rune { 100 switch r { 101 case ':': 102 fallthrough 103 case ' ': 104 return '_' 105 default: 106 return r 107 } 108 }, joined) 109 } 110 111 // Flattens the key along with labels for formatting, removes spaces 112 func (s *StatsiteSink) flattenKeyLabels(parts []string, labels []Label) string { 113 for _, label := range labels { 114 parts = append(parts, label.Value) 115 } 116 return s.flattenKey(parts) 117 } 118 119 // Does a non-blocking push to the metrics queue 120 func (s *StatsiteSink) pushMetric(m string) { 121 select { 122 case s.metricQueue <- m: 123 default: 124 } 125 } 126 127 // Flushes metrics 128 func (s *StatsiteSink) flushMetrics() { 129 var sock net.Conn 130 var err error 131 var wait <-chan time.Time 132 var buffered *bufio.Writer 133 ticker := time.NewTicker(flushInterval) 134 defer ticker.Stop() 135 136 CONNECT: 137 // Attempt to connect 138 sock, err = net.Dial("tcp", s.addr) 139 if err != nil { 140 log.Printf("[ERR] Error connecting to statsite! Err: %s", err) 141 goto WAIT 142 } 143 defer sock.Close() 144 145 // Create a buffered writer 146 buffered = bufio.NewWriter(sock) 147 148 for { 149 select { 150 case metric, ok := <-s.metricQueue: 151 // Get a metric from the queue 152 if !ok { 153 goto QUIT 154 } 155 156 // Try to send to statsite 157 _, err := buffered.Write([]byte(metric)) 158 if err != nil { 159 log.Printf("[ERR] Error writing to statsite! Err: %s", err) 160 goto WAIT 161 } 162 case <-ticker.C: 163 if err := buffered.Flush(); err != nil { 164 log.Printf("[ERR] Error flushing to statsite! Err: %s", err) 165 goto WAIT 166 } 167 } 168 } 169 170 WAIT: 171 // Wait for a while 172 wait = time.After(time.Duration(5) * time.Second) 173 for { 174 select { 175 // Dequeue the messages to avoid backlog 176 case _, ok := <-s.metricQueue: 177 if !ok { 178 goto QUIT 179 } 180 case <-wait: 181 goto CONNECT 182 } 183 } 184 QUIT: 185 s.metricQueue = nil 186 }