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