github.com/rudderlabs/rudder-go-kit@v0.30.0/stats/metric/registry.go (about)

     1  package metric
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  )
     7  
     8  type (
     9  	Tags          map[string]string
    10  	TagsWithValue struct {
    11  		Tags  Tags
    12  		Value interface{}
    13  	}
    14  )
    15  
    16  // Registry is a safe way to capture metrics in a highly concurrent environment.
    17  // The registry is responsible for creating and storing the various measurements and
    18  // guarantees consistency when competing goroutines try to update the same measurement
    19  // at the same time.
    20  //
    21  // E.g.
    22  // assuming that you already have created a new registry
    23  //
    24  //	registry :=  NewRegistry()
    25  //
    26  // the following is guaranteed to be executed atomically:
    27  //
    28  //	registry.MustGetCounter("key").Inc()
    29  type Registry interface {
    30  	// GetCounter gets a counter by key. If a value for this key
    31  	// already exists but corresponds to another measurement type,
    32  	// e.g. a Gauge, an error is returned
    33  	GetCounter(Measurement) (Counter, error)
    34  
    35  	// MustGetCounter gets a counter by key. If a value for this key
    36  	// already exists but corresponds to another measurement type,
    37  	// e.g. a Gauge, it panics
    38  	MustGetCounter(Measurement) Counter
    39  
    40  	// GetGauge gets a gauge by key. If a value for this key
    41  	// already exists but corresponds to another measurement
    42  	// type, e.g. a Counter, an error is returned
    43  	GetGauge(Measurement) (Gauge, error)
    44  
    45  	// MustGetGauge gets a gauge by key. If a value for this key
    46  	// already exists but corresponds to another measurement type,
    47  	// e.g. a Counter, it panics
    48  	MustGetGauge(Measurement) Gauge
    49  
    50  	// GetSimpleMovingAvg gets a moving average by key. If a value for this key
    51  	// already exists but corresponds to another measurement
    52  	// type, e.g. a Counter, an error is returned
    53  	GetSimpleMovingAvg(Measurement) (MovingAverage, error)
    54  
    55  	// MustGetSimpleMovingAvg gets a moving average by key. If a value for this key
    56  	// already exists but corresponds to another measurement type,
    57  	// e.g. a Counter, it panics
    58  	MustGetSimpleMovingAvg(Measurement) MovingAverage
    59  
    60  	// GetVarMovingAvg gets a moving average by key. If a value for this key
    61  	// already exists but corresponds to another measurement
    62  	// type, e.g. a Counter, an error is returned
    63  	GetVarMovingAvg(m Measurement, age float64) (MovingAverage, error)
    64  
    65  	// MustGetVarMovingAvg gets a moving average by key. If a value for this key
    66  	// already exists but corresponds to another measurement type,
    67  	// e.g. a Counter, it panics
    68  	MustGetVarMovingAvg(m Measurement, age float64) MovingAverage
    69  
    70  	// Range scans across all metrics
    71  	Range(f func(key, value interface{}) bool)
    72  
    73  	// GetMetricsByName gets all metrics with this name
    74  	GetMetricsByName(name string) []TagsWithValue
    75  }
    76  
    77  // mutexWithMap bundles a lock along with the map it is protecting
    78  type mutexWithMap struct {
    79  	lock  *sync.RWMutex
    80  	value map[Measurement]TagsWithValue
    81  }
    82  
    83  func NewRegistry() Registry {
    84  	counterGenerator := func() interface{} {
    85  		return NewCounter()
    86  	}
    87  	gaugeGenerator := func() interface{} {
    88  		return NewGauge()
    89  	}
    90  	varEwmaGenerator := func() interface{} {
    91  		return &VariableEWMA{}
    92  	}
    93  	simpleEwmaGenerator := func() interface{} {
    94  		return &SimpleEWMA{}
    95  	}
    96  	indexGenerator := func() interface{} {
    97  		var lock sync.RWMutex
    98  		v := &mutexWithMap{&lock, map[Measurement]TagsWithValue{}}
    99  		return v
   100  	}
   101  	return &registry{
   102  		counters:    sync.Pool{New: counterGenerator},
   103  		gauges:      sync.Pool{New: gaugeGenerator},
   104  		simpleEwmas: sync.Pool{New: simpleEwmaGenerator},
   105  		varEwmas:    sync.Pool{New: varEwmaGenerator},
   106  		sets:        sync.Pool{New: indexGenerator},
   107  	}
   108  }
   109  
   110  type registry struct {
   111  	store       sync.Map
   112  	nameIndex   sync.Map
   113  	counters    sync.Pool
   114  	gauges      sync.Pool
   115  	simpleEwmas sync.Pool
   116  	varEwmas    sync.Pool
   117  	sets        sync.Pool
   118  }
   119  
   120  func (r *registry) GetCounter(m Measurement) (Counter, error) {
   121  	res := r.get(m, &r.counters)
   122  	c, ok := res.(Counter)
   123  	if !ok {
   124  		return nil, fmt.Errorf("a different type of metric exists in the registry with the same key [%+v]: %T", m, res)
   125  	}
   126  	return c, nil
   127  }
   128  
   129  func (r *registry) MustGetCounter(m Measurement) Counter {
   130  	c, err := r.GetCounter(m)
   131  	if err != nil {
   132  		panic(err)
   133  	}
   134  	return c
   135  }
   136  
   137  func (r *registry) GetGauge(m Measurement) (Gauge, error) {
   138  	res := r.get(m, &r.gauges)
   139  	g, ok := res.(Gauge)
   140  	if !ok {
   141  		return nil, fmt.Errorf("a different type of metric exists in the registry with the same key [%+v]: %T", m, res)
   142  	}
   143  	return g, nil
   144  }
   145  
   146  func (r *registry) MustGetGauge(m Measurement) Gauge {
   147  	c, err := r.GetGauge(m)
   148  	if err != nil {
   149  		panic(err)
   150  	}
   151  	return c
   152  }
   153  
   154  func (r *registry) GetSimpleMovingAvg(m Measurement) (MovingAverage, error) {
   155  	res := r.get(m, &r.simpleEwmas)
   156  	ma, ok := res.(MovingAverage)
   157  	if !ok {
   158  		return nil, fmt.Errorf("a different type of metric exists in the registry with the same key [%+v]: %T", m, res)
   159  	}
   160  	return ma, nil
   161  }
   162  
   163  func (r *registry) MustGetSimpleMovingAvg(m Measurement) MovingAverage {
   164  	ma, err := r.GetSimpleMovingAvg(m)
   165  	if err != nil {
   166  		panic(err)
   167  	}
   168  	return ma
   169  }
   170  
   171  func (r *registry) GetVarMovingAvg(m Measurement, age float64) (MovingAverage, error) {
   172  	decay := 2 / (age + 1)
   173  	newEwma := r.varEwmas.Get()
   174  	newEwma.(*VariableEWMA).decay = decay
   175  	res, ok := r.store.Load(m)
   176  	if !ok {
   177  		res, ok = r.store.LoadOrStore(m, newEwma)
   178  		if ok {
   179  			r.varEwmas.Put(newEwma)
   180  		} else {
   181  			r.updateIndex(m, res)
   182  		}
   183  	}
   184  	ma, ok := res.(*VariableEWMA)
   185  	if !ok {
   186  		return nil, fmt.Errorf("a different type of metric exists in the registry with the same key [%+v]: %T", m, res)
   187  	}
   188  	if ma.decay != decay {
   189  		currentAge := 2/ma.decay + 1
   190  		return nil, fmt.Errorf("another moving average with age %f instead of %f exists in the registry with the same key [%+v]: %T", currentAge, age, m, res)
   191  	}
   192  	return ma, nil
   193  }
   194  
   195  func (r *registry) MustGetVarMovingAvg(m Measurement, age float64) MovingAverage {
   196  	ma, err := r.GetVarMovingAvg(m, age)
   197  	if err != nil {
   198  		panic(err)
   199  	}
   200  	return ma
   201  }
   202  
   203  func (r *registry) Range(f func(key, value interface{}) bool) {
   204  	r.store.Range(f)
   205  }
   206  
   207  func (r *registry) GetMetricsByName(name string) []TagsWithValue {
   208  	metricsSet, ok := r.nameIndex.Load(name)
   209  	if !ok {
   210  		return nil
   211  	}
   212  
   213  	var values []TagsWithValue
   214  	lock := metricsSet.(*mutexWithMap).lock
   215  	lock.RLock()
   216  	for _, value := range metricsSet.(*mutexWithMap).value {
   217  		values = append(values, value)
   218  	}
   219  	lock.RUnlock()
   220  	return values
   221  }
   222  
   223  func (r *registry) updateIndex(m Measurement, metric interface{}) {
   224  	name := m.GetName()
   225  	newSet := r.sets.Get()
   226  	res, putBack := r.nameIndex.LoadOrStore(name, newSet)
   227  	if putBack {
   228  		r.sets.Put(newSet)
   229  	}
   230  
   231  	lock := res.(*mutexWithMap).lock
   232  	lock.Lock()
   233  	res.(*mutexWithMap).value[m] = TagsWithValue{m.GetTags(), metric}
   234  	lock.Unlock()
   235  }
   236  
   237  func (r *registry) get(m Measurement, pool *sync.Pool) interface{} {
   238  	res, ok := r.store.Load(m)
   239  	if !ok {
   240  		newValue := pool.Get()
   241  		res, ok = r.store.LoadOrStore(m, newValue)
   242  		if ok {
   243  			pool.Put(newValue)
   244  		} else {
   245  			r.updateIndex(m, res)
   246  		}
   247  	}
   248  	return res
   249  }