github.com/aristanetworks/goarista@v0.0.0-20240514173732-cca2755bbd44/monitor/stats/timeseries.go (about) 1 /* 2 * 3 * Copyright 2017 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 // Copyright (c) 2015 Arista Networks, Inc. 20 // Use of this source code is governed by the Apache License 2.0 21 // that can be found in the COPYING file. 22 23 package stats 24 25 import ( 26 "math" 27 "time" 28 ) 29 30 // timeseries holds the history of a changing value over a predefined period of 31 // time. 32 type timeseries struct { 33 size int // The number of time slots. Equivalent to len(slots). 34 resolution time.Duration // The time resolution of each slot. 35 stepCount int64 // The number of intervals seen since creation. 36 head int // The position of the current time in slots. 37 time time.Time // The time at the beginning of the current time slot. 38 slots []int64 // A circular buffer of time slots. 39 } 40 41 // newTimeSeries returns a newly allocated timeseries that covers the requested 42 // period with the given resolution. 43 func newTimeSeries(initialTime time.Time, period, resolution time.Duration) *timeseries { 44 size := int(period.Nanoseconds()/resolution.Nanoseconds()) + 1 45 return ×eries{ 46 size: size, 47 resolution: resolution, 48 stepCount: 1, 49 time: initialTime, 50 slots: make([]int64, size), 51 } 52 } 53 54 // advanceTimeWithFill moves the timeseries forward to time t and fills in any 55 // slots that get skipped in the process with the given value. Values older than 56 // the timeseries period are lost. 57 func (ts *timeseries) advanceTimeWithFill(t time.Time, value int64) { 58 advanceTo := t.Truncate(ts.resolution) 59 if !advanceTo.After(ts.time) { 60 // This is shortcut for the most common case of a busy counter 61 // where updates come in many times per ts.resolution. 62 ts.time = advanceTo 63 return 64 } 65 steps := int(advanceTo.Sub(ts.time).Nanoseconds() / ts.resolution.Nanoseconds()) 66 ts.stepCount += int64(steps) 67 if steps > ts.size { 68 steps = ts.size 69 } 70 for steps > 0 { 71 ts.head = (ts.head + 1) % ts.size 72 ts.slots[ts.head] = value 73 steps-- 74 } 75 ts.time = advanceTo 76 } 77 78 // advanceTime moves the timeseries forward to time t and fills in any slots 79 // that get skipped in the process with the head value. Values older than the 80 // timeseries period are lost. 81 func (ts *timeseries) advanceTime(t time.Time) { 82 ts.advanceTimeWithFill(t, ts.slots[ts.head]) 83 } 84 85 // set sets the current value of the timeseries. 86 func (ts *timeseries) set(value int64) { 87 ts.slots[ts.head] = value 88 } 89 90 // incr sets the current value of the timeseries. 91 func (ts *timeseries) incr(delta int64) { 92 ts.slots[ts.head] += delta 93 } 94 95 // headValue returns the latest value from the timeseries. 96 func (ts *timeseries) headValue() int64 { 97 return ts.slots[ts.head] 98 } 99 100 // headTime returns the time of the latest value from the timeseries. 101 func (ts *timeseries) headTime() time.Time { 102 return ts.time 103 } 104 105 // tailValue returns the oldest value from the timeseries. 106 func (ts *timeseries) tailValue() int64 { 107 if ts.stepCount < int64(ts.size) { 108 return 0 109 } 110 return ts.slots[(ts.head+1)%ts.size] 111 } 112 113 // tailTime returns the time of the oldest value from the timeseries. 114 func (ts *timeseries) tailTime() time.Time { 115 size := int64(ts.size) 116 if ts.stepCount < size { 117 size = ts.stepCount 118 } 119 return ts.time.Add(-time.Duration(size-1) * ts.resolution) 120 } 121 122 // delta returns the difference between the newest and oldest values from the 123 // timeseries. 124 func (ts *timeseries) delta() int64 { 125 return ts.headValue() - ts.tailValue() 126 } 127 128 // rate returns the rate of change between the oldest and newest values from 129 // the timeseries in units per second. 130 func (ts *timeseries) rate() float64 { 131 deltaTime := ts.headTime().Sub(ts.tailTime()).Seconds() 132 if deltaTime == 0 { 133 return 0 134 } 135 return float64(ts.delta()) / deltaTime 136 } 137 138 // min returns the smallest value from the timeseries. 139 func (ts *timeseries) min() int64 { 140 to := ts.size 141 if ts.stepCount < int64(ts.size) { 142 to = ts.head + 1 143 } 144 tail := (ts.head + 1) % ts.size 145 min := int64(math.MaxInt64) 146 for b := 0; b < to; b++ { 147 if b != tail && ts.slots[b] < min { 148 min = ts.slots[b] 149 } 150 } 151 return min 152 } 153 154 // max returns the largest value from the timeseries. 155 func (ts *timeseries) max() int64 { 156 to := ts.size 157 if ts.stepCount < int64(ts.size) { 158 to = ts.head + 1 159 } 160 tail := (ts.head + 1) % ts.size 161 max := int64(math.MinInt64) 162 for b := 0; b < to; b++ { 163 if b != tail && ts.slots[b] > max { 164 max = ts.slots[b] 165 } 166 } 167 return max 168 } 169 170 // reset resets the timeseries to an empty state. 171 func (ts *timeseries) reset(t time.Time) { 172 ts.head = 0 173 ts.time = t 174 ts.stepCount = 1 175 ts.slots = make([]int64, ts.size) 176 }