github.com/haraldrudell/parl@v0.4.176/counter/rate-counter.go (about) 1 /* 2 © 2022–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package counter 7 8 import ( 9 "sync" 10 "time" 11 12 "github.com/haraldrudell/parl" 13 "golang.org/x/exp/maps" 14 ) 15 16 const ( 17 averagerSize = 10 18 minInterval = time.Microsecond 19 ) 20 21 // RateCounter is a value/running/max counter with averaging. 22 // - rate of increase, maximum and average rate of increase in value 23 // - rate of increase, maximum increase and decrease rates and average of value 24 type RateCounter struct { 25 Counter // value-running-max atomic-access container 26 27 lock sync.Mutex 28 lastDoInvocation time.Time // used to calculate true duration of each period 29 hasValues bool // indicates that value and running was initialized at start of period 30 value uint64 // value at beginning of period 31 running uint64 // running at beginning of period 32 m map[parl.RateType]float64 33 valueAvg Averager 34 runningAvg Averager 35 } 36 37 var _ parl.RateCounterValues = &RateCounter{} // RateCounter is parl.RateCounterValues 38 39 // newRateCounter returns a rate-counter, an extension to a regular 3-value counter 40 func newRateCounter() (counter *RateCounter) { 41 return &RateCounter{ 42 lastDoInvocation: time.Now(), 43 m: make(map[parl.RateType]float64), 44 valueAvg: *NewAverager(averagerSize), 45 runningAvg: *NewAverager(averagerSize), 46 } 47 } 48 49 // Rates returns a map of current rate-results 50 func (r *RateCounter) Rates() (rates map[parl.RateType]float64) { 51 r.lock.Lock() 52 defer r.lock.Unlock() 53 54 rates = maps.Clone(r.m) 55 return 56 } 57 58 // Do completes rate-calculations for a period 59 // - Do is invoked by the task container 60 // - at is an accurate timestamp, ie. not from a time.Interval 61 func (r *RateCounter) Do(at time.Time) { 62 r.lock.Lock() 63 defer r.lock.Unlock() 64 65 // calculate interval duration: positive, at least minInterval 66 var duration = at.Sub(r.lastDoInvocation) 67 if duration < minInterval { 68 return // ignore if too close to last occasion, or negative 69 } 70 r.lastDoInvocation = at 71 var seconds = duration.Seconds() 72 73 // get current values from the underlying rate/running/max counter 74 var value, running, _ = r.Counter.Get() 75 76 // running goes up and down 77 // - running average is total change over the period divided by duration of all periods 78 r.m[parl.RunningAverage] = r.runningAvg.Add(running, duration) 79 80 // rates requires values from beginning of the period 81 // - collect values at beginning of the very first period 82 if !r.hasValues { 83 84 // first invocation: initialize values 85 r.value = value 86 r.running = running 87 r.hasValues = true 88 return // populated start of period return 89 } 90 91 // update rates since previous period 92 r.do(r.value, value, seconds, parl.ValueRate, parl.ValueMaxRate, parl.NotAValue) 93 r.do(r.running, running, seconds, parl.RunningRate, parl.RunningMaxRate, parl.RunningMaxDecRate) 94 95 // update last period’s values 96 r.value = value 97 r.running = running 98 99 // for value, average its rate of increase 100 // - since value is monotonically increasing, this is meaningful 101 r.m[parl.ValueRateAverage] = r.valueAvg.Add(uint64(r.m[parl.ValueRate]), duration) 102 } 103 104 // do performs rate counter calculation over a period starting at from value and 105 // ending at to value. 106 // - from is value at beginning of period 107 // - to is current value 108 func (r *RateCounter) do(from, to uint64, seconds float64, rateIndex, maxRateIndex, maxDecRateIndex parl.RateType) { 109 var m = r.m 110 if to == from { 111 return // value is zero, rate is zero return: keep last rate 112 } 113 114 // calculate positive rate and max rate 115 if to > from { // not negative 116 rate := float64(to-from) / seconds 117 m[rateIndex] = rate 118 if rate > m[maxRateIndex] { 119 m[maxRateIndex] = rate 120 } 121 return // positive rate return 122 } 123 124 // calculate decreasing rate 125 if maxDecRateIndex == parl.NotAValue { 126 return // max decrease rate should not be calculated 127 } 128 rate := (float64(to) - float64(from)) / seconds 129 if rate < m[maxDecRateIndex] { 130 m[maxDecRateIndex] = rate 131 } 132 // negative rate return 133 }