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