github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/structs/flamebearer/convert/convert_test.go (about) 1 package convert 2 3 import ( 4 "io/ioutil" 5 "reflect" 6 7 . "github.com/onsi/ginkgo/v2" 8 . "github.com/onsi/gomega" 9 10 "github.com/pyroscope-io/pyroscope/pkg/structs/flamebearer" 11 ) 12 13 var _ = Describe("Server", func() { 14 Describe("Detecting format", func() { 15 Context("with a valid pprof type", func() { 16 When("there's only type", func() { 17 var m ProfileFile 18 19 BeforeEach(func() { 20 m = ProfileFile{ 21 Type: "pprof", 22 } 23 }) 24 It("should return pprof as type is be enough to detect the type", func() { 25 // We want to compare functions, which is not ideal. 26 expected := reflect.ValueOf(PprofToProfile).Pointer() 27 f, err := converter(m) 28 Expect(err).To(BeNil()) 29 Expect(f).ToNot(BeNil()) 30 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 31 }) 32 }) 33 When("there's pprof type and json filename", func() { 34 var m ProfileFile 35 36 BeforeEach(func() { 37 m = ProfileFile{ 38 Name: "profile.json", 39 Type: "pprof", 40 } 41 }) 42 It("should return pprof as type takes precedence over filename", func() { 43 // We want to compare functions, which is not ideal. 44 expected := reflect.ValueOf(PprofToProfile).Pointer() 45 f, err := converter(m) 46 Expect(err).To(BeNil()) 47 Expect(f).ToNot(BeNil()) 48 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 49 }) 50 }) 51 52 When("there's pprof type and json profile contents", func() { 53 var m ProfileFile 54 55 BeforeEach(func() { 56 m = ProfileFile{ 57 Data: []byte(`{"flamebearer":""}`), 58 Type: "pprof", 59 } 60 }) 61 It("should return pprof as type takes precedence over profile contents", func() { 62 // We want to compare functions, which is not ideal. 63 expected := reflect.ValueOf(PprofToProfile).Pointer() 64 f, err := converter(m) 65 Expect(err).To(BeNil()) 66 Expect(f).ToNot(BeNil()) 67 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 68 }) 69 }) 70 }) 71 72 Context("with no (valid) type and a valid pprof filename", func() { 73 When("there's pprof filename and json profile contents", func() { 74 var m ProfileFile 75 BeforeEach(func() { 76 m = ProfileFile{ 77 Name: "profile.pprof", 78 Data: []byte(`{"flamebearer":""}`), 79 } 80 }) 81 82 It("should return pprof as filename takes precedence over profile contents", func() { 83 // We want to compare functions, which is not ideal. 84 expected := reflect.ValueOf(PprofToProfile).Pointer() 85 f, err := converter(m) 86 Expect(err).To(BeNil()) 87 Expect(f).ToNot(BeNil()) 88 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 89 }) 90 }) 91 When("there's pprof filename and an unsupported type", func() { 92 var m ProfileFile 93 BeforeEach(func() { 94 m = ProfileFile{ 95 Name: "profile.pprof", 96 Type: "unsupported", 97 } 98 }) 99 100 It("should return pprof as unsupported type is ignored", func() { 101 // We want to compare functions, which is not ideal. 102 expected := reflect.ValueOf(PprofToProfile).Pointer() 103 f, err := converter(m) 104 Expect(err).To(BeNil()) 105 Expect(f).ToNot(BeNil()) 106 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 107 }) 108 }) 109 }) 110 111 Context("with no (valid) type and filename, a valid pprof profile", func() { 112 When("there's a profile with uncompressed pprof content", func() { 113 var m ProfileFile 114 115 BeforeEach(func() { 116 m = ProfileFile{ 117 Data: []byte{0x0a, 0x04}, 118 } 119 }) 120 121 It("should return pprof", func() { 122 // We want to compare functions, which is not ideal. 123 expected := reflect.ValueOf(PprofToProfile).Pointer() 124 f, err := converter(m) 125 Expect(err).To(BeNil()) 126 Expect(f).ToNot(BeNil()) 127 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 128 }) 129 }) 130 131 When("there's a profile with compressed pprof content", func() { 132 var m ProfileFile 133 134 BeforeEach(func() { 135 m = ProfileFile{ 136 Data: []byte{0x1f, 0x8b}, 137 } 138 }) 139 140 It("should return pprof", func() { 141 // We want to compare functions, which is not ideal. 142 expected := reflect.ValueOf(PprofToProfile).Pointer() 143 f, err := converter(m) 144 Expect(err).To(BeNil()) 145 Expect(f).ToNot(BeNil()) 146 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 147 }) 148 }) 149 150 When("there's a profile with compressed pprof content and an unsupported type", func() { 151 var m ProfileFile 152 153 BeforeEach(func() { 154 m = ProfileFile{ 155 Data: []byte{0x1f, 0x8b}, 156 Type: "unsupported", 157 } 158 }) 159 160 It("should return pprof as unsupported types are ignored", func() { 161 // We want to compare functions, which is not ideal. 162 expected := reflect.ValueOf(PprofToProfile).Pointer() 163 f, err := converter(m) 164 Expect(err).To(BeNil()) 165 Expect(f).ToNot(BeNil()) 166 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 167 }) 168 }) 169 170 When("there's a profile with compressed pprof content and unsupported filename", func() { 171 var m ProfileFile 172 173 BeforeEach(func() { 174 m = ProfileFile{ 175 Name: "profile.unsupported", 176 Data: []byte{0x1f, 0x8b}, 177 } 178 }) 179 180 It("should return pprof as unsupported filenames are ignored", func() { 181 // We want to compare functions, which is not ideal. 182 expected := reflect.ValueOf(PprofToProfile).Pointer() 183 f, err := converter(m) 184 Expect(err).To(BeNil()) 185 Expect(f).ToNot(BeNil()) 186 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 187 }) 188 }) 189 }) 190 191 Context("with a valid json type", func() { 192 When("there's only type", func() { 193 var m ProfileFile 194 195 BeforeEach(func() { 196 m = ProfileFile{ 197 Type: "json", 198 } 199 }) 200 It("should return json as type is be enough to detect the type", func() { 201 // We want to compare functions, which is not ideal. 202 expected := reflect.ValueOf(JSONToProfile).Pointer() 203 f, err := converter(m) 204 Expect(err).To(BeNil()) 205 Expect(f).ToNot(BeNil()) 206 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 207 }) 208 }) 209 When("there's json type and pprof filename", func() { 210 var m ProfileFile 211 212 BeforeEach(func() { 213 m = ProfileFile{ 214 Name: "profile.pprof", 215 Type: "json", 216 } 217 }) 218 It("should return json as type takes precedence over filename", func() { 219 // We want to compare functions, which is not ideal. 220 expected := reflect.ValueOf(JSONToProfile).Pointer() 221 f, err := converter(m) 222 Expect(err).To(BeNil()) 223 Expect(f).ToNot(BeNil()) 224 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 225 }) 226 }) 227 228 When("there's json type and pprof profile contents", func() { 229 var m ProfileFile 230 231 BeforeEach(func() { 232 m = ProfileFile{ 233 Data: []byte{0x1f, 0x8b}, 234 Type: "json", 235 } 236 }) 237 It("should return json as type takes precedence over profile contents", func() { 238 // We want to compare functions, which is not ideal. 239 expected := reflect.ValueOf(JSONToProfile).Pointer() 240 f, err := converter(m) 241 Expect(err).To(BeNil()) 242 Expect(f).ToNot(BeNil()) 243 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 244 }) 245 }) 246 }) 247 248 Context("with no (valid) type and a valid json filename", func() { 249 When("there's json filename and pprof profile contents", func() { 250 var m ProfileFile 251 BeforeEach(func() { 252 m = ProfileFile{ 253 Name: "profile.json", 254 Data: []byte{0x1f, 0x8b}, 255 } 256 }) 257 258 It("should return json as filename takes precedence over profile contents", func() { 259 // We want to compare functions, which is not ideal. 260 expected := reflect.ValueOf(JSONToProfile).Pointer() 261 f, err := converter(m) 262 Expect(err).To(BeNil()) 263 Expect(f).ToNot(BeNil()) 264 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 265 }) 266 }) 267 When("there's json filename and an unsupported type", func() { 268 var m ProfileFile 269 BeforeEach(func() { 270 m = ProfileFile{ 271 Name: "profile.json", 272 Type: "unsupported", 273 } 274 }) 275 276 It("should return json as unsupported type is ignored", func() { 277 // We want to compare functions, which is not ideal. 278 expected := reflect.ValueOf(JSONToProfile).Pointer() 279 f, err := converter(m) 280 Expect(err).To(BeNil()) 281 Expect(f).ToNot(BeNil()) 282 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 283 }) 284 }) 285 }) 286 287 Context("with no (valid) type and filename, a valid json profile", func() { 288 When("there's a profile with json content", func() { 289 var m ProfileFile 290 291 BeforeEach(func() { 292 m = ProfileFile{ 293 Data: []byte(`{"flamebearer":""}`), 294 } 295 }) 296 297 It("should return json", func() { 298 // We want to compare functions, which is not ideal. 299 expected := reflect.ValueOf(JSONToProfile).Pointer() 300 f, err := converter(m) 301 Expect(err).To(BeNil()) 302 Expect(f).ToNot(BeNil()) 303 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 304 }) 305 }) 306 307 When("there's a profile with json content and an unsupported type", func() { 308 var m ProfileFile 309 310 BeforeEach(func() { 311 m = ProfileFile{ 312 Data: []byte(`{"flamebearer":""}`), 313 Type: "unsupported", 314 } 315 }) 316 317 It("should return json as unsupported types are ignored", func() { 318 // We want to compare functions, which is not ideal. 319 expected := reflect.ValueOf(JSONToProfile).Pointer() 320 f, err := converter(m) 321 Expect(err).To(BeNil()) 322 Expect(f).ToNot(BeNil()) 323 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 324 }) 325 }) 326 327 When("there's a profile with json content and unsupported filename", func() { 328 var m ProfileFile 329 330 BeforeEach(func() { 331 m = ProfileFile{ 332 Name: "profile.unsupported", 333 Data: []byte(`{"flamebearer":""}`), 334 } 335 }) 336 337 It("should return json as unsupported filenames are ignored", func() { 338 // We want to compare functions, which is not ideal. 339 expected := reflect.ValueOf(JSONToProfile).Pointer() 340 f, err := converter(m) 341 Expect(err).To(BeNil()) 342 Expect(f).ToNot(BeNil()) 343 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 344 }) 345 }) 346 }) 347 348 Context("with a valid collapsed type", func() { 349 When("there's only type", func() { 350 var m ProfileFile 351 352 BeforeEach(func() { 353 m = ProfileFile{ 354 Type: "collapsed", 355 } 356 }) 357 It("should return collapsed as type is be enough to detect the type", func() { 358 // We want to compare functions, which is not ideal. 359 expected := reflect.ValueOf(CollapsedToProfile).Pointer() 360 f, err := converter(m) 361 Expect(err).To(BeNil()) 362 Expect(f).ToNot(BeNil()) 363 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 364 }) 365 }) 366 When("there's collapsed type and pprof filename", func() { 367 var m ProfileFile 368 369 BeforeEach(func() { 370 m = ProfileFile{ 371 Name: "profile.pprof", 372 Type: "collapsed", 373 } 374 }) 375 It("should return collapsed as type takes precedence over filename", func() { 376 // We want to compare functions, which is not ideal. 377 expected := reflect.ValueOf(CollapsedToProfile).Pointer() 378 f, err := converter(m) 379 Expect(err).To(BeNil()) 380 Expect(f).ToNot(BeNil()) 381 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 382 }) 383 }) 384 385 When("there's json type and pprof profile contents", func() { 386 var m ProfileFile 387 388 BeforeEach(func() { 389 m = ProfileFile{ 390 Data: []byte{0x1f, 0x8b}, 391 Type: "collapsed", 392 } 393 }) 394 It("should return collapsed as type takes precedence over profile contents", func() { 395 // We want to compare functions, which is not ideal. 396 expected := reflect.ValueOf(CollapsedToProfile).Pointer() 397 f, err := converter(m) 398 Expect(err).To(BeNil()) 399 Expect(f).ToNot(BeNil()) 400 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 401 }) 402 }) 403 }) 404 405 Context("with no (valid) type and a valid collapsed filename", func() { 406 When("there's collapsed filename and pprof profile contents", func() { 407 var m ProfileFile 408 BeforeEach(func() { 409 m = ProfileFile{ 410 Name: "profile.collapsed", 411 Data: []byte{0x1f, 0x8b}, 412 } 413 }) 414 415 It("should return collapsed as filename takes precedence over profile contents", func() { 416 // We want to compare functions, which is not ideal. 417 expected := reflect.ValueOf(CollapsedToProfile).Pointer() 418 f, err := converter(m) 419 Expect(err).To(BeNil()) 420 Expect(f).ToNot(BeNil()) 421 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 422 }) 423 }) 424 When("there's collapsed filename and an unsupported type", func() { 425 var m ProfileFile 426 BeforeEach(func() { 427 m = ProfileFile{ 428 Name: "profile.collapsed", 429 Type: "unsupported", 430 } 431 }) 432 433 It("should return collapsed as unsupported type is ignored", func() { 434 // We want to compare functions, which is not ideal. 435 expected := reflect.ValueOf(CollapsedToProfile).Pointer() 436 f, err := converter(m) 437 Expect(err).To(BeNil()) 438 Expect(f).ToNot(BeNil()) 439 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 440 }) 441 }) 442 443 When("there's collapsed text filename and an unsupported type", func() { 444 var m ProfileFile 445 BeforeEach(func() { 446 m = ProfileFile{ 447 Name: "profile.collapsed.txt", 448 Type: "unsupported", 449 } 450 }) 451 452 It("should return collapsed as unsupported type is ignored", func() { 453 // We want to compare functions, which is not ideal. 454 expected := reflect.ValueOf(CollapsedToProfile).Pointer() 455 f, err := converter(m) 456 Expect(err).To(BeNil()) 457 Expect(f).ToNot(BeNil()) 458 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 459 }) 460 }) 461 }) 462 463 Context("with no (valid) type and filename, a valid collapsed profile", func() { 464 When("there's a profile with collapsed content", func() { 465 var m ProfileFile 466 467 BeforeEach(func() { 468 m = ProfileFile{ 469 Data: []byte("fn1 1\nfn2 2"), 470 } 471 }) 472 473 It("should return collapsed", func() { 474 // We want to compare functions, which is not ideal. 475 expected := reflect.ValueOf(CollapsedToProfile).Pointer() 476 f, err := converter(m) 477 Expect(err).To(BeNil()) 478 Expect(f).ToNot(BeNil()) 479 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 480 }) 481 }) 482 483 When("there's a profile with collapsed content and an unsupported type", func() { 484 var m ProfileFile 485 486 BeforeEach(func() { 487 m = ProfileFile{ 488 Data: []byte("fn1 1\nfn2 2"), 489 Type: "unsupported", 490 } 491 }) 492 493 It("should return collapsed as unsupported types are ignored", func() { 494 // We want to compare functions, which is not ideal. 495 expected := reflect.ValueOf(CollapsedToProfile).Pointer() 496 f, err := converter(m) 497 Expect(err).To(BeNil()) 498 Expect(f).ToNot(BeNil()) 499 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 500 }) 501 }) 502 503 When("there's a profile with collapsed content and unsupported filename", func() { 504 var m ProfileFile 505 506 BeforeEach(func() { 507 m = ProfileFile{ 508 Name: "profile.unsupported", 509 Data: []byte("fn1 1\nfn2 2"), 510 } 511 }) 512 513 It("should return collapsed as unsupported filenames are ignored", func() { 514 // We want to compare functions, which is not ideal. 515 expected := reflect.ValueOf(CollapsedToProfile).Pointer() 516 f, err := converter(m) 517 Expect(err).To(BeNil()) 518 Expect(f).ToNot(BeNil()) 519 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 520 }) 521 }) 522 }) 523 524 Context("perf script", func() { 525 When("detect by content", func() { 526 var m ProfileFile 527 528 BeforeEach(func() { 529 m = ProfileFile{ 530 Data: []byte("java 12688 [002] 6544038.708352: cpu-clock:\n\n"), 531 } 532 }) 533 534 It("should return perf_script", func() { 535 // We want to compare functions, which is not ideal. 536 expected := reflect.ValueOf(PerfScriptToProfile).Pointer() 537 f, err := converter(m) 538 Expect(err).To(BeNil()) 539 Expect(f).ToNot(BeNil()) 540 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 541 }) 542 }) 543 When("detect by .txt extension and content", func() { 544 var m ProfileFile 545 546 BeforeEach(func() { 547 m = ProfileFile{ 548 Name: "foo.txt", 549 Data: []byte("java 12688 [002] 6544038.708352: cpu-clock:\n\n"), 550 } 551 }) 552 553 It("should return perf_script", func() { 554 // We want to compare functions, which is not ideal. 555 expected := reflect.ValueOf(PerfScriptToProfile).Pointer() 556 f, err := converter(m) 557 Expect(err).To(BeNil()) 558 Expect(f).ToNot(BeNil()) 559 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 560 }) 561 }) 562 When("detect by .perf_script extension", func() { 563 var m ProfileFile 564 565 BeforeEach(func() { 566 m = ProfileFile{ 567 Name: "foo.perf_script", 568 Data: []byte("foo;bar 239"), 569 } 570 }) 571 572 It("should return perf_script", func() { 573 // We want to compare functions, which is not ideal. 574 expected := reflect.ValueOf(PerfScriptToProfile).Pointer() 575 f, err := converter(m) 576 Expect(err).To(BeNil()) 577 Expect(f).ToNot(BeNil()) 578 Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) 579 }) 580 }) 581 }) 582 583 Context("with an empty ProfileFile", func() { 584 var m ProfileFile 585 It("should return an error", func() { 586 _, err := converter(m) 587 Expect(err).ToNot(Succeed()) 588 }) 589 }) 590 }) 591 592 Describe("Calling DiffV1", func() { 593 Context("with v1 profiles", func() { 594 var base, diff *flamebearer.FlamebearerProfile 595 596 When("Diff is called with valid and equal base and diff profiles", func() { 597 BeforeEach(func() { 598 base = &flamebearer.FlamebearerProfile{ 599 Version: 1, 600 FlamebearerProfileV1: flamebearer.FlamebearerProfileV1{ 601 Metadata: flamebearer.FlamebearerMetadataV1{ 602 Format: "single", 603 }, 604 // Taken from flamebearer test 605 Flamebearer: flamebearer.FlamebearerV1{ 606 Names: []string{"total", "a", "c", "b"}, 607 Levels: [][]int{ 608 {0, 3, 0, 0}, 609 {0, 3, 0, 1}, 610 {0, 1, 1, 3, 0, 2, 2, 2}, 611 }, 612 NumTicks: 3, 613 MaxSelf: 2, 614 }, 615 }, 616 } 617 618 diff = &flamebearer.FlamebearerProfile{ 619 Version: 1, 620 FlamebearerProfileV1: flamebearer.FlamebearerProfileV1{ 621 Metadata: flamebearer.FlamebearerMetadataV1{ 622 Format: "single", 623 }, 624 // Taken from flamebearer test 625 Flamebearer: flamebearer.FlamebearerV1{ 626 Names: []string{"total", "a", "c", "b"}, 627 Levels: [][]int{ 628 {0, 3, 0, 0}, 629 {0, 3, 0, 1}, 630 {0, 1, 1, 3, 0, 2, 2, 2}, 631 }, 632 NumTicks: 3, 633 MaxSelf: 2, 634 }, 635 }, 636 } 637 }) 638 639 It("returns the diff profile", func() { 640 fb, err := flamebearer.Diff("name", base, diff, 1024) 641 Expect(err).To(Succeed()) 642 Expect(fb.Version).To(Equal(uint(1))) 643 Expect(fb.Metadata.Name).To(Equal("name")) 644 Expect(fb.Metadata.Format).To(Equal("double")) 645 Expect(fb.Flamebearer.Names).To(Equal([]string{"total", "a", "c", "b"})) 646 Expect(fb.Flamebearer.Levels).To(Equal([][]int{ 647 {0, 3, 0, 0, 3, 0, 0}, 648 {0, 3, 0, 0, 3, 0, 1}, 649 {0, 1, 1, 0, 1, 1, 3, 0, 2, 2, 0, 2, 2, 2}, 650 })) 651 Expect(fb.Flamebearer.NumTicks).To(Equal(6)) 652 Expect(fb.Flamebearer.MaxSelf).To(Equal(2)) 653 Expect(fb.LeftTicks).To(Equal(uint64(3))) 654 Expect(fb.RightTicks).To(Equal(uint64(3))) 655 }) 656 }) 657 }) 658 }) 659 }) 660 661 var _ = Describe("Convert", func() { 662 It("converts malformed pprof", func() { 663 m := ProfileFile{ 664 Type: "pprof", 665 Data: readFile("./testdata/cpu-unknown.pb.gz"), 666 } 667 668 f, err := converter(m) 669 Expect(err).To(BeNil()) 670 Expect(f).ToNot(BeNil()) 671 672 b, err := f(m.Data, "appname", 1024) 673 Expect(err).To(BeNil()) 674 Expect(b).ToNot(BeNil()) 675 }) 676 677 Describe("JSON", func() { 678 It("prunes tree", func() { 679 m := ProfileFile{ 680 Type: "json", 681 Data: readFile("./testdata/profile.json"), 682 } 683 684 f, err := converter(m) 685 Expect(err).To(BeNil()) 686 Expect(f).ToNot(BeNil()) 687 688 b, err := f(m.Data, "appname", 1) 689 Expect(err).To(BeNil()) 690 Expect(b).ToNot(BeNil()) 691 692 // 1 + total 693 Expect(len(b.FlamebearerProfileV1.Flamebearer.Levels)).To(Equal(2)) 694 }) 695 }) 696 }) 697 698 func readFile(path string) []byte { 699 f, err := ioutil.ReadFile(path) 700 if err != nil { 701 panic(err) 702 } 703 return f 704 }