github.com/livekit/protocol@v1.16.1-0.20240517185851-47e4c6bba773/utils/timed_aggregator.go (about) 1 // Copyright 2023 LiveKit, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package utils 16 17 import ( 18 "errors" 19 "sync" 20 "time" 21 ) 22 23 // ------------------------------------------------ 24 25 var ( 26 ErrAnachronousSample = errors.New("anachronous sample") 27 ) 28 29 // ------------------------------------------------ 30 31 type timedAggregatorNumber interface { 32 int64 | float64 33 } 34 35 type TimedAggregatorParams struct { 36 CapNegativeValues bool 37 } 38 39 type TimedAggregator[T timedAggregatorNumber] struct { 40 params TimedAggregatorParams 41 42 lock sync.RWMutex 43 lastSample T 44 lastSampleAt time.Time 45 aggregate T 46 aggregateDuration time.Duration 47 } 48 49 func NewTimedAggregator[T timedAggregatorNumber](params TimedAggregatorParams) *TimedAggregator[T] { 50 return &TimedAggregator[T]{ 51 params: params, 52 } 53 } 54 55 func (t *TimedAggregator[T]) AddSampleAt(val T, at time.Time) error { 56 t.lock.Lock() 57 defer t.lock.Unlock() 58 59 if val < 0 && t.params.CapNegativeValues { 60 val = 0 61 } 62 63 return t.addSampleAtLocked(val, at) 64 } 65 66 func (t *TimedAggregator[T]) AddSample(val T) error { 67 return t.AddSampleAt(val, time.Now()) 68 } 69 70 func (t *TimedAggregator[T]) addSampleAtLocked(val T, at time.Time) error { 71 var sinceLast time.Duration 72 if !t.lastSampleAt.IsZero() { 73 if t.lastSampleAt.After(at) { 74 return ErrAnachronousSample 75 } 76 77 sinceLast = at.Sub(t.lastSampleAt) 78 } 79 lastVal := t.lastSample 80 81 t.lastSample = val 82 t.lastSampleAt = at 83 84 t.aggregate += T(sinceLast.Seconds() * float64(lastVal)) 85 t.aggregateDuration += sinceLast 86 return nil 87 } 88 89 func (t *TimedAggregator[T]) GetAggregate() (T, time.Duration) { 90 t.lock.RLock() 91 defer t.lock.RUnlock() 92 93 return t.aggregate, t.aggregateDuration 94 } 95 96 func (t *TimedAggregator[T]) GetAggregateAt(at time.Time) (T, time.Duration, error) { 97 t.lock.Lock() 98 defer t.lock.Unlock() 99 100 return t.getAggregateAtLocked(at) 101 } 102 103 func (t *TimedAggregator[T]) GetAggregateAndRestartAt(at time.Time) (T, time.Duration, error) { 104 t.lock.Lock() 105 defer t.lock.Unlock() 106 107 aggregate, aggregateDuration, err := t.getAggregateAtLocked(at) 108 t.restartAtLocked(at) 109 return aggregate, aggregateDuration, err 110 } 111 112 func (t *TimedAggregator[T]) getAggregateAtLocked(at time.Time) (T, time.Duration, error) { 113 if !t.lastSampleAt.IsZero() { 114 // re-add last sample at given time 115 if err := t.addSampleAtLocked(t.lastSample, at); err != nil { 116 return 0, 0, ErrAnachronousSample 117 } 118 } 119 120 return t.aggregate, t.aggregateDuration, nil 121 } 122 123 func (t *TimedAggregator[T]) GetAverage() float64 { 124 t.lock.RLock() 125 defer t.lock.RUnlock() 126 127 return t.getAverageLocked() 128 } 129 130 func (t *TimedAggregator[T]) GetAverageAt(at time.Time) (float64, error) { 131 t.lock.Lock() 132 defer t.lock.Unlock() 133 134 return t.getAverageAtLocked(at) 135 } 136 137 func (t *TimedAggregator[T]) GetAverageAndRestartAt(at time.Time) (float64, error) { 138 t.lock.Lock() 139 defer t.lock.Unlock() 140 141 average, err := t.getAverageAtLocked(at) 142 t.restartAtLocked(at) 143 return average, err 144 } 145 146 func (t *TimedAggregator[T]) getAverageLocked() float64 { 147 seconds := t.aggregateDuration.Seconds() 148 if seconds == 0.0 { 149 return 0.0 150 } 151 152 return float64(t.aggregate) / seconds 153 } 154 155 func (t *TimedAggregator[T]) getAverageAtLocked(at time.Time) (float64, error) { 156 if !t.lastSampleAt.IsZero() { 157 // re-add last sample at given time 158 if err := t.addSampleAtLocked(t.lastSample, at); err != nil { 159 return 0.0, err 160 } 161 } 162 163 return t.getAverageLocked(), nil 164 } 165 166 func (t *TimedAggregator[T]) Reset() { 167 t.lock.Lock() 168 defer t.lock.Unlock() 169 170 t.lastSample = 0 171 t.lastSampleAt = time.Time{} 172 t.aggregate = 0 173 t.aggregateDuration = 0 174 } 175 176 func (t *TimedAggregator[T]) RestartAt(at time.Time) { 177 t.lock.Lock() 178 defer t.lock.Unlock() 179 180 t.restartAtLocked(at) 181 } 182 183 func (t *TimedAggregator[T]) Restart() { 184 t.RestartAt(time.Now()) 185 } 186 187 func (t *TimedAggregator[T]) restartAtLocked(at time.Time) { 188 if t.lastSampleAt.IsZero() { 189 // no samples yet, nothing to restart 190 return 191 } 192 193 t.lastSampleAt = at 194 t.aggregate = 0 195 t.aggregateDuration = 0 196 }