github.com/m3db/m3@v1.5.0/src/query/functions/temporal/base_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 temporal 22 23 import ( 24 "fmt" 25 "math" 26 "testing" 27 "time" 28 29 "github.com/m3db/m3/src/query/block" 30 "github.com/m3db/m3/src/query/executor/transform" 31 "github.com/m3db/m3/src/query/models" 32 "github.com/m3db/m3/src/query/parser" 33 "github.com/m3db/m3/src/query/test" 34 "github.com/m3db/m3/src/query/test/compare" 35 "github.com/m3db/m3/src/query/test/executor" 36 "github.com/m3db/m3/src/query/test/transformtest" 37 "github.com/m3db/m3/src/query/ts" 38 xtest "github.com/m3db/m3/src/x/test" 39 xtime "github.com/m3db/m3/src/x/time" 40 41 "github.com/golang/mock/gomock" 42 "github.com/stretchr/testify/assert" 43 "github.com/stretchr/testify/require" 44 ) 45 46 var nan = math.NaN() 47 48 type testCase struct { 49 name string 50 opType string 51 vals [][]float64 52 expected [][]float64 53 withWarning bool 54 } 55 56 type opGenerator func(t *testing.T, tc testCase) transform.Params 57 58 const expectedWarning = "resolution larger than query range_" + 59 "range: 1m0s, resolutions: 1h0m0s, 1m1s" 60 61 func buildMetadata() block.ResultMetadata { 62 resultMeta := block.NewResultMetadata() 63 resultMeta.Resolutions = []time.Duration{time.Second, time.Minute} 64 65 return resultMeta 66 } 67 68 func buildWarningMetadata() block.ResultMetadata { 69 resultMeta := buildMetadata() 70 resultMeta.Resolutions = append(resultMeta.Resolutions, 71 time.Second*61, time.Hour) 72 return resultMeta 73 } 74 75 func verifyResultMetadata(t *testing.T, m block.ResultMetadata, exWarn bool) { 76 warnings := m.WarningStrings() 77 if !exWarn { 78 assert.Equal(t, 0, len(warnings)) 79 return 80 } 81 82 require.Equal(t, 1, len(warnings)) 83 assert.Equal(t, expectedWarning, warnings[0]) 84 } 85 86 func testTemporalFunc(t *testing.T, opGen opGenerator, tests []testCase) { 87 for _, tt := range tests { 88 for _, runBatched := range []bool{true, false} { 89 name := tt.name + "_unbatched" 90 if runBatched { 91 name = tt.name + "_batched" 92 } 93 t.Run(name, func(t *testing.T) { 94 values, bounds := test.GenerateValuesAndBounds(tt.vals, nil) 95 boundStart := bounds.Start 96 97 seriesMetas := []block.SeriesMeta{ 98 { 99 Name: []byte("s1"), 100 Tags: models.EmptyTags().AddTags([]models.Tag{{ 101 Name: []byte("t1"), 102 Value: []byte("v1"), 103 }}).SetName([]byte("foobar")), 104 }, 105 { 106 Name: []byte("s2"), 107 Tags: models.EmptyTags().AddTags([]models.Tag{{ 108 Name: []byte("t1"), 109 Value: []byte("v2"), 110 }}).SetName([]byte("foobar")), 111 }, 112 } 113 114 resultMeta := buildMetadata() 115 if tt.withWarning { 116 resultMeta = buildWarningMetadata() 117 } 118 119 bl := test.NewUnconsolidatedBlockFromDatapointsWithMeta(models.Bounds{ 120 Start: bounds.Start.Add(-2 * bounds.Duration), 121 Duration: bounds.Duration * 2, 122 StepSize: bounds.StepSize, 123 }, seriesMetas, resultMeta, values, runBatched) 124 125 c, sink := executor.NewControllerWithSink(parser.NodeID(rune(1))) 126 baseOp := opGen(t, tt) 127 node := baseOp.Node(c, transformtest.Options(t, transform.OptionsParams{ 128 TimeSpec: transform.TimeSpec{ 129 Start: boundStart.Add(-2 * bounds.Duration), 130 End: bounds.End(), 131 Step: time.Second, 132 }, 133 })) 134 135 err := node.Process(models.NoopQueryContext(), parser.NodeID(rune(0)), bl) 136 require.NoError(t, err) 137 138 compare.EqualsWithNansWithDelta(t, tt.expected, sink.Values, 0.0001) 139 metaOne := block.SeriesMeta{ 140 Name: []byte("{t1=\"v1\"}"), 141 Tags: models.EmptyTags().AddTags([]models.Tag{{ 142 Name: []byte("t1"), 143 Value: []byte("v1"), 144 }}), 145 } 146 147 metaTwo := block.SeriesMeta{ 148 Name: []byte("{t1=\"v2\"}"), 149 Tags: models.EmptyTags().AddTags([]models.Tag{{ 150 Name: []byte("t1"), 151 Value: []byte("v2"), 152 }})} 153 154 // The last_over_time function acts like offset; 155 // thus, it should keep the metric name. 156 // For all other functions, 157 // name should be dropped from series tags, 158 // and the name should be the updated ID. 159 var expectedSeriesMetas []block.SeriesMeta 160 if tt.opType != LastType { 161 expectedSeriesMetas = []block.SeriesMeta{metaOne, metaTwo} 162 } else { 163 expectedSeriesMetas = seriesMetas 164 } 165 require.Equal(t, expectedSeriesMetas, sink.Metas) 166 }) 167 } 168 } 169 } 170 171 func TestGetIndicesError(t *testing.T) { 172 size := 10 173 now := xtime.Now().Truncate(time.Minute) 174 dps := make([]ts.Datapoint, size) 175 s := int64(time.Second) 176 for i := range dps { 177 dps[i] = ts.Datapoint{ 178 Timestamp: now.Add(time.Duration(int(s) * i)), 179 Value: float64(i), 180 } 181 } 182 183 l, r, ok := getIndices(dps, 0, 0, -1) 184 require.Equal(t, -1, l) 185 require.Equal(t, -1, r) 186 require.False(t, ok) 187 188 l, r, ok = getIndices(dps, 0, 0, size) 189 require.Equal(t, -1, l) 190 require.Equal(t, -1, r) 191 require.False(t, ok) 192 193 pastBound := now.Add(time.Hour) 194 l, r, ok = getIndices(dps, pastBound, pastBound+10, 0) 195 require.Equal(t, 0, l) 196 require.Equal(t, 10, r) 197 require.False(t, ok) 198 } 199 200 var _ block.SeriesIter = (*dummySeriesIter)(nil) 201 202 type dummySeriesIter struct { 203 metas []block.SeriesMeta 204 vals []float64 205 idx int 206 } 207 208 func (it *dummySeriesIter) SeriesMeta() []block.SeriesMeta { 209 return it.metas 210 } 211 212 func (it *dummySeriesIter) SeriesCount() int { 213 return len(it.metas) 214 } 215 216 func (it *dummySeriesIter) Current() block.UnconsolidatedSeries { 217 return block.NewUnconsolidatedSeries( 218 ts.Datapoints{ts.Datapoint{Value: it.vals[it.idx]}}, 219 it.metas[it.idx], 220 block.UnconsolidatedSeriesStats{}, 221 ) 222 } 223 224 func (it *dummySeriesIter) Next() bool { 225 if it.idx >= len(it.metas)-1 { 226 return false 227 } 228 229 it.idx++ 230 return true 231 } 232 233 func (it *dummySeriesIter) Err() error { 234 return nil 235 } 236 237 func (it *dummySeriesIter) Close() { 238 //no-op 239 } 240 241 func TestParallelProcess(t *testing.T) { 242 t.Run("no expected warning", func(t *testing.T) { testParallelProcess(t, false) }) 243 t.Run("expected warning", func(t *testing.T) { testParallelProcess(t, true) }) 244 } 245 246 func testParallelProcess(t *testing.T, warning bool) { 247 ctrl := xtest.NewController(t) 248 defer ctrl.Finish() 249 250 tagName := "tag" 251 c, sink := executor.NewControllerWithSink(parser.NodeID(rune(1))) 252 aggProcess := aggProcessor{ 253 aggFunc: func(fs []float64) float64 { 254 require.Equal(t, 1, len(fs)) 255 return fs[0] 256 }, 257 } 258 259 node := baseNode{ 260 controller: c, 261 op: baseOp{duration: time.Minute}, 262 makeProcessor: aggProcess, 263 transformOpts: transform.Options{}, 264 } 265 266 stepSize := time.Minute 267 bl := block.NewMockBlock(ctrl) 268 resultMeta := buildMetadata() 269 if warning { 270 resultMeta = buildWarningMetadata() 271 } 272 273 bl.EXPECT().Meta().Return(block.Metadata{ 274 ResultMetadata: resultMeta, 275 Bounds: models.Bounds{ 276 StepSize: stepSize, 277 Duration: stepSize, 278 }}).AnyTimes() 279 280 numSeries := 10 281 seriesMetas := make([]block.SeriesMeta, 0, numSeries) 282 vals := make([]float64, 0, numSeries) 283 for i := 0; i < numSeries; i++ { 284 number := fmt.Sprint(i) 285 name := []byte(fmt.Sprintf("%d_should_not_appear_after_func_applied", i)) 286 meta := block.SeriesMeta{ 287 Name: []byte(number), 288 Tags: models.MustMakeTags(tagName, number).SetName(name), 289 } 290 291 seriesMetas = append(seriesMetas, meta) 292 vals = append(vals, float64(i)) 293 } 294 295 fullIter := &dummySeriesIter{ 296 idx: -1, 297 vals: vals, 298 metas: seriesMetas, 299 } 300 301 bl.EXPECT().SeriesIter().Return(fullIter, nil).MaxTimes(1) 302 303 numBatches := 3 304 blockMetas := make([][]block.SeriesMeta, 0, numBatches) 305 blockVals := make([][]float64, 0, numBatches) 306 for i := 0; i < numBatches; i++ { 307 l := numSeries/numBatches + 1 308 blockMetas = append(blockMetas, make([]block.SeriesMeta, 0, l)) 309 blockVals = append(blockVals, make([]float64, 0, l)) 310 } 311 312 for i, meta := range seriesMetas { 313 idx := i % numBatches 314 blockMetas[idx] = append(blockMetas[idx], meta) 315 blockVals[idx] = append(blockVals[idx], float64(i)) 316 } 317 318 batches := make([]block.SeriesIterBatch, 0, numBatches) 319 for i := 0; i < numBatches; i++ { 320 iter := &dummySeriesIter{ 321 idx: -1, 322 vals: blockVals[i], 323 metas: blockMetas[i], 324 } 325 326 batches = append(batches, block.SeriesIterBatch{ 327 Iter: iter, 328 Size: len(blockVals[i]), 329 }) 330 } 331 332 bl.EXPECT().MultiSeriesIter(gomock.Any()).Return(batches, nil).MaxTimes(1) 333 bl.EXPECT().Close().Times(1) 334 335 err := node.Process(models.NoopQueryContext(), parser.NodeID(rune(0)), bl) 336 require.NoError(t, err) 337 338 expected := []float64{ 339 0, 3, 6, 9, 340 1, 4, 7, 341 2, 5, 8, 342 } 343 344 for i, v := range sink.Values { 345 assert.Equal(t, expected[i], v[0]) 346 } 347 348 for i, m := range sink.Metas { 349 expected := fmt.Sprint(expected[i]) 350 expectedName := fmt.Sprintf("{tag=\"%s\"}", expected) 351 assert.Equal(t, expectedName, string(m.Name)) 352 require.Equal(t, 1, m.Tags.Len()) 353 tag, found := m.Tags.Get([]byte(tagName)) 354 require.True(t, found) 355 assert.Equal(t, expected, string(tag)) 356 } 357 358 verifyResultMetadata(t, sink.Meta.ResultMetadata, warning) 359 }