github.com/grafana/pyroscope@v1.18.0/pkg/segmentwriter/client/distributor/placement/adaptiveplacement/ewma/ewma_test.go (about)

     1  package ewma
     2  
     3  import (
     4  	"testing"
     5  	"time"
     6  
     7  	"github.com/stretchr/testify/assert"
     8  )
     9  
    10  func Test_Rate_HalfLife(t *testing.T) {
    11  	s := int64(0)
    12  	r := NewHalfLife(time.Second * 10)
    13  
    14  	// Expected rate 100.
    15  	step := int64(1e9 / 10)    // 100ms
    16  	for i := 0; i < 500; i++ { // 50s.
    17  		r.update(10, s)
    18  		if i == 100 { // 10s (half-life)
    19  			// Half-life: 10s => 100 * 0.5 = 50.
    20  			assert.InEpsilon(t, float64(50), r.value(s), 0.0001)
    21  		}
    22  		s += step
    23  	}
    24  	// Here and below: Value takes into account the time
    25  	// since the last update, so we need to adjust it to
    26  	// compensate the last iteration.
    27  	assert.InEpsilon(t, 100, r.value(s-step), 0.05)
    28  
    29  	// Rate decreases.
    30  	step = int64(1e9 / 10)     // 100ms
    31  	for i := 0; i < 500; i++ { // 50s.
    32  		r.update(5, s)
    33  		s += step
    34  	}
    35  	assert.InEpsilon(t, 50, r.value(s-step), 0.05)
    36  
    37  	// Exactly 1s rate.
    38  	step = int64(1e9)         // 1s
    39  	for i := 0; i < 50; i++ { // 50s
    40  		r.update(50, s)
    41  		s += step
    42  	}
    43  	assert.InEpsilon(t, 50, r.value(s-step), 0.005)
    44  
    45  	// Sub-second rate.
    46  	step = int64(1e9 * 2)     // 2s
    47  	for i := 0; i < 50; i++ { // 50s.
    48  		r.update(1, s)
    49  		if i == 5 { // 10s (half-life)
    50  			// We expect that after expiration of the half-life interval,
    51  			// the rate should be roughly 25: (~50 + 0.5) / 2 = ~25.
    52  			// The numbers are not exact because r has state:
    53  			// in the beginning it is slightly greater than 50.
    54  			assert.InEpsilon(t, 25, int(r.value(s)), 0.0001)
    55  		}
    56  		s += step // Once per two seconds.
    57  	}
    58  	assert.InEpsilon(t, 0.5, r.value(s-step), 0.5)
    59  }
    60  
    61  func Test_Rate_HalfLife_Tail(t *testing.T) {
    62  	// Expected rate 100.
    63  	const (
    64  		step   int64 = 1e9 / 10 // 100ms
    65  		steps  int64 = 1200     // 120s.
    66  		update int64 = 10
    67  
    68  		rate     = update * int64(time.Second) / step
    69  		halflife = time.Second * 10
    70  	)
    71  
    72  	r := NewHalfLife(halflife)
    73  	var s int64
    74  	for i := int64(0); i < steps; i++ {
    75  		r.update(float64(update), s)
    76  		s += step
    77  		if i == int64(halflife)/step {
    78  			// Just in case: check half-life value.
    79  			assert.InEpsilon(t, float64(rate/2), r.value(s), 0.0001)
    80  		}
    81  	}
    82  
    83  	assert.InEpsilon(t, float64(rate), r.value(s), 0.05)
    84  	// Now we stop updating the rate and
    85  	// expect that it will decay to zero.
    86  	timespan := s + (steps * step)
    87  	assert.Less(t, r.value(timespan), float64(1))
    88  	// Half-life check: note that r is not exactly 100,
    89  	// therefore we will have some error here.
    90  	timespan = s + int64(halflife)
    91  	assert.InEpsilon(t, r.value(timespan), r.value(s)/2, 0.05)
    92  }
    93  
    94  func Test_Rate_HalfLife_Complement(t *testing.T) {
    95  	// The test examines the complementarity of two rates,
    96  	// when the sum of the rates is constant.
    97  	const (
    98  		step   = int64(1e9 / 10) // 100ms
    99  		steps  = 600             // 60s.
   100  		update = 10
   101  
   102  		rate     = update * int64(time.Second) / step
   103  		halflife = time.Second * 10
   104  	)
   105  
   106  	s := int64(0)
   107  	a := NewHalfLife(halflife)
   108  	for i := 0; i < steps; i++ {
   109  		a.update(update, s)
   110  		s += step
   111  	}
   112  	assert.InEpsilon(t, float64(rate), a.value(s), 0.05)
   113  
   114  	b := NewHalfLife(halflife)
   115  	const interval = steps / 10
   116  	for n := 0; n < steps; n += interval {
   117  		for i := 0; i < interval; i++ {
   118  			b.update(update, s)
   119  			s += step
   120  		}
   121  		// The sum of the rates is expected to be constant.
   122  		assert.InEpsilon(t, float64(rate), a.value(s)+b.value(s), 0.05)
   123  	}
   124  }
   125  
   126  func Test_Rate_Lifetime(t *testing.T) {
   127  	// Expected rate 100.
   128  	const (
   129  		step   int64 = 1e9 / 10 // 100ms
   130  		steps  int64 = 100      // 10s.
   131  		update int64 = 10
   132  
   133  		rate = update * int64(time.Second) / step
   134  		// lifetime/3 approximates SMA (error is ~5%).
   135  		lifetime = time.Second * 10 / 3
   136  	)
   137  
   138  	r := New(lifetime)
   139  	var s int64
   140  	for i := int64(0); i < steps; i++ {
   141  		r.update(float64(update), s)
   142  		s += step
   143  	}
   144  
   145  	assert.InEpsilon(t, float64(rate), r.value(s), 0.05)
   146  	// Now we stop updating the rate and expect
   147  	// that it will decay to zero. Note that the
   148  	// value decays more slowly: after 20 seconds
   149  	// we still observe a non-zero value (~5%).
   150  	for i := int64(0); i < 2*steps; i++ {
   151  		s += step
   152  	}
   153  	assert.Less(t, r.value(s), float64(5))
   154  }