github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/aggregator/aggregation/timer_test.go (about) 1 // Copyright (c) 2016 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package aggregation 22 23 import ( 24 "math" 25 "math/rand" 26 "sort" 27 "testing" 28 "time" 29 30 "github.com/m3db/m3/src/aggregator/aggregation/quantile/cm" 31 "github.com/m3db/m3/src/metrics/aggregation" 32 "github.com/m3db/m3/src/x/instrument" 33 "github.com/m3db/m3/src/x/pool" 34 35 "github.com/stretchr/testify/require" 36 ) 37 38 var ( 39 testQuantiles = []float64{0.5, 0.95, 0.99} 40 testAggTypes = aggregation.Types{ 41 aggregation.Sum, 42 aggregation.SumSq, 43 aggregation.Mean, 44 aggregation.Min, 45 aggregation.Max, 46 aggregation.Count, 47 aggregation.Stdev, 48 aggregation.Median, 49 aggregation.P50, 50 aggregation.P95, 51 aggregation.P99, 52 } 53 ) 54 55 func testStreamOptions() cm.Options { 56 return cm.NewOptions() 57 } 58 59 func getTimerSamples( 60 num int, 61 generator func(*rand.Rand) float64, 62 q []float64, 63 ) ([]float64, []float64) { 64 if generator == nil { 65 generator = func(r *rand.Rand) float64 { 66 return r.Float64() 67 } 68 } 69 var ( 70 quantiles = make([]float64, len(q)) 71 samples = make([]float64, num) 72 sorted = make([]float64, num) 73 rnd = rand.New(rand.NewSource(0)) //nolint:gosec 74 ) 75 76 for i := 0; i < len(samples); i++ { 77 samples[i] = generator(rnd) 78 } 79 80 copy(sorted, samples) 81 sort.Float64s(sorted) 82 n := float64(len(sorted) - 1) 83 for i, quantile := range q { 84 quantiles[i] = sorted[int(n*quantile)] 85 } 86 87 return samples, quantiles 88 } 89 90 func TestCreateTimerResetStream(t *testing.T) { 91 floatsPool := pool.NewFloatsPool([]pool.Bucket{{Capacity: 2048, Count: 100}}, nil) 92 floatsPool.Init() 93 streamOpts := cm.NewOptions() 94 // Add a value to the timer and close the timer, which returns the 95 // underlying stream to the pool. 96 timer := NewTimer(testQuantiles, streamOpts, NewOptions(instrument.NewOptions())) 97 timer.Add(time.Now(), 1.0, nil) 98 require.Equal(t, 1.0, timer.Min()) 99 timer.Close() 100 101 // Create a new timer and assert the underlying stream has been closed. 102 timer = NewTimer(testQuantiles, streamOpts, NewOptions(instrument.NewOptions())) 103 timer.Add(time.Now(), 1.0, nil) 104 require.Equal(t, 1.0, timer.Min()) 105 timer.Close() 106 require.Equal(t, 0.0, timer.stream.Min()) 107 } 108 109 func TestTimerAggregations(t *testing.T) { 110 opts := NewOptions(instrument.NewOptions()) 111 opts.ResetSetData(testAggTypes) 112 113 timer := NewTimer(testQuantiles, testStreamOptions(), opts) 114 115 // Assert the state of an empty timer. 116 require.True(t, timer.hasExpensiveAggregations) 117 require.Equal(t, int64(0), timer.Count()) 118 require.Equal(t, 0.0, timer.Sum()) 119 require.Equal(t, 0.0, timer.SumSq()) 120 require.Equal(t, 0.0, timer.Min()) 121 require.Equal(t, 0.0, timer.Max()) 122 require.Equal(t, 0.0, timer.Mean()) 123 require.Equal(t, 0.0, timer.Stdev()) 124 require.Equal(t, 0.0, timer.Quantile(0.5)) 125 require.Equal(t, 0.0, timer.Quantile(0.95)) 126 require.Equal(t, 0.0, timer.Quantile(0.99)) 127 128 // Add values. 129 at := time.Now() 130 for i := 1; i <= 100; i++ { 131 timer.Add(at, float64(i), nil) 132 } 133 134 // Validate the timer values match expectations. 135 require.Equal(t, int64(100), timer.Count()) 136 require.Equal(t, 5050.0, timer.Sum()) 137 require.Equal(t, 338350.0, timer.SumSq()) 138 require.Equal(t, 1.0, timer.Min()) 139 require.Equal(t, 100.0, timer.Max()) 140 require.Equal(t, 50.5, timer.Mean()) 141 require.Equal(t, 29.011, math.Trunc(timer.Stdev()*1000+0.5)/1000.0) 142 require.Equal(t, 50.0, timer.Quantile(0.5)) 143 require.True(t, timer.Quantile(0.95) >= 94 && timer.Quantile(0.95) <= 96) 144 require.True(t, timer.Quantile(0.99) >= 98 && timer.Quantile(0.99) <= 100) 145 146 for aggType := range aggregation.ValidTypes { 147 v := timer.ValueOf(aggType) 148 switch aggType { 149 case aggregation.Last: 150 require.Equal(t, 0.0, v) 151 case aggregation.Min: 152 require.Equal(t, 1.0, v) 153 case aggregation.Max: 154 require.Equal(t, 100.0, v) 155 case aggregation.Mean: 156 require.Equal(t, 50.5, v) 157 case aggregation.Median: 158 require.Equal(t, 50.0, v) 159 case aggregation.Count: 160 require.Equal(t, 100.0, v) 161 case aggregation.Sum: 162 require.Equal(t, 5050.0, v) 163 case aggregation.SumSq: 164 require.Equal(t, 338350.0, v) 165 case aggregation.Stdev: 166 require.InDelta(t, 29.01149, v, 0.001) 167 case aggregation.P50: 168 require.Equal(t, 50.0, v) 169 case aggregation.P95: 170 require.Equal(t, 95.0, v) 171 case aggregation.P99: 172 require.True(t, v >= 99 && v <= 100) 173 } 174 } 175 // Closing the timer should close the underlying stream. 176 timer.Close() 177 require.Equal(t, 0.0, timer.stream.Quantile(0.5)) 178 179 // Closing the timer a second time should be a no op. 180 timer.Close() 181 } 182 183 func TestTimerAggregationsNotExpensive(t *testing.T) { 184 opts := NewOptions(instrument.NewOptions()) 185 opts.ResetSetData(aggregation.Types{aggregation.Sum}) 186 187 timer := NewTimer(testQuantiles, testStreamOptions(), opts) 188 189 // Assert the state of an empty timer. 190 require.False(t, timer.hasExpensiveAggregations) 191 192 // Add values. 193 at := time.Now() 194 for i := 1; i <= 100; i++ { 195 timer.Add(at, float64(i), nil) 196 } 197 198 // All Non expensive calculations should be performed. 199 require.Equal(t, int64(100), timer.Count()) 200 require.Equal(t, 5050.0, timer.Sum()) 201 require.Equal(t, 1.0, timer.Min()) 202 require.Equal(t, 100.0, timer.Max()) 203 require.Equal(t, 50.5, timer.Mean()) 204 205 // Expensive calculations are not performed. 206 require.Equal(t, 0.0, timer.SumSq()) 207 208 // Closing the timer a second time should be a no op. 209 timer.Close() 210 } 211 212 func TestTimerReturnsLastNonEmptyAnnotation(t *testing.T) { 213 opts := NewOptions(instrument.NewOptions()) 214 opts.ResetSetData(testAggTypes) 215 timer := NewTimer(testQuantiles, cm.NewOptions(), opts) 216 217 timer.Add(time.Now(), 1.1, []byte("first")) 218 timer.Add(time.Now(), 2.1, []byte("second")) 219 timer.Add(time.Now(), 3.1, nil) 220 221 require.Equal(t, []byte("second"), timer.Annotation()) 222 }