github.com/grafana/pyroscope@v1.18.0/pkg/distributor/aggregator/aggregator_test.go (about) 1 package aggregator 2 3 import ( 4 "context" 5 "sync" 6 "sync/atomic" 7 "testing" 8 "time" 9 10 "github.com/stretchr/testify/assert" 11 ) 12 13 func Test_Aggregation(t *testing.T) { 14 w := time.Second * 15 15 d := time.Millisecond * 10 16 17 fn := func(i int) (int, error) { 18 return i + 1, nil 19 } 20 21 a := NewAggregator[int](w, d) 22 var start int64 23 a.now = func() int64 { 24 start += 10 25 return start 26 } 27 28 _, ok, err := a.Aggregate(0, 0, fn) 29 assert.NoError(t, err) 30 assert.False(t, ok) 31 r2, ok, err := a.Aggregate(0, 1, fn) 32 assert.NoError(t, err) 33 assert.True(t, ok) 34 r3, ok, err := a.Aggregate(0, 2, fn) 35 assert.NoError(t, err) 36 assert.True(t, ok) 37 38 assert.NoError(t, r2.Wait()) 39 v, ok := r2.Value() 40 assert.Equal(t, 2, v) 41 assert.True(t, ok) 42 r2.Close(nil) 43 44 assert.NoError(t, r3.Wait()) 45 _, ok = r3.Value() 46 assert.False(t, ok) 47 r3.Close(nil) 48 } 49 50 func Test_Aggregation_Concurrency(t *testing.T) { 51 const ( 52 N = 10 53 M = 300 54 w = time.Millisecond * 100 55 d = time.Millisecond 56 ) 57 58 var ( 59 wg sync.WaitGroup 60 sum int64 61 cnt int64 62 ) 63 fn := func(i int) (int, error) { 64 return i + 1, nil 65 } 66 67 a := NewAggregator[int](w, d) 68 var start atomic.Int64 69 a.now = func() int64 { 70 return start.Add(10) 71 } 72 73 wg.Add(N) 74 for i := 0; i < N; i++ { 75 go func() { 76 defer wg.Done() 77 for j := int64(0); j < M; j++ { 78 r, ok, err := a.Aggregate(0, j, fn) 79 if !ok { 80 continue 81 } 82 assert.NoError(t, err) 83 assert.NoError(t, r.Wait()) 84 v, ok := r.Value() 85 if ok { 86 atomic.AddInt64(&sum, int64(v)) 87 atomic.AddInt64(&cnt, 1) 88 } 89 r.Close(nil) 90 if j%(M/30) == 0 { 91 a.prune(j) 92 } 93 } 94 }() 95 } 96 97 wg.Wait() 98 assert.Equal(t, int64(N*M)-1, sum) 99 // The number of aggregation is not deterministic. 100 // However, we can assess if they happen at all. 101 assert.Less(t, cnt, int64(M*N)) 102 } 103 104 func Test_Aggregation_Error(t *testing.T) { 105 w := time.Second * 15 106 d := time.Millisecond * 10 107 108 fn := func(i int) (int, error) { 109 return i + 1, nil 110 } 111 112 t.Run("Close with error", func(t *testing.T) { 113 a := NewAggregator[int](w, d) 114 var start int64 115 a.now = func() int64 { 116 start += 10 117 return start 118 } 119 120 _, ok, err := a.Aggregate(0, 0, fn) 121 assert.NoError(t, err) 122 assert.False(t, ok) 123 124 r2, ok, err := a.Aggregate(0, 1, fn) 125 assert.NoError(t, err) 126 assert.True(t, ok) 127 128 r3, ok, err := a.Aggregate(0, 2, fn) 129 assert.NoError(t, err) 130 assert.True(t, ok) 131 132 assert.NoError(t, r2.Wait()) 133 v, ok := r2.Value() 134 assert.Equal(t, 2, v) 135 assert.True(t, ok) 136 r2.Close(context.Canceled) 137 138 assert.ErrorIs(t, r3.Wait(), context.Canceled) 139 }) 140 141 t.Run("First aggregation failed", func(t *testing.T) { 142 a := NewAggregator[int](w, d) 143 var start int64 144 a.now = func() int64 { 145 start += 10 146 return start 147 } 148 149 _, ok, err := a.Aggregate(0, 0, fn) 150 assert.NoError(t, err) 151 assert.False(t, ok) 152 153 r3, _, err := a.Aggregate(0, 2, func(int) (int, error) { 154 return 0, context.Canceled 155 }) 156 assert.ErrorIs(t, err, context.Canceled) 157 158 var results []func() error 159 160 // now a call into the same aggregation bucket is either failing or 161 // succeeding, depending if the aggregation has been removed in the meantime 162 for { 163 result, _, err := a.Aggregate(0, 1, fn) 164 if err == nil { 165 results = append(results, result.Wait) 166 break 167 } 168 169 if err != nil { 170 assert.ErrorIs(t, err, context.Canceled) 171 results = append(results, result.Wait) 172 } 173 174 // slow down 175 <-time.After(1 * time.Millisecond) 176 } 177 178 // Caller does not have to wait, actually. 179 // Testing it backwards to make sure it is not blocking. 180 for idx := range results { 181 wait := results[len(results)-1-idx] 182 183 if idx == 0 { 184 assert.NoError(t, wait(), context.Canceled) 185 continue 186 } 187 188 assert.Error(t, wait(), context.Canceled) 189 } 190 assert.Error(t, r3.Wait(), context.Canceled) 191 192 assert.Equal(t, uint64(1), a.stats.errors.Load()) 193 }) 194 195 t.Run("Last aggregation failed", func(t *testing.T) { 196 a := NewAggregator[int](w, d) 197 var start int64 198 a.now = func() int64 { 199 start += 10 200 return start 201 } 202 203 _, ok, err := a.Aggregate(0, 0, fn) 204 assert.NoError(t, err) 205 assert.False(t, ok) 206 207 r2, ok, err := a.Aggregate(0, 1, fn) 208 assert.NoError(t, err) 209 assert.True(t, ok) 210 211 r3, _, err := a.Aggregate(0, 2, func(i int) (int, error) { return 0, context.Canceled }) 212 assert.ErrorIs(t, err, context.Canceled) 213 assert.ErrorIs(t, r2.Wait(), context.Canceled) 214 assert.ErrorIs(t, r3.Wait(), context.Canceled) 215 216 assert.Equal(t, uint64(1), a.stats.errors.Load()) 217 }) 218 } 219 220 func Test_Aggregation_Pruning(t *testing.T) { 221 w := time.Second * 15 222 d := time.Millisecond * 10 223 224 fn := func(i int) (int, error) { 225 return i + 1, nil 226 } 227 228 a := NewAggregator[int](w, d) 229 var start int64 230 a.now = func() int64 { 231 start += 10 232 return start 233 } 234 235 _, ok, err := a.Aggregate(0, 0, fn) 236 assert.NoError(t, err) 237 assert.False(t, ok) 238 239 // In order to create aggregate we need at least 240 // two requests within the aggregation window. 241 r2, ok, err := a.Aggregate(0, 1, fn) 242 assert.NoError(t, err) 243 assert.True(t, ok) 244 245 assert.NoError(t, r2.Wait()) 246 // Evict stale aggregates and keys. 247 a.prune(int64(d) + a.now()) 248 assert.Zero(t, len(a.aggregates)) 249 assert.Zero(t, a.tracker.update(0, 1)) 250 }