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