github.com/m3db/m3@v1.5.0/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 }