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  }