github.com/intfoundation/intchain@v0.0.0-20220727031208-4316ad31ca73/metrics/resetting_timer.go (about) 1 package metrics 2 3 import ( 4 "math" 5 "sort" 6 "sync" 7 "time" 8 ) 9 10 // Initial slice capacity for the values stored in a ResettingTimer 11 const InitialResettingTimerSliceCap = 10 12 13 // ResettingTimer is used for storing aggregated values for timers, which are reset on every flush interval. 14 type ResettingTimer interface { 15 Values() []int64 16 Snapshot() ResettingTimer 17 Percentiles([]float64) []int64 18 Mean() float64 19 Time(func()) 20 Update(time.Duration) 21 UpdateSince(time.Time) 22 } 23 24 // GetOrRegisterResettingTimer returns an existing ResettingTimer or constructs and registers a 25 // new StandardResettingTimer. 26 func GetOrRegisterResettingTimer(name string, r Registry) ResettingTimer { 27 if nil == r { 28 r = DefaultRegistry 29 } 30 return r.GetOrRegister(name, NewResettingTimer).(ResettingTimer) 31 } 32 33 // NewRegisteredResettingTimer constructs and registers a new StandardResettingTimer. 34 func NewRegisteredResettingTimer(name string, r Registry) ResettingTimer { 35 c := NewResettingTimer() 36 if nil == r { 37 r = DefaultRegistry 38 } 39 r.Register(name, c) 40 return c 41 } 42 43 // NewResettingTimer constructs a new StandardResettingTimer 44 func NewResettingTimer() ResettingTimer { 45 if !Enabled { 46 return NilResettingTimer{} 47 } 48 return &StandardResettingTimer{ 49 values: make([]int64, 0, InitialResettingTimerSliceCap), 50 } 51 } 52 53 // NilResettingTimer is a no-op ResettingTimer. 54 type NilResettingTimer struct { 55 } 56 57 // Values is a no-op. 58 func (NilResettingTimer) Values() []int64 { return nil } 59 60 // Snapshot is a no-op. 61 func (NilResettingTimer) Snapshot() ResettingTimer { return NilResettingTimer{} } 62 63 // Time is a no-op. 64 func (NilResettingTimer) Time(func()) {} 65 66 // Update is a no-op. 67 func (NilResettingTimer) Update(time.Duration) {} 68 69 // Percentiles panics. 70 func (NilResettingTimer) Percentiles([]float64) []int64 { 71 panic("Percentiles called on a NilResettingTimer") 72 } 73 74 // Mean panics. 75 func (NilResettingTimer) Mean() float64 { 76 panic("Mean called on a NilResettingTimer") 77 } 78 79 // UpdateSince is a no-op. 80 func (NilResettingTimer) UpdateSince(time.Time) {} 81 82 // StandardResettingTimer is the standard implementation of a ResettingTimer. 83 // and Meter. 84 type StandardResettingTimer struct { 85 values []int64 86 mutex sync.Mutex 87 } 88 89 // Values returns a slice with all measurements. 90 func (t *StandardResettingTimer) Values() []int64 { 91 return t.values 92 } 93 94 // Snapshot resets the timer and returns a read-only copy of its contents. 95 func (t *StandardResettingTimer) Snapshot() ResettingTimer { 96 t.mutex.Lock() 97 defer t.mutex.Unlock() 98 currentValues := t.values 99 t.values = make([]int64, 0, InitialResettingTimerSliceCap) 100 101 return &ResettingTimerSnapshot{ 102 values: currentValues, 103 } 104 } 105 106 // Percentiles panics. 107 func (t *StandardResettingTimer) Percentiles([]float64) []int64 { 108 panic("Percentiles called on a StandardResettingTimer") 109 } 110 111 // Mean panics. 112 func (t *StandardResettingTimer) Mean() float64 { 113 panic("Mean called on a StandardResettingTimer") 114 } 115 116 // Record the duration of the execution of the given function. 117 func (t *StandardResettingTimer) Time(f func()) { 118 ts := time.Now() 119 f() 120 t.Update(time.Since(ts)) 121 } 122 123 // Record the duration of an event. 124 func (t *StandardResettingTimer) Update(d time.Duration) { 125 t.mutex.Lock() 126 defer t.mutex.Unlock() 127 t.values = append(t.values, int64(d)) 128 } 129 130 // Record the duration of an event that started at a time and ends now. 131 func (t *StandardResettingTimer) UpdateSince(ts time.Time) { 132 t.mutex.Lock() 133 defer t.mutex.Unlock() 134 t.values = append(t.values, int64(time.Since(ts))) 135 } 136 137 // ResettingTimerSnapshot is a point-in-time copy of another ResettingTimer. 138 type ResettingTimerSnapshot struct { 139 values []int64 140 mean float64 141 thresholdBoundaries []int64 142 calculated bool 143 } 144 145 // Snapshot returns the snapshot. 146 func (t *ResettingTimerSnapshot) Snapshot() ResettingTimer { return t } 147 148 // Time panics. 149 func (*ResettingTimerSnapshot) Time(func()) { 150 panic("Time called on a ResettingTimerSnapshot") 151 } 152 153 // Update panics. 154 func (*ResettingTimerSnapshot) Update(time.Duration) { 155 panic("Update called on a ResettingTimerSnapshot") 156 } 157 158 // UpdateSince panics. 159 func (*ResettingTimerSnapshot) UpdateSince(time.Time) { 160 panic("UpdateSince called on a ResettingTimerSnapshot") 161 } 162 163 // Values returns all values from snapshot. 164 func (t *ResettingTimerSnapshot) Values() []int64 { 165 return t.values 166 } 167 168 // Percentiles returns the boundaries for the input percentiles. 169 func (t *ResettingTimerSnapshot) Percentiles(percentiles []float64) []int64 { 170 t.calc(percentiles) 171 172 return t.thresholdBoundaries 173 } 174 175 // Mean returns the mean of the snapshotted values 176 func (t *ResettingTimerSnapshot) Mean() float64 { 177 if !t.calculated { 178 t.calc([]float64{}) 179 } 180 181 return t.mean 182 } 183 184 func (t *ResettingTimerSnapshot) calc(percentiles []float64) { 185 sort.Sort(Int64Slice(t.values)) 186 187 count := len(t.values) 188 if count > 0 { 189 min := t.values[0] 190 max := t.values[count-1] 191 192 cumulativeValues := make([]int64, count) 193 cumulativeValues[0] = min 194 for i := 1; i < count; i++ { 195 cumulativeValues[i] = t.values[i] + cumulativeValues[i-1] 196 } 197 198 t.thresholdBoundaries = make([]int64, len(percentiles)) 199 200 thresholdBoundary := max 201 202 for i, pct := range percentiles { 203 if count > 1 { 204 var abs float64 205 if pct >= 0 { 206 abs = pct 207 } else { 208 abs = 100 + pct 209 } 210 // poor man's math.Round(x): 211 // math.Floor(x + 0.5) 212 indexOfPerc := int(math.Floor(((abs / 100.0) * float64(count)) + 0.5)) 213 if pct >= 0 { 214 indexOfPerc -= 1 // index offset=0 215 } 216 thresholdBoundary = t.values[indexOfPerc] 217 } 218 219 t.thresholdBoundaries[i] = thresholdBoundary 220 } 221 222 sum := cumulativeValues[count-1] 223 t.mean = float64(sum) / float64(count) 224 } else { 225 t.thresholdBoundaries = make([]int64, len(percentiles)) 226 t.mean = 0 227 } 228 229 t.calculated = true 230 } 231 232 // Int64Slice attaches the methods of sort.Interface to []int64, sorting in increasing order. 233 type Int64Slice []int64 234 235 func (s Int64Slice) Len() int { return len(s) } 236 func (s Int64Slice) Less(i, j int) bool { return s[i] < s[j] } 237 func (s Int64Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }