github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/graphite/native/aggregation_functions_test.go (about) 1 // Copyright (c) 2019 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 native 22 23 import ( 24 "fmt" 25 "math" 26 "sort" 27 "testing" 28 "time" 29 30 "github.com/m3db/m3/src/query/block" 31 "github.com/m3db/m3/src/query/graphite/common" 32 "github.com/m3db/m3/src/query/graphite/context" 33 "github.com/m3db/m3/src/query/graphite/storage" 34 "github.com/m3db/m3/src/query/graphite/ts" 35 xgomock "github.com/m3db/m3/src/x/test" 36 "github.com/stretchr/testify/assert" 37 "github.com/stretchr/testify/require" 38 39 "github.com/golang/mock/gomock" 40 ) 41 42 var ( 43 consolidationStartTime = time.Now().Truncate(time.Minute).Add(10 * time.Second) 44 consolidationEndTime = consolidationStartTime.Add(1 * time.Minute) 45 ) 46 47 func newConsolidationTestSeries() (*common.Context, []*ts.Series) { 48 ctx := common.NewContext(common.ContextOptions{Start: consolidationStartTime, End: consolidationEndTime}) 49 50 testSeries := []*ts.Series{ 51 ts.NewSeries(ctx, "a", consolidationStartTime, 52 ts.NewConstantValues(ctx, 10, 6, 10000)), 53 ts.NewSeries(ctx, "b", consolidationStartTime.Add(-30*time.Second), 54 ts.NewConstantValues(ctx, 15, 6, 10000)), 55 ts.NewSeries(ctx, "c", consolidationStartTime.Add(30*time.Second), 56 ts.NewConstantValues(ctx, 17, 6, 10000)), 57 ts.NewSeries(ctx, "d", consolidationStartTime, 58 ts.NewConstantValues(ctx, 3, 60, 1000)), 59 } 60 61 return ctx, testSeries 62 } 63 64 func testAggregatedSeries( 65 t *testing.T, 66 f func(ctx *common.Context, series multiplePathSpecs) (ts.SeriesList, error), 67 ev1, ev2, ev3, ev4 float64, 68 errorMessage string, 69 ) { 70 ctx, consolidationTestSeries := newConsolidationTestSeries() 71 defer ctx.Close() 72 73 input := ts.SeriesList{Values: consolidationTestSeries} 74 75 r, err := f(ctx, multiplePathSpecs(input)) 76 require.Nil(t, err) 77 78 series := r.Values 79 require.Equal(t, 1, len(series)) 80 81 require.Equal(t, consolidationTestSeries[1].StartTime(), series[0].StartTime()) 82 require.Equal(t, consolidationTestSeries[2].EndTime(), series[0].EndTime()) 83 require.Equal(t, 12, series[0].Len()) 84 require.Equal(t, 10000, series[0].MillisPerStep()) 85 for i := 0; i < 3; i++ { 86 n := series[0].ValueAt(i) 87 assert.Equal(t, ev1, n, errorMessage, i) 88 } 89 for i := 3; i < 6; i++ { 90 n := series[0].ValueAt(i) 91 assert.Equal(t, ev2, n, errorMessage, i) 92 } 93 for i := 6; i < 9; i++ { 94 n := series[0].ValueAt(i) 95 assert.Equal(t, ev3, n, errorMessage, i) 96 } 97 for i := 9; i < 12; i++ { 98 n := series[0].ValueAt(i) 99 assert.Equal(t, ev4, n, errorMessage, i) 100 } 101 102 // nil input -> nil output 103 for _, in := range [][]*ts.Series{nil, {}} { 104 series, err := f(ctx, multiplePathSpecs(ts.SeriesList{ 105 Values: in, 106 })) 107 require.Nil(t, err) 108 require.Equal(t, in, series.Values) 109 } 110 111 // single input -> same output 112 singleSeries := []*ts.Series{consolidationTestSeries[0]} 113 r, err = f(ctx, multiplePathSpecs(ts.SeriesList{ 114 Values: singleSeries, 115 })) 116 require.Nil(t, err) 117 118 series = r.Values 119 require.Equal(t, singleSeries[0].Len(), series[0].Len()) 120 for i := 0; i < series[0].Len(); i++ { 121 assert.Equal(t, singleSeries[0].ValueAt(i), series[0].ValueAt(i)) 122 } 123 } 124 125 func TestMinSeries(t *testing.T) { 126 testAggregatedSeries(t, minSeries, 15.0, 3.0, 3.0, 17.0, "invalid min value for step %d") 127 } 128 129 func TestMaxSeries(t *testing.T) { 130 testAggregatedSeries(t, maxSeries, 15.0, 15.0, 17.0, 17.0, "invalid max value for step %d") 131 } 132 133 func TestSumSeries(t *testing.T) { 134 testAggregatedSeries(t, func(ctx *common.Context, series multiplePathSpecs) (ts.SeriesList, error) { 135 return sumSeries(ctx, series) 136 }, 15.0, 28.0, 30.0, 17.0, "invalid sum value for step %d") 137 } 138 139 func TestStdDevSeries(t *testing.T) { 140 var ( 141 ctrl = xgomock.NewController(t) 142 store = storage.NewMockStorage(ctrl) 143 engine = NewEngine(store, CompileOptions{}) 144 start, _ = time.Parse(time.RFC1123, "Mon, 27 Jul 2015 19:41:19 GMT") 145 end, _ = time.Parse(time.RFC1123, "Mon, 27 Jul 2015 19:43:19 GMT") 146 ctx = common.NewContext(common.ContextOptions{Start: start, End: end, Engine: engine}) 147 millisPerStep = 60000 148 inputs = []*ts.Series{ 149 ts.NewSeries(ctx, "servers.s2", start, 150 common.NewTestSeriesValues(ctx, millisPerStep, []float64{10, 20, 30})), 151 ts.NewSeries(ctx, "servers.s1", start, 152 common.NewTestSeriesValues(ctx, millisPerStep, []float64{90, 80, 70})), 153 } 154 ) 155 156 expectedResults := []common.TestSeries{ 157 { 158 Name: "stddevSeries(servers.s2,servers.s1)", 159 Data: []float64{40, 30, 20}, 160 }, 161 } 162 result, err := stddevSeries(ctx, multiplePathSpecs{ 163 Values: inputs, 164 }) 165 require.NoError(t, err) 166 common.CompareOutputsAndExpected(t, 60000, start, expectedResults, result.Values) 167 } 168 169 func TestPowSeries(t *testing.T) { 170 var ( 171 ctrl = xgomock.NewController(t) 172 store = storage.NewMockStorage(ctrl) 173 now = time.Now().Truncate(time.Hour) 174 engine = NewEngine(store, CompileOptions{}) 175 startTime = now.Add(-3 * time.Minute) 176 endTime = now.Add(-time.Minute) 177 ctx = common.NewContext(common.ContextOptions{ 178 Start: startTime, 179 End: endTime, 180 Engine: engine, 181 }) 182 ) 183 184 fakeSeries1 := ts.NewSeries(ctx, "foo.bar.g.zed.g", startTime, 185 common.NewTestSeriesValues(ctx, 60000, []float64{0, 1, 2, 3, 4})) 186 fakeSeries2 := ts.NewSeries(ctx, "foo.bar.g.zed.g", startTime, 187 common.NewTestSeriesValues(ctx, 60000, []float64{2, 4, 1, 3, 3})) 188 fakeSeries3 := ts.NewSeries(ctx, "foo.bar.g.zed.g", startTime, 189 common.NewTestSeriesValues(ctx, 60000, []float64{5, 4, 3, 2, 1})) 190 191 listOfFakeSeries := []*ts.Series{fakeSeries1, fakeSeries2, fakeSeries3} 192 193 expectedValues := []float64{0, 1, 8, 729, 64} 194 result, err := powSeries(ctx, multiplePathSpecs(singlePathSpec{Values: listOfFakeSeries})) 195 if err != nil { 196 fmt.Println(err) 197 } 198 for i := 0; i < result.Values[0].Len(); i++ { 199 require.Equal(t, result.Values[0].ValueAt(i), expectedValues[i]) 200 } 201 } 202 203 func TestAggregate(t *testing.T) { 204 testAggregatedSeries(t, func(ctx *common.Context, series multiplePathSpecs) (ts.SeriesList, error) { 205 return aggregate(ctx, singlePathSpec(series), "sum") 206 }, 15.0, 28.0, 30.0, 17.0, "invalid sum value for step %d") 207 208 testAggregatedSeries(t, func(ctx *common.Context, series multiplePathSpecs) (ts.SeriesList, error) { 209 return aggregate(ctx, singlePathSpec(series), "maxSeries") 210 }, 15.0, 15.0, 17.0, 17.0, "invalid max value for step %d") 211 } 212 213 func TestAggregateSeriesMedian(t *testing.T) { 214 var ( 215 ctrl = xgomock.NewController(t) 216 store = storage.NewMockStorage(ctrl) 217 engine = NewEngine(store, CompileOptions{}) 218 start, _ = time.Parse(time.RFC1123, "Mon, 27 Jul 2015 19:41:19 GMT") 219 end, _ = time.Parse(time.RFC1123, "Mon, 27 Jul 2015 19:43:19 GMT") 220 ctx = common.NewContext(common.ContextOptions{Start: start, End: end, Engine: engine}) 221 millisPerStep = 60000 222 inputs = []*ts.Series{ 223 ts.NewSeries(ctx, "servers.s2", start, 224 common.NewTestSeriesValues(ctx, millisPerStep, []float64{10, 20, 30})), 225 ts.NewSeries(ctx, "servers.s1", start, 226 common.NewTestSeriesValues(ctx, millisPerStep, []float64{90, 80, 70})), 227 ts.NewSeries(ctx, "servers.s3", start, 228 common.NewTestSeriesValues(ctx, millisPerStep, []float64{5, 100, 45})), 229 } 230 ) 231 232 expectedResults := []common.TestSeries{ 233 { 234 Name: "medianSeries(servers.s2,servers.s1,servers.s3)", 235 Data: []float64{10, 80, 45}, 236 }, 237 } 238 result, err := aggregate(ctx, singlePathSpec{ 239 Values: inputs, 240 }, "median") 241 require.NoError(t, err) 242 common.CompareOutputsAndExpected(t, 60000, start, expectedResults, result.Values) 243 } 244 245 type mockEngine struct { 246 fn func( 247 ctx context.Context, 248 query string, 249 options storage.FetchOptions, 250 ) (*storage.FetchResult, error) 251 252 storage storage.Storage 253 } 254 255 func (e mockEngine) FetchByQuery( 256 ctx context.Context, 257 query string, 258 opts storage.FetchOptions, 259 ) (*storage.FetchResult, error) { 260 return e.fn(ctx, query, opts) 261 } 262 263 func (e mockEngine) Storage() storage.Storage { 264 return nil 265 } 266 267 func TestVariadicSumSeries(t *testing.T) { 268 expr, err := Compile("sumSeries(foo.bar.*, foo.baz.*)", CompileOptions{}) 269 require.NoError(t, err) 270 ctx := common.NewTestContext() 271 ctx.Engine = mockEngine{fn: func( 272 ctx context.Context, 273 query string, 274 options storage.FetchOptions, 275 ) (*storage.FetchResult, error) { 276 start := options.StartTime 277 switch query { 278 case "foo.bar.*": 279 return storage.NewFetchResult(ctx, []*ts.Series{ 280 ts.NewSeries(ctx, "foo.bar.a", start, ts.NewConstantValues(ctx, 1, 3, 1000)), 281 ts.NewSeries(ctx, "foo.bar.b", start, ts.NewConstantValues(ctx, 2, 3, 1000)), 282 }, block.NewResultMetadata()), nil 283 case "foo.baz.*": 284 return storage.NewFetchResult(ctx, []*ts.Series{ 285 ts.NewSeries(ctx, "foo.baz.a", start, ts.NewConstantValues(ctx, 3, 3, 1000)), 286 ts.NewSeries(ctx, "foo.baz.b", start, ts.NewConstantValues(ctx, 4, 3, 1000)), 287 }, block.ResultMetadata{ 288 Exhaustive: false, 289 LocalOnly: false, 290 Warnings: []block.Warning{{Name: "foo", Message: "bar"}}, 291 }), nil 292 } 293 return nil, fmt.Errorf("unexpected query: %s", query) 294 }} 295 296 r, err := expr.Execute(ctx) 297 require.NoError(t, err) 298 299 require.Equal(t, 1, r.Len()) 300 assert.Equal(t, []float64{10, 10, 10}, r.Values[0].SafeValues()) 301 assert.False(t, r.Metadata.Exhaustive) 302 assert.False(t, r.Metadata.LocalOnly) 303 require.Equal(t, 1, len(r.Metadata.Warnings)) 304 assert.Equal(t, "foo_bar", r.Metadata.Warnings[0].Header()) 305 } 306 307 func TestDiffSeries(t *testing.T) { 308 testAggregatedSeries(t, diffSeries, -15.0, -8.0, -10.0, -17.0, "invalid diff value for step %d") 309 } 310 311 func TestMultiplySeries(t *testing.T) { 312 testAggregatedSeries(t, multiplySeries, 15.0, 450.0, 510.0, 17.0, "invalid product value for step %d") 313 } 314 315 func TestAverageSeries(t *testing.T) { 316 testAggregatedSeries(t, averageSeries, 15.0, 28.0/3, 10.0, 17.0, "invalid avg value for step %d") 317 } 318 319 func TestDivideSeries(t *testing.T) { 320 ctx, consolidationTestSeries := newConsolidationTestSeries() 321 defer ctx.Close() 322 323 // multiple series, different start/end times 324 nan := math.NaN() 325 series, err := divideSeries(ctx, singlePathSpec{ 326 Values: consolidationTestSeries[0:2], 327 }, singlePathSpec{ 328 Values: consolidationTestSeries[2:3], 329 }) 330 require.Nil(t, err) 331 expected := []common.TestSeries{ 332 { 333 Name: "divideSeries(a,c)", 334 Data: []float64{nan, nan, nan, 0.5882, 0.5882, 0.5882, nan, nan, nan}, 335 }, 336 { 337 Name: "divideSeries(b,c)", 338 Data: []float64{nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan}, 339 }, 340 } 341 342 common.CompareOutputsAndExpected(t, 10000, consolidationStartTime, 343 []common.TestSeries{expected[0]}, []*ts.Series{series.Values[0]}) 344 common.CompareOutputsAndExpected(t, 10000, consolidationStartTime.Add(-30*time.Second), 345 []common.TestSeries{expected[1]}, []*ts.Series{series.Values[1]}) 346 347 // different millisPerStep, same start/end times 348 series, err = divideSeries(ctx, singlePathSpec{ 349 Values: consolidationTestSeries[0:1], 350 }, singlePathSpec{ 351 Values: consolidationTestSeries[3:4], 352 }) 353 require.Nil(t, err) 354 expected = []common.TestSeries{ 355 { 356 Name: "divideSeries(a,d)", 357 Data: []float64{3.3333, 3.3333, 3.3333, 3.3333, 3.33333, 3.3333}, 358 }, 359 } 360 common.CompareOutputsAndExpected(t, 10000, consolidationStartTime, 361 []common.TestSeries{expected[0]}, []*ts.Series{series.Values[0]}) 362 363 // empty series 364 series, err = divideSeries(ctx, singlePathSpec{ 365 Values: []*ts.Series{}, 366 }, singlePathSpec{ 367 Values: consolidationTestSeries, 368 }) 369 require.Nil(t, err) 370 require.Equal(t, series, ts.NewSeriesList()) 371 } 372 373 func TestDivideSeriesError(t *testing.T) { 374 ctx, consolidationTestSeries := newConsolidationTestSeries() 375 defer ctx.Close() 376 377 // error - multiple divisor series 378 _, err := divideSeries(ctx, singlePathSpec{ 379 Values: consolidationTestSeries, 380 }, singlePathSpec{ 381 Values: consolidationTestSeries, 382 }) 383 require.Error(t, err) 384 require.Equal(t, err.Error(), "divideSeries second argument must reference exactly one series but instead has 4") 385 } 386 387 func TestDivideSeriesLists(t *testing.T) { 388 ctx, consolidationTestSeries := newConsolidationTestSeries() 389 defer ctx.Close() 390 391 // multiple series, different start/end times 392 nan := math.NaN() 393 series, err := divideSeriesLists(ctx, singlePathSpec{ 394 Values: consolidationTestSeries[:2], 395 }, singlePathSpec{ 396 Values: consolidationTestSeries[2:], 397 }) 398 require.Nil(t, err) 399 expected := []common.TestSeries{ 400 { 401 Name: "divideSeries(a,c)", 402 Data: []float64{nan, nan, nan, 0.5882, 0.5882, 0.5882, nan, nan, nan}, 403 }, 404 { 405 Name: "divideSeries(b,d)", 406 Data: []float64{nan, nan, nan, 5, 5, 5, nan, nan, nan}, 407 }, 408 } 409 410 common.CompareOutputsAndExpected(t, 10000, consolidationStartTime, 411 []common.TestSeries{expected[0]}, []*ts.Series{series.Values[0]}) 412 common.CompareOutputsAndExpected(t, 10000, consolidationStartTime.Add(-30*time.Second), 413 []common.TestSeries{expected[1]}, []*ts.Series{series.Values[1]}) 414 415 // different millisPerStep, same start/end times 416 consolidationTestSeries[0], consolidationTestSeries[2] = consolidationTestSeries[2], consolidationTestSeries[0] 417 consolidationTestSeries[1], consolidationTestSeries[3] = consolidationTestSeries[3], consolidationTestSeries[1] 418 series, err = divideSeriesLists(ctx, singlePathSpec{ 419 Values: consolidationTestSeries[:2], 420 }, singlePathSpec{ 421 Values: consolidationTestSeries[2:], 422 }) 423 require.Nil(t, err) 424 expected = []common.TestSeries{ 425 { 426 Name: "divideSeries(c,a)", 427 Data: []float64{nan, nan, nan, 1.7, 1.7, 1.7, nan, nan, nan}, 428 }, 429 { 430 Name: "divideSeries(d,b)", 431 Data: []float64{nan, nan, nan, 0.2, 0.2, 0.2, nan, nan, nan}, 432 }, 433 } 434 common.CompareOutputsAndExpected(t, 10000, consolidationStartTime, 435 []common.TestSeries{expected[0]}, []*ts.Series{series.Values[0]}) 436 437 // error - multiple divisor series 438 series, err = divideSeries(ctx, singlePathSpec{ 439 Values: consolidationTestSeries, 440 }, singlePathSpec{ 441 Values: consolidationTestSeries, 442 }) 443 require.Error(t, err) 444 } 445 446 // TestDivideSeriesListsWithUnsortedSeriesInput ensures that if input into 447 // the function wasn't sorted as input that it becomes sorted before dividing 448 // two series lists (to ensure deterministic results). 449 func TestDivideSeriesListsWithUnsortedSeriesInput(t *testing.T) { 450 start := time.Now().Truncate(time.Minute).Add(-10 * time.Minute) 451 end := start.Add(5 * time.Minute) 452 ctx := common.NewContext(common.ContextOptions{Start: start, End: end}) 453 454 dividend := []*ts.Series{ 455 ts.NewSeries(ctx, "a", start, 456 ts.NewConstantValues(ctx, 1, 5, 60000)), 457 ts.NewSeries(ctx, "c", start, 458 ts.NewConstantValues(ctx, 3, 5, 60000)), 459 ts.NewSeries(ctx, "b", start, 460 ts.NewConstantValues(ctx, 2, 5, 60000)), 461 } 462 463 divisor := []*ts.Series{ 464 ts.NewSeries(ctx, "b", start, 465 ts.NewConstantValues(ctx, 2, 5, 60000)), 466 ts.NewSeries(ctx, "a", start, 467 ts.NewConstantValues(ctx, 1, 5, 60000)), 468 ts.NewSeries(ctx, "d", start, 469 ts.NewConstantValues(ctx, 3, 5, 60000)), 470 } 471 472 actual, err := divideSeriesLists(ctx, singlePathSpec{ 473 Values: dividend, 474 }, singlePathSpec{ 475 Values: divisor, 476 }) 477 require.Nil(t, err) 478 expected := []common.TestSeries{ 479 { 480 Name: "divideSeries(a,a)", 481 Data: []float64{1, 1, 1, 1, 1}, 482 }, 483 { 484 Name: "divideSeries(b,b)", 485 Data: []float64{1, 1, 1, 1, 1}, 486 }, 487 { 488 Name: "divideSeries(c,d)", 489 Data: []float64{1, 1, 1, 1, 1}, 490 }, 491 } 492 493 common.CompareOutputsAndExpected(t, 60000, start, expected, actual.Values) 494 } 495 496 //nolint:govet 497 func TestAverageSeriesWithWildcards(t *testing.T) { 498 ctx, _ := newConsolidationTestSeries() 499 defer ctx.Close() 500 501 input := []common.TestSeries{ 502 {"web.host-1.avg-response.value", []float64{70.0, 20.0, 30.0, 40.0, 50.0}}, 503 {"web.host-2.avg-response.value", []float64{20.0, 30.0, 40.0, 50.0, 60.0}}, 504 {"web.host-3.avg-response.value", []float64{30.0, 40.0, 80.0, 60.0, 70.0}}, 505 {"web.host-4.num-requests.value", []float64{10.0, 10.0, 15.0, 10.0, 15.0}}, 506 } 507 expected := []common.TestSeries{ 508 {"web.avg-response", []float64{40.0, 30.0, 50.0, 50.0, 60.0}}, 509 {"web.num-requests", []float64{10.0, 10.0, 15.0, 10.0, 15.0}}, 510 } 511 512 start := consolidationStartTime 513 step := 12000 514 timeSeries := generateSeriesList(ctx, start, input, step) 515 output, err := averageSeriesWithWildcards(ctx, singlePathSpec{ 516 Values: timeSeries, 517 }, 1, 3) 518 require.NoError(t, err) 519 sort.Sort(TimeSeriesPtrVector(output.Values)) 520 common.CompareOutputsAndExpected(t, step, start, expected, output.Values) 521 } 522 523 func createTestSeriesForAggregation() (*common.Context, []*ts.Series) { 524 var ( 525 start, _ = time.Parse(time.RFC1123, "Mon, 27 Jul 2015 19:41:19 GMT") 526 end, _ = time.Parse(time.RFC1123, "Mon, 27 Jul 2015 19:43:19 GMT") 527 ctx = common.NewContext(common.ContextOptions{Start: start, End: end}) 528 series = []*ts.Series{ 529 ts.NewSeries(ctx, "servers.foo-1.pod1.status.500", start, 530 ts.NewConstantValues(ctx, 2, 12, 10000)), 531 ts.NewSeries(ctx, "servers.foo-2.pod1.status.500", start, 532 ts.NewConstantValues(ctx, 4, 12, 10000)), 533 ts.NewSeries(ctx, "servers.foo-3.pod1.status.500", start, 534 ts.NewConstantValues(ctx, 6, 12, 10000)), 535 ts.NewSeries(ctx, "servers.foo-1.pod2.status.500", start, 536 ts.NewConstantValues(ctx, 8, 12, 10000)), 537 ts.NewSeries(ctx, "servers.foo-2.pod2.status.500", start, 538 ts.NewConstantValues(ctx, 10, 12, 10000)), 539 540 ts.NewSeries(ctx, "servers.foo-1.pod1.status.400", start, 541 ts.NewConstantValues(ctx, 20, 12, 10000)), 542 ts.NewSeries(ctx, "servers.foo-2.pod1.status.400", start, 543 ts.NewConstantValues(ctx, 30, 12, 10000)), 544 ts.NewSeries(ctx, "servers.foo-3.pod2.status.400", start, 545 ts.NewConstantValues(ctx, 40, 12, 10000)), 546 } 547 ) 548 return ctx, series 549 } 550 551 func TestSumSeriesWithWildcards(t *testing.T) { 552 ctx, inputs := createTestSeriesForAggregation() 553 defer ctx.Close() 554 555 outSeries, err := sumSeriesWithWildcards(ctx, singlePathSpec{ 556 Values: inputs, 557 }, 1, 2) 558 require.NoError(t, err) 559 require.Equal(t, 2, len(outSeries.Values)) 560 561 outSeries, _ = sortByName(ctx, singlePathSpec(outSeries), false, false) 562 563 expectedOutputs := []struct { 564 name string 565 sumOfVals float64 566 }{ 567 {"servers.status.400", 90 * 12}, 568 {"servers.status.500", 30 * 12}, 569 } 570 571 for i, expected := range expectedOutputs { 572 series := outSeries.Values[i] 573 assert.Equal(t, expected.name, series.Name()) 574 assert.Equal(t, expected.sumOfVals, series.SafeSum()) 575 } 576 } 577 578 func TestMultiplySeriesWithWildcards(t *testing.T) { 579 ctx, inputs := createTestSeriesForAggregation() 580 defer func() { _ = ctx.Close() }() 581 582 outSeries, err := multiplySeriesWithWildcards(ctx, singlePathSpec{ 583 Values: inputs, 584 }, 1, 2) 585 require.NoError(t, err) 586 require.Equal(t, 2, len(outSeries.Values)) 587 588 outSeries, _ = sortByName(ctx, singlePathSpec(outSeries), false, false) 589 590 expectedOutputs := []struct { 591 name string 592 sumOfVals float64 593 }{ 594 {"servers.status.400", 20 * 30 * 40 * 12}, 595 {"servers.status.500", 2 * 4 * 6 * 8 * 10 * 12}, 596 } 597 598 for i, expected := range expectedOutputs { 599 series := outSeries.Values[i] 600 assert.Equal(t, expected.name, series.Name()) 601 assert.Equal(t, expected.sumOfVals, series.SafeSum()) 602 } 603 } 604 605 func TestApplyByNode(t *testing.T) { 606 var ( 607 ctrl = xgomock.NewController(t) 608 store = storage.NewMockStorage(ctrl) 609 engine = NewEngine(store, CompileOptions{}) 610 start, _ = time.Parse(time.RFC1123, "Mon, 27 Jul 2015 19:41:19 GMT") 611 end, _ = time.Parse(time.RFC1123, "Mon, 27 Jul 2015 19:43:19 GMT") 612 ctx = common.NewContext(common.ContextOptions{Start: start, End: end, Engine: engine}) 613 millisPerStep = 60000 614 inputs = []*ts.Series{ 615 ts.NewSeries(ctx, "servers.s1.disk.bytes_used", start, 616 common.NewTestSeriesValues(ctx, millisPerStep, []float64{10, 20, 30})), 617 ts.NewSeries(ctx, "servers.s1.disk.bytes_free", start, 618 common.NewTestSeriesValues(ctx, millisPerStep, []float64{90, 80, 70})), 619 ts.NewSeries(ctx, "servers.s2.disk.bytes_used", start, 620 common.NewTestSeriesValues(ctx, millisPerStep, []float64{1, 2, 3})), 621 ts.NewSeries(ctx, "servers.s2.disk.bytes_free", start, 622 common.NewTestSeriesValues(ctx, millisPerStep, []float64{99, 98, 97})), 623 } 624 ) 625 626 defer ctrl.Finish() 627 defer ctx.Close() 628 629 store.EXPECT().FetchByQuery(gomock.Any(), "servers.s1.disk.bytes_used", gomock.Any()).Return( 630 &storage.FetchResult{SeriesList: []*ts.Series{ts.NewSeries(ctx, "servers.s1.disk.bytes_used", start, 631 common.NewTestSeriesValues(ctx, 60000, []float64{10, 20, 30}))}}, nil).Times(2) 632 633 store.EXPECT().FetchByQuery(gomock.Any(), "servers.s1.disk.bytes_*", gomock.Any()).Return( 634 &storage.FetchResult{SeriesList: []*ts.Series{ 635 ts.NewSeries(ctx, "servers.s1.disk.bytes_free", start, 636 common.NewTestSeriesValues(ctx, 60000, []float64{90, 80, 70})), 637 ts.NewSeries(ctx, "servers.s1.disk.bytes_used", start, 638 common.NewTestSeriesValues(ctx, 60000, []float64{10, 20, 30})), 639 }}, nil).Times(2) 640 641 store.EXPECT().FetchByQuery(gomock.Any(), "servers.s2.disk.bytes_used", gomock.Any()).Return( 642 &storage.FetchResult{SeriesList: []*ts.Series{ts.NewSeries(ctx, "servers.s2.disk.bytes_used", start, 643 common.NewTestSeriesValues(ctx, 60000, []float64{1, 2, 3}))}}, nil).Times(2) 644 645 store.EXPECT().FetchByQuery(gomock.Any(), "servers.s2.disk.bytes_*", gomock.Any()).Return( 646 &storage.FetchResult{SeriesList: []*ts.Series{ 647 ts.NewSeries(ctx, "servers.s2.disk.bytes_free", start, 648 common.NewTestSeriesValues(ctx, 60000, []float64{99, 98, 97})), 649 ts.NewSeries(ctx, "servers.s2.disk.bytes_used", start, 650 common.NewTestSeriesValues(ctx, 60000, []float64{1, 2, 3})), 651 }}, nil).Times(2) 652 653 tests := []struct { 654 nodeNum int 655 templateFunction string 656 newName string 657 expectedResults []common.TestSeries 658 }{ 659 { 660 nodeNum: 1, 661 templateFunction: "divideSeries(%.disk.bytes_used, sumSeries(%.disk.bytes_*))", 662 newName: "", 663 expectedResults: []common.TestSeries{ 664 { 665 Name: "divideSeries(servers.s1.disk.bytes_used,sumSeries(servers.s1.disk.bytes_*))", 666 Data: []float64{0.10, 0.20, 0.30}, 667 }, 668 { 669 Name: "divideSeries(servers.s2.disk.bytes_used,sumSeries(servers.s2.disk.bytes_*))", 670 Data: []float64{0.01, 0.02, 0.03}, 671 }, 672 }, 673 }, 674 { 675 nodeNum: 1, 676 templateFunction: "divideSeries(%.disk.bytes_used, sumSeries(%.disk.bytes_*))", 677 newName: "%.disk.pct_used", 678 expectedResults: []common.TestSeries{ 679 { 680 Name: "servers.s1.disk.pct_used", 681 Data: []float64{0.10, 0.20, 0.30}, 682 }, 683 { 684 Name: "servers.s2.disk.pct_used", 685 Data: []float64{0.01, 0.02, 0.03}, 686 }, 687 }, 688 }, 689 } 690 691 for _, test := range tests { 692 outSeries, err := applyByNode( 693 ctx, 694 singlePathSpec{ 695 Values: inputs, 696 }, 697 test.nodeNum, 698 test.templateFunction, 699 test.newName, 700 ) 701 require.NoError(t, err) 702 require.Equal(t, len(test.expectedResults), len(outSeries.Values)) 703 704 outSeries, _ = sortByName(ctx, singlePathSpec(outSeries), false, false) 705 common.CompareOutputsAndExpected(t, 60000, start, test.expectedResults, outSeries.Values) 706 } 707 } 708 709 func TestAggregateWithWildcards(t *testing.T) { 710 ctx, inputs := createTestSeriesForAggregation() 711 defer ctx.Close() 712 713 type result struct { 714 name string 715 sumOfVals float64 716 } 717 718 tests := []struct { 719 fname string 720 nodes []int 721 expectedResults []result 722 }{ 723 {"avg", []int{1, 2}, []result{ 724 {"servers.status.400", ((20 + 30 + 40) / 3) * 12}, 725 {"servers.status.500", ((2 + 4 + 6 + 8 + 10) / 5) * 12}, 726 }}, 727 {"max", []int{2, 4}, []result{ 728 {"servers.status.400", 40 * 12}, 729 {"servers.status.500", 10 * 12}, 730 }}, 731 {"min", []int{2, -1}, []result{ 732 {"servers.status.400", 20 * 12}, 733 {"servers.status.500", 2 * 12}, 734 }}, 735 {"median", []int{1, 2}, []result{ 736 {"servers.status.400", 30 * 12}, 737 {"servers.status.500", 6 * 12}, 738 }}, 739 } 740 741 for _, test := range tests { 742 outSeries, err := aggregateWithWildcards(ctx, singlePathSpec{ 743 Values: inputs, 744 }, test.fname, 1, 2) 745 require.NoError(t, err) 746 require.Equal(t, 2, len(outSeries.Values)) 747 748 outSeries, _ = sortByName(ctx, singlePathSpec(outSeries), false, false) 749 750 for i, expected := range test.expectedResults { 751 series := outSeries.Values[i] 752 assert.Equal(t, expected.name, series.Name(), "wrong name for %v %s (%d)", test.nodes, test.fname, i) 753 754 assert.Equal(t, expected.sumOfVals, series.SafeSum(), 755 "wrong result for %v %s (%d)", test.nodes, test.fname, i) 756 } 757 } 758 } 759 760 func TestGroupByNode(t *testing.T) { 761 ctx, inputs := createTestSeriesForAggregation() 762 defer ctx.Close() 763 764 type result struct { 765 name string 766 sumOfVals float64 767 } 768 769 tests := []struct { 770 fname string 771 node int 772 expectedResults []result 773 }{ 774 {"avg", 4, []result{ 775 {"400", ((20 + 30 + 40) / 3) * 12}, 776 {"500", ((2 + 4 + 6 + 8 + 10) / 5) * 12}, 777 }}, 778 {"max", 2, []result{ 779 {"pod1", 30 * 12}, 780 {"pod2", 40 * 12}, 781 }}, 782 {"min", -1, []result{ 783 {"400", 20 * 12}, 784 {"500", 2 * 12}, 785 }}, 786 } 787 788 for _, test := range tests { 789 outSeries, err := groupByNode(ctx, singlePathSpec{ 790 Values: inputs, 791 }, test.node, test.fname) 792 require.NoError(t, err) 793 require.Equal(t, len(test.expectedResults), len(outSeries.Values)) 794 795 outSeries, _ = sortByName(ctx, singlePathSpec(outSeries), false, false) 796 797 for i, expected := range test.expectedResults { 798 series := outSeries.Values[i] 799 assert.Equal(t, expected.name, series.Name(), 800 "wrong name for %d %s (%d)", test.node, test.fname, i) 801 assert.Equal(t, expected.sumOfVals, series.SafeSum(), 802 "wrong result for %d %s (%d)", test.node, test.fname, i) 803 } 804 } 805 } 806 807 func TestGroupByNodes(t *testing.T) { 808 var ( 809 start, _ = time.Parse(time.RFC1123, "Mon, 27 Jul 2015 19:41:19 GMT") 810 end, _ = time.Parse(time.RFC1123, "Mon, 27 Jul 2015 19:43:19 GMT") 811 ctx = common.NewContext(common.ContextOptions{Start: start, End: end}) 812 inputs = []*ts.Series{ 813 ts.NewSeries(ctx, "transformNull(servers.foo-1.pod1.status.500)", start, 814 ts.NewConstantValues(ctx, 2, 12, 10000)), 815 ts.NewSeries(ctx, "scaleToSeconds(servers.foo-2.pod1.status.500,1)", start, 816 ts.NewConstantValues(ctx, 4, 12, 10000)), 817 ts.NewSeries(ctx, "servers.foo-3.pod1.status.500", start, 818 ts.NewConstantValues(ctx, 6, 12, 10000)), 819 ts.NewSeries(ctx, "servers.foo-1.pod2.status.500", start, 820 ts.NewConstantValues(ctx, 8, 12, 10000)), 821 ts.NewSeries(ctx, "servers.foo-2.pod2.status.500", start, 822 ts.NewConstantValues(ctx, 10, 12, 10000)), 823 824 ts.NewSeries(ctx, "servers.foo-1.pod1.status.400", start, 825 ts.NewConstantValues(ctx, 20, 12, 10000)), 826 ts.NewSeries(ctx, "servers.foo-2.pod1.status.400", start, 827 ts.NewConstantValues(ctx, 30, 12, 10000)), 828 ts.NewSeries(ctx, "servers.foo-3.pod2.status.400", start, 829 ts.NewConstantValues(ctx, 40, 12, 10000)), 830 } 831 ) 832 833 defer ctx.Close() 834 835 type result struct { 836 name string 837 sumOfVals float64 838 } 839 840 tests := []struct { 841 fname string 842 nodes []int 843 expectedResults []result 844 }{ 845 {"avg", []int{2, 4}, []result{ // test normal group by nodes 846 {"pod1.400", ((20 + 30) / 2) * 12}, 847 {"pod1.500", ((2 + 4 + 6) / 3) * 12}, 848 {"pod2.400", (40 / 1) * 12}, 849 {"pod2.500", ((8 + 10) / 2) * 12}, 850 }}, 851 {"max", []int{2, 4}, []result{ // test with different function 852 {"pod1.400", 30 * 12}, 853 {"pod1.500", 6 * 12}, 854 {"pod2.400", 40 * 12}, 855 {"pod2.500", 10 * 12}, 856 }}, 857 {"median", []int{2, 4}, []result{ // test with different function 858 {"pod1.400", ((20 + 30) / 2) * 12}, 859 {"pod1.500", 4 * 12}, 860 {"pod2.400", 40 * 12}, 861 {"pod2.500", ((8 + 10) / 2) * 12}, 862 }}, 863 {"max", []int{2, 4, 100}, []result{ // test with a node number that exceeds num parts 864 {"pod1.400.", 30 * 12}, 865 {"pod1.500.", 6 * 12}, 866 {"pod2.400.", 40 * 12}, 867 {"pod2.500.", 10 * 12}, 868 }}, 869 {"min", []int{2, -1}, []result{ // test negative index handling 870 {"pod1.400", 20 * 12}, 871 {"pod1.500", 2 * 12}, 872 {"pod2.400", 40 * 12}, 873 {"pod2.500", 8 * 12}, 874 }}, 875 {"sum", []int{}, []result{ // test empty slice handing. 876 { 877 "sumSeries(transformNull(servers.foo-1.pod1.status.500)," + 878 "scaleToSeconds(servers.foo-2.pod1.status.500,1)," + 879 "servers.foo-3.pod1.status.500,servers.foo-1.pod2.status.500," + 880 "servers.foo-2.pod2.status.500,servers.foo-1.pod1.status.400," + 881 "servers.foo-2.pod1.status.400,servers.foo-3.pod2.status.400)", 882 (2 + 4 + 6 + 8 + 10 + 20 + 30 + 40) * 12, 883 }, 884 }}, 885 {"sum", []int{100}, []result{ // test all nodes out of bounds 886 {"", (2 + 4 + 6 + 8 + 10 + 20 + 30 + 40) * 12}, 887 }}, 888 } 889 890 for _, test := range tests { 891 outSeries, err := groupByNodes(ctx, singlePathSpec{ 892 Values: inputs, 893 }, test.fname, test.nodes...) 894 require.NoError(t, err) 895 require.Equal(t, len(test.expectedResults), len(outSeries.Values)) 896 897 outSeries, _ = sortByName(ctx, singlePathSpec(outSeries), false, false) 898 899 for i, expected := range test.expectedResults { 900 series := outSeries.Values[i] 901 assert.Equal(t, expected.name, series.Name(), 902 "wrong name for %v %s (%d)", test.nodes, test.fname, i) 903 assert.Equal(t, expected.sumOfVals, series.SafeSum(), 904 "wrong result for %v %s (%d)", test.nodes, test.fname, i) 905 } 906 } 907 } 908 909 func TestWeightedAverage(t *testing.T) { 910 ctx, _ := newConsolidationTestSeries() 911 defer ctx.Close() 912 913 means := []common.TestSeries{ 914 {Name: "web.host-1.avg-response.mean", Data: []float64{70.0, 20.0, 30.0, 0.0, 50.0}}, 915 {Name: "web.host-2.avg-response.mean", Data: []float64{20.0, 30.0, 40.0, 50.0, 60.0}}, 916 {Name: "web.host-3.avg-response.mean", Data: []float64{20.0, 30.0, 40.0, 50.0, 60.0}}, // no match 917 } 918 counts := []common.TestSeries{ 919 {Name: "web.host-1.avg-response.count", Data: []float64{1, 2, 3, 4, 5}}, 920 {Name: "web.host-2.avg-response.count", Data: []float64{10, 20, 30, 40, 50}}, 921 {Name: "web.host-4.avg-response.count", Data: []float64{10, 20, 30, 40, 50}}, // no match 922 } 923 expected := []common.TestSeries{ 924 {Name: "weightedAverage", Data: []float64{24.5454, 29.0909, 39.0909, 45.4545, 59.0909}}, 925 } 926 927 // normal series 928 start := consolidationStartTime 929 step := 12000 930 values := ts.SeriesList{Values: generateSeriesList(ctx, start, means, step)} 931 weights := ts.SeriesList{Values: generateSeriesList(ctx, start, counts, step)} 932 output, err := weightedAverage(ctx, singlePathSpec(values), singlePathSpec(weights), 1) 933 require.NoError(t, err) 934 sort.Sort(TimeSeriesPtrVector(output.Values)) 935 common.CompareOutputsAndExpected(t, step, start, expected, output.Values) 936 937 // one series as input, should return the same as output no matter what the weight 938 values = ts.SeriesList{Values: generateSeriesList(ctx, start, means[:1], step)} 939 weights = ts.SeriesList{Values: generateSeriesList(ctx, start, counts[:1], step)} 940 output, err = weightedAverage(ctx, singlePathSpec(values), singlePathSpec(weights), 1) 941 require.NoError(t, err) 942 common.CompareOutputsAndExpected(t, step, start, 943 []common.TestSeries{{Name: "weightedAverage", Data: means[0].Data}}, output.Values) 944 945 // different steps should lead to error -- not supported yet 946 values = ts.SeriesList{Values: generateSeriesList(ctx, start, means, step)} 947 weights = ts.SeriesList{Values: generateSeriesList(ctx, start, counts, step*2)} 948 output, err = weightedAverage(ctx, singlePathSpec(values), singlePathSpec(weights), 1) 949 require.EqualError(t, err, "different step sizes in input series not supported") 950 } 951 952 func TestCountSeries(t *testing.T) { 953 ctx, input := newConsolidationTestSeries() 954 defer ctx.Close() 955 956 results, err := countSeries(ctx, multiplePathSpecs(ts.SeriesList{ 957 Values: input, 958 })) 959 expected := common.TestSeries{ 960 Name: "countSeries(a,b,c,d)", 961 Data: []float64{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, 962 } 963 require.Nil(t, err) 964 common.CompareOutputsAndExpected(t, input[1].MillisPerStep(), input[1].StartTime(), 965 []common.TestSeries{expected}, results.Values) 966 }