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  }