github.com/XiaoMi/Gaea@v1.2.5/stats/export.go (about) 1 /* 2 Copyright 2017 Google Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package stats is a wrapper for expvar. It addtionally 18 // exports new types that can be used to track performance. 19 // It also provides a callback hook that allows a program 20 // to export the variables using methods other than /debug/vars. 21 // All variables support a String function that 22 // is expected to return a JSON representation 23 // of the variable. 24 // Any function named Add will add the specified 25 // number to the variable. 26 // Any function named Counts returns a map of counts 27 // that can be used by Rates to track rates over time. 28 package stats 29 30 import ( 31 "bytes" 32 "expvar" 33 "fmt" 34 "strconv" 35 "sync" 36 "time" 37 38 "github.com/XiaoMi/Gaea/log" 39 ) 40 41 const defaultEmitPeriod = 60 * time.Second 42 43 // NewVarHook is the type of a hook to export variables in a different way 44 type NewVarHook func(name string, v expvar.Var) 45 46 type varGroup struct { 47 sync.Mutex 48 vars map[string]expvar.Var 49 newVarHook NewVarHook 50 } 51 52 func (vg *varGroup) register(nvh NewVarHook) { 53 vg.Lock() 54 defer vg.Unlock() 55 if vg.newVarHook != nil { 56 panic("You've already registered a function") 57 } 58 if nvh == nil { 59 panic("nil not allowed") 60 } 61 vg.newVarHook = nvh 62 // Call hook on existing vars because some might have been 63 // created before the call to register 64 for k, v := range vg.vars { 65 nvh(k, v) 66 } 67 vg.vars = nil 68 } 69 70 func (vg *varGroup) publish(name string, v expvar.Var) { 71 vg.Lock() 72 defer vg.Unlock() 73 74 expvar.Publish(name, v) 75 if vg.newVarHook != nil { 76 vg.newVarHook(name, v) 77 } else { 78 vg.vars[name] = v 79 } 80 } 81 82 var defaultVarGroup = varGroup{vars: make(map[string]expvar.Var)} 83 84 // Register allows you to register a callback function 85 // that will be called whenever a new stats variable gets 86 // created. This can be used to build alternate methods 87 // of exporting stats variables. 88 func Register(nvh NewVarHook) { 89 defaultVarGroup.register(nvh) 90 } 91 92 // Publish is expvar.Publish+hook 93 func Publish(name string, v expvar.Var) { 94 publish(name, v) 95 } 96 97 func publish(name string, v expvar.Var) { 98 defaultVarGroup.publish(name, v) 99 } 100 101 // PushBackend is an interface for any stats/metrics backend that requires data 102 // to be pushed to it. It's used to support push-based metrics backends, as expvar 103 // by default only supports pull-based ones. 104 type PushBackend interface { 105 // PushAll pushes all stats from expvar to the backend 106 PushAll() error 107 } 108 109 var pushBackends = make(map[string]PushBackend) 110 var pushBackendsLock sync.Mutex 111 var once sync.Once 112 113 // GetPushBackend return push backends 114 func GetPushBackend() map[string]PushBackend { 115 return pushBackends 116 } 117 118 // RegisterPushBackend allows modules to register PushBackend implementations. 119 // Should be called on init(). 120 func RegisterPushBackend(name string, backend PushBackend, emitPeriod time.Duration) { 121 pushBackendsLock.Lock() 122 defer pushBackendsLock.Unlock() 123 if _, ok := pushBackends[name]; ok { 124 log.Fatal(fmt.Sprintf("PushBackend %s already exists; can't register the same name multiple times", name)) 125 } 126 pushBackends[name] = backend 127 128 if emitPeriod <= 0 { 129 log.Warn("[stats] push backend got invalid emitPeriod: %v, use default emitPeriod instead: %v", emitPeriod, defaultEmitPeriod) 130 emitPeriod = defaultEmitPeriod 131 } 132 // Start a single goroutine to emit stats periodically 133 once.Do(func() { 134 go emitToBackend(name, emitPeriod) 135 }) 136 } 137 138 // emitToBackend does a periodic emit to the selected PushBackend. If a push fails, 139 // it will be logged as a warning (but things will otherwise proceed as normal). 140 func emitToBackend(name string, emitPeriod time.Duration) { 141 ticker := time.NewTicker(emitPeriod) 142 defer ticker.Stop() 143 for range ticker.C { 144 backend, ok := pushBackends[name] 145 if !ok { 146 log.Fatal(fmt.Sprintf("No PushBackend registered with name %s", name)) 147 return 148 } 149 err := backend.PushAll() 150 if err != nil { 151 // TODO(aaijazi): This might cause log spam... 152 log.Warn("Pushing stats to backend %v failed: %v", name, err) 153 } 154 } 155 } 156 157 // Float is expvar.Float+Get+hook 158 type Float struct { 159 mu sync.Mutex 160 f float64 161 } 162 163 // NewFloat creates a new Float and exports it. 164 func NewFloat(name string) *Float { 165 v := new(Float) 166 publish(name, v) 167 return v 168 } 169 170 // Add adds the provided value to the Float 171 func (v *Float) Add(delta float64) { 172 v.mu.Lock() 173 v.f += delta 174 v.mu.Unlock() 175 } 176 177 // Set sets the value 178 func (v *Float) Set(value float64) { 179 v.mu.Lock() 180 v.f = value 181 v.mu.Unlock() 182 } 183 184 // Get returns the value 185 func (v *Float) Get() float64 { 186 v.mu.Lock() 187 f := v.f 188 v.mu.Unlock() 189 return f 190 } 191 192 // String is the implementation of expvar.var 193 func (v *Float) String() string { 194 return strconv.FormatFloat(v.Get(), 'g', -1, 64) 195 } 196 197 // FloatFunc converts a function that returns 198 // a float64 as an expvar. 199 type FloatFunc func() float64 200 201 // String is the implementation of expvar.var 202 func (f FloatFunc) String() string { 203 return strconv.FormatFloat(f(), 'g', -1, 64) 204 } 205 206 // String is expvar.String+Get+hook 207 type String struct { 208 mu sync.Mutex 209 s string 210 } 211 212 // NewString returns a new String 213 func NewString(name string) *String { 214 v := new(String) 215 publish(name, v) 216 return v 217 } 218 219 // Set sets the value 220 func (v *String) Set(value string) { 221 v.mu.Lock() 222 v.s = value 223 v.mu.Unlock() 224 } 225 226 // Get returns the value 227 func (v *String) Get() string { 228 v.mu.Lock() 229 s := v.s 230 v.mu.Unlock() 231 return s 232 } 233 234 // String is the implementation of expvar.var 235 func (v *String) String() string { 236 return strconv.Quote(v.Get()) 237 } 238 239 // StringFunc converts a function that returns 240 // an string as an expvar. 241 type StringFunc func() string 242 243 // String is the implementation of expvar.var 244 func (f StringFunc) String() string { 245 return strconv.Quote(f()) 246 } 247 248 // JSONFunc is the public type for a single function that returns json directly. 249 type JSONFunc func() string 250 251 // String is the implementation of expvar.var 252 func (f JSONFunc) String() string { 253 return f() 254 } 255 256 // PublishJSONFunc publishes any function that returns 257 // a JSON string as a variable. The string is sent to 258 // expvar as is. 259 func PublishJSONFunc(name string, f func() string) { 260 publish(name, JSONFunc(f)) 261 } 262 263 // StringMap is a map of string -> string 264 type StringMap struct { 265 mu sync.Mutex 266 values map[string]string 267 } 268 269 // NewStringMap returns a new StringMap 270 func NewStringMap(name string) *StringMap { 271 v := &StringMap{values: make(map[string]string)} 272 publish(name, v) 273 return v 274 } 275 276 // Set will set a value (existing or not) 277 func (v *StringMap) Set(name, value string) { 278 v.mu.Lock() 279 v.values[name] = value 280 v.mu.Unlock() 281 } 282 283 // Get will return the value, or "" f not set. 284 func (v *StringMap) Get(name string) string { 285 v.mu.Lock() 286 s := v.values[name] 287 v.mu.Unlock() 288 return s 289 } 290 291 // String is the implementation of expvar.Var 292 func (v *StringMap) String() string { 293 v.mu.Lock() 294 defer v.mu.Unlock() 295 return stringMapToString(v.values) 296 } 297 298 // StringMapFunc is the function equivalent of StringMap 299 type StringMapFunc func() map[string]string 300 301 // String is used by expvar. 302 func (f StringMapFunc) String() string { 303 m := f() 304 if m == nil { 305 return "{}" 306 } 307 return stringMapToString(m) 308 } 309 310 func stringMapToString(m map[string]string) string { 311 b := bytes.NewBuffer(make([]byte, 0, 4096)) 312 fmt.Fprintf(b, "{") 313 firstValue := true 314 for k, v := range m { 315 if firstValue { 316 firstValue = false 317 } else { 318 fmt.Fprintf(b, ", ") 319 } 320 fmt.Fprintf(b, "\"%v\": %v", k, strconv.Quote(v)) 321 } 322 fmt.Fprintf(b, "}") 323 return b.String() 324 }