github.com/riscv/riscv-go@v0.0.0-20200123204226-124ebd6fcc8e/src/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 // +build !nacl 6 7 package pprof_test 8 9 import ( 10 "bytes" 11 "compress/gzip" 12 "fmt" 13 "internal/pprof/profile" 14 "internal/testenv" 15 "io" 16 "io/ioutil" 17 "math/big" 18 "os" 19 "os/exec" 20 "regexp" 21 "runtime" 22 . "runtime/pprof" 23 "strings" 24 "sync" 25 "testing" 26 "time" 27 ) 28 29 func cpuHogger(f func(), dur time.Duration) { 30 // We only need to get one 100 Hz clock tick, so we've got 31 // a large safety buffer. 32 // But do at least 500 iterations (which should take about 100ms), 33 // otherwise TestCPUProfileMultithreaded can fail if only one 34 // thread is scheduled during the testing period. 35 t0 := time.Now() 36 for i := 0; i < 500 || time.Since(t0) < dur; i++ { 37 f() 38 } 39 } 40 41 var ( 42 salt1 = 0 43 salt2 = 0 44 ) 45 46 // The actual CPU hogging function. 47 // Must not call other functions nor access heap/globals in the loop, 48 // otherwise under race detector the samples will be in the race runtime. 49 func cpuHog1() { 50 foo := salt1 51 for i := 0; i < 1e5; i++ { 52 if foo > 0 { 53 foo *= foo 54 } else { 55 foo *= foo + 1 56 } 57 } 58 salt1 = foo 59 } 60 61 func cpuHog2() { 62 foo := salt2 63 for i := 0; i < 1e5; i++ { 64 if foo > 0 { 65 foo *= foo 66 } else { 67 foo *= foo + 2 68 } 69 } 70 salt2 = foo 71 } 72 73 func TestCPUProfile(t *testing.T) { 74 testCPUProfile(t, []string{"runtime/pprof_test.cpuHog1"}, func(dur time.Duration) { 75 cpuHogger(cpuHog1, dur) 76 }) 77 } 78 79 func TestCPUProfileMultithreaded(t *testing.T) { 80 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2)) 81 testCPUProfile(t, []string{"runtime/pprof_test.cpuHog1", "runtime/pprof_test.cpuHog2"}, func(dur time.Duration) { 82 c := make(chan int) 83 go func() { 84 cpuHogger(cpuHog1, dur) 85 c <- 1 86 }() 87 cpuHogger(cpuHog2, dur) 88 <-c 89 }) 90 } 91 92 func parseProfile(t *testing.T, valBytes []byte, f func(uintptr, []uintptr)) { 93 p, err := profile.Parse(bytes.NewReader(valBytes)) 94 if err != nil { 95 t.Fatal(err) 96 } 97 for _, sample := range p.Sample { 98 count := uintptr(sample.Value[0]) 99 stk := make([]uintptr, len(sample.Location)) 100 for i := range sample.Location { 101 stk[i] = uintptr(sample.Location[i].Address) 102 } 103 f(count, stk) 104 } 105 } 106 107 func testCPUProfile(t *testing.T, need []string, f func(dur time.Duration)) { 108 switch runtime.GOOS { 109 case "darwin": 110 switch runtime.GOARCH { 111 case "arm", "arm64": 112 // nothing 113 default: 114 out, err := exec.Command("uname", "-a").CombinedOutput() 115 if err != nil { 116 t.Fatal(err) 117 } 118 vers := string(out) 119 t.Logf("uname -a: %v", vers) 120 } 121 case "plan9": 122 t.Skip("skipping on plan9") 123 } 124 125 const maxDuration = 5 * time.Second 126 // If we're running a long test, start with a long duration 127 // because some of the tests (e.g., TestStackBarrierProfiling) 128 // are trying to make sure something *doesn't* happen. 129 duration := 5 * time.Second 130 if testing.Short() { 131 duration = 200 * time.Millisecond 132 } 133 134 // Profiling tests are inherently flaky, especially on a 135 // loaded system, such as when this test is running with 136 // several others under go test std. If a test fails in a way 137 // that could mean it just didn't run long enough, try with a 138 // longer duration. 139 for duration <= maxDuration { 140 var prof bytes.Buffer 141 if err := StartCPUProfile(&prof); err != nil { 142 t.Fatal(err) 143 } 144 f(duration) 145 StopCPUProfile() 146 147 if profileOk(t, need, prof, duration) { 148 return 149 } 150 151 duration *= 2 152 if duration <= maxDuration { 153 t.Logf("retrying with %s duration", duration) 154 } 155 } 156 157 if badOS[runtime.GOOS] { 158 t.Skipf("ignoring failure on %s; see golang.org/issue/13841", runtime.GOOS) 159 return 160 } 161 // Ignore the failure if the tests are running in a QEMU-based emulator, 162 // QEMU is not perfect at emulating everything. 163 // IN_QEMU environmental variable is set by some of the Go builders. 164 // IN_QEMU=1 indicates that the tests are running in QEMU. See issue 9605. 165 if os.Getenv("IN_QEMU") == "1" { 166 t.Skip("ignore the failure in QEMU; see golang.org/issue/9605") 167 return 168 } 169 t.FailNow() 170 } 171 172 func profileOk(t *testing.T, need []string, prof bytes.Buffer, duration time.Duration) (ok bool) { 173 ok = true 174 175 // Check that profile is well formed and contains need. 176 have := make([]uintptr, len(need)) 177 var samples uintptr 178 parseProfile(t, prof.Bytes(), func(count uintptr, stk []uintptr) { 179 samples += count 180 for _, pc := range stk { 181 f := runtime.FuncForPC(pc) 182 if f == nil { 183 continue 184 } 185 for i, name := range need { 186 if strings.Contains(f.Name(), name) { 187 have[i] += count 188 } 189 } 190 if strings.Contains(f.Name(), "stackBarrier") { 191 // The runtime should have unwound this. 192 t.Fatalf("profile includes stackBarrier") 193 } 194 } 195 }) 196 t.Logf("total %d CPU profile samples collected", samples) 197 198 if samples < 10 && runtime.GOOS == "windows" { 199 // On some windows machines we end up with 200 // not enough samples due to coarse timer 201 // resolution. Let it go. 202 t.Log("too few samples on Windows (golang.org/issue/10842)") 203 return false 204 } 205 206 // Check that we got a reasonable number of samples. 207 // We used to always require at least ideal/4 samples, 208 // but that is too hard to guarantee on a loaded system. 209 // Now we accept 10 or more samples, which we take to be 210 // enough to show that at least some profiling is occurring. 211 if ideal := uintptr(duration * 100 / time.Second); samples == 0 || (samples < ideal/4 && samples < 10) { 212 t.Logf("too few samples; got %d, want at least %d, ideally %d", samples, ideal/4, ideal) 213 ok = false 214 } 215 216 if len(need) == 0 { 217 return ok 218 } 219 220 var total uintptr 221 for i, name := range need { 222 total += have[i] 223 t.Logf("%s: %d\n", name, have[i]) 224 } 225 if total == 0 { 226 t.Logf("no samples in expected functions") 227 ok = false 228 } 229 // We'd like to check a reasonable minimum, like 230 // total / len(have) / smallconstant, but this test is 231 // pretty flaky (see bug 7095). So we'll just test to 232 // make sure we got at least one sample. 233 min := uintptr(1) 234 for i, name := range need { 235 if have[i] < min { 236 t.Logf("%s has %d samples out of %d, want at least %d, ideally %d", name, have[i], total, min, total/uintptr(len(have))) 237 ok = false 238 } 239 } 240 return ok 241 } 242 243 // Fork can hang if preempted with signals frequently enough (see issue 5517). 244 // Ensure that we do not do this. 245 func TestCPUProfileWithFork(t *testing.T) { 246 testenv.MustHaveExec(t) 247 248 heap := 1 << 30 249 if runtime.GOOS == "android" { 250 // Use smaller size for Android to avoid crash. 251 heap = 100 << 20 252 } 253 if testing.Short() { 254 heap = 100 << 20 255 } 256 // This makes fork slower. 257 garbage := make([]byte, heap) 258 // Need to touch the slice, otherwise it won't be paged in. 259 done := make(chan bool) 260 go func() { 261 for i := range garbage { 262 garbage[i] = 42 263 } 264 done <- true 265 }() 266 <-done 267 268 var prof bytes.Buffer 269 if err := StartCPUProfile(&prof); err != nil { 270 t.Fatal(err) 271 } 272 defer StopCPUProfile() 273 274 for i := 0; i < 10; i++ { 275 exec.Command(os.Args[0], "-h").CombinedOutput() 276 } 277 } 278 279 // Test that profiler does not observe runtime.gogo as "user" goroutine execution. 280 // If it did, it would see inconsistent state and would either record an incorrect stack 281 // or crash because the stack was malformed. 282 func TestGoroutineSwitch(t *testing.T) { 283 // How much to try. These defaults take about 1 seconds 284 // on a 2012 MacBook Pro. The ones in short mode take 285 // about 0.1 seconds. 286 tries := 10 287 count := 1000000 288 if testing.Short() { 289 tries = 1 290 } 291 for try := 0; try < tries; try++ { 292 var prof bytes.Buffer 293 if err := StartCPUProfile(&prof); err != nil { 294 t.Fatal(err) 295 } 296 for i := 0; i < count; i++ { 297 runtime.Gosched() 298 } 299 StopCPUProfile() 300 301 // Read profile to look for entries for runtime.gogo with an attempt at a traceback. 302 // The special entry 303 parseProfile(t, prof.Bytes(), func(count uintptr, stk []uintptr) { 304 // An entry with two frames with 'System' in its top frame 305 // exists to record a PC without a traceback. Those are okay. 306 if len(stk) == 2 { 307 f := runtime.FuncForPC(stk[1]) 308 if f != nil && (f.Name() == "runtime._System" || f.Name() == "runtime._ExternalCode" || f.Name() == "runtime._GC") { 309 return 310 } 311 } 312 313 // Otherwise, should not see runtime.gogo. 314 // The place we'd see it would be the inner most frame. 315 f := runtime.FuncForPC(stk[0]) 316 if f != nil && f.Name() == "runtime.gogo" { 317 var buf bytes.Buffer 318 for _, pc := range stk { 319 f := runtime.FuncForPC(pc) 320 if f == nil { 321 fmt.Fprintf(&buf, "%#x ?:0\n", pc) 322 } else { 323 file, line := f.FileLine(pc) 324 fmt.Fprintf(&buf, "%#x %s:%d\n", pc, file, line) 325 } 326 } 327 t.Fatalf("found profile entry for runtime.gogo:\n%s", buf.String()) 328 } 329 }) 330 } 331 } 332 333 // Test that profiling of division operations is okay, especially on ARM. See issue 6681. 334 func TestMathBigDivide(t *testing.T) { 335 testCPUProfile(t, nil, func(duration time.Duration) { 336 t := time.After(duration) 337 pi := new(big.Int) 338 for { 339 for i := 0; i < 100; i++ { 340 n := big.NewInt(2646693125139304345) 341 d := big.NewInt(842468587426513207) 342 pi.Div(n, d) 343 } 344 select { 345 case <-t: 346 return 347 default: 348 } 349 } 350 }) 351 } 352 353 func slurpString(r io.Reader) string { 354 slurp, _ := ioutil.ReadAll(r) 355 return string(slurp) 356 } 357 358 func getLinuxKernelConfig() string { 359 if f, err := os.Open("/proc/config"); err == nil { 360 defer f.Close() 361 return slurpString(f) 362 } 363 if f, err := os.Open("/proc/config.gz"); err == nil { 364 defer f.Close() 365 r, err := gzip.NewReader(f) 366 if err != nil { 367 return "" 368 } 369 return slurpString(r) 370 } 371 if f, err := os.Open("/boot/config"); err == nil { 372 defer f.Close() 373 return slurpString(f) 374 } 375 uname, _ := exec.Command("uname", "-r").Output() 376 if len(uname) > 0 { 377 if f, err := os.Open("/boot/config-" + strings.TrimSpace(string(uname))); err == nil { 378 defer f.Close() 379 return slurpString(f) 380 } 381 } 382 return "" 383 } 384 385 func haveLinuxHiresTimers() bool { 386 config := getLinuxKernelConfig() 387 return strings.Contains(config, "CONFIG_HIGH_RES_TIMERS=y") 388 } 389 390 func TestStackBarrierProfiling(t *testing.T) { 391 if (runtime.GOOS == "linux" && runtime.GOARCH == "arm") || 392 runtime.GOOS == "openbsd" || 393 runtime.GOOS == "solaris" || 394 runtime.GOOS == "dragonfly" || 395 runtime.GOOS == "freebsd" { 396 // This test currently triggers a large number of 397 // usleep(100)s. These kernels/arches have poor 398 // resolution timers, so this gives up a whole 399 // scheduling quantum. On Linux and the BSDs (and 400 // probably Solaris), profiling signals are only 401 // generated when a process completes a whole 402 // scheduling quantum, so this test often gets zero 403 // profiling signals and fails. 404 t.Skipf("low resolution timers inhibit profiling signals (golang.org/issue/13405)") 405 return 406 } 407 408 if runtime.GOOS == "linux" && strings.HasPrefix(runtime.GOARCH, "mips") { 409 if !haveLinuxHiresTimers() { 410 t.Skipf("low resolution timers inhibit profiling signals (golang.org/issue/13405, golang.org/issue/17936)") 411 } 412 } 413 414 if !strings.Contains(os.Getenv("GODEBUG"), "gcstackbarrierall=1") { 415 // Re-execute this test with constant GC and stack 416 // barriers at every frame. 417 testenv.MustHaveExec(t) 418 if runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" { 419 t.Skip("gcstackbarrierall doesn't work on ppc64") 420 } 421 args := []string{"-test.run=TestStackBarrierProfiling"} 422 if testing.Short() { 423 args = append(args, "-test.short") 424 } 425 cmd := exec.Command(os.Args[0], args...) 426 cmd.Env = append([]string{"GODEBUG=gcstackbarrierall=1", "GOGC=1", "GOTRACEBACK=system"}, os.Environ()...) 427 if out, err := cmd.CombinedOutput(); err != nil { 428 t.Fatalf("subprocess failed with %v:\n%s", err, out) 429 } 430 return 431 } 432 433 testCPUProfile(t, nil, func(duration time.Duration) { 434 // In long mode, we're likely to get one or two 435 // samples in stackBarrier. 436 t := time.After(duration) 437 for { 438 deepStack(1000) 439 select { 440 case <-t: 441 return 442 default: 443 } 444 } 445 }) 446 } 447 448 var x []byte 449 450 func deepStack(depth int) int { 451 if depth == 0 { 452 return 0 453 } 454 x = make([]byte, 1024) 455 return deepStack(depth-1) + 1 456 } 457 458 // Operating systems that are expected to fail the tests. See issue 13841. 459 var badOS = map[string]bool{ 460 "darwin": true, 461 "netbsd": true, 462 "plan9": true, 463 "dragonfly": true, 464 "solaris": true, 465 } 466 467 func TestBlockProfile(t *testing.T) { 468 type TestCase struct { 469 name string 470 f func() 471 re string 472 } 473 tests := [...]TestCase{ 474 {"chan recv", blockChanRecv, ` 475 [0-9]+ [0-9]+ @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 476 # 0x[0-9,a-f]+ runtime\.chanrecv1\+0x[0-9,a-f]+ .*/src/runtime/chan.go:[0-9]+ 477 # 0x[0-9,a-f]+ runtime/pprof_test\.blockChanRecv\+0x[0-9,a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 478 # 0x[0-9,a-f]+ runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 479 `}, 480 {"chan send", blockChanSend, ` 481 [0-9]+ [0-9]+ @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 482 # 0x[0-9,a-f]+ runtime\.chansend1\+0x[0-9,a-f]+ .*/src/runtime/chan.go:[0-9]+ 483 # 0x[0-9,a-f]+ runtime/pprof_test\.blockChanSend\+0x[0-9,a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 484 # 0x[0-9,a-f]+ runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 485 `}, 486 {"chan close", blockChanClose, ` 487 [0-9]+ [0-9]+ @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 488 # 0x[0-9,a-f]+ runtime\.chanrecv1\+0x[0-9,a-f]+ .*/src/runtime/chan.go:[0-9]+ 489 # 0x[0-9,a-f]+ runtime/pprof_test\.blockChanClose\+0x[0-9,a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 490 # 0x[0-9,a-f]+ runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 491 `}, 492 {"select recv async", blockSelectRecvAsync, ` 493 [0-9]+ [0-9]+ @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 494 # 0x[0-9,a-f]+ runtime\.selectgo\+0x[0-9,a-f]+ .*/src/runtime/select.go:[0-9]+ 495 # 0x[0-9,a-f]+ runtime/pprof_test\.blockSelectRecvAsync\+0x[0-9,a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 496 # 0x[0-9,a-f]+ runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 497 `}, 498 {"select send sync", blockSelectSendSync, ` 499 [0-9]+ [0-9]+ @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 500 # 0x[0-9,a-f]+ runtime\.selectgo\+0x[0-9,a-f]+ .*/src/runtime/select.go:[0-9]+ 501 # 0x[0-9,a-f]+ runtime/pprof_test\.blockSelectSendSync\+0x[0-9,a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 502 # 0x[0-9,a-f]+ runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 503 `}, 504 {"mutex", blockMutex, ` 505 [0-9]+ [0-9]+ @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 506 # 0x[0-9,a-f]+ sync\.\(\*Mutex\)\.Lock\+0x[0-9,a-f]+ .*/src/sync/mutex\.go:[0-9]+ 507 # 0x[0-9,a-f]+ runtime/pprof_test\.blockMutex\+0x[0-9,a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 508 # 0x[0-9,a-f]+ runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 509 `}, 510 {"cond", blockCond, ` 511 [0-9]+ [0-9]+ @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 512 # 0x[0-9,a-f]+ sync\.\(\*Cond\)\.Wait\+0x[0-9,a-f]+ .*/src/sync/cond\.go:[0-9]+ 513 # 0x[0-9,a-f]+ runtime/pprof_test\.blockCond\+0x[0-9,a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 514 # 0x[0-9,a-f]+ runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+ 515 `}, 516 } 517 518 runtime.SetBlockProfileRate(1) 519 defer runtime.SetBlockProfileRate(0) 520 for _, test := range tests { 521 test.f() 522 } 523 var w bytes.Buffer 524 Lookup("block").WriteTo(&w, 1) 525 prof := w.String() 526 527 if !strings.HasPrefix(prof, "--- contention:\ncycles/second=") { 528 t.Fatalf("Bad profile header:\n%v", prof) 529 } 530 531 if strings.HasSuffix(prof, "#\t0x0\n\n") { 532 t.Errorf("Useless 0 suffix:\n%v", prof) 533 } 534 535 for _, test := range tests { 536 if !regexp.MustCompile(strings.Replace(test.re, "\t", "\t+", -1)).MatchString(prof) { 537 t.Fatalf("Bad %v entry, expect:\n%v\ngot:\n%v", test.name, test.re, prof) 538 } 539 } 540 } 541 542 const blockDelay = 10 * time.Millisecond 543 544 func blockChanRecv() { 545 c := make(chan bool) 546 go func() { 547 time.Sleep(blockDelay) 548 c <- true 549 }() 550 <-c 551 } 552 553 func blockChanSend() { 554 c := make(chan bool) 555 go func() { 556 time.Sleep(blockDelay) 557 <-c 558 }() 559 c <- true 560 } 561 562 func blockChanClose() { 563 c := make(chan bool) 564 go func() { 565 time.Sleep(blockDelay) 566 close(c) 567 }() 568 <-c 569 } 570 571 func blockSelectRecvAsync() { 572 const numTries = 3 573 c := make(chan bool, 1) 574 c2 := make(chan bool, 1) 575 go func() { 576 for i := 0; i < numTries; i++ { 577 time.Sleep(blockDelay) 578 c <- true 579 } 580 }() 581 for i := 0; i < numTries; i++ { 582 select { 583 case <-c: 584 case <-c2: 585 } 586 } 587 } 588 589 func blockSelectSendSync() { 590 c := make(chan bool) 591 c2 := make(chan bool) 592 go func() { 593 time.Sleep(blockDelay) 594 <-c 595 }() 596 select { 597 case c <- true: 598 case c2 <- true: 599 } 600 } 601 602 func blockMutex() { 603 var mu sync.Mutex 604 mu.Lock() 605 go func() { 606 time.Sleep(blockDelay) 607 mu.Unlock() 608 }() 609 mu.Lock() 610 } 611 612 func blockCond() { 613 var mu sync.Mutex 614 c := sync.NewCond(&mu) 615 mu.Lock() 616 go func() { 617 time.Sleep(blockDelay) 618 mu.Lock() 619 c.Signal() 620 mu.Unlock() 621 }() 622 c.Wait() 623 mu.Unlock() 624 } 625 626 func TestMutexProfile(t *testing.T) { 627 old := runtime.SetMutexProfileFraction(1) 628 defer runtime.SetMutexProfileFraction(old) 629 if old != 0 { 630 t.Fatalf("need MutexProfileRate 0, got %d", old) 631 } 632 633 blockMutex() 634 635 var w bytes.Buffer 636 Lookup("mutex").WriteTo(&w, 1) 637 prof := w.String() 638 639 if !strings.HasPrefix(prof, "--- mutex:\ncycles/second=") { 640 t.Errorf("Bad profile header:\n%v", prof) 641 } 642 prof = strings.Trim(prof, "\n") 643 lines := strings.Split(prof, "\n") 644 if len(lines) != 6 { 645 t.Errorf("expected 6 lines, got %d %q\n%s", len(lines), prof, prof) 646 } 647 if len(lines) < 6 { 648 return 649 } 650 // checking that the line is like "35258904 1 @ 0x48288d 0x47cd28 0x458931" 651 r2 := `^\d+ 1 @(?: 0x[[:xdigit:]]+)+` 652 //r2 := "^[0-9]+ 1 @ 0x[0-9a-f x]+$" 653 if ok, err := regexp.MatchString(r2, lines[3]); err != nil || !ok { 654 t.Errorf("%q didn't match %q", lines[3], r2) 655 } 656 r3 := "^#.*runtime/pprof_test.blockMutex.*$" 657 if ok, err := regexp.MatchString(r3, lines[5]); err != nil || !ok { 658 t.Errorf("%q didn't match %q", lines[5], r3) 659 } 660 } 661 662 func func1(c chan int) { <-c } 663 func func2(c chan int) { <-c } 664 func func3(c chan int) { <-c } 665 func func4(c chan int) { <-c } 666 667 func TestGoroutineCounts(t *testing.T) { 668 if runtime.GOOS == "openbsd" { 669 testenv.SkipFlaky(t, 15156) 670 } 671 c := make(chan int) 672 for i := 0; i < 100; i++ { 673 if i%10 == 0 { 674 go func1(c) 675 continue 676 } 677 if i%2 == 0 { 678 go func2(c) 679 continue 680 } 681 go func3(c) 682 } 683 time.Sleep(10 * time.Millisecond) // let goroutines block on channel 684 685 var w bytes.Buffer 686 goroutineProf := Lookup("goroutine") 687 688 // Check debug profile 689 goroutineProf.WriteTo(&w, 1) 690 prof := w.String() 691 692 if !containsInOrder(prof, "\n50 @ ", "\n40 @", "\n10 @", "\n1 @") { 693 t.Errorf("expected sorted goroutine counts:\n%s", prof) 694 } 695 696 // Check proto profile 697 w.Reset() 698 goroutineProf.WriteTo(&w, 0) 699 p, err := profile.Parse(&w) 700 if err != nil { 701 t.Errorf("error parsing protobuf profile: %v", err) 702 } 703 if err := p.CheckValid(); err != nil { 704 t.Errorf("protobuf profile is invalid: %v", err) 705 } 706 if !containsCounts(p, []int64{50, 40, 10, 1}) { 707 t.Errorf("expected count profile to contain goroutines with counts %v, got %v", 708 []int64{50, 40, 10, 1}, p) 709 } 710 711 close(c) 712 713 time.Sleep(10 * time.Millisecond) // let goroutines exit 714 } 715 716 func containsInOrder(s string, all ...string) bool { 717 for _, t := range all { 718 i := strings.Index(s, t) 719 if i < 0 { 720 return false 721 } 722 s = s[i+len(t):] 723 } 724 return true 725 } 726 727 func containsCounts(prof *profile.Profile, counts []int64) bool { 728 m := make(map[int64]int) 729 for _, c := range counts { 730 m[c]++ 731 } 732 for _, s := range prof.Sample { 733 // The count is the single value in the sample 734 if len(s.Value) != 1 { 735 return false 736 } 737 m[s.Value[0]]-- 738 } 739 for _, n := range m { 740 if n > 0 { 741 return false 742 } 743 } 744 return true 745 }