github.com/theQRL/go-zond@v0.1.1/metrics/meter.go (about)

     1  package metrics
     2  
     3  import (
     4  	"math"
     5  	"sync"
     6  	"sync/atomic"
     7  	"time"
     8  )
     9  
    10  type MeterSnapshot interface {
    11  	Count() int64
    12  	Rate1() float64
    13  	Rate5() float64
    14  	Rate15() float64
    15  	RateMean() float64
    16  }
    17  
    18  // Meters count events to produce exponentially-weighted moving average rates
    19  // at one-, five-, and fifteen-minutes and a mean rate.
    20  type Meter interface {
    21  	Mark(int64)
    22  	Snapshot() MeterSnapshot
    23  	Stop()
    24  }
    25  
    26  // GetOrRegisterMeter returns an existing Meter or constructs and registers a
    27  // new StandardMeter.
    28  // Be sure to unregister the meter from the registry once it is of no use to
    29  // allow for garbage collection.
    30  func GetOrRegisterMeter(name string, r Registry) Meter {
    31  	if nil == r {
    32  		r = DefaultRegistry
    33  	}
    34  	return r.GetOrRegister(name, NewMeter).(Meter)
    35  }
    36  
    37  // NewMeter constructs a new StandardMeter and launches a goroutine.
    38  // Be sure to call Stop() once the meter is of no use to allow for garbage collection.
    39  func NewMeter() Meter {
    40  	if !Enabled {
    41  		return NilMeter{}
    42  	}
    43  	m := newStandardMeter()
    44  	arbiter.Lock()
    45  	defer arbiter.Unlock()
    46  	arbiter.meters[m] = struct{}{}
    47  	if !arbiter.started {
    48  		arbiter.started = true
    49  		go arbiter.tick()
    50  	}
    51  	return m
    52  }
    53  
    54  // NewInactiveMeter returns a meter but does not start any goroutines. This
    55  // method is mainly intended for testing.
    56  func NewInactiveMeter() Meter {
    57  	if !Enabled {
    58  		return NilMeter{}
    59  	}
    60  	m := newStandardMeter()
    61  	return m
    62  }
    63  
    64  // NewRegisteredMeter constructs and registers a new StandardMeter
    65  // and launches a goroutine.
    66  // Be sure to unregister the meter from the registry once it is of no use to
    67  // allow for garbage collection.
    68  func NewRegisteredMeter(name string, r Registry) Meter {
    69  	return GetOrRegisterMeter(name, r)
    70  }
    71  
    72  // meterSnapshot is a read-only copy of the meter's internal values.
    73  type meterSnapshot struct {
    74  	count                          int64
    75  	rate1, rate5, rate15, rateMean float64
    76  }
    77  
    78  // Count returns the count of events at the time the snapshot was taken.
    79  func (m *meterSnapshot) Count() int64 { return m.count }
    80  
    81  // Rate1 returns the one-minute moving average rate of events per second at the
    82  // time the snapshot was taken.
    83  func (m *meterSnapshot) Rate1() float64 { return m.rate1 }
    84  
    85  // Rate5 returns the five-minute moving average rate of events per second at
    86  // the time the snapshot was taken.
    87  func (m *meterSnapshot) Rate5() float64 { return m.rate5 }
    88  
    89  // Rate15 returns the fifteen-minute moving average rate of events per second
    90  // at the time the snapshot was taken.
    91  func (m *meterSnapshot) Rate15() float64 { return m.rate15 }
    92  
    93  // RateMean returns the meter's mean rate of events per second at the time the
    94  // snapshot was taken.
    95  func (m *meterSnapshot) RateMean() float64 { return m.rateMean }
    96  
    97  // NilMeter is a no-op Meter.
    98  type NilMeter struct{}
    99  
   100  func (NilMeter) Count() int64            { return 0 }
   101  func (NilMeter) Mark(n int64)            {}
   102  func (NilMeter) Snapshot() MeterSnapshot { return (*emptySnapshot)(nil) }
   103  func (NilMeter) Stop()                   {}
   104  
   105  // StandardMeter is the standard implementation of a Meter.
   106  type StandardMeter struct {
   107  	count     atomic.Int64
   108  	uncounted atomic.Int64 // not yet added to the EWMAs
   109  	rateMean  atomic.Uint64
   110  
   111  	a1, a5, a15 EWMA
   112  	startTime   time.Time
   113  	stopped     atomic.Bool
   114  }
   115  
   116  func newStandardMeter() *StandardMeter {
   117  	return &StandardMeter{
   118  		a1:        NewEWMA1(),
   119  		a5:        NewEWMA5(),
   120  		a15:       NewEWMA15(),
   121  		startTime: time.Now(),
   122  	}
   123  }
   124  
   125  // Stop stops the meter, Mark() will be a no-op if you use it after being stopped.
   126  func (m *StandardMeter) Stop() {
   127  	if stopped := m.stopped.Swap(true); !stopped {
   128  		arbiter.Lock()
   129  		delete(arbiter.meters, m)
   130  		arbiter.Unlock()
   131  	}
   132  }
   133  
   134  // Mark records the occurrence of n events.
   135  func (m *StandardMeter) Mark(n int64) {
   136  	m.uncounted.Add(n)
   137  }
   138  
   139  // Snapshot returns a read-only copy of the meter.
   140  func (m *StandardMeter) Snapshot() MeterSnapshot {
   141  	return &meterSnapshot{
   142  		count:    m.count.Load() + m.uncounted.Load(),
   143  		rate1:    m.a1.Snapshot().Rate(),
   144  		rate5:    m.a5.Snapshot().Rate(),
   145  		rate15:   m.a15.Snapshot().Rate(),
   146  		rateMean: math.Float64frombits(m.rateMean.Load()),
   147  	}
   148  }
   149  
   150  func (m *StandardMeter) tick() {
   151  	// Take the uncounted values, add to count
   152  	n := m.uncounted.Swap(0)
   153  	count := m.count.Add(n)
   154  	m.rateMean.Store(math.Float64bits(float64(count) / time.Since(m.startTime).Seconds()))
   155  	// Update the EWMA's internal state
   156  	m.a1.Update(n)
   157  	m.a5.Update(n)
   158  	m.a15.Update(n)
   159  	// And trigger them to calculate the rates
   160  	m.a1.Tick()
   161  	m.a5.Tick()
   162  	m.a15.Tick()
   163  }
   164  
   165  // meterArbiter ticks meters every 5s from a single goroutine.
   166  // meters are references in a set for future stopping.
   167  type meterArbiter struct {
   168  	sync.RWMutex
   169  	started bool
   170  	meters  map[*StandardMeter]struct{}
   171  	ticker  *time.Ticker
   172  }
   173  
   174  var arbiter = meterArbiter{ticker: time.NewTicker(5 * time.Second), meters: make(map[*StandardMeter]struct{})}
   175  
   176  // Ticks meters on the scheduled interval
   177  func (ma *meterArbiter) tick() {
   178  	for range ma.ticker.C {
   179  		ma.tickMeters()
   180  	}
   181  }
   182  
   183  func (ma *meterArbiter) tickMeters() {
   184  	ma.RLock()
   185  	defer ma.RUnlock()
   186  	for meter := range ma.meters {
   187  		meter.tick()
   188  	}
   189  }