github.com/psiphon-inc/goarista@v0.0.0-20160825065156-d002785f4c67/monitor/stats/timeseries.go (about) 1 package stats 2 3 import ( 4 "math" 5 "time" 6 ) 7 8 // timeseries holds the history of a changing value over a predefined period of 9 // time. 10 type timeseries struct { 11 size int // The number of time slots. Equivalent to len(slots). 12 resolution time.Duration // The time resolution of each slot. 13 stepCount int64 // The number of intervals seen since creation. 14 head int // The position of the current time in slots. 15 time time.Time // The time at the beginning of the current time slot. 16 slots []int64 // A circular buffer of time slots. 17 } 18 19 // newTimeSeries returns a newly allocated timeseries that covers the requested 20 // period with the given resolution. 21 func newTimeSeries(initialTime time.Time, period, resolution time.Duration) *timeseries { 22 size := int(period.Nanoseconds()/resolution.Nanoseconds()) + 1 23 return ×eries{ 24 size: size, 25 resolution: resolution, 26 stepCount: 1, 27 time: initialTime, 28 slots: make([]int64, size), 29 } 30 } 31 32 // advanceTimeWithFill moves the timeseries forward to time t and fills in any 33 // slots that get skipped in the process with the given value. Values older than 34 // the timeseries period are lost. 35 func (ts *timeseries) advanceTimeWithFill(t time.Time, value int64) { 36 advanceTo := t.Truncate(ts.resolution) 37 if !advanceTo.After(ts.time) { 38 // This is shortcut for the most common case of a busy counter 39 // where updates come in many times per ts.resolution. 40 ts.time = advanceTo 41 return 42 } 43 steps := int(advanceTo.Sub(ts.time).Nanoseconds() / ts.resolution.Nanoseconds()) 44 ts.stepCount += int64(steps) 45 if steps > ts.size { 46 steps = ts.size 47 } 48 for steps > 0 { 49 ts.head = (ts.head + 1) % ts.size 50 ts.slots[ts.head] = value 51 steps-- 52 } 53 ts.time = advanceTo 54 } 55 56 // advanceTime moves the timeseries forward to time t and fills in any slots 57 // that get skipped in the process with the head value. Values older than the 58 // timeseries period are lost. 59 func (ts *timeseries) advanceTime(t time.Time) { 60 ts.advanceTimeWithFill(t, ts.slots[ts.head]) 61 } 62 63 // set sets the current value of the timeseries. 64 func (ts *timeseries) set(value int64) { 65 ts.slots[ts.head] = value 66 } 67 68 // incr sets the current value of the timeseries. 69 func (ts *timeseries) incr(delta int64) { 70 ts.slots[ts.head] += delta 71 } 72 73 // headValue returns the latest value from the timeseries. 74 func (ts *timeseries) headValue() int64 { 75 return ts.slots[ts.head] 76 } 77 78 // headTime returns the time of the latest value from the timeseries. 79 func (ts *timeseries) headTime() time.Time { 80 return ts.time 81 } 82 83 // tailValue returns the oldest value from the timeseries. 84 func (ts *timeseries) tailValue() int64 { 85 if ts.stepCount < int64(ts.size) { 86 return 0 87 } 88 return ts.slots[(ts.head+1)%ts.size] 89 } 90 91 // tailTime returns the time of the oldest value from the timeseries. 92 func (ts *timeseries) tailTime() time.Time { 93 size := int64(ts.size) 94 if ts.stepCount < size { 95 size = ts.stepCount 96 } 97 return ts.time.Add(-time.Duration(size-1) * ts.resolution) 98 } 99 100 // delta returns the difference between the newest and oldest values from the 101 // timeseries. 102 func (ts *timeseries) delta() int64 { 103 return ts.headValue() - ts.tailValue() 104 } 105 106 // rate returns the rate of change between the oldest and newest values from 107 // the timeseries in units per second. 108 func (ts *timeseries) rate() float64 { 109 deltaTime := ts.headTime().Sub(ts.tailTime()).Seconds() 110 if deltaTime == 0 { 111 return 0 112 } 113 return float64(ts.delta()) / deltaTime 114 } 115 116 // min returns the smallest value from the timeseries. 117 func (ts *timeseries) min() int64 { 118 to := ts.size 119 if ts.stepCount < int64(ts.size) { 120 to = ts.head + 1 121 } 122 tail := (ts.head + 1) % ts.size 123 min := int64(math.MaxInt64) 124 for b := 0; b < to; b++ { 125 if b != tail && ts.slots[b] < min { 126 min = ts.slots[b] 127 } 128 } 129 return min 130 } 131 132 // max returns the largest value from the timeseries. 133 func (ts *timeseries) max() int64 { 134 to := ts.size 135 if ts.stepCount < int64(ts.size) { 136 to = ts.head + 1 137 } 138 tail := (ts.head + 1) % ts.size 139 max := int64(math.MinInt64) 140 for b := 0; b < to; b++ { 141 if b != tail && ts.slots[b] > max { 142 max = ts.slots[b] 143 } 144 } 145 return max 146 } 147 148 // reset resets the timeseries to an empty state. 149 func (ts *timeseries) reset(t time.Time) { 150 ts.head = 0 151 ts.time = t 152 ts.stepCount = 1 153 ts.slots = make([]int64, ts.size) 154 }