github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/functions/aggregation/quantile_test.go (about)

     1  // Copyright (c) 2018 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  	"testing"
    26  
    27  	"github.com/m3db/m3/src/query/block"
    28  	"github.com/m3db/m3/src/query/models"
    29  	"github.com/m3db/m3/src/query/test"
    30  	"github.com/m3db/m3/src/query/test/compare"
    31  
    32  	"github.com/stretchr/testify/assert"
    33  	"github.com/stretchr/testify/require"
    34  )
    35  
    36  var (
    37  	typeBytesQuantile = []byte(QuantileType)
    38  )
    39  
    40  func TestQuantileFn(t *testing.T) {
    41  	values := []float64{3.1, 100, 200, 300, 2.1, 800, 1.1, 4.1, 5.1}
    42  	// NB Taken values by bucket: [3.1, 2.1, 1.1, 4.1]
    43  	buckets := []int{0, 4, 7, 6}
    44  	ns := make([]float64, 13)
    45  	// set ns to -0.1, 0, ..., 1, 1.1
    46  	for i := range ns {
    47  		ns[i] = -0.1 + 0.1*float64(i)
    48  	}
    49  
    50  	// 10 steps over length of 3 with uniform step sizes,
    51  	// expected to go up from values[0] by 0.3 each step.
    52  	expected := make([]float64, len(ns))
    53  	for i, v := range ns {
    54  		expected[i] = 1.1 + v*3
    55  	}
    56  	// Set expected at q < 0 || q > 1
    57  	expected[0] = math.Inf(-1)
    58  	expected[len(ns)-1] = math.Inf(1)
    59  
    60  	actual := make([]float64, len(ns))
    61  	for i, n := range ns {
    62  		actual[i] = bucketedQuantileFn(n, values, buckets)
    63  	}
    64  
    65  	compare.EqualsWithNansWithDelta(t, expected, actual, math.Pow10(-5))
    66  }
    67  
    68  func TestQuantileFnMostlyNan(t *testing.T) {
    69  	values := []float64{math.NaN(), math.NaN(), 1, math.NaN(), 0.5}
    70  	buckets := []int{0, 1, 2, 3, 4}
    71  	ns := make([]float64, 13)
    72  	// set ns to -0.1, 0, ..., 1, 1.1
    73  	for i := range ns {
    74  		ns[i] = -0.1 + 0.1*float64(i)
    75  	}
    76  
    77  	// 10 steps over length of 0.5 with uniform step sizes,
    78  	// expected to go up from values[0] by 0.05 each step.
    79  	expected := make([]float64, len(ns))
    80  	for i, v := range ns {
    81  		expected[i] = 0.5 + v*0.5
    82  	}
    83  	// Set expected at q < 0 || q > 1
    84  	expected[0] = math.Inf(-1)
    85  	expected[len(ns)-1] = math.Inf(1)
    86  
    87  	actual := make([]float64, len(ns))
    88  	for i, n := range ns {
    89  		actual[i] = bucketedQuantileFn(n, values, buckets)
    90  	}
    91  
    92  	compare.EqualsWithNansWithDelta(t, expected, actual, math.Pow10(-5))
    93  }
    94  
    95  func TestQuantileFnSingleNonNan(t *testing.T) {
    96  	values := []float64{math.NaN(), math.NaN(), 1, math.NaN(), math.NaN()}
    97  	buckets := []int{0, 1, 2, 3, 4}
    98  	ns := make([]float64, 13)
    99  	// set ns to -0.1, 0, ..., 1, 1.1
   100  	for i := range ns {
   101  		ns[i] = -0.1 + 0.1*float64(i)
   102  	}
   103  
   104  	// Only non Nan value is 1, all values should be 1
   105  	expected := make([]float64, len(ns))
   106  	for i := range expected {
   107  		expected[i] = 1
   108  	}
   109  	// Set expected at q < 0 || q > 1
   110  	expected[0] = math.Inf(-1)
   111  	expected[len(ns)-1] = math.Inf(1)
   112  
   113  	actual := make([]float64, len(ns))
   114  	for i, n := range ns {
   115  		actual[i] = bucketedQuantileFn(n, values, buckets)
   116  	}
   117  
   118  	compare.EqualsWithNansWithDelta(t, expected, actual, math.Pow10(-5))
   119  }
   120  
   121  func TestQuantileNanAndEmptyArguments(t *testing.T) {
   122  	tests := []struct {
   123  		name   string
   124  		q      float64
   125  		values []float64
   126  		bucket []int
   127  	}{
   128  		{
   129  			name:   "q = NaN",
   130  			q:      math.NaN(),
   131  			values: []float64{0.0, 1.0},
   132  			bucket: []int{0, 1},
   133  		},
   134  		{
   135  			name:   "empty values and bucket",
   136  			q:      0.8,
   137  			values: []float64{},
   138  			bucket: []int{},
   139  		},
   140  		{
   141  			name:   "empty bucket",
   142  			q:      0.8,
   143  			values: []float64{0.0, 1.0},
   144  			bucket: []int{},
   145  		},
   146  		{
   147  			name:   "empty values",
   148  			q:      0.8,
   149  			values: []float64{},
   150  			bucket: []int{0, 1},
   151  		},
   152  	}
   153  	for _, tt := range tests {
   154  		t.Run(tt.name, func(t *testing.T) {
   155  			results := bucketedQuantileFn(tt.q, tt.values, tt.bucket)
   156  			assert.True(t, math.IsNaN(results))
   157  		})
   158  	}
   159  }
   160  
   161  func TestQuantileCreationFn(t *testing.T) {
   162  	n := 0.145
   163  	op, success := makeQuantileFn("badOp", n)
   164  	assert.False(t, success)
   165  	assert.Nil(t, op)
   166  
   167  	op, success = makeQuantileFn(QuantileType, n)
   168  	assert.True(t, success)
   169  
   170  	values := []float64{11, math.NaN(), 13.1, 0.1, -5.1}
   171  	buckets := []int{0, 1, 2, 3, 4}
   172  
   173  	quantile := op(values, buckets)
   174  	// NB: expected calculated independently
   175  	expected := -2.838
   176  	compare.EqualsWithNansWithDelta(t, expected, quantile, math.Pow10(-5))
   177  }
   178  
   179  func TestQuantileFunctionFilteringWithoutA(t *testing.T) {
   180  	op, err := NewAggregationOp(QuantileType, NodeParams{
   181  		MatchingTags: [][]byte{[]byte("a")}, Without: true, Parameter: 0.6,
   182  	})
   183  	require.NoError(t, err)
   184  	sink := processAggregationOp(t, op)
   185  	expected := [][]float64{
   186  		// 0.6 quantile of third, fourth, and fifth series
   187  		{60, 88, 116, 144, 172},
   188  		// stddev of sixth series
   189  		{600, 700, 800, 900, 1000},
   190  		// 0.6 quantile of first two series
   191  		{0, 6, 5, 6, 7},
   192  	}
   193  
   194  	expectedMetas := []block.SeriesMeta{
   195  		{Name: typeBytesQuantile, Tags: test.TagSliceToTags([]models.Tag{{Name: []byte("b"), Value: []byte("2")}})},
   196  		{Name: typeBytesQuantile, Tags: test.TagSliceToTags([]models.Tag{{Name: []byte("c"), Value: []byte("3")}})},
   197  		{Name: typeBytesQuantile, Tags: models.EmptyTags()},
   198  	}
   199  	expectedMetaTags := test.TagSliceToTags([]models.Tag{{Name: []byte("d"), Value: []byte("4")}})
   200  
   201  	compare.CompareValuesInOrder(t, sink.Metas, expectedMetas, sink.Values, expected)
   202  	assert.Equal(t, bounds, sink.Meta.Bounds)
   203  	assert.Equal(t, expectedMetaTags.Tags, sink.Meta.Tags.Tags)
   204  }
   205  
   206  func TestNans(t *testing.T) {
   207  	actual := bucketedQuantileFn(0.5, []float64{}, []int{})
   208  	assert.True(t, math.IsNaN(actual))
   209  
   210  	actual = bucketedQuantileFn(0.5, []float64{1}, []int{})
   211  	assert.True(t, math.IsNaN(actual))
   212  
   213  	actual = bucketedQuantileFn(0.5, []float64{}, []int{1})
   214  	assert.True(t, math.IsNaN(actual))
   215  
   216  	// all NaNs in bucket
   217  	values := []float64{math.NaN(), math.NaN(), 1, math.NaN(), math.NaN()}
   218  	buckets := []int{0, 1, 3, 4}
   219  	actual = bucketedQuantileFn(0.5, values, buckets)
   220  	assert.True(t, math.IsNaN(actual))
   221  }