vitess.io/vitess@v0.16.2/go/stats/statsd/statsd.go (about) 1 package statsd 2 3 import ( 4 "encoding/json" 5 "expvar" 6 "fmt" 7 "hash/crc32" 8 "strings" 9 "sync" 10 11 "github.com/DataDog/datadog-go/statsd" 12 "github.com/spf13/pflag" 13 14 "vitess.io/vitess/go/stats" 15 "vitess.io/vitess/go/vt/log" 16 "vitess.io/vitess/go/vt/servenv" 17 ) 18 19 var ( 20 statsdAddress string 21 statsdSampleRate = 1.0 22 ) 23 24 func registerFlags(fs *pflag.FlagSet) { 25 fs.StringVar(&statsdAddress, "statsd_address", statsdAddress, "Address for statsd client") 26 fs.Float64Var(&statsdSampleRate, "statsd_sample_rate", statsdSampleRate, "Sample rate for statsd metrics") 27 } 28 29 func init() { 30 servenv.OnParseFor("vtgate", registerFlags) 31 servenv.OnParseFor("vttablet", registerFlags) 32 } 33 34 // StatsBackend implements PullBackend using statsd 35 type StatsBackend struct { 36 namespace string 37 statsdClient *statsd.Client 38 sampleRate float64 39 } 40 41 var ( 42 sb StatsBackend 43 buildGitRecOnce sync.Once 44 ) 45 46 // makeLabel builds a tag list with a single label + value. 47 func makeLabel(labelName string, labelVal string) []string { 48 return []string{fmt.Sprintf("%s:%s", labelName, labelVal)} 49 } 50 51 // makeLabels takes the vitess stat representation of label values ("."-separated list) and breaks it 52 // apart into a map of label name -> label value. 53 func makeLabels(labelNames []string, labelValsCombined string) []string { 54 tags := make([]string, len(labelNames)) 55 labelVals := strings.Split(labelValsCombined, ".") 56 for i, v := range labelVals { 57 tags[i] = fmt.Sprintf("%s:%s", labelNames[i], v) 58 } 59 return tags 60 } 61 62 func makeCommonTags(tags map[string]string) []string { 63 var commonTags []string 64 for k, v := range tags { 65 commonTag := fmt.Sprintf("%s:%s", k, v) 66 commonTags = append(commonTags, commonTag) 67 } 68 return commonTags 69 } 70 71 // Init initializes the statsd with the given namespace. 72 func Init(namespace string) { 73 servenv.OnRun(func() { 74 InitWithoutServenv(namespace) 75 }) 76 } 77 78 // InitWithoutServenv initializes the statsd using the namespace but without servenv 79 func InitWithoutServenv(namespace string) { 80 if statsdAddress == "" { 81 log.Info("statsdAddress is empty") 82 return 83 } 84 statsdC, err := statsd.NewBuffered(statsdAddress, 100) 85 if err != nil { 86 log.Errorf("Failed to create statsd client %v", err) 87 return 88 } 89 statsdC.Namespace = namespace + "." 90 if tags := stats.ParseCommonTags(stats.CommonTags); len(tags) > 0 { 91 statsdC.Tags = makeCommonTags(tags) 92 } 93 sb.namespace = namespace 94 sb.statsdClient = statsdC 95 sb.sampleRate = statsdSampleRate 96 stats.RegisterPushBackend("statsd", sb) 97 stats.RegisterTimerHook(func(statsName, name string, value int64, timings *stats.Timings) { 98 tags := makeLabels(strings.Split(timings.Label(), "."), name) 99 if err := statsdC.TimeInMilliseconds(statsName, float64(value), tags, sb.sampleRate); err != nil { 100 log.Errorf("Fail to TimeInMilliseconds %v: %v", statsName, err) 101 } 102 }) 103 stats.RegisterHistogramHook(func(statsName string, val int64) { 104 if err := statsdC.Histogram(statsName, float64(val), []string{}, sb.sampleRate); err != nil { 105 log.Errorf("Fail to Histogram for %v: %v", statsName, err) 106 } 107 }) 108 } 109 110 func (sb StatsBackend) addExpVar(kv expvar.KeyValue) { 111 k := kv.Key 112 switch v := kv.Value.(type) { 113 case *stats.Counter: 114 if err := sb.statsdClient.Count(k, v.Get(), nil, sb.sampleRate); err != nil { 115 log.Errorf("Failed to add Counter %v for key %v", v, k) 116 } 117 case *stats.Gauge: 118 if err := sb.statsdClient.Gauge(k, float64(v.Get()), nil, sb.sampleRate); err != nil { 119 log.Errorf("Failed to add Gauge %v for key %v", v, k) 120 } 121 case *stats.GaugeFloat64: 122 if err := sb.statsdClient.Gauge(k, v.Get(), nil, sb.sampleRate); err != nil { 123 log.Errorf("Failed to add GaugeFloat64 %v for key %v", v, k) 124 } 125 case *stats.GaugeFunc: 126 if err := sb.statsdClient.Gauge(k, float64(v.F()), nil, sb.sampleRate); err != nil { 127 log.Errorf("Failed to add GaugeFunc %v for key %v", v, k) 128 } 129 case *stats.CounterFunc: 130 if err := sb.statsdClient.Gauge(k, float64(v.F()), nil, sb.sampleRate); err != nil { 131 log.Errorf("Failed to add CounterFunc %v for key %v", v, k) 132 } 133 case *stats.CounterDuration: 134 if err := sb.statsdClient.TimeInMilliseconds(k, float64(v.Get().Milliseconds()), nil, sb.sampleRate); err != nil { 135 log.Errorf("Failed to add CounterDuration %v for key %v", v, k) 136 } 137 case *stats.CounterDurationFunc: 138 if err := sb.statsdClient.TimeInMilliseconds(k, float64(v.F().Milliseconds()), nil, sb.sampleRate); err != nil { 139 log.Errorf("Failed to add CounterDuration %v for key %v", v, k) 140 } 141 case *stats.GaugeDuration: 142 if err := sb.statsdClient.TimeInMilliseconds(k, float64(v.Get().Milliseconds()), nil, sb.sampleRate); err != nil { 143 log.Errorf("Failed to add GaugeDuration %v for key %v", v, k) 144 } 145 case *stats.GaugeDurationFunc: 146 if err := sb.statsdClient.TimeInMilliseconds(k, float64(v.F().Milliseconds()), nil, sb.sampleRate); err != nil { 147 log.Errorf("Failed to add GaugeDuration %v for key %v", v, k) 148 } 149 case *stats.CountersWithSingleLabel: 150 for labelVal, val := range v.Counts() { 151 if err := sb.statsdClient.Count(k, val, makeLabel(v.Label(), labelVal), sb.sampleRate); err != nil { 152 log.Errorf("Failed to add CountersWithSingleLabel %v for key %v", v, k) 153 } 154 } 155 case *stats.CountersWithMultiLabels: 156 for labelVals, val := range v.Counts() { 157 if err := sb.statsdClient.Count(k, val, makeLabels(v.Labels(), labelVals), sb.sampleRate); err != nil { 158 log.Errorf("Failed to add CountersFuncWithMultiLabels %v for key %v", v, k) 159 } 160 } 161 case *stats.CountersFuncWithMultiLabels: 162 for labelVals, val := range v.Counts() { 163 if err := sb.statsdClient.Count(k, val, makeLabels(v.Labels(), labelVals), sb.sampleRate); err != nil { 164 log.Errorf("Failed to add CountersFuncWithMultiLabels %v for key %v", v, k) 165 } 166 } 167 case *stats.GaugesWithMultiLabels: 168 for labelVals, val := range v.Counts() { 169 if err := sb.statsdClient.Gauge(k, float64(val), makeLabels(v.Labels(), labelVals), sb.sampleRate); err != nil { 170 log.Errorf("Failed to add GaugesWithMultiLabels %v for key %v", v, k) 171 } 172 } 173 case *stats.GaugesFuncWithMultiLabels: 174 for labelVals, val := range v.Counts() { 175 if err := sb.statsdClient.Gauge(k, float64(val), makeLabels(v.Labels(), labelVals), sb.sampleRate); err != nil { 176 log.Errorf("Failed to add GaugesFuncWithMultiLabels %v for key %v", v, k) 177 } 178 } 179 case *stats.GaugesWithSingleLabel: 180 for labelVal, val := range v.Counts() { 181 if err := sb.statsdClient.Gauge(k, float64(val), makeLabel(v.Label(), labelVal), sb.sampleRate); err != nil { 182 log.Errorf("Failed to add GaugesWithSingleLabel %v for key %v", v, k) 183 } 184 } 185 case *stats.Timings, *stats.MultiTimings, *stats.Histogram: 186 // it does not make sense to export static expvar to statsd, 187 // instead we rely on hooks to integrate with statsd' timing and histogram api directly 188 case expvar.Func: 189 // Export memstats as gauge so that we don't need to call extra ReadMemStats 190 if k == "memstats" { 191 var obj map[string]any 192 if err := json.Unmarshal([]byte(v.String()), &obj); err != nil { 193 return 194 } 195 for k, v := range obj { 196 memstatsVal, ok := v.(float64) 197 if ok { 198 memstatsKey := "memstats." + k 199 if err := sb.statsdClient.Gauge(memstatsKey, memstatsVal, []string{}, sb.sampleRate); err != nil { 200 log.Errorf("Failed to export %v %v", k, v) 201 } 202 } 203 } 204 } 205 case *stats.String: 206 if k == "BuildGitRev" { 207 buildGitRecOnce.Do(func() { 208 checksum := crc32.ChecksumIEEE([]byte(v.Get())) 209 if err := sb.statsdClient.Gauge(k, float64(checksum), []string{}, sb.sampleRate); err != nil { 210 log.Errorf("Failed to export %v %v", k, v) 211 } 212 }) 213 } 214 case *stats.Rates, *stats.RatesFunc, *stats.StringFunc, *stats.StringMapFunc, 215 stats.StringFunc, stats.StringMapFunc: 216 // Silently ignore metrics that does not make sense to be exported to statsd 217 default: 218 log.Warningf("Silently ignore metrics with key %v [%T]", k, kv.Value) 219 } 220 } 221 222 // PushAll flush out the pending metrics 223 func (sb StatsBackend) PushAll() error { 224 expvar.Do(func(kv expvar.KeyValue) { 225 sb.addExpVar(kv) 226 }) 227 if err := sb.statsdClient.Flush(); err != nil { 228 return err 229 } 230 return nil 231 }