github.com/gidoBOSSftw5731/go/src@v0.0.0-20210226122457-d24b0edbf019/runtime/pprof/pprof_test.go (about) 1 // Copyright 2011 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build !js 6 // +build !js 7 8 package pprof 9 10 import ( 11 "bytes" 12 "context" 13 "fmt" 14 "internal/profile" 15 "internal/race" 16 "internal/testenv" 17 "io" 18 "math/big" 19 "os" 20 "os/exec" 21 "regexp" 22 "runtime" 23 "strings" 24 "sync" 25 "sync/atomic" 26 "testing" 27 "time" 28 ) 29 30 func cpuHogger(f func(x int) int, y *int, dur time.Duration) { 31 // We only need to get one 100 Hz clock tick, so we've got 32 // a large safety buffer. 33 // But do at least 500 iterations (which should take about 100ms), 34 // otherwise TestCPUProfileMultithreaded can fail if only one 35 // thread is scheduled during the testing period. 36 t0 := time.Now() 37 accum := *y 38 for i := 0; i < 500 || time.Since(t0) < dur; i++ { 39 accum = f(accum) 40 } 41 *y = accum 42 } 43 44 var ( 45 salt1 = 0 46 salt2 = 0 47 ) 48 49 // The actual CPU hogging function. 50 // Must not call other functions nor access heap/globals in the loop, 51 // otherwise under race detector the samples will be in the race runtime. 52 func cpuHog1(x int) int { 53 return cpuHog0(x, 1e5) 54 } 55 56 func cpuHog0(x, n int) int { 57 foo := x 58 for i := 0; i < n; i++ { 59 if foo > 0 { 60 foo *= foo 61 } else { 62 foo *= foo + 1 63 } 64 } 65 return foo 66 } 67 68 func cpuHog2(x int) int { 69 foo := x 70 for i := 0; i < 1e5; i++ { 71 if foo > 0 { 72 foo *= foo 73 } else { 74 foo *= foo + 2 75 } 76 } 77 return foo 78 } 79 80 // Return a list of functions that we don't want to ever appear in CPU 81 // profiles. For gccgo, that list includes the sigprof handler itself. 82 func avoidFunctions() []string { 83 if runtime.Compiler == "gccgo" { 84 return []string{"runtime.sigprof"} 85 } 86 return nil 87 } 88 89 func TestCPUProfile(t *testing.T) { 90 testCPUProfile(t, stackContains, []string{"runtime/pprof.cpuHog1"}, avoidFunctions(), func(dur time.Duration) { 91 cpuHogger(cpuHog1, &salt1, dur) 92 }) 93 } 94 95 func TestCPUProfileMultithreaded(t *testing.T) { 96 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2)) 97 testCPUProfile(t, stackContains, []string{"runtime/pprof.cpuHog1", "runtime/pprof.cpuHog2"}, avoidFunctions(), func(dur time.Duration) { 98 c := make(chan int) 99 go func() { 100 cpuHogger(cpuHog1, &salt1, dur) 101 c <- 1 102 }() 103 cpuHogger(cpuHog2, &salt2, dur) 104 <-c 105 }) 106 } 107 108 // containsInlinedCall reports whether the function body for the function f is 109 // known to contain an inlined function call within the first maxBytes bytes. 110 func containsInlinedCall(f interface{}, maxBytes int) bool { 111 _, found := findInlinedCall(f, maxBytes) 112 return found 113 } 114 115 // findInlinedCall returns the PC of an inlined function call within 116 // the function body for the function f if any. 117 func findInlinedCall(f interface{}, maxBytes int) (pc uint64, found bool) { 118 fFunc := runtime.FuncForPC(uintptr(funcPC(f))) 119 if fFunc == nil || fFunc.Entry() == 0 { 120 panic("failed to locate function entry") 121 } 122 123 for offset := 0; offset < maxBytes; offset++ { 124 innerPC := fFunc.Entry() + uintptr(offset) 125 inner := runtime.FuncForPC(innerPC) 126 if inner == nil { 127 // No function known for this PC value. 128 // It might simply be misaligned, so keep searching. 129 continue 130 } 131 if inner.Entry() != fFunc.Entry() { 132 // Scanned past f and didn't find any inlined functions. 133 break 134 } 135 if inner.Name() != fFunc.Name() { 136 // This PC has f as its entry-point, but is not f. Therefore, it must be a 137 // function inlined into f. 138 return uint64(innerPC), true 139 } 140 } 141 142 return 0, false 143 } 144 145 func TestCPUProfileInlining(t *testing.T) { 146 if !containsInlinedCall(inlinedCaller, 4<<10) { 147 t.Skip("Can't determine whether inlinedCallee was inlined into inlinedCaller.") 148 } 149 150 p := testCPUProfile(t, stackContains, []string{"runtime/pprof.inlinedCallee", "runtime/pprof.inlinedCaller"}, avoidFunctions(), func(dur time.Duration) { 151 cpuHogger(inlinedCaller, &salt1, dur) 152 }) 153 154 // Check if inlined function locations are encoded correctly. The inlinedCalee and inlinedCaller should be in one location. 155 for _, loc := range p.Location { 156 hasInlinedCallerAfterInlinedCallee, hasInlinedCallee := false, false 157 for _, line := range loc.Line { 158 if line.Function.Name == "runtime/pprof.inlinedCallee" { 159 hasInlinedCallee = true 160 } 161 if hasInlinedCallee && line.Function.Name == "runtime/pprof.inlinedCaller" { 162 hasInlinedCallerAfterInlinedCallee = true 163 } 164 } 165 if hasInlinedCallee != hasInlinedCallerAfterInlinedCallee { 166 t.Fatalf("want inlinedCallee followed by inlinedCaller, got separate Location entries:\n%v", p) 167 } 168 } 169 } 170 171 func inlinedCaller(x int) int { 172 x = inlinedCallee(x, 1e5) 173 return x 174 } 175 176 func inlinedCallee(x, n int) int { 177 return cpuHog0(x, n) 178 } 179 180 //go:noinline 181 func dumpCallers(pcs []uintptr) { 182 if pcs == nil { 183 return 184 } 185 186 skip := 2 // Callers and dumpCallers 187 runtime.Callers(skip, pcs) 188 } 189 190 //go:noinline 191 func inlinedCallerDump(pcs []uintptr) { 192 inlinedCalleeDump(pcs) 193 } 194 195 func inlinedCalleeDump(pcs []uintptr) { 196 dumpCallers(pcs) 197 } 198 199 func TestCPUProfileRecursion(t *testing.T) { 200 p := testCPUProfile(t, stackContains, []string{"runtime/pprof.inlinedCallee", "runtime/pprof.recursionCallee", "runtime/pprof.recursionCaller"}, avoidFunctions(), func(dur time.Duration) { 201 cpuHogger(recursionCaller, &salt1, dur) 202 }) 203 204 // check the Location encoding was not confused by recursive calls. 205 for i, loc := range p.Location { 206 recursionFunc := 0 207 for _, line := range loc.Line { 208 if name := line.Function.Name; name == "runtime/pprof.recursionCaller" || name == "runtime/pprof.recursionCallee" { 209 recursionFunc++ 210 } 211 } 212 if recursionFunc > 1 { 213 t.Fatalf("want at most one recursionCaller or recursionCallee in one Location, got a violating Location (index: %d):\n%v", i, p) 214 } 215 } 216 } 217 218 func recursionCaller(x int) int { 219 y := recursionCallee(3, x) 220 return y 221 } 222 223 func recursionCallee(n, x int) int { 224 if n == 0 { 225 return 1 226 } 227 y := inlinedCallee(x, 1e4) 228 return y * recursionCallee(n-1, x) 229 } 230 231 func recursionChainTop(x int, pcs []uintptr) { 232 if x < 0 { 233 return 234 } 235 recursionChainMiddle(x, pcs) 236 } 237 238 func recursionChainMiddle(x int, pcs []uintptr) { 239 recursionChainBottom(x, pcs) 240 } 241 242 func recursionChainBottom(x int, pcs []uintptr) { 243 // This will be called each time, we only care about the last. We 244 // can't make this conditional or this function won't be inlined. 245 dumpCallers(pcs) 246 247 recursionChainTop(x-1, pcs) 248 } 249 250 func parseProfile(t *testing.T, valBytes []byte, f func(uintptr, []*profile.Location, map[string][]string)) *profile.Profile { 251 p, err := profile.Parse(bytes.NewReader(valBytes)) 252 if err != nil { 253 t.Fatal(err) 254 } 255 for _, sample := range p.Sample { 256 count := uintptr(sample.Value[0]) 257 f(count, sample.Location, sample.Label) 258 } 259 return p 260 } 261 262 // testCPUProfile runs f under the CPU profiler, checking for some conditions specified by need, 263 // as interpreted by matches, and returns the parsed profile. 264 func testCPUProfile(t *testing.T, matches matchFunc, need []string, avoid []string, f func(dur time.Duration)) *profile.Profile { 265 switch runtime.GOOS { 266 case "darwin": 267 out, err := exec.Command("uname", "-a").CombinedOutput() 268 if err != nil { 269 t.Fatal(err) 270 } 271 vers := string(out) 272 t.Logf("uname -a: %v", vers) 273 case "plan9": 274 t.Skip("skipping on plan9") 275 } 276 277 broken := false 278 switch runtime.GOOS { 279 case "ios", "dragonfly", "netbsd", "illumos", "solaris": 280 broken = true 281 case "openbsd": 282 if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" { 283 broken = true 284 } 285 } 286 287 maxDuration := 5 * time.Second 288 if testing.Short() && broken { 289 // If it's expected to be broken, no point waiting around. 290 maxDuration /= 10 291 } 292 293 // If we're running a long test, start with a long duration 294 // for tests that try to make sure something *doesn't* happen. 295 duration := 5 * time.Second 296 if testing.Short() { 297 duration = 100 * time.Millisecond 298 } 299 300 // Profiling tests are inherently flaky, especially on a 301 // loaded system, such as when this test is running with 302 // several others under go test std. If a test fails in a way 303 // that could mean it just didn't run long enough, try with a 304 // longer duration. 305 for duration <= maxDuration { 306 var prof bytes.Buffer 307 if err := StartCPUProfile(&prof); err != nil { 308 t.Fatal(err) 309 } 310 f(duration) 311 StopCPUProfile() 312 313 if p, ok := profileOk(t, matches, need, avoid, prof, duration); ok { 314 return p 315 } 316 317 duration *= 2 318 if duration <= maxDuration { 319 t.Logf("retrying with %s duration", duration) 320 } 321 } 322 323 if broken { 324 t.Skipf("ignoring failure on %s/%s; see golang.org/issue/13841", runtime.GOOS, runtime.GOARCH) 325 } 326 327 // Ignore the failure if the tests are running in a QEMU-based emulator, 328 // QEMU is not perfect at emulating everything. 329 // IN_QEMU environmental variable is set by some of the Go builders. 330 // IN_QEMU=1 indicates that the tests are running in QEMU. See issue 9605. 331 if os.Getenv("IN_QEMU") == "1" { 332 t.Skip("ignore the failure in QEMU; see golang.org/issue/9605") 333 } 334 t.FailNow() 335 return nil 336 } 337 338 func contains(slice []string, s string) bool { 339 for i := range slice { 340 if slice[i] == s { 341 return true 342 } 343 } 344 return false 345 } 346 347 // stackContains matches if a function named spec appears anywhere in the stack trace. 348 func stackContains(spec string, count uintptr, stk []*profile.Location, labels map[string][]string) bool { 349 for _, loc := range stk { 350 for _, line := range loc.Line { 351 if strings.Contains(line.Function.Name, spec) { 352 return true 353 } 354 } 355 } 356 return false 357 } 358 359 type matchFunc func(spec string, count uintptr, stk []*profile.Location, labels map[string][]string) bool 360 361 func profileOk(t *testing.T, matches matchFunc, need []string, avoid []string, prof bytes.Buffer, duration time.Duration) (_ *profile.Profile, ok bool) { 362 ok = true 363 364 // Check that profile is well formed, contains 'need', and does not contain 365 // anything from 'avoid'. 366 have := make([]uintptr, len(need)) 367 avoidSamples := make([]uintptr, len(avoid)) 368 var samples uintptr 369 var buf bytes.Buffer 370 p := parseProfile(t, prof.Bytes(), func(count uintptr, stk []*profile.Location, labels map[string][]string) { 371 fmt.Fprintf(&buf, "%d:", count) 372 fprintStack(&buf, stk) 373 samples += count 374 for i, spec := range need { 375 if matches(spec, count, stk, labels) { 376 have[i] += count 377 } 378 } 379 for i, name := range avoid { 380 for _, loc := range stk { 381 for _, line := range loc.Line { 382 if strings.Contains(line.Function.Name, name) { 383 avoidSamples[i] += count 384 } 385 } 386 } 387 } 388 fmt.Fprintf(&buf, "\n") 389 }) 390 t.Logf("total %d CPU profile samples collected:\n%s", samples, buf.String()) 391 392 if samples < 10 && runtime.GOOS == "windows" { 393 // On some windows machines we end up with 394 // not enough samples due to coarse timer 395 // resolution. Let it go. 396 t.Log("too few samples on Windows (golang.org/issue/10842)") 397 return p, false 398 } 399 400 // Check that we got a reasonable number of samples. 401 // We used to always require at least ideal/4 samples, 402 // but that is too hard to guarantee on a loaded system. 403 // Now we accept 10 or more samples, which we take to be 404 // enough to show that at least some profiling is occurring. 405 if ideal := uintptr(duration * 100 / time.Second); samples == 0 || (samples < ideal/4 && samples < 10) { 406 t.Logf("too few samples; got %d, want at least %d, ideally %d", samples, ideal/4, ideal) 407 ok = false 408 } 409 410 for i, name := range avoid { 411 bad := avoidSamples[i] 412 if bad != 0 { 413 t.Logf("found %d samples in avoid-function %s\n", bad, name) 414 ok = false 415 } 416 } 417 418 if len(need) == 0 { 419 return p, ok 420 } 421 422 var total uintptr 423 for i, name := range need { 424 total += have[i] 425 t.Logf("%s: %d\n", name, have[i]) 426 } 427 if total == 0 { 428 t.Logf("no samples in expected functions") 429 ok = false 430 } 431 // We'd like to check a reasonable minimum, like 432 // total / len(have) / smallconstant, but this test is 433 // pretty flaky (see bug 7095). So we'll just test to 434 // make sure we got at least one sample. 435 min := uintptr(1) 436 for i, name := range need { 437 if have[i] < min { 438 t.Logf("%s has %d samples out of %d, want at least %d, ideally %d", name, have[i], total, min, total/uintptr(len(have))) 439 ok = false 440 } 441 } 442 return p, ok 443 } 444 445 // Fork can hang if preempted with signals frequently enough (see issue 5517). 446 // Ensure that we do not do this. 447 func TestCPUProfileWithFork(t *testing.T) { 448 testenv.MustHaveExec(t) 449 450 heap := 1 << 30 451 if runtime.GOOS == "android" { 452 // Use smaller size for Android to avoid crash. 453 heap = 100 << 20 454 } 455 if runtime.GOOS == "windows" && runtime.GOARCH == "arm" { 456 // Use smaller heap for Windows/ARM to avoid crash. 457 heap = 100 << 20 458 } 459 if testing.Short() { 460 heap = 100 << 20 461 } 462 // This makes fork slower. 463 garbage := make([]byte, heap) 464 // Need to touch the slice, otherwise it won't be paged in. 465 done := make(chan bool) 466 go func() { 467 for i := range garbage { 468 garbage[i] = 42 469 } 470 done <- true 471 }() 472 <-done 473 474 var prof bytes.Buffer 475 if err := StartCPUProfile(&prof); err != nil { 476 t.Fatal(err) 477 } 478 defer StopCPUProfile() 479 480 for i := 0; i < 10; i++ { 481 exec.Command(os.Args[0], "-h").CombinedOutput() 482 } 483 } 484 485 // Test that profiler does not observe runtime.gogo as "user" goroutine execution. 486 // If it did, it would see inconsistent state and would either record an incorrect stack 487 // or crash because the stack was malformed. 488 func TestGoroutineSwitch(t *testing.T) { 489 if runtime.Compiler == "gccgo" { 490 t.Skip("not applicable for gccgo") 491 } 492 // How much to try. These defaults take about 1 seconds 493 // on a 2012 MacBook Pro. The ones in short mode take 494 // about 0.1 seconds. 495 tries := 10 496 count := 1000000 497 if testing.Short() { 498 tries = 1 499 } 500 for try := 0; try < tries; try++ { 501 var prof bytes.Buffer 502 if err := StartCPUProfile(&prof); err != nil { 503 t.Fatal(err) 504 } 505 for i := 0; i < count; i++ { 506 runtime.Gosched() 507 } 508 StopCPUProfile() 509 510 // Read profile to look for entries for gogo with an attempt at a traceback. 511 // "runtime.gogo" is OK, because that's the part of the context switch 512 // before the actual switch begins. But we should not see "gogo", 513 // aka "gogo<>(SB)", which does the actual switch and is marked SPWRITE. 514 parseProfile(t, prof.Bytes(), func(count uintptr, stk []*profile.Location, _ map[string][]string) { 515 // An entry with two frames with 'System' in its top frame 516 // exists to record a PC without a traceback. Those are okay. 517 if len(stk) == 2 { 518 name := stk[1].Line[0].Function.Name 519 if name == "runtime._System" || name == "runtime._ExternalCode" || name == "runtime._GC" { 520 return 521 } 522 } 523 524 // An entry with just one frame is OK too: 525 // it knew to stop at gogo. 526 if len(stk) == 1 { 527 return 528 } 529 530 // Otherwise, should not see gogo. 531 // The place we'd see it would be the inner most frame. 532 name := stk[0].Line[0].Function.Name 533 if name == "gogo" { 534 var buf bytes.Buffer 535 fprintStack(&buf, stk) 536 t.Fatalf("found profile entry for gogo:\n%s", buf.String()) 537 } 538 }) 539 } 540 } 541 542 func fprintStack(w io.Writer, stk []*profile.Location) { 543 for _, loc := range stk { 544 fmt.Fprintf(w, " %#x", loc.Address) 545 fmt.Fprintf(w, " (") 546 for i, line := range loc.Line { 547 if i > 0 { 548 fmt.Fprintf(w, " ") 549 } 550 fmt.Fprintf(w, "%s:%d", line.Function.Name, line.Line) 551 } 552 fmt.Fprintf(w, ")") 553 } 554 fmt.Fprintf(w, "\n") 555 } 556 557 // Test that profiling of division operations is okay, especially on ARM. See issue 6681. 558 func TestMathBigDivide(t *testing.T) { 559 testCPUProfile(t, nil, nil, nil, func(duration time.Duration) { 560 t := time.After(duration) 561 pi := new(big.Int) 562 for { 563 for i := 0; i < 100; i++ { 564 n := big.NewInt(2646693125139304345) 565 d := big.NewInt(842468587426513207) 566 pi.Div(n, d) 567 } 568 select { 569 case <-t: 570 return 571 default: 572 } 573 } 574 }) 575 } 576 577 // stackContainsAll matches if all functions in spec (comma-separated) appear somewhere in the stack trace. 578 func stackContainsAll(spec string, count uintptr, stk []*profile.Location, labels map[string][]string) bool { 579 for _, f := range strings.Split(spec, ",") { 580 if !stackContains(f, count, stk, labels) { 581 return false 582 } 583 } 584 return true 585 } 586 587 func TestMorestack(t *testing.T) { 588 if runtime.GOOS == "darwin" && race.Enabled { 589 // For whatever reason, using the race detector on macOS keeps us 590 // from finding the newstack/growstack calls in the profile. 591 // Not worth worrying about. 592 // https://build.golang.org/log/280d387327806e17c8aabeb38b9503dbbd942ed1 593 t.Skip("skipping on darwin race detector") 594 } 595 if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { 596 // For whatever reason, darwin/arm64 also doesn't work. 597 // https://build.golang.org/log/c45e82cc25f152642e6fb90d882ef5a8cd130ce5 598 t.Skip("skipping on darwin/arm64") 599 } 600 testCPUProfile(t, stackContainsAll, []string{"runtime.newstack,runtime/pprof.growstack"}, avoidFunctions(), func(duration time.Duration) { 601 t := time.After(duration) 602 c := make(chan bool) 603 for { 604 go func() { 605 growstack1() 606 c <- true 607 }() 608 select { 609 case <-t: 610 return 611 case <-c: 612 } 613 } 614 }) 615 } 616 617 //go:noinline 618 func growstack1() { 619 growstack() 620 } 621 622 //go:noinline 623 func growstack() { 624 var buf [8 << 10]byte 625 use(buf) 626 } 627 628 //go:noinline 629 func use(x [8 << 10]byte) {} 630 631 func TestBlockProfile(t *testing.T) { 632 type TestCase struct { 633 name string 634 f func() 635 stk []string 636 re string 637 } 638 tests := [...]TestCase{ 639 { 640 name: "chan recv", 641 f: blockChanRecv, 642 stk: []string{ 643 "runtime.chanrecv1", 644 "runtime/pprof.blockChanRecv", 645 "runtime/pprof.TestBlockProfile", 646 }, 647 re: ` 648 [0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+ 649 # 0x[0-9a-f]+ runtime\.chanrecv1\+0x[0-9a-f]+ .*/src/runtime/chan.go:[0-9]+ 650 # 0x[0-9a-f]+ runtime/pprof\.blockChanRecv\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 651 # 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 652 `}, 653 { 654 name: "chan send", 655 f: blockChanSend, 656 stk: []string{ 657 "runtime.chansend1", 658 "runtime/pprof.blockChanSend", 659 "runtime/pprof.TestBlockProfile", 660 }, 661 re: ` 662 [0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+ 663 # 0x[0-9a-f]+ runtime\.chansend1\+0x[0-9a-f]+ .*/src/runtime/chan.go:[0-9]+ 664 # 0x[0-9a-f]+ runtime/pprof\.blockChanSend\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 665 # 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 666 `}, 667 { 668 name: "chan close", 669 f: blockChanClose, 670 stk: []string{ 671 "runtime.chanrecv1", 672 "runtime/pprof.blockChanClose", 673 "runtime/pprof.TestBlockProfile", 674 }, 675 re: ` 676 [0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+ 677 # 0x[0-9a-f]+ runtime\.chanrecv1\+0x[0-9a-f]+ .*/src/runtime/chan.go:[0-9]+ 678 # 0x[0-9a-f]+ runtime/pprof\.blockChanClose\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 679 # 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 680 `}, 681 { 682 name: "select recv async", 683 f: blockSelectRecvAsync, 684 stk: []string{ 685 "runtime.selectgo", 686 "runtime/pprof.blockSelectRecvAsync", 687 "runtime/pprof.TestBlockProfile", 688 }, 689 re: ` 690 [0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+ 691 # 0x[0-9a-f]+ runtime\.selectgo\+0x[0-9a-f]+ .*/src/runtime/select.go:[0-9]+ 692 # 0x[0-9a-f]+ runtime/pprof\.blockSelectRecvAsync\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 693 # 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 694 `}, 695 { 696 name: "select send sync", 697 f: blockSelectSendSync, 698 stk: []string{ 699 "runtime.selectgo", 700 "runtime/pprof.blockSelectSendSync", 701 "runtime/pprof.TestBlockProfile", 702 }, 703 re: ` 704 [0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+ 705 # 0x[0-9a-f]+ runtime\.selectgo\+0x[0-9a-f]+ .*/src/runtime/select.go:[0-9]+ 706 # 0x[0-9a-f]+ runtime/pprof\.blockSelectSendSync\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 707 # 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 708 `}, 709 { 710 name: "mutex", 711 f: blockMutex, 712 stk: []string{ 713 "sync.(*Mutex).Lock", 714 "runtime/pprof.blockMutex", 715 "runtime/pprof.TestBlockProfile", 716 }, 717 re: ` 718 [0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+ 719 # 0x[0-9a-f]+ sync\.\(\*Mutex\)\.Lock\+0x[0-9a-f]+ .*/src/sync/mutex\.go:[0-9]+ 720 # 0x[0-9a-f]+ runtime/pprof\.blockMutex\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 721 # 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 722 `}, 723 { 724 name: "cond", 725 f: blockCond, 726 stk: []string{ 727 "sync.(*Cond).Wait", 728 "runtime/pprof.blockCond", 729 "runtime/pprof.TestBlockProfile", 730 }, 731 re: ` 732 [0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+ 733 # 0x[0-9a-f]+ sync\.\(\*Cond\)\.Wait\+0x[0-9a-f]+ .*/src/sync/cond\.go:[0-9]+ 734 # 0x[0-9a-f]+ runtime/pprof\.blockCond\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 735 # 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 736 `}, 737 } 738 739 // Generate block profile 740 runtime.SetBlockProfileRate(1) 741 defer runtime.SetBlockProfileRate(0) 742 for _, test := range tests { 743 test.f() 744 } 745 746 t.Run("debug=1", func(t *testing.T) { 747 var w bytes.Buffer 748 Lookup("block").WriteTo(&w, 1) 749 prof := w.String() 750 751 if !strings.HasPrefix(prof, "--- contention:\ncycles/second=") { 752 t.Fatalf("Bad profile header:\n%v", prof) 753 } 754 755 if strings.HasSuffix(prof, "#\t0x0\n\n") { 756 t.Errorf("Useless 0 suffix:\n%v", prof) 757 } 758 759 for _, test := range tests { 760 if !regexp.MustCompile(strings.ReplaceAll(test.re, "\t", "\t+")).MatchString(prof) { 761 t.Errorf("Bad %v entry, expect:\n%v\ngot:\n%v", test.name, test.re, prof) 762 } 763 } 764 }) 765 766 t.Run("proto", func(t *testing.T) { 767 // proto format 768 var w bytes.Buffer 769 Lookup("block").WriteTo(&w, 0) 770 p, err := profile.Parse(&w) 771 if err != nil { 772 t.Fatalf("failed to parse profile: %v", err) 773 } 774 t.Logf("parsed proto: %s", p) 775 if err := p.CheckValid(); err != nil { 776 t.Fatalf("invalid profile: %v", err) 777 } 778 779 stks := stacks(p) 780 for _, test := range tests { 781 if !containsStack(stks, test.stk) { 782 t.Errorf("No matching stack entry for %v, want %+v", test.name, test.stk) 783 } 784 } 785 }) 786 787 } 788 789 func stacks(p *profile.Profile) (res [][]string) { 790 for _, s := range p.Sample { 791 var stk []string 792 for _, l := range s.Location { 793 for _, line := range l.Line { 794 stk = append(stk, line.Function.Name) 795 } 796 } 797 res = append(res, stk) 798 } 799 return res 800 } 801 802 func containsStack(got [][]string, want []string) bool { 803 for _, stk := range got { 804 if len(stk) < len(want) { 805 continue 806 } 807 for i, f := range want { 808 if f != stk[i] { 809 break 810 } 811 if i == len(want)-1 { 812 return true 813 } 814 } 815 } 816 return false 817 } 818 819 const blockDelay = 10 * time.Millisecond 820 821 func blockChanRecv() { 822 c := make(chan bool) 823 go func() { 824 time.Sleep(blockDelay) 825 c <- true 826 }() 827 <-c 828 } 829 830 func blockChanSend() { 831 c := make(chan bool) 832 go func() { 833 time.Sleep(blockDelay) 834 <-c 835 }() 836 c <- true 837 } 838 839 func blockChanClose() { 840 c := make(chan bool) 841 go func() { 842 time.Sleep(blockDelay) 843 close(c) 844 }() 845 <-c 846 } 847 848 func blockSelectRecvAsync() { 849 const numTries = 3 850 c := make(chan bool, 1) 851 c2 := make(chan bool, 1) 852 go func() { 853 for i := 0; i < numTries; i++ { 854 time.Sleep(blockDelay) 855 c <- true 856 } 857 }() 858 for i := 0; i < numTries; i++ { 859 select { 860 case <-c: 861 case <-c2: 862 } 863 } 864 } 865 866 func blockSelectSendSync() { 867 c := make(chan bool) 868 c2 := make(chan bool) 869 go func() { 870 time.Sleep(blockDelay) 871 <-c 872 }() 873 select { 874 case c <- true: 875 case c2 <- true: 876 } 877 } 878 879 func blockMutex() { 880 var mu sync.Mutex 881 mu.Lock() 882 go func() { 883 time.Sleep(blockDelay) 884 mu.Unlock() 885 }() 886 // Note: Unlock releases mu before recording the mutex event, 887 // so it's theoretically possible for this to proceed and 888 // capture the profile before the event is recorded. As long 889 // as this is blocked before the unlock happens, it's okay. 890 mu.Lock() 891 } 892 893 func blockCond() { 894 var mu sync.Mutex 895 c := sync.NewCond(&mu) 896 mu.Lock() 897 go func() { 898 time.Sleep(blockDelay) 899 mu.Lock() 900 c.Signal() 901 mu.Unlock() 902 }() 903 c.Wait() 904 mu.Unlock() 905 } 906 907 func TestMutexProfile(t *testing.T) { 908 // Generate mutex profile 909 910 old := runtime.SetMutexProfileFraction(1) 911 defer runtime.SetMutexProfileFraction(old) 912 if old != 0 { 913 t.Fatalf("need MutexProfileRate 0, got %d", old) 914 } 915 916 blockMutex() 917 918 t.Run("debug=1", func(t *testing.T) { 919 var w bytes.Buffer 920 Lookup("mutex").WriteTo(&w, 1) 921 prof := w.String() 922 t.Logf("received profile: %v", prof) 923 924 if !strings.HasPrefix(prof, "--- mutex:\ncycles/second=") { 925 t.Errorf("Bad profile header:\n%v", prof) 926 } 927 prof = strings.Trim(prof, "\n") 928 lines := strings.Split(prof, "\n") 929 if len(lines) != 6 { 930 t.Errorf("expected 6 lines, got %d %q\n%s", len(lines), prof, prof) 931 } 932 if len(lines) < 6 { 933 return 934 } 935 // checking that the line is like "35258904 1 @ 0x48288d 0x47cd28 0x458931" 936 r2 := `^\d+ \d+ @(?: 0x[[:xdigit:]]+)+` 937 //r2 := "^[0-9]+ 1 @ 0x[0-9a-f x]+$" 938 if ok, err := regexp.MatchString(r2, lines[3]); err != nil || !ok { 939 t.Errorf("%q didn't match %q", lines[3], r2) 940 } 941 r3 := "^#.*runtime/pprof.blockMutex.*$" 942 if ok, err := regexp.MatchString(r3, lines[5]); err != nil || !ok { 943 t.Errorf("%q didn't match %q", lines[5], r3) 944 } 945 t.Logf(prof) 946 }) 947 t.Run("proto", func(t *testing.T) { 948 // proto format 949 var w bytes.Buffer 950 Lookup("mutex").WriteTo(&w, 0) 951 p, err := profile.Parse(&w) 952 if err != nil { 953 t.Fatalf("failed to parse profile: %v", err) 954 } 955 t.Logf("parsed proto: %s", p) 956 if err := p.CheckValid(); err != nil { 957 t.Fatalf("invalid profile: %v", err) 958 } 959 960 stks := stacks(p) 961 for _, want := range [][]string{ 962 {"sync.(*Mutex).Unlock", "runtime/pprof.blockMutex.func1"}, 963 } { 964 if !containsStack(stks, want) { 965 t.Errorf("No matching stack entry for %+v", want) 966 } 967 } 968 }) 969 } 970 971 func func1(c chan int) { <-c } 972 func func2(c chan int) { <-c } 973 func func3(c chan int) { <-c } 974 func func4(c chan int) { <-c } 975 976 func TestGoroutineCounts(t *testing.T) { 977 // Setting GOMAXPROCS to 1 ensures we can force all goroutines to the 978 // desired blocking point. 979 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1)) 980 981 c := make(chan int) 982 for i := 0; i < 100; i++ { 983 switch { 984 case i%10 == 0: 985 go func1(c) 986 case i%2 == 0: 987 go func2(c) 988 default: 989 go func3(c) 990 } 991 // Let goroutines block on channel 992 for j := 0; j < 5; j++ { 993 runtime.Gosched() 994 } 995 } 996 ctx := context.Background() 997 998 // ... and again, with labels this time (just with fewer iterations to keep 999 // sorting deterministic). 1000 Do(ctx, Labels("label", "value"), func(context.Context) { 1001 for i := 0; i < 89; i++ { 1002 switch { 1003 case i%10 == 0: 1004 go func1(c) 1005 case i%2 == 0: 1006 go func2(c) 1007 default: 1008 go func3(c) 1009 } 1010 // Let goroutines block on channel 1011 for j := 0; j < 5; j++ { 1012 runtime.Gosched() 1013 } 1014 } 1015 }) 1016 1017 var w bytes.Buffer 1018 goroutineProf := Lookup("goroutine") 1019 1020 // Check debug profile 1021 goroutineProf.WriteTo(&w, 1) 1022 prof := w.String() 1023 1024 labels := labelMap{"label": "value"} 1025 labelStr := "\n# labels: " + labels.String() 1026 if !containsInOrder(prof, "\n50 @ ", "\n44 @", labelStr, 1027 "\n40 @", "\n36 @", labelStr, "\n10 @", "\n9 @", labelStr, "\n1 @") { 1028 t.Errorf("expected sorted goroutine counts with Labels:\n%s", prof) 1029 } 1030 1031 // Check proto profile 1032 w.Reset() 1033 goroutineProf.WriteTo(&w, 0) 1034 p, err := profile.Parse(&w) 1035 if err != nil { 1036 t.Errorf("error parsing protobuf profile: %v", err) 1037 } 1038 if err := p.CheckValid(); err != nil { 1039 t.Errorf("protobuf profile is invalid: %v", err) 1040 } 1041 expectedLabels := map[int64]map[string]string{ 1042 50: map[string]string{}, 1043 44: map[string]string{"label": "value"}, 1044 40: map[string]string{}, 1045 36: map[string]string{"label": "value"}, 1046 10: map[string]string{}, 1047 9: map[string]string{"label": "value"}, 1048 1: map[string]string{}, 1049 } 1050 if !containsCountsLabels(p, expectedLabels) { 1051 t.Errorf("expected count profile to contain goroutines with counts and labels %v, got %v", 1052 expectedLabels, p) 1053 } 1054 1055 close(c) 1056 1057 time.Sleep(10 * time.Millisecond) // let goroutines exit 1058 } 1059 1060 func containsInOrder(s string, all ...string) bool { 1061 for _, t := range all { 1062 i := strings.Index(s, t) 1063 if i < 0 { 1064 return false 1065 } 1066 s = s[i+len(t):] 1067 } 1068 return true 1069 } 1070 1071 func containsCountsLabels(prof *profile.Profile, countLabels map[int64]map[string]string) bool { 1072 m := make(map[int64]int) 1073 type nkey struct { 1074 count int64 1075 key, val string 1076 } 1077 n := make(map[nkey]int) 1078 for c, kv := range countLabels { 1079 m[c]++ 1080 for k, v := range kv { 1081 n[nkey{ 1082 count: c, 1083 key: k, 1084 val: v, 1085 }]++ 1086 1087 } 1088 } 1089 for _, s := range prof.Sample { 1090 // The count is the single value in the sample 1091 if len(s.Value) != 1 { 1092 return false 1093 } 1094 m[s.Value[0]]-- 1095 for k, vs := range s.Label { 1096 for _, v := range vs { 1097 n[nkey{ 1098 count: s.Value[0], 1099 key: k, 1100 val: v, 1101 }]-- 1102 } 1103 } 1104 } 1105 for _, n := range m { 1106 if n > 0 { 1107 return false 1108 } 1109 } 1110 for _, ncnt := range n { 1111 if ncnt != 0 { 1112 return false 1113 } 1114 } 1115 return true 1116 } 1117 1118 var emptyCallStackTestRun int64 1119 1120 // Issue 18836. 1121 func TestEmptyCallStack(t *testing.T) { 1122 name := fmt.Sprintf("test18836_%d", emptyCallStackTestRun) 1123 emptyCallStackTestRun++ 1124 1125 t.Parallel() 1126 var buf bytes.Buffer 1127 p := NewProfile(name) 1128 1129 p.Add("foo", 47674) 1130 p.WriteTo(&buf, 1) 1131 p.Remove("foo") 1132 got := buf.String() 1133 prefix := name + " profile: total 1\n" 1134 if !strings.HasPrefix(got, prefix) { 1135 t.Fatalf("got:\n\t%q\nwant prefix:\n\t%q\n", got, prefix) 1136 } 1137 lostevent := "lostProfileEvent" 1138 if !strings.Contains(got, lostevent) { 1139 t.Fatalf("got:\n\t%q\ndoes not contain:\n\t%q\n", got, lostevent) 1140 } 1141 } 1142 1143 // stackContainsLabeled takes a spec like funcname;key=value and matches if the stack has that key 1144 // and value and has funcname somewhere in the stack. 1145 func stackContainsLabeled(spec string, count uintptr, stk []*profile.Location, labels map[string][]string) bool { 1146 semi := strings.Index(spec, ";") 1147 if semi == -1 { 1148 panic("no semicolon in key/value spec") 1149 } 1150 kv := strings.SplitN(spec[semi+1:], "=", 2) 1151 if len(kv) != 2 { 1152 panic("missing = in key/value spec") 1153 } 1154 if !contains(labels[kv[0]], kv[1]) { 1155 return false 1156 } 1157 return stackContains(spec[:semi], count, stk, labels) 1158 } 1159 1160 func TestCPUProfileLabel(t *testing.T) { 1161 testCPUProfile(t, stackContainsLabeled, []string{"runtime/pprof.cpuHogger;key=value"}, avoidFunctions(), func(dur time.Duration) { 1162 Do(context.Background(), Labels("key", "value"), func(context.Context) { 1163 cpuHogger(cpuHog1, &salt1, dur) 1164 }) 1165 }) 1166 } 1167 1168 func TestLabelRace(t *testing.T) { 1169 // Test the race detector annotations for synchronization 1170 // between settings labels and consuming them from the 1171 // profile. 1172 testCPUProfile(t, stackContainsLabeled, []string{"runtime/pprof.cpuHogger;key=value"}, nil, func(dur time.Duration) { 1173 start := time.Now() 1174 var wg sync.WaitGroup 1175 for time.Since(start) < dur { 1176 var salts [10]int 1177 for i := 0; i < 10; i++ { 1178 wg.Add(1) 1179 go func(j int) { 1180 Do(context.Background(), Labels("key", "value"), func(context.Context) { 1181 cpuHogger(cpuHog1, &salts[j], time.Millisecond) 1182 }) 1183 wg.Done() 1184 }(i) 1185 } 1186 wg.Wait() 1187 } 1188 }) 1189 } 1190 1191 // Check that there is no deadlock when the program receives SIGPROF while in 1192 // 64bit atomics' critical section. Used to happen on mips{,le}. See #20146. 1193 func TestAtomicLoadStore64(t *testing.T) { 1194 f, err := os.CreateTemp("", "profatomic") 1195 if err != nil { 1196 t.Fatalf("TempFile: %v", err) 1197 } 1198 defer os.Remove(f.Name()) 1199 defer f.Close() 1200 1201 if err := StartCPUProfile(f); err != nil { 1202 t.Fatal(err) 1203 } 1204 defer StopCPUProfile() 1205 1206 var flag uint64 1207 done := make(chan bool, 1) 1208 1209 go func() { 1210 for atomic.LoadUint64(&flag) == 0 { 1211 runtime.Gosched() 1212 } 1213 done <- true 1214 }() 1215 time.Sleep(50 * time.Millisecond) 1216 atomic.StoreUint64(&flag, 1) 1217 <-done 1218 } 1219 1220 func TestTracebackAll(t *testing.T) { 1221 // With gccgo, if a profiling signal arrives at the wrong time 1222 // during traceback, it may crash or hang. See issue #29448. 1223 f, err := os.CreateTemp("", "proftraceback") 1224 if err != nil { 1225 t.Fatalf("TempFile: %v", err) 1226 } 1227 defer os.Remove(f.Name()) 1228 defer f.Close() 1229 1230 if err := StartCPUProfile(f); err != nil { 1231 t.Fatal(err) 1232 } 1233 defer StopCPUProfile() 1234 1235 ch := make(chan int) 1236 defer close(ch) 1237 1238 count := 10 1239 for i := 0; i < count; i++ { 1240 go func() { 1241 <-ch // block 1242 }() 1243 } 1244 1245 N := 10000 1246 if testing.Short() { 1247 N = 500 1248 } 1249 buf := make([]byte, 10*1024) 1250 for i := 0; i < N; i++ { 1251 runtime.Stack(buf, true) 1252 } 1253 } 1254 1255 // TestTryAdd tests the cases that are hard to test with real program execution. 1256 // 1257 // For example, the current go compilers may not always inline functions 1258 // involved in recursion but that may not be true in the future compilers. This 1259 // tests such cases by using fake call sequences and forcing the profile build 1260 // utilizing translateCPUProfile defined in proto_test.go 1261 func TestTryAdd(t *testing.T) { 1262 if _, found := findInlinedCall(inlinedCallerDump, 4<<10); !found { 1263 t.Skip("Can't determine whether anything was inlined into inlinedCallerDump.") 1264 } 1265 1266 // inlinedCallerDump 1267 // inlinedCalleeDump 1268 pcs := make([]uintptr, 2) 1269 inlinedCallerDump(pcs) 1270 inlinedCallerStack := make([]uint64, 2) 1271 for i := range pcs { 1272 inlinedCallerStack[i] = uint64(pcs[i]) 1273 } 1274 1275 if _, found := findInlinedCall(recursionChainBottom, 4<<10); !found { 1276 t.Skip("Can't determine whether anything was inlined into recursionChainBottom.") 1277 } 1278 1279 // recursionChainTop 1280 // recursionChainMiddle 1281 // recursionChainBottom 1282 // recursionChainTop 1283 // recursionChainMiddle 1284 // recursionChainBottom 1285 pcs = make([]uintptr, 6) 1286 recursionChainTop(1, pcs) 1287 recursionStack := make([]uint64, len(pcs)) 1288 for i := range pcs { 1289 recursionStack[i] = uint64(pcs[i]) 1290 } 1291 1292 period := int64(2000 * 1000) // 1/500*1e9 nanosec. 1293 1294 testCases := []struct { 1295 name string 1296 input []uint64 // following the input format assumed by profileBuilder.addCPUData. 1297 wantLocs [][]string // ordered location entries with function names. 1298 wantSamples []*profile.Sample // ordered samples, we care only about Value and the profile location IDs. 1299 }{{ 1300 // Sanity test for a normal, complete stack trace. 1301 name: "full_stack_trace", 1302 input: []uint64{ 1303 3, 0, 500, // hz = 500. Must match the period. 1304 5, 0, 50, inlinedCallerStack[0], inlinedCallerStack[1], 1305 }, 1306 wantLocs: [][]string{ 1307 {"runtime/pprof.inlinedCalleeDump", "runtime/pprof.inlinedCallerDump"}, 1308 }, 1309 wantSamples: []*profile.Sample{ 1310 {Value: []int64{50, 50 * period}, Location: []*profile.Location{{ID: 1}}}, 1311 }, 1312 }, { 1313 name: "bug35538", 1314 input: []uint64{ 1315 3, 0, 500, // hz = 500. Must match the period. 1316 // Fake frame: tryAdd will have inlinedCallerDump 1317 // (stack[1]) on the deck when it encounters the next 1318 // inline function. It should accept this. 1319 7, 0, 10, inlinedCallerStack[0], inlinedCallerStack[1], inlinedCallerStack[0], inlinedCallerStack[1], 1320 5, 0, 20, inlinedCallerStack[0], inlinedCallerStack[1], 1321 }, 1322 wantLocs: [][]string{{"runtime/pprof.inlinedCalleeDump", "runtime/pprof.inlinedCallerDump"}}, 1323 wantSamples: []*profile.Sample{ 1324 {Value: []int64{10, 10 * period}, Location: []*profile.Location{{ID: 1}, {ID: 1}}}, 1325 {Value: []int64{20, 20 * period}, Location: []*profile.Location{{ID: 1}}}, 1326 }, 1327 }, { 1328 name: "bug38096", 1329 input: []uint64{ 1330 3, 0, 500, // hz = 500. Must match the period. 1331 // count (data[2]) == 0 && len(stk) == 1 is an overflow 1332 // entry. The "stk" entry is actually the count. 1333 4, 0, 0, 4242, 1334 }, 1335 wantLocs: [][]string{{"runtime/pprof.lostProfileEvent"}}, 1336 wantSamples: []*profile.Sample{ 1337 {Value: []int64{4242, 4242 * period}, Location: []*profile.Location{{ID: 1}}}, 1338 }, 1339 }, { 1340 // If a function is directly called recursively then it must 1341 // not be inlined in the caller. 1342 // 1343 // N.B. We're generating an impossible profile here, with a 1344 // recursive inlineCalleeDump call. This is simulating a non-Go 1345 // function that looks like an inlined Go function other than 1346 // its recursive property. See pcDeck.tryAdd. 1347 name: "directly_recursive_func_is_not_inlined", 1348 input: []uint64{ 1349 3, 0, 500, // hz = 500. Must match the period. 1350 5, 0, 30, inlinedCallerStack[0], inlinedCallerStack[0], 1351 4, 0, 40, inlinedCallerStack[0], 1352 }, 1353 // inlinedCallerDump shows up here because 1354 // runtime_expandFinalInlineFrame adds it to the stack frame. 1355 wantLocs: [][]string{{"runtime/pprof.inlinedCalleeDump"}, {"runtime/pprof.inlinedCallerDump"}}, 1356 wantSamples: []*profile.Sample{ 1357 {Value: []int64{30, 30 * period}, Location: []*profile.Location{{ID: 1}, {ID: 1}, {ID: 2}}}, 1358 {Value: []int64{40, 40 * period}, Location: []*profile.Location{{ID: 1}, {ID: 2}}}, 1359 }, 1360 }, { 1361 name: "recursion_chain_inline", 1362 input: []uint64{ 1363 3, 0, 500, // hz = 500. Must match the period. 1364 9, 0, 10, recursionStack[0], recursionStack[1], recursionStack[2], recursionStack[3], recursionStack[4], recursionStack[5], 1365 }, 1366 wantLocs: [][]string{ 1367 {"runtime/pprof.recursionChainBottom"}, 1368 { 1369 "runtime/pprof.recursionChainMiddle", 1370 "runtime/pprof.recursionChainTop", 1371 "runtime/pprof.recursionChainBottom", 1372 }, 1373 { 1374 "runtime/pprof.recursionChainMiddle", 1375 "runtime/pprof.recursionChainTop", 1376 "runtime/pprof.TestTryAdd", // inlined into the test. 1377 }, 1378 }, 1379 wantSamples: []*profile.Sample{ 1380 {Value: []int64{10, 10 * period}, Location: []*profile.Location{{ID: 1}, {ID: 2}, {ID: 3}}}, 1381 }, 1382 }, { 1383 name: "truncated_stack_trace_later", 1384 input: []uint64{ 1385 3, 0, 500, // hz = 500. Must match the period. 1386 5, 0, 50, inlinedCallerStack[0], inlinedCallerStack[1], 1387 4, 0, 60, inlinedCallerStack[0], 1388 }, 1389 wantLocs: [][]string{{"runtime/pprof.inlinedCalleeDump", "runtime/pprof.inlinedCallerDump"}}, 1390 wantSamples: []*profile.Sample{ 1391 {Value: []int64{50, 50 * period}, Location: []*profile.Location{{ID: 1}}}, 1392 {Value: []int64{60, 60 * period}, Location: []*profile.Location{{ID: 1}}}, 1393 }, 1394 }, { 1395 name: "truncated_stack_trace_first", 1396 input: []uint64{ 1397 3, 0, 500, // hz = 500. Must match the period. 1398 4, 0, 70, inlinedCallerStack[0], 1399 5, 0, 80, inlinedCallerStack[0], inlinedCallerStack[1], 1400 }, 1401 wantLocs: [][]string{{"runtime/pprof.inlinedCalleeDump", "runtime/pprof.inlinedCallerDump"}}, 1402 wantSamples: []*profile.Sample{ 1403 {Value: []int64{70, 70 * period}, Location: []*profile.Location{{ID: 1}}}, 1404 {Value: []int64{80, 80 * period}, Location: []*profile.Location{{ID: 1}}}, 1405 }, 1406 }, { 1407 // We can recover the inlined caller from a truncated stack. 1408 name: "truncated_stack_trace_only", 1409 input: []uint64{ 1410 3, 0, 500, // hz = 500. Must match the period. 1411 4, 0, 70, inlinedCallerStack[0], 1412 }, 1413 wantLocs: [][]string{{"runtime/pprof.inlinedCalleeDump", "runtime/pprof.inlinedCallerDump"}}, 1414 wantSamples: []*profile.Sample{ 1415 {Value: []int64{70, 70 * period}, Location: []*profile.Location{{ID: 1}}}, 1416 }, 1417 }, { 1418 // The same location is used for duplicated stacks. 1419 name: "truncated_stack_trace_twice", 1420 input: []uint64{ 1421 3, 0, 500, // hz = 500. Must match the period. 1422 4, 0, 70, inlinedCallerStack[0], 1423 // Fake frame: add a fake call to 1424 // inlinedCallerDump to prevent this sample 1425 // from getting merged into above. 1426 5, 0, 80, inlinedCallerStack[1], inlinedCallerStack[0], 1427 }, 1428 wantLocs: [][]string{ 1429 {"runtime/pprof.inlinedCalleeDump", "runtime/pprof.inlinedCallerDump"}, 1430 {"runtime/pprof.inlinedCallerDump"}, 1431 }, 1432 wantSamples: []*profile.Sample{ 1433 {Value: []int64{70, 70 * period}, Location: []*profile.Location{{ID: 1}}}, 1434 {Value: []int64{80, 80 * period}, Location: []*profile.Location{{ID: 2}, {ID: 1}}}, 1435 }, 1436 }} 1437 1438 for _, tc := range testCases { 1439 t.Run(tc.name, func(t *testing.T) { 1440 p, err := translateCPUProfile(tc.input) 1441 if err != nil { 1442 t.Fatalf("translating profile: %v", err) 1443 } 1444 t.Logf("Profile: %v\n", p) 1445 1446 // One location entry with all inlined functions. 1447 var gotLoc [][]string 1448 for _, loc := range p.Location { 1449 var names []string 1450 for _, line := range loc.Line { 1451 names = append(names, line.Function.Name) 1452 } 1453 gotLoc = append(gotLoc, names) 1454 } 1455 if got, want := fmtJSON(gotLoc), fmtJSON(tc.wantLocs); got != want { 1456 t.Errorf("Got Location = %+v\n\twant %+v", got, want) 1457 } 1458 // All samples should point to one location. 1459 var gotSamples []*profile.Sample 1460 for _, sample := range p.Sample { 1461 var locs []*profile.Location 1462 for _, loc := range sample.Location { 1463 locs = append(locs, &profile.Location{ID: loc.ID}) 1464 } 1465 gotSamples = append(gotSamples, &profile.Sample{Value: sample.Value, Location: locs}) 1466 } 1467 if got, want := fmtJSON(gotSamples), fmtJSON(tc.wantSamples); got != want { 1468 t.Errorf("Got Samples = %+v\n\twant %+v", got, want) 1469 } 1470 }) 1471 } 1472 }