github.com/go-graphite/carbonapi@v0.17.0/expr/expr_test.go (about) 1 package expr 2 3 import ( 4 "context" 5 "fmt" 6 "math" 7 "testing" 8 "time" 9 "unicode" 10 11 pb "github.com/go-graphite/protocol/carbonapi_v3_pb" 12 13 "github.com/go-graphite/carbonapi/expr/functions" 14 "github.com/go-graphite/carbonapi/expr/helper" 15 "github.com/go-graphite/carbonapi/expr/rewrite" 16 "github.com/go-graphite/carbonapi/expr/types" 17 "github.com/go-graphite/carbonapi/pkg/parser" 18 th "github.com/go-graphite/carbonapi/tests" 19 "github.com/go-graphite/carbonapi/tests/compare" 20 ) 21 22 func init() { 23 rewrite.New(make(map[string]string)) 24 functions.New(make(map[string]string)) 25 } 26 27 func TestGetBuckets(t *testing.T) { 28 tests := []struct { 29 start int64 30 stop int64 31 bucketSize int64 32 wantBuckets int64 33 }{ 34 {13, 18, 5, 1}, 35 {13, 17, 5, 1}, 36 {13, 19, 5, 2}, 37 } 38 39 for _, test := range tests { 40 buckets := helper.GetBuckets(test.start, test.stop, test.bucketSize) 41 if buckets != test.wantBuckets { 42 t.Errorf("TestGetBuckets failed!\n%v\ngot buckets %d", 43 test, 44 buckets, 45 ) 46 } 47 } 48 } 49 50 func TestAlignToBucketSize(t *testing.T) { 51 tests := []struct { 52 inputStart int64 53 inputStop int64 54 bucketSize int64 55 wantStart int64 56 wantStop int64 57 }{ 58 { 59 13, 18, 5, 60 10, 20, 61 }, 62 { 63 13, 17, 5, 64 10, 20, 65 }, 66 { 67 13, 19, 5, 68 10, 20, 69 }, 70 } 71 72 for _, test := range tests { 73 start, stop := helper.AlignToBucketSize(test.inputStart, test.inputStop, test.bucketSize) 74 if start != test.wantStart || stop != test.wantStop { 75 t.Errorf("TestAlignToBucketSize failed!\n%v\ngot start %d stop %d", 76 test, 77 start, 78 stop, 79 ) 80 } 81 } 82 } 83 84 func TestAlignToInterval(t *testing.T) { 85 tests := []struct { 86 inputStart int64 87 inputStop int64 88 bucketSize int64 89 wantStart int64 90 }{ 91 { 92 91111, 92222, 5, 93 91111, 94 }, 95 { 96 91111, 92222, 60, 97 91080, 98 }, 99 { 100 91111, 92222, 3600, 101 90000, 102 }, 103 { 104 91111, 92222, 86400, 105 86400, 106 }, 107 } 108 109 for _, test := range tests { 110 start := helper.AlignStartToInterval(test.inputStart, test.inputStop, test.bucketSize) 111 if start != test.wantStart { 112 t.Errorf("TestAlignToInterval failed!\n%v\ngot start %d", 113 test, 114 start, 115 ) 116 } 117 } 118 } 119 120 type evalExprTestCase struct { 121 metric string 122 request string 123 metricRequest parser.MetricRequest 124 values []float64 125 isAbsent []bool 126 stepTime int64 127 from int64 128 until int64 129 } 130 131 func TestEvalExpr(t *testing.T) { 132 tests := map[string]evalExprTestCase{ 133 "EvalExp with summarize": { 134 metric: "metric1", 135 request: "summarize(metric1,'1min')", 136 metricRequest: parser.MetricRequest{ 137 Metric: "metric1", 138 From: 1437127020, 139 Until: 1437127140, 140 }, 141 values: []float64{343, 407, 385}, 142 isAbsent: []bool{false, false, false}, 143 stepTime: 60, 144 from: 1437127020, 145 until: 1437127140, 146 }, 147 "metric name starts with digit": { 148 metric: "1metric", 149 request: "1metric", 150 metricRequest: parser.MetricRequest{ 151 Metric: "1metric", 152 From: 1437127020, 153 Until: 1437127140, 154 }, 155 values: []float64{343, 407, 385}, 156 isAbsent: []bool{false, false, false}, 157 stepTime: 60, 158 from: 1437127020, 159 until: 1437127140, 160 }, 161 "metric unicode name starts with digit": { 162 metric: "1Метрика", 163 request: "1Метрика", 164 metricRequest: parser.MetricRequest{ 165 Metric: "1Метрика", 166 From: 1437127020, 167 Until: 1437127140, 168 }, 169 values: []float64{343, 407, 385}, 170 isAbsent: []bool{false, false, false}, 171 stepTime: 60, 172 from: 1437127020, 173 until: 1437127140, 174 }, 175 } 176 177 parser.RangeTables = append(parser.RangeTables, unicode.Cyrillic) 178 for name, test := range tests { 179 t.Run(fmt.Sprintf("%s: %s", "TestEvalExpr", name), func(t *testing.T) { 180 exp, e, err := parser.ParseExpr(test.request) 181 if err != nil || e != "" { 182 t.Errorf("error='%v', leftovers='%v'", err, e) 183 } 184 185 metricMap := make(map[parser.MetricRequest][]*types.MetricData) 186 request := parser.MetricRequest{ 187 Metric: test.metric, 188 From: test.from, 189 Until: test.until, 190 } 191 192 data := types.MetricData{ 193 FetchResponse: pb.FetchResponse{ 194 Name: request.Metric, 195 StartTime: request.From, 196 StopTime: request.Until, 197 StepTime: test.stepTime, 198 Values: test.values, 199 ConsolidationFunc: "average", 200 PathExpression: request.Metric, 201 }, 202 Tags: map[string]string{"name": request.Metric}, 203 } 204 205 metricMap[request] = []*types.MetricData{ 206 &data, 207 } 208 209 eval, err := NewEvaluator(nil, th.NewTestZipper(nil), false) 210 if err == nil { 211 _, err = EvalExpr(context.Background(), eval, exp, request.From, request.Until, metricMap) 212 } 213 if err != nil { 214 t.Errorf("error='%v'", err) 215 } 216 }) 217 } 218 } 219 220 func TestEvalExpression(t *testing.T) { 221 222 now32 := time.Now().Unix() 223 224 tests := []th.EvalTestItem{ 225 { 226 "metric", 227 map[parser.MetricRequest][]*types.MetricData{ 228 {Metric: "metric", From: 0, Until: 1}: {types.MakeMetricData("metric", []float64{1, 2, 3, 4, 5}, 1, now32)}, 229 }, 230 []*types.MetricData{types.MakeMetricData("metric", []float64{1, 2, 3, 4, 5}, 1, now32)}, 231 }, 232 { 233 "42", 234 map[parser.MetricRequest][]*types.MetricData{}, 235 []*types.MetricData{types.MakeMetricData("42", []float64{42}, 1, 0)}, 236 }, 237 { 238 "metric*", 239 map[parser.MetricRequest][]*types.MetricData{ 240 {"metric*", "", 0, 1}: { 241 types.MakeMetricData("metric1", []float64{1, 2, 3, 4, 5}, 1, now32), 242 types.MakeMetricData("metric2", []float64{2, 3, 4, 5, 6}, 1, now32), 243 }, 244 }, 245 []*types.MetricData{ 246 types.MakeMetricData("metric1", []float64{1, 2, 3, 4, 5}, 1, now32), 247 types.MakeMetricData("metric2", []float64{2, 3, 4, 5, 6}, 1, now32), 248 }, 249 }, 250 { 251 "reduceSeries(mapSeries(devops.service.*.filter.received.*.count,2), \"asPercent\", 5,\"valid\",\"total\")", 252 map[parser.MetricRequest][]*types.MetricData{ 253 {Metric: "devops.service.*.filter.received.*.count", From: 0, Until: 1}: { 254 types.MakeMetricData("devops.service.server1.filter.received.valid.count", []float64{2, 4, 8}, 1, now32), 255 types.MakeMetricData("devops.service.server1.filter.received.total.count", []float64{8, 2, 4}, 1, now32), 256 types.MakeMetricData("devops.service.server2.filter.received.valid.count", []float64{3, 9, 12}, 1, now32), 257 types.MakeMetricData("devops.service.server2.filter.received.total.count", []float64{12, 9, 3}, 1, now32), 258 }, 259 }, 260 []*types.MetricData{ 261 types.MakeMetricData("devops.service.server1.filter.received.reduce.asPercent.count", []float64{25, 200, 200}, 1, now32).SetNameTag("devops.service.server1.filter.received.valid.count"), 262 types.MakeMetricData("devops.service.server2.filter.received.reduce.asPercent.count", []float64{25, 100, 400}, 1, now32).SetNameTag("devops.service.server2.filter.received.valid.count"), 263 }, 264 }, 265 { 266 "reduceSeries(mapSeries(devops.service.*.filter.received.*.count,2), \"asPercent\", 5,\"valid\",\"total\")", 267 map[parser.MetricRequest][]*types.MetricData{ 268 {Metric: "devops.service.*.filter.received.*.count", From: 0, Until: 1}: { 269 types.MakeMetricData("devops.service.server1.filter.received.total.count", []float64{8, 2, 4}, 1, now32), 270 types.MakeMetricData("devops.service.server2.filter.received.valid.count", []float64{3, 9, 12}, 1, now32), 271 types.MakeMetricData("devops.service.server2.filter.received.total.count", []float64{12, 9, 3}, 1, now32), 272 }, 273 }, 274 []*types.MetricData{ 275 types.MakeMetricData("devops.service.server2.filter.received.reduce.asPercent.count", []float64{25, 100, 400}, 1, now32).SetNameTag("devops.service.server2.filter.received.valid.count"), 276 }, 277 }, 278 { 279 "sumSeries(pow(devops.service.*.filter.received.*.count, 0))", 280 map[parser.MetricRequest][]*types.MetricData{ 281 {Metric: "devops.service.*.filter.received.*.count", From: 0, Until: 1}: { 282 types.MakeMetricData("devops.service.server1.filter.received.total.count", []float64{8, 2, 4}, 1, now32), 283 types.MakeMetricData("devops.service.server2.filter.received.valid.count", []float64{3, 9, 12}, 1, now32), 284 types.MakeMetricData("devops.service.server2.filter.received.total.count", []float64{math.NaN(), math.NaN(), math.NaN()}, 1, now32), 285 }, 286 }, 287 []*types.MetricData{types.MakeMetricData("sumSeries(pow(devops.service.*.filter.received.*.count, 0))", []float64{2, 2, 2}, 1, now32).SetTag("aggregatedBy", "sum")}, 288 }, 289 { 290 "multiplySeriesWithWildcards(metric1.foo.*.*,1,2)", 291 map[parser.MetricRequest][]*types.MetricData{ 292 {Metric: "metric1.foo.*.*", From: 0, Until: 1}: { 293 types.MakeMetricData("metric1.foo.bar1.baz", []float64{1, 2, 3, 4, 5}, 1, now32), 294 types.MakeMetricData("metric1.foo.bar2.baz", []float64{11, 12, 13, 14, 15}, 1, now32), 295 types.MakeMetricData("metric1.foo.bar3.baz", []float64{2, 2, 2, 2, 2}, 1, now32), 296 }, 297 }, 298 []*types.MetricData{types.MakeMetricData("metric1.baz", []float64{22, 48, 78, 112, 150}, 1, now32).SetTag("aggregatedBy", "multiply").SetNameTag("metric1.foo.*.*")}, 299 }, 300 { 301 "groupByNode(metric1foo.*,0,\"asPercent\")", 302 map[parser.MetricRequest][]*types.MetricData{ 303 {Metric: "metric1foo.*", From: 0, Until: 1}: { 304 types.MakeMetricData("metric1foo.bar1.baz", []float64{1, 2, 3, 4, 5}, 1, now32), 305 types.MakeMetricData("metric1foo.bar1.qux", []float64{6, 7, 8, 9, 10}, 1, now32), 306 types.MakeMetricData("metric1foo.bar2.baz", []float64{11, 12, 13, 14, 15}, 1, now32), 307 types.MakeMetricData("metric1foo.bar2.qux", []float64{7, 8, 9, 10, 11}, 1, now32), 308 }, 309 }, 310 []*types.MetricData{types.MakeMetricData("metric1foo", []float64{4, 6.896551724137931, 9.09090909090909, 10.81081081081081, 12.195121951219512}, 1, now32)}, 311 }, 312 { 313 "groupByNodes(test.metric*.foo*,\"keepLastValue\",1,0)", 314 map[parser.MetricRequest][]*types.MetricData{ 315 {Metric: "test.metric*.foo*", From: 0, Until: 1}: { 316 types.MakeMetricData("test.metric1.foo1", []float64{0}, 1, now32), 317 types.MakeMetricData("test.metric1.foo2", []float64{0}, 1, now32), 318 types.MakeMetricData("test.metric2.foo1", []float64{0}, 1, now32), 319 types.MakeMetricData("test.metric2.foo2", []float64{0}, 1, now32), 320 }, 321 }, 322 []*types.MetricData{ 323 types.MakeMetricData("metric1.test", []float64{0}, 1, now32), 324 types.MakeMetricData("metric2.test", []float64{0}, 1, now32), 325 }, 326 }, 327 { 328 "groupByNodes(test.metric*.foo*,\"keepLastValue\",1,2)", 329 map[parser.MetricRequest][]*types.MetricData{ 330 {Metric: "test.metric*.foo*", From: 0, Until: 1}: { 331 types.MakeMetricData("test.metric1.foo1", []float64{0}, 1, now32), 332 types.MakeMetricData("test.metric1.foo2", []float64{0}, 1, now32), 333 types.MakeMetricData("test.metric2.foo1", []float64{0}, 1, now32), 334 types.MakeMetricData("test.metric2.foo2", []float64{0}, 1, now32), 335 }, 336 }, 337 []*types.MetricData{ 338 types.MakeMetricData("metric1.foo1", []float64{0}, 1, now32), 339 types.MakeMetricData("metric1.foo2", []float64{0}, 1, now32), 340 types.MakeMetricData("metric2.foo1", []float64{0}, 1, now32), 341 types.MakeMetricData("metric2.foo2", []float64{0}, 1, now32), 342 }, 343 }, 344 { 345 "groupByNodes(test.metric*.foo*,\"keepLastValue\",1)", 346 map[parser.MetricRequest][]*types.MetricData{ 347 {Metric: "test.metric*.foo*", From: 0, Until: 1}: { 348 types.MakeMetricData("test.metric1.foo1", []float64{0}, 1, now32), 349 types.MakeMetricData("test.metric1.foo2", []float64{0}, 1, now32), 350 types.MakeMetricData("test.metric2.foo1", []float64{0}, 1, now32), 351 types.MakeMetricData("test.metric2.foo2", []float64{0}, 1, now32), 352 }, 353 }, 354 []*types.MetricData{ 355 types.MakeMetricData("metric1", []float64{0}, 1, now32), 356 types.MakeMetricData("metric2", []float64{0}, 1, now32), 357 }, 358 }, 359 } 360 361 for _, tt := range tests { 362 testName := tt.Target 363 t.Run(testName, func(t *testing.T) { 364 eval, err := NewEvaluator(nil, th.NewTestZipper(nil), false) 365 if err == nil { 366 th.TestEvalExpr(t, eval, &tt) 367 } else { 368 t.Errorf("error='%v'", err) 369 } 370 }) 371 } 372 } 373 374 func TestRewriteExpr(t *testing.T) { 375 now32 := time.Now().Unix() 376 377 tests := []struct { 378 name string 379 e parser.Expr 380 m map[parser.MetricRequest][]*types.MetricData 381 rewritten bool 382 newTargets []string 383 }{ 384 { 385 "ignore non-applyByNode", 386 parser.NewExpr("sumSeries", 387 388 "metric*", 389 ), 390 map[parser.MetricRequest][]*types.MetricData{ 391 {Metric: "metric*", From: 0, Until: 1}: { 392 types.MakeMetricData("metric1", []float64{1, 2, 3}, 1, now32), 393 }, 394 {Metric: "metric1", From: 0, Until: 1}: { 395 types.MakeMetricData("metric1", []float64{1, 2, 3}, 1, now32), 396 }, 397 }, 398 false, 399 []string{}, 400 }, 401 { 402 "applyByNode", 403 parser.NewExpr("applyByNode", 404 405 "metric*", 406 0, 407 parser.ArgValue("%.count"), 408 ), 409 map[parser.MetricRequest][]*types.MetricData{ 410 {Metric: "metric*", From: 0, Until: 1}: { 411 types.MakeMetricData("metric1", []float64{1, 2, 3}, 1, now32), 412 }, 413 {Metric: "metric1", From: 0, Until: 1}: { 414 types.MakeMetricData("metric1", []float64{1, 2, 3}, 1, now32), 415 }, 416 }, 417 true, 418 []string{"metric1.count"}, 419 }, 420 { 421 "applyByNode", 422 parser.NewExpr("applyByNode", 423 424 "metric*", 425 0, 426 parser.ArgValue("%.count"), 427 parser.ArgValue("% count"), 428 ), 429 map[parser.MetricRequest][]*types.MetricData{ 430 {Metric: "metric*", From: 0, Until: 1}: { 431 types.MakeMetricData("metric1", []float64{1, 2, 3}, 1, now32), 432 }, 433 {Metric: "metric1", From: 0, Until: 1}: { 434 types.MakeMetricData("metric1", []float64{1, 2, 3}, 1, now32), 435 }, 436 }, 437 true, 438 []string{"alias(metric1.count,\"metric1 count\")"}, 439 }, 440 { 441 "applyByNode", 442 parser.NewExpr("applyByNode", 443 444 "foo.metric*", 445 1, 446 parser.ArgValue("%.count"), 447 ), 448 map[parser.MetricRequest][]*types.MetricData{ 449 {Metric: "foo.metric*", From: 0, Until: 1}: { 450 types.MakeMetricData("foo.metric1", []float64{1, 2, 3}, 1, now32), 451 types.MakeMetricData("foo.metric2", []float64{1, 2, 3}, 1, now32), 452 }, 453 {Metric: "foo.metric1", From: 0, Until: 1}: { 454 types.MakeMetricData("foo.metric1", []float64{1, 2, 3}, 1, now32), 455 }, 456 {Metric: "foo.metric2", From: 0, Until: 1}: { 457 types.MakeMetricData("foo.metric2", []float64{1, 2, 3}, 1, now32), 458 }, 459 }, 460 true, 461 []string{"foo.metric1.count", "foo.metric2.count"}, 462 }, 463 } 464 465 for _, tt := range tests { 466 t.Run(tt.name, func(t *testing.T) { 467 eval, err := NewEvaluator(nil, th.NewTestZipper(nil), false) 468 if err == nil { 469 rewritten, newTargets, err := RewriteExpr(context.Background(), eval, tt.e, 0, 1, tt.m) 470 471 if err != nil { 472 t.Errorf("failed to rewrite %v: %+v", tt.name, err) 473 return 474 } 475 476 if rewritten != tt.rewritten { 477 t.Errorf("failed to rewrite %v: expected rewritten=%v but was %v", tt.name, tt.rewritten, rewritten) 478 return 479 } 480 481 var targetsMatch = true 482 if len(tt.newTargets) != len(newTargets) { 483 targetsMatch = false 484 } else { 485 for i := range tt.newTargets { 486 targetsMatch = targetsMatch && tt.newTargets[i] == newTargets[i] 487 } 488 } 489 490 if !targetsMatch { 491 t.Errorf("failed to rewrite %v: expected newTargets=%v but was %v", tt.name, tt.newTargets, newTargets) 492 return 493 } 494 } else { 495 t.Errorf("error='%v'", err) 496 } 497 }) 498 } 499 } 500 501 func TestEvalCustomFromUntil(t *testing.T) { 502 tests := []struct { 503 target string 504 m map[parser.MetricRequest][]*types.MetricData 505 w []float64 506 name string 507 from int64 508 until int64 509 }{ 510 { 511 "timeFunction(\"footime\")", 512 map[parser.MetricRequest][]*types.MetricData{}, 513 []float64{4200.0, 4260.0, 4320.0}, 514 "footime", 515 4200, 516 4350, 517 }, 518 } 519 520 for _, tt := range tests { 521 t.Run(tt.name, func(t *testing.T) { 522 originalMetrics := th.DeepClone(tt.m) 523 exp, _, _ := parser.ParseExpr(tt.target) 524 eval, err := NewEvaluator(nil, th.NewTestZipper(nil), false) 525 if err == nil { 526 g, err := EvalExpr(context.Background(), eval, exp, tt.from, tt.until, tt.m) 527 if err != nil { 528 t.Errorf("failed to eval %v: %s", tt.name, err) 529 return 530 } 531 if g[0] == nil { 532 t.Errorf("returned no value %v", tt.target) 533 return 534 } 535 536 th.DeepEqual(t, tt.target, originalMetrics, tt.m, false) 537 538 if g[0].StepTime == 0 { 539 t.Errorf("missing step for %+v", g) 540 } 541 if !compare.NearlyEqual(g[0].Values, tt.w) { 542 t.Errorf("failed: %s: got %+v, want %+v", g[0].Name, g[0].Values, tt.w) 543 } 544 if g[0].Name != tt.name { 545 t.Errorf("bad name for %+v: got %v, want %v", g, g[0].Name, tt.name) 546 } 547 } else { 548 t.Errorf("error='%v'", err) 549 } 550 }) 551 } 552 }