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