bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/cmd/bosun/expr/funcs_test.go (about) 1 package expr 2 3 import ( 4 "fmt" 5 "math" 6 "testing" 7 "time" 8 9 "bosun.org/opentsdb" 10 11 "github.com/influxdata/influxdb/client/v2" 12 ) 13 14 type exprInOut struct { 15 expr string 16 out Results 17 shouldParseErr bool 18 } 19 20 func testExpression(eio exprInOut, t *testing.T) error { 21 e, err := New(eio.expr, builtins) 22 if eio.shouldParseErr { 23 if err == nil { 24 return fmt.Errorf("no error when expected error on %v", eio.expr) 25 } 26 return nil 27 } 28 if err != nil { 29 return err 30 } 31 backends := &Backends{ 32 InfluxConfig: client.HTTPConfig{}, 33 } 34 providers := &BosunProviders{} 35 r, _, err := e.Execute(backends, providers, nil, queryTime, 0, false, t.Name()) 36 if err != nil { 37 return err 38 } 39 if _, err := eio.out.Equal(r); err != nil { 40 return err 41 } 42 return nil 43 } 44 45 func TestDuration(t *testing.T) { 46 d := exprInOut{ 47 `d("1h")`, 48 Results{ 49 Results: ResultSlice{ 50 &Result{ 51 Value: Scalar(3600), 52 }, 53 }, 54 }, 55 false, 56 } 57 err := testExpression(d, t) 58 if err != nil { 59 t.Error(err) 60 } 61 } 62 63 func TestToDuration(t *testing.T) { 64 inputs := []int{ 65 0, 66 1, 67 60, 68 3600, 69 86400, 70 604800, 71 31536000, 72 } 73 outputs := []string{ 74 "0ms", 75 "1s", 76 "1m", 77 "1h", 78 "1d", 79 "1w", 80 "1y", 81 } 82 83 for i := range inputs { 84 d := exprInOut{ 85 fmt.Sprintf(`tod(%d)`, inputs[i]), 86 Results{ 87 Results: ResultSlice{ 88 &Result{ 89 Value: String(outputs[i]), 90 }, 91 }, 92 }, 93 false, 94 } 95 err := testExpression(d, t) 96 if err != nil { 97 t.Error(err) 98 } 99 } 100 } 101 102 func TestUngroup(t *testing.T) { 103 dictum := `series("foo=bar", 0, ungroup(last(series("foo=baz", 0, 1))))` 104 err := testExpression(exprInOut{ 105 dictum, 106 Results{ 107 Results: ResultSlice{ 108 &Result{ 109 Value: Series{ 110 time.Unix(0, 0): 1, 111 }, 112 Group: opentsdb.TagSet{"foo": "bar"}, 113 }, 114 }, 115 }, 116 false, 117 }, t) 118 119 if err != nil { 120 t.Error(err) 121 } 122 } 123 124 func TestMerge(t *testing.T) { 125 seriesA := `series("foo=bar", 0, 1)` 126 seriesB := `series("foo=baz", 0, 1)` 127 err := testExpression(exprInOut{ 128 fmt.Sprintf("merge(%v, %v)", seriesA, seriesB), 129 Results{ 130 Results: ResultSlice{ 131 &Result{ 132 Value: Series{ 133 time.Unix(0, 0): 1, 134 }, 135 Group: opentsdb.TagSet{"foo": "bar"}, 136 }, 137 &Result{ 138 Value: Series{ 139 time.Unix(0, 0): 1, 140 }, 141 Group: opentsdb.TagSet{"foo": "baz"}, 142 }, 143 }, 144 }, 145 false, 146 }, t) 147 if err != nil { 148 t.Error(err) 149 } 150 151 //Should Error due to identical groups in merge 152 err = testExpression(exprInOut{ 153 fmt.Sprintf("merge(%v, %v)", seriesA, seriesA), 154 Results{ 155 Results: ResultSlice{ 156 &Result{ 157 Value: Series{ 158 time.Unix(0, 0): 1, 159 }, 160 Group: opentsdb.TagSet{"foo": "bar"}, 161 }, 162 &Result{ 163 Value: Series{ 164 time.Unix(0, 0): 1, 165 }, 166 Group: opentsdb.TagSet{"foo": "bar"}, 167 }, 168 }, 169 }, 170 false, 171 }, t) 172 if err == nil { 173 t.Errorf("error expected due to identical groups in merge but did not get one") 174 } 175 } 176 177 func TestTimedelta(t *testing.T) { 178 for _, i := range []struct { 179 input string 180 expected Series 181 }{ 182 { 183 `timedelta(series("foo=bar", 1466133600, 1, 1466133610, 1, 1466133710, 1))`, 184 Series{ 185 time.Unix(1466133610, 0): 10, 186 time.Unix(1466133710, 0): 100, 187 }, 188 }, 189 { 190 `timedelta(series("foo=bar", 1466133600, 1))`, 191 Series{ 192 time.Unix(1466133600, 0): 0, 193 }, 194 }, 195 { 196 `timedelta(series("foo=bar"))`, 197 Series{}, 198 }, 199 } { 200 201 err := testExpression(exprInOut{ 202 i.input, 203 Results{ 204 Results: ResultSlice{ 205 &Result{ 206 Value: i.expected, 207 Group: opentsdb.TagSet{"foo": "bar"}, 208 }, 209 }, 210 }, 211 false, 212 }, t) 213 214 if err != nil { 215 t.Error(err) 216 } 217 } 218 } 219 220 func TestTail(t *testing.T) { 221 for _, i := range []struct { 222 input string 223 expected Series 224 }{ 225 { 226 `tail(series("foo=bar", 1466133600, 1, 1466133610, 1, 1466133710, 1), 2)`, 227 Series{ 228 time.Unix(1466133610, 0): 1, 229 time.Unix(1466133710, 0): 1, 230 }, 231 }, 232 { 233 `tail(series("foo=bar", 1466133600, 1), 2)`, 234 Series{ 235 time.Unix(1466133600, 0): 1, 236 }, 237 }, 238 { 239 `tail(series("foo=bar", 1466133600, 1, 1466133610, 1, 1466133710, 1), last(series("foo=bar", 1466133600, 2)))`, 240 Series{ 241 time.Unix(1466133610, 0): 1, 242 time.Unix(1466133710, 0): 1, 243 }, 244 }, 245 } { 246 247 err := testExpression(exprInOut{ 248 i.input, 249 Results{ 250 Results: ResultSlice{ 251 &Result{ 252 Value: i.expected, 253 Group: opentsdb.TagSet{"foo": "bar"}, 254 }, 255 }, 256 }, 257 false, 258 }, t) 259 260 if err != nil { 261 t.Error(err) 262 } 263 } 264 } 265 266 func TestAggr(t *testing.T) { 267 seriesA := `series("foo=bar", 0, 1, 100, 2)` 268 seriesB := `series("foo=baz", 0, 3, 100, 4)` 269 seriesC := `series("foo=bat", 0, 5, 100, 6)` 270 271 seriesGroupsA := `series("color=blue,type=apple,name=bob", 0, 1)` 272 seriesGroupsB := `series("color=blue,type=apple", 1, 3)` 273 seriesGroupsC := `series("color=green,type=apple", 0, 5)` 274 275 seriesMathA := `series("color=blue,type=apple,name=bob", 0, 1)` 276 seriesMathB := `series("color=blue,type=apple", 1, 3)` 277 seriesMathC := `series("color=green,type=apple", 0, 5)` 278 279 aggrTestCases := []struct { 280 name string 281 expr string 282 want Results 283 shouldErr bool 284 }{ 285 { 286 name: "median aggregator", 287 expr: fmt.Sprintf("aggr(merge(%v, %v, %v), \"\", \"p.50\")", seriesA, seriesB, seriesC), 288 want: Results{ 289 Results: ResultSlice{ 290 &Result{ 291 Value: Series{ 292 time.Unix(0, 0): 3, 293 time.Unix(100, 0): 4, 294 }, 295 Group: opentsdb.TagSet{}, 296 }, 297 }, 298 }, 299 shouldErr: false, 300 }, 301 { 302 name: "average aggregator", 303 expr: fmt.Sprintf("aggr(merge(%v, %v, %v), \"\", \"avg\")", seriesA, seriesB, seriesC), 304 want: Results{ 305 Results: ResultSlice{ 306 &Result{ 307 Value: Series{ 308 time.Unix(0, 0): 3, 309 time.Unix(100, 0): 4, 310 }, 311 Group: opentsdb.TagSet{}, 312 }, 313 }, 314 }, 315 shouldErr: false, 316 }, 317 { 318 name: "min aggregator", 319 expr: fmt.Sprintf("aggr(merge(%v, %v, %v), \"\", \"min\")", seriesA, seriesB, seriesC), 320 want: Results{ 321 Results: ResultSlice{ 322 &Result{ 323 Value: Series{ 324 time.Unix(0, 0): 1, 325 time.Unix(100, 0): 2, 326 }, 327 Group: opentsdb.TagSet{}, 328 }, 329 }, 330 }, 331 shouldErr: false, 332 }, 333 { 334 name: "max aggregator", 335 expr: fmt.Sprintf("aggr(merge(%v, %v, %v), \"\", \"max\")", seriesA, seriesB, seriesC), 336 want: Results{ 337 Results: ResultSlice{ 338 &Result{ 339 Value: Series{ 340 time.Unix(0, 0): 5, 341 time.Unix(100, 0): 6, 342 }, 343 Group: opentsdb.TagSet{}, 344 }, 345 }, 346 }, 347 shouldErr: false, 348 }, 349 { 350 name: "check p0 == min", 351 expr: fmt.Sprintf("aggr(merge(%v, %v, %v), \"\", \"p0\")", seriesA, seriesB, seriesC), 352 want: Results{ 353 Results: ResultSlice{ 354 &Result{ 355 Value: Series{ 356 time.Unix(0, 0): 1, 357 time.Unix(100, 0): 2, 358 }, 359 Group: opentsdb.TagSet{}, 360 }, 361 }, 362 }, 363 shouldErr: false, 364 }, 365 { 366 name: "check that sum aggregator sums up the aligned points in the series", 367 expr: fmt.Sprintf("aggr(merge(%v, %v, %v), \"\", \"sum\")", seriesA, seriesB, seriesC), 368 want: Results{ 369 Results: ResultSlice{ 370 &Result{ 371 Value: Series{ 372 time.Unix(0, 0): 9, 373 time.Unix(100, 0): 12, 374 }, 375 Group: opentsdb.TagSet{}, 376 }, 377 }, 378 }, 379 shouldErr: false, 380 }, 381 { 382 name: "check that unknown aggregator errors out", 383 expr: fmt.Sprintf("aggr(merge(%v, %v, %v), \"\", \"unknown\")", seriesA, seriesB, seriesC), 384 want: Results{}, 385 shouldErr: true, 386 }, 387 { 388 name: "single group", 389 expr: fmt.Sprintf("aggr(merge(%v, %v, %v), \"color\", \"p.50\")", seriesGroupsA, seriesGroupsB, seriesGroupsC), 390 want: Results{ 391 Results: ResultSlice{ 392 &Result{ 393 Value: Series{ 394 time.Unix(0, 0): 1, 395 time.Unix(1, 0): 3, 396 }, 397 Group: opentsdb.TagSet{"color": "blue"}, 398 }, 399 &Result{ 400 Value: Series{ 401 time.Unix(0, 0): 5, 402 }, 403 Group: opentsdb.TagSet{"color": "green"}, 404 }, 405 }, 406 }, 407 shouldErr: false, 408 }, 409 { 410 name: "multiple groups", 411 expr: fmt.Sprintf("aggr(merge(%v, %v, %v), \"color,type\", \"p.50\")", seriesGroupsA, seriesGroupsB, seriesGroupsC), 412 want: Results{ 413 Results: ResultSlice{ 414 &Result{ 415 Value: Series{ 416 time.Unix(0, 0): 1, 417 time.Unix(1, 0): 3, 418 }, 419 Group: opentsdb.TagSet{"color": "blue", "type": "apple"}, 420 }, 421 &Result{ 422 Value: Series{ 423 time.Unix(0, 0): 5, 424 }, 425 Group: opentsdb.TagSet{"color": "green", "type": "apple"}, 426 }, 427 }, 428 }, 429 shouldErr: false, 430 }, 431 { 432 name: "aggregator with no groups and math operation", 433 expr: fmt.Sprintf("aggr(merge(%v, %v, %v), \"\", \"p.50\") * 2", seriesMathA, seriesMathB, seriesMathC), 434 want: Results{ 435 Results: ResultSlice{ 436 &Result{ 437 Value: Series{ 438 time.Unix(0, 0): 10, 439 time.Unix(1, 0): 6, 440 }, 441 Group: opentsdb.TagSet{}, 442 }, 443 }, 444 }, 445 shouldErr: false, 446 }, 447 { 448 name: "aggregator with one group and math operation", 449 expr: fmt.Sprintf("aggr(merge(%v, %v, %v), \"color\", \"p.50\") * 2", seriesMathA, seriesMathB, seriesMathC), 450 want: Results{ 451 Results: ResultSlice{ 452 &Result{ 453 Value: Series{ 454 time.Unix(0, 0): 2, 455 time.Unix(1, 0): 6, 456 }, 457 Group: opentsdb.TagSet{"color": "blue"}, 458 }, 459 &Result{ 460 Value: Series{ 461 time.Unix(0, 0): 10, 462 }, 463 Group: opentsdb.TagSet{"color": "green"}, 464 }, 465 }, 466 }, 467 shouldErr: false, 468 }, 469 { 470 name: "aggregator with multiple groups and math operation", 471 expr: fmt.Sprintf("aggr(merge(%v, %v, %v), \"color,type\", \"p.50\") * 2", seriesMathA, seriesMathB, seriesMathC), 472 want: Results{ 473 Results: ResultSlice{ 474 &Result{ 475 Value: Series{ 476 time.Unix(0, 0): 2, 477 time.Unix(1, 0): 6, 478 }, 479 Group: opentsdb.TagSet{"color": "blue", "type": "apple"}, 480 }, 481 &Result{ 482 Value: Series{ 483 time.Unix(0, 0): 10, 484 }, 485 Group: opentsdb.TagSet{"color": "green", "type": "apple"}, 486 }, 487 }, 488 }, 489 shouldErr: false, 490 }, 491 } 492 493 for _, tc := range aggrTestCases { 494 err := testExpression(exprInOut{ 495 expr: tc.expr, 496 out: tc.want, 497 shouldParseErr: false, 498 }, t) 499 if !tc.shouldErr && err != nil { 500 t.Errorf("Case %q: Got error: %v", tc.name, err) 501 } else if tc.shouldErr && err == nil { 502 t.Errorf("Case %q: Expected parse error, but got nil", tc.name) 503 } 504 } 505 } 506 507 func TestAggrNaNHandling(t *testing.T) { 508 // test behavior when NaN is encountered. 509 seriesD := `series("foo=bar", 0, 0 / 0, 100, 1)` 510 seriesE := `series("foo=baz", 0, 1, 100, 3)` 511 512 // expect NaN points to be dropped 513 eio := exprInOut{ 514 fmt.Sprintf("aggr(merge(%v, %v), \"\", \"p.90\")", seriesD, seriesE), 515 Results{ 516 Results: ResultSlice{ 517 &Result{ 518 Value: Series{ 519 time.Unix(0, 0): math.NaN(), 520 time.Unix(100, 0): 2, 521 }, 522 Group: opentsdb.TagSet{}, 523 }, 524 }, 525 }, 526 false, 527 } 528 e, err := New(eio.expr, builtins) 529 if err != nil { 530 t.Fatal(err.Error()) 531 } 532 backends := &Backends{ 533 InfluxConfig: client.HTTPConfig{}, 534 } 535 providers := &BosunProviders{} 536 _, _, err = e.Execute(backends, providers, nil, queryTime, 0, false, t.Name()) 537 if err != nil { 538 t.Fatal(err.Error()) 539 } 540 541 results := eio.out.Results 542 if len(results) != 1 { 543 t.Errorf("got len(results) == %d, want 1", len(results)) 544 } 545 val0 := results[0].Value.(Series)[time.Unix(0, 0)] 546 if !math.IsNaN(val0) { 547 t.Errorf("got first point = %f, want NaN", val0) 548 } 549 val1 := results[0].Value.(Series)[time.Unix(100, 0)] 550 if val1 != 2.0 { 551 t.Errorf("got second point = %f, want %f", val1, 2.0) 552 } 553 }