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  }