github.com/XiaoMi/Gaea@v1.2.5/stats/counters.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 18 19 import ( 20 "bytes" 21 "fmt" 22 "sync" 23 "sync/atomic" 24 25 "github.com/XiaoMi/Gaea/log" 26 ) 27 28 // counters is similar to expvar.Map, except that it doesn't allow floats. 29 // It is used to build CountersWithSingleLabel and GaugesWithSingleLabel. 30 type counters struct { 31 // mu only protects adding and retrieving the value (*int64) from the 32 // map. 33 // The modification to the actual number (int64) must be done with 34 // atomic funcs. 35 // If a value for a given name already exists in the map, we only have 36 // to use a read-lock to retrieve it. This is an important performance 37 // optimizations because it allows to concurrently increment a counter. 38 mu sync.RWMutex 39 counts map[string]*int64 40 help string 41 } 42 43 // String implements the expvar.Var interface. 44 func (c *counters) String() string { 45 b := bytes.NewBuffer(make([]byte, 0, 4096)) 46 47 c.mu.RLock() 48 defer c.mu.RUnlock() 49 50 fmt.Fprintf(b, "{") 51 firstValue := true 52 for k, a := range c.counts { 53 if firstValue { 54 firstValue = false 55 } else { 56 fmt.Fprintf(b, ", ") 57 } 58 fmt.Fprintf(b, "%q: %v", k, atomic.LoadInt64(a)) 59 } 60 fmt.Fprintf(b, "}") 61 return b.String() 62 } 63 64 func (c *counters) getValueAddr(name string) *int64 { 65 c.mu.RLock() 66 a, ok := c.counts[name] 67 c.mu.RUnlock() 68 69 if ok { 70 return a 71 } 72 73 c.mu.Lock() 74 defer c.mu.Unlock() 75 // we need to check the existence again 76 // as it may be created by other goroutine. 77 a, ok = c.counts[name] 78 if ok { 79 return a 80 } 81 a = new(int64) 82 c.counts[name] = a 83 return a 84 } 85 86 // Add adds a value to a named counter. 87 func (c *counters) Add(name string, value int64) { 88 a := c.getValueAddr(name) 89 atomic.AddInt64(a, value) 90 } 91 92 // ResetAll resets all counter values and clears all keys. 93 func (c *counters) ResetAll() { 94 c.mu.Lock() 95 defer c.mu.Unlock() 96 c.counts = make(map[string]*int64) 97 } 98 99 // ZeroAll resets all counter values to zero 100 func (c *counters) ZeroAll() { 101 c.mu.Lock() 102 defer c.mu.Unlock() 103 for _, a := range c.counts { 104 atomic.StoreInt64(a, int64(0)) 105 } 106 } 107 108 // Reset resets a specific counter value to 0. 109 func (c *counters) Reset(name string) { 110 a := c.getValueAddr(name) 111 atomic.StoreInt64(a, int64(0)) 112 } 113 114 // Counts returns a copy of the Counters' map. 115 func (c *counters) Counts() map[string]int64 { 116 c.mu.RLock() 117 defer c.mu.RUnlock() 118 119 counts := make(map[string]int64, len(c.counts)) 120 for k, a := range c.counts { 121 counts[k] = atomic.LoadInt64(a) 122 } 123 return counts 124 } 125 126 // Help returns the help string. 127 func (c *counters) Help() string { 128 return c.help 129 } 130 131 // CountersWithSingleLabel tracks multiple counter values for a single 132 // dimension ("label"). 133 // It provides a Counts method which can be used for tracking rates. 134 type CountersWithSingleLabel struct { 135 counters 136 label string 137 } 138 139 // NewCountersWithSingleLabel create a new Counters instance. 140 // If name is set, the variable gets published. 141 // The function also accepts an optional list of tags that pre-creates them 142 // initialized to 0. 143 // label is a category name used to organize the tags. It is currently only 144 // used by Prometheus, but not by the expvar package. 145 func NewCountersWithSingleLabel(name, help, label string, tags ...string) *CountersWithSingleLabel { 146 c := &CountersWithSingleLabel{ 147 counters: counters{ 148 counts: make(map[string]*int64), 149 help: help, 150 }, 151 label: label, 152 } 153 154 for _, tag := range tags { 155 c.counts[tag] = new(int64) 156 } 157 if name != "" { 158 publish(name, c) 159 } 160 return c 161 } 162 163 // Label returns the label name. 164 func (c *CountersWithSingleLabel) Label() string { 165 return c.label 166 } 167 168 // Add adds a value to a named counter. 169 func (c *CountersWithSingleLabel) Add(name string, value int64) { 170 if value < 0 { 171 log.Warn("[stats] Adding a negative value to a counter, %v should be a gauge instead", c) 172 } 173 a := c.getValueAddr(name) 174 atomic.AddInt64(a, value) 175 } 176 177 // CountersWithMultiLabels is a multidimensional counters implementation. 178 // Internally, each tuple of dimensions ("labels") is stored as a single 179 // label value where all label values are joined with ".". 180 type CountersWithMultiLabels struct { 181 counters 182 labels []string 183 } 184 185 // NewCountersWithMultiLabels creates a new CountersWithMultiLabels 186 // instance, and publishes it if name is set. 187 func NewCountersWithMultiLabels(name, help string, labels []string) *CountersWithMultiLabels { 188 t := &CountersWithMultiLabels{ 189 counters: counters{ 190 counts: make(map[string]*int64), 191 help: help}, 192 labels: labels, 193 } 194 if name != "" { 195 publish(name, t) 196 } 197 198 return t 199 } 200 201 // Labels returns the list of labels. 202 func (mc *CountersWithMultiLabels) Labels() []string { 203 return mc.labels 204 } 205 206 // Add adds a value to a named counter. 207 // len(names) must be equal to len(Labels) 208 func (mc *CountersWithMultiLabels) Add(names []string, value int64) { 209 if len(names) != len(mc.labels) { 210 panic("CountersWithMultiLabels: wrong number of values in Add") 211 } 212 if value < 0 { 213 log.Warn("[stats] Adding a negative value to a counter, %v should be a gauge instead", mc) 214 } 215 216 mc.counters.Add(safeJoinLabels(names), value) 217 } 218 219 // Reset resets the value of a named counter back to 0. 220 // len(names) must be equal to len(Labels). 221 func (mc *CountersWithMultiLabels) Reset(names []string) { 222 if len(names) != len(mc.labels) { 223 panic("CountersWithMultiLabels: wrong number of values in Reset") 224 } 225 226 mc.counters.Reset(safeJoinLabels(names)) 227 } 228 229 // Counts returns a copy of the Counters' map. 230 // The key is a single string where all labels are joined by a "." e.g. 231 // "label1.label2". 232 func (mc *CountersWithMultiLabels) Counts() map[string]int64 { 233 return mc.counters.Counts() 234 } 235 236 // CountersFuncWithMultiLabels is a multidimensional counters implementation 237 // where names of categories are compound names made with joining 238 // multiple strings with '.'. Since the map is returned by the 239 // function, we assume it's in the right format (meaning each key is 240 // of the form 'aaa.bbb.ccc' with as many elements as there are in 241 // Labels). 242 // 243 // Note that there is no CountersFuncWithSingleLabel object. That this 244 // because such an object would be identical to this one because these 245 // function-based counters have no Add() or Set() method which are different 246 // for the single vs. multiple labels cases. 247 // If you have only a single label, pass an array with a single element. 248 type CountersFuncWithMultiLabels struct { 249 f func() map[string]int64 250 help string 251 labels []string 252 } 253 254 // Labels returns the list of labels. 255 func (c CountersFuncWithMultiLabels) Labels() []string { 256 return c.labels 257 } 258 259 // Help returns the help string. 260 func (c CountersFuncWithMultiLabels) Help() string { 261 return c.help 262 } 263 264 // NewCountersFuncWithMultiLabels creates a new CountersFuncWithMultiLabels 265 // mapping to the provided function. 266 func NewCountersFuncWithMultiLabels(name, help string, labels []string, f func() map[string]int64) *CountersFuncWithMultiLabels { 267 t := &CountersFuncWithMultiLabels{ 268 f: f, 269 help: help, 270 labels: labels, 271 } 272 if name != "" { 273 publish(name, t) 274 } 275 276 return t 277 } 278 279 // Counts returns a copy of the counters' map. 280 func (c CountersFuncWithMultiLabels) Counts() map[string]int64 { 281 return c.f() 282 } 283 284 // String implements the expvar.Var interface. 285 func (c CountersFuncWithMultiLabels) String() string { 286 m := c.f() 287 if m == nil { 288 return "{}" 289 } 290 b := bytes.NewBuffer(make([]byte, 0, 4096)) 291 fmt.Fprintf(b, "{") 292 firstValue := true 293 for k, v := range m { 294 if firstValue { 295 firstValue = false 296 } else { 297 fmt.Fprintf(b, ", ") 298 } 299 fmt.Fprintf(b, "%q: %v", k, v) 300 } 301 fmt.Fprintf(b, "}") 302 return b.String() 303 } 304 305 // GaugesWithSingleLabel is similar to CountersWithSingleLabel, except its 306 // meant to track the current value and not a cumulative count. 307 type GaugesWithSingleLabel struct { 308 CountersWithSingleLabel 309 } 310 311 // NewGaugesWithSingleLabel creates a new GaugesWithSingleLabel and 312 // publishes it if the name is set. 313 func NewGaugesWithSingleLabel(name, help, label string, tags ...string) *GaugesWithSingleLabel { 314 g := &GaugesWithSingleLabel{ 315 CountersWithSingleLabel: CountersWithSingleLabel{ 316 counters: counters{ 317 counts: make(map[string]*int64), 318 help: help, 319 }, 320 label: label, 321 }, 322 } 323 324 for _, tag := range tags { 325 g.counts[tag] = new(int64) 326 } 327 if name != "" { 328 publish(name, g) 329 } 330 return g 331 } 332 333 // Set sets the value of a named gauge. 334 func (g *GaugesWithSingleLabel) Set(name string, value int64) { 335 a := g.getValueAddr(name) 336 atomic.StoreInt64(a, value) 337 } 338 339 // Add adds a value to a named gauge. 340 func (g *GaugesWithSingleLabel) Add(name string, value int64) { 341 a := g.getValueAddr(name) 342 atomic.AddInt64(a, value) 343 } 344 345 // GaugesWithMultiLabels is a CountersWithMultiLabels implementation where 346 // the values can go up and down. 347 type GaugesWithMultiLabels struct { 348 CountersWithMultiLabels 349 } 350 351 // NewGaugesWithMultiLabels creates a new GaugesWithMultiLabels instance, 352 // and publishes it if name is set. 353 func NewGaugesWithMultiLabels(name, help string, labels []string) *GaugesWithMultiLabels { 354 t := &GaugesWithMultiLabels{ 355 CountersWithMultiLabels: CountersWithMultiLabels{ 356 counters: counters{ 357 counts: make(map[string]*int64), 358 help: help, 359 }, 360 labels: labels, 361 }} 362 if name != "" { 363 publish(name, t) 364 } 365 366 return t 367 } 368 369 // Set sets the value of a named counter. 370 // len(names) must be equal to len(Labels). 371 func (mg *GaugesWithMultiLabels) Set(names []string, value int64) { 372 if len(names) != len(mg.CountersWithMultiLabels.labels) { 373 panic("GaugesWithMultiLabels: wrong number of values in Set") 374 } 375 a := mg.getValueAddr(safeJoinLabels(names)) 376 atomic.StoreInt64(a, value) 377 } 378 379 // Add adds a value to a named gauge. 380 // len(names) must be equal to len(Labels). 381 func (mg *GaugesWithMultiLabels) Add(names []string, value int64) { 382 if len(names) != len(mg.labels) { 383 panic("CountersWithMultiLabels: wrong number of values in Add") 384 } 385 386 mg.counters.Add(safeJoinLabels(names), value) 387 } 388 389 // GaugesFuncWithMultiLabels is a wrapper around CountersFuncWithMultiLabels 390 // for values that go up/down for implementations (like Prometheus) that 391 // need to differ between Counters and Gauges. 392 type GaugesFuncWithMultiLabels struct { 393 CountersFuncWithMultiLabels 394 } 395 396 // NewGaugesFuncWithMultiLabels creates a new GaugesFuncWithMultiLabels 397 // mapping to the provided function. 398 func NewGaugesFuncWithMultiLabels(name, help string, labels []string, f func() map[string]int64) *GaugesFuncWithMultiLabels { 399 t := &GaugesFuncWithMultiLabels{ 400 CountersFuncWithMultiLabels: CountersFuncWithMultiLabels{ 401 f: f, 402 help: help, 403 labels: labels, 404 }} 405 406 if name != "" { 407 publish(name, t) 408 } 409 410 return t 411 }