github.com/livekit/protocol@v1.16.1-0.20240517185851-47e4c6bba773/utils/timed_aggregator_test.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 "testing" 19 "time" 20 21 "github.com/stretchr/testify/require" 22 ) 23 24 func TestTimedAggregator(t *testing.T) { 25 t.Run("functions", func(t *testing.T) { 26 ta := NewTimedAggregator[float64](TimedAggregatorParams{}) 27 28 aggregate, aggregateDuration := ta.GetAggregate() 29 require.Equal(t, 0.0, aggregate) 30 require.Equal(t, time.Duration(0), aggregateDuration) 31 require.Equal(t, 0.0, ta.GetAverage()) 32 33 now := time.Now() 34 require.NoError(t, ta.AddSampleAt(1.0, now)) 35 // nothing to aggregate with just one sample 36 aggregate, aggregateDuration = ta.GetAggregate() 37 require.Equal(t, 0.0, aggregate) 38 require.Equal(t, time.Duration(0), aggregateDuration) 39 require.Equal(t, 0.0, ta.GetAverage()) 40 41 require.NoError(t, ta.AddSampleAt(2.0, now.Add(500*time.Millisecond))) 42 // second sample added should make stats available 43 aggregate, aggregateDuration = ta.GetAggregate() 44 require.Equal(t, 0.5, aggregate) 45 require.Equal(t, 500*time.Millisecond, aggregateDuration) 46 require.Equal(t, 1.0, ta.GetAverage()) 47 48 // cannot add anachronous sample 49 require.Error(t, ErrAnachronousSample, ta.AddSampleAt(10.0, now.Add(200*time.Millisecond))) 50 51 // cannot get aggregate or average at an older time 52 _, _, err := ta.GetAggregateAt(now.Add(200 * time.Millisecond)) 53 require.Error(t, ErrAnachronousSample, err) 54 55 // check a bit later, last value should continue 56 // 1.0 (0.5s) + 2.0 (1.0s) 57 aggregate, aggregateDuration, err = ta.GetAggregateAt(now.Add(1500 * time.Millisecond)) 58 require.Equal(t, 2.5, aggregate) 59 require.Equal(t, 1500*time.Millisecond, aggregateDuration) 60 require.NoError(t, err) 61 62 require.Equal(t, 2.5/1.5, ta.GetAverage()) 63 64 // another second out and restart 65 // 1.0 (0.5s) + 2.0 (2.0s) 66 aggregate, aggregateDuration, err = ta.GetAggregateAndRestartAt(now.Add(2500 * time.Millisecond)) 67 require.Equal(t, 4.5, aggregate) 68 require.Equal(t, 2500*time.Millisecond, aggregateDuration) 69 require.NoError(t, err) 70 71 // restart should have reset the aggregates 72 aggregate, aggregateDuration = ta.GetAggregate() 73 require.Equal(t, 0.0, aggregate) 74 require.Equal(t, time.Duration(0), aggregateDuration) 75 76 require.Equal(t, 0.0, ta.GetAverage()) 77 78 // add a sample a bit later and check aggregates 79 require.NoError(t, ta.AddSampleAt(20.0, now.Add(2800*time.Millisecond))) 80 81 // 2.0 (0.3s) + 20.0 (0.2s) 82 aggregate, aggregateDuration, err = ta.GetAggregateAt(now.Add(3000 * time.Millisecond)) 83 require.Equal(t, 4.6, aggregate) 84 require.Equal(t, 500*time.Millisecond, aggregateDuration) 85 require.NoError(t, err) 86 87 require.Equal(t, 4.6/0.5, ta.GetAverage()) 88 89 // get average a bit later 90 // 2.0 (0.3s) + 20.0 (0.5s) 91 average, err := ta.GetAverageAt(now.Add(3300 * time.Millisecond)) 92 require.Equal(t, float64(10.6)/float64(0.8), average) 93 require.NoError(t, err) 94 95 aggregate, aggregateDuration = ta.GetAggregate() 96 require.Equal(t, 10.6, aggregate) 97 require.Equal(t, 800*time.Millisecond, aggregateDuration) 98 99 // get average and restart a bit later 100 // 2.0 (0.3s) + 20.0 (1.0s) 101 average, err = ta.GetAverageAndRestartAt(now.Add(3800 * time.Millisecond)) 102 require.Equal(t, float64(20.6)/float64(1.3), average) 103 require.NoError(t, err) 104 105 // restart should have reset the aggregates 106 aggregate, aggregateDuration = ta.GetAggregate() 107 require.Equal(t, 0.0, aggregate) 108 require.Equal(t, time.Duration(0), aggregateDuration) 109 110 // add a negative value sample 111 require.NoError(t, ta.AddSampleAt(-2.0, now.Add(4000*time.Millisecond))) 112 113 // get average a bit later 114 // 20.0 (0.2s) + -2.0 (0.5s) 115 average, err = ta.GetAverageAt(now.Add(4500 * time.Millisecond)) 116 require.Equal(t, float64(3.0)/float64(0.7), average) 117 require.NoError(t, err) 118 119 aggregate, aggregateDuration = ta.GetAggregate() 120 require.Equal(t, 3.0, aggregate) 121 require.Equal(t, 700*time.Millisecond, aggregateDuration) 122 }) 123 124 t.Run("negative_values", func(t *testing.T) { 125 ta := NewTimedAggregator[int64](TimedAggregatorParams{ 126 CapNegativeValues: true, 127 }) 128 129 now := time.Now() 130 require.NoError(t, ta.AddSampleAt(1, now)) 131 require.NoError(t, ta.AddSampleAt(-1, now.Add(time.Second))) 132 require.NoError(t, ta.AddSampleAt(1, now.Add(2*time.Second))) 133 134 // 1 (1.0s) + 0 (capped value) (1.0s) + 1 (1.0s) 135 aggregate, aggregateDuration, err := ta.GetAggregateAt(now.Add(3 * time.Second)) 136 require.Equal(t, int64(2), aggregate) 137 require.Equal(t, 3*time.Second, aggregateDuration) 138 require.NoError(t, err) 139 140 require.Equal(t, float64(2.0)/float64(3.0), ta.GetAverage()) 141 }) 142 }