1
2
3
4
5
6
7
8 package pprof
9
10 import (
11 "bytes"
12 "context"
13 "fmt"
14 "internal/profile"
15 "internal/testenv"
16 "io"
17 "math"
18 "math/big"
19 "os"
20 "os/exec"
21 "regexp"
22 "runtime"
23 "strings"
24 "sync"
25 "sync/atomic"
26 "testing"
27 "time"
28 _ "unsafe"
29 )
30
31 func cpuHogger(f func(x int) int, y *int, dur time.Duration) {
32
33
34
35
36
37 t0 := time.Now()
38 accum := *y
39 for i := 0; i < 500 || time.Since(t0) < dur; i++ {
40 accum = f(accum)
41 }
42 *y = accum
43 }
44
45 var (
46 salt1 = 0
47 salt2 = 0
48 )
49
50
51
52
53 func cpuHog1(x int) int {
54 return cpuHog0(x, 1e5)
55 }
56
57 func cpuHog0(x, n int) int {
58 foo := x
59 for i := 0; i < n; i++ {
60 if foo > 0 {
61 foo *= foo
62 } else {
63 foo *= foo + 1
64 }
65 }
66 return foo
67 }
68
69 func cpuHog2(x int) int {
70 foo := x
71 for i := 0; i < 1e5; i++ {
72 if foo > 0 {
73 foo *= foo
74 } else {
75 foo *= foo + 2
76 }
77 }
78 return foo
79 }
80
81
82
83 func avoidFunctions() []string {
84 if runtime.Compiler == "gccgo" {
85 return []string{"runtime.sigprof"}
86 }
87 return nil
88 }
89
90 func TestCPUProfile(t *testing.T) {
91 testCPUProfile(t, stackContains, []string{"runtime/pprof.cpuHog1"}, avoidFunctions(), func(dur time.Duration) {
92 cpuHogger(cpuHog1, &salt1, dur)
93 })
94 }
95
96 func TestCPUProfileMultithreaded(t *testing.T) {
97 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2))
98 testCPUProfile(t, stackContains, []string{"runtime/pprof.cpuHog1", "runtime/pprof.cpuHog2"}, avoidFunctions(), func(dur time.Duration) {
99 c := make(chan int)
100 go func() {
101 cpuHogger(cpuHog1, &salt1, dur)
102 c <- 1
103 }()
104 cpuHogger(cpuHog2, &salt2, dur)
105 <-c
106 })
107 }
108
109
110
111 func containsInlinedCall(f interface{}, maxBytes int) bool {
112 _, found := findInlinedCall(f, maxBytes)
113 return found
114 }
115
116
117
118 func findInlinedCall(f interface{}, maxBytes int) (pc uint64, found bool) {
119 fFunc := runtime.FuncForPC(uintptr(funcPC(f)))
120 if fFunc == nil || fFunc.Entry() == 0 {
121 panic("failed to locate function entry")
122 }
123
124 for offset := 0; offset < maxBytes; offset++ {
125 innerPC := fFunc.Entry() + uintptr(offset)
126 inner := runtime.FuncForPC(innerPC)
127 if inner == nil {
128
129
130 continue
131 }
132 if inner.Entry() != fFunc.Entry() {
133
134 break
135 }
136 if inner.Name() != fFunc.Name() {
137
138
139 return uint64(innerPC), true
140 }
141 }
142
143 return 0, false
144 }
145
146 func TestCPUProfileInlining(t *testing.T) {
147 if !containsInlinedCall(inlinedCaller, 4<<10) {
148 t.Skip("Can't determine whether inlinedCallee was inlined into inlinedCaller.")
149 }
150
151 p := testCPUProfile(t, stackContains, []string{"runtime/pprof.inlinedCallee", "runtime/pprof.inlinedCaller"}, avoidFunctions(), func(dur time.Duration) {
152 cpuHogger(inlinedCaller, &salt1, dur)
153 })
154
155
156 for _, loc := range p.Location {
157 hasInlinedCallerAfterInlinedCallee, hasInlinedCallee := false, false
158 for _, line := range loc.Line {
159 if line.Function.Name == "runtime/pprof.inlinedCallee" {
160 hasInlinedCallee = true
161 }
162 if hasInlinedCallee && line.Function.Name == "runtime/pprof.inlinedCaller" {
163 hasInlinedCallerAfterInlinedCallee = true
164 }
165 }
166 if hasInlinedCallee != hasInlinedCallerAfterInlinedCallee {
167 t.Fatalf("want inlinedCallee followed by inlinedCaller, got separate Location entries:\n%v", p)
168 }
169 }
170 }
171
172 func inlinedCaller(x int) int {
173 x = inlinedCallee(x, 1e5)
174 return x
175 }
176
177 func inlinedCallee(x, n int) int {
178 return cpuHog0(x, n)
179 }
180
181
182 func dumpCallers(pcs []uintptr) {
183 if pcs == nil {
184 return
185 }
186
187 skip := 2
188 runtime.Callers(skip, pcs)
189 }
190
191
192 func inlinedCallerDump(pcs []uintptr) {
193 inlinedCalleeDump(pcs)
194 }
195
196 func inlinedCalleeDump(pcs []uintptr) {
197 dumpCallers(pcs)
198 }
199
200 func TestCPUProfileRecursion(t *testing.T) {
201 p := testCPUProfile(t, stackContains, []string{"runtime/pprof.inlinedCallee", "runtime/pprof.recursionCallee", "runtime/pprof.recursionCaller"}, avoidFunctions(), func(dur time.Duration) {
202 cpuHogger(recursionCaller, &salt1, dur)
203 })
204
205
206 for i, loc := range p.Location {
207 recursionFunc := 0
208 for _, line := range loc.Line {
209 if name := line.Function.Name; name == "runtime/pprof.recursionCaller" || name == "runtime/pprof.recursionCallee" {
210 recursionFunc++
211 }
212 }
213 if recursionFunc > 1 {
214 t.Fatalf("want at most one recursionCaller or recursionCallee in one Location, got a violating Location (index: %d):\n%v", i, p)
215 }
216 }
217 }
218
219 func recursionCaller(x int) int {
220 y := recursionCallee(3, x)
221 return y
222 }
223
224 func recursionCallee(n, x int) int {
225 if n == 0 {
226 return 1
227 }
228 y := inlinedCallee(x, 1e4)
229 return y * recursionCallee(n-1, x)
230 }
231
232 func recursionChainTop(x int, pcs []uintptr) {
233 if x < 0 {
234 return
235 }
236 recursionChainMiddle(x, pcs)
237 }
238
239 func recursionChainMiddle(x int, pcs []uintptr) {
240 recursionChainBottom(x, pcs)
241 }
242
243 func recursionChainBottom(x int, pcs []uintptr) {
244
245
246 dumpCallers(pcs)
247
248 recursionChainTop(x-1, pcs)
249 }
250
251 func parseProfile(t *testing.T, valBytes []byte, f func(uintptr, []*profile.Location, map[string][]string)) *profile.Profile {
252 p, err := profile.Parse(bytes.NewReader(valBytes))
253 if err != nil {
254 t.Fatal(err)
255 }
256 for _, sample := range p.Sample {
257 count := uintptr(sample.Value[0])
258 f(count, sample.Location, sample.Label)
259 }
260 return p
261 }
262
263 func cpuProfilingBroken() bool {
264 switch runtime.GOOS {
265 case "plan9":
266
267 return true
268 case "aix":
269
270 return true
271 case "ios", "dragonfly", "netbsd", "illumos", "solaris":
272
273 return true
274 case "openbsd":
275 if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" {
276
277 return true
278 }
279 }
280
281 return false
282 }
283
284
285
286 func testCPUProfile(t *testing.T, matches matchFunc, need []string, avoid []string, f func(dur time.Duration)) *profile.Profile {
287 switch runtime.GOOS {
288 case "darwin":
289 out, err := exec.Command("uname", "-a").CombinedOutput()
290 if err != nil {
291 t.Fatal(err)
292 }
293 vers := string(out)
294 t.Logf("uname -a: %v", vers)
295 case "plan9":
296 t.Skip("skipping on plan9")
297 }
298
299 broken := cpuProfilingBroken()
300
301 maxDuration := 5 * time.Second
302 if testing.Short() && broken {
303
304 maxDuration /= 10
305 }
306
307
308
309 duration := 5 * time.Second
310 if testing.Short() {
311 duration = 100 * time.Millisecond
312 }
313
314
315
316
317
318
319 for duration <= maxDuration {
320 var prof bytes.Buffer
321 if err := StartCPUProfile(&prof); err != nil {
322 t.Fatal(err)
323 }
324 f(duration)
325 StopCPUProfile()
326
327 if p, ok := profileOk(t, matches, need, avoid, prof, duration); ok {
328 return p
329 }
330
331 duration *= 2
332 if duration <= maxDuration {
333 t.Logf("retrying with %s duration", duration)
334 }
335 }
336
337 if broken {
338 t.Skipf("ignoring failure on %s/%s; see golang.org/issue/13841", runtime.GOOS, runtime.GOARCH)
339 }
340
341
342
343
344
345 if os.Getenv("IN_QEMU") == "1" {
346 t.Skip("ignore the failure in QEMU; see golang.org/issue/9605")
347 }
348 t.FailNow()
349 return nil
350 }
351
352 func contains(slice []string, s string) bool {
353 for i := range slice {
354 if slice[i] == s {
355 return true
356 }
357 }
358 return false
359 }
360
361
362 func stackContains(spec string, count uintptr, stk []*profile.Location, labels map[string][]string) bool {
363 for _, loc := range stk {
364 for _, line := range loc.Line {
365 if strings.Contains(line.Function.Name, spec) {
366 return true
367 }
368 }
369 }
370 return false
371 }
372
373 type matchFunc func(spec string, count uintptr, stk []*profile.Location, labels map[string][]string) bool
374
375 func profileOk(t *testing.T, matches matchFunc, need []string, avoid []string, prof bytes.Buffer, duration time.Duration) (_ *profile.Profile, ok bool) {
376 ok = true
377
378
379
380 have := make([]uintptr, len(need))
381 avoidSamples := make([]uintptr, len(avoid))
382 var samples uintptr
383 var buf bytes.Buffer
384 p := parseProfile(t, prof.Bytes(), func(count uintptr, stk []*profile.Location, labels map[string][]string) {
385 fmt.Fprintf(&buf, "%d:", count)
386 fprintStack(&buf, stk)
387 samples += count
388 for i, spec := range need {
389 if matches(spec, count, stk, labels) {
390 have[i] += count
391 }
392 }
393 for i, name := range avoid {
394 for _, loc := range stk {
395 for _, line := range loc.Line {
396 if strings.Contains(line.Function.Name, name) {
397 avoidSamples[i] += count
398 }
399 }
400 }
401 }
402 fmt.Fprintf(&buf, "\n")
403 })
404 t.Logf("total %d CPU profile samples collected:\n%s", samples, buf.String())
405
406 if samples < 10 && runtime.GOOS == "windows" {
407
408
409
410 t.Log("too few samples on Windows (golang.org/issue/10842)")
411 return p, false
412 }
413
414
415
416
417
418
419 if ideal := uintptr(duration * 100 / time.Second); samples == 0 || (samples < ideal/4 && samples < 10) {
420 t.Logf("too few samples; got %d, want at least %d, ideally %d", samples, ideal/4, ideal)
421 ok = false
422 }
423
424 for i, name := range avoid {
425 bad := avoidSamples[i]
426 if bad != 0 {
427 t.Logf("found %d samples in avoid-function %s\n", bad, name)
428 ok = false
429 }
430 }
431
432 if len(need) == 0 {
433 return p, ok
434 }
435
436 var total uintptr
437 for i, name := range need {
438 total += have[i]
439 t.Logf("%s: %d\n", name, have[i])
440 }
441 if total == 0 {
442 t.Logf("no samples in expected functions")
443 ok = false
444 }
445
446
447
448
449 min := uintptr(1)
450 for i, name := range need {
451 if have[i] < min {
452 t.Logf("%s has %d samples out of %d, want at least %d, ideally %d", name, have[i], total, min, total/uintptr(len(have)))
453 ok = false
454 }
455 }
456 return p, ok
457 }
458
459
460
461 func TestCPUProfileWithFork(t *testing.T) {
462 testenv.MustHaveExec(t)
463
464 heap := 1 << 30
465 if runtime.GOOS == "android" {
466
467 heap = 100 << 20
468 }
469 if runtime.GOOS == "windows" && runtime.GOARCH == "arm" {
470
471 heap = 100 << 20
472 }
473 if testing.Short() {
474 heap = 100 << 20
475 }
476
477 garbage := make([]byte, heap)
478
479 done := make(chan bool)
480 go func() {
481 for i := range garbage {
482 garbage[i] = 42
483 }
484 done <- true
485 }()
486 <-done
487
488 var prof bytes.Buffer
489 if err := StartCPUProfile(&prof); err != nil {
490 t.Fatal(err)
491 }
492 defer StopCPUProfile()
493
494 for i := 0; i < 10; i++ {
495 exec.Command(os.Args[0], "-h").CombinedOutput()
496 }
497 }
498
499
500
501
502 func TestGoroutineSwitch(t *testing.T) {
503 if runtime.Compiler == "gccgo" {
504 t.Skip("not applicable for gccgo")
505 }
506
507
508
509 tries := 10
510 count := 1000000
511 if testing.Short() {
512 tries = 1
513 }
514 for try := 0; try < tries; try++ {
515 var prof bytes.Buffer
516 if err := StartCPUProfile(&prof); err != nil {
517 t.Fatal(err)
518 }
519 for i := 0; i < count; i++ {
520 runtime.Gosched()
521 }
522 StopCPUProfile()
523
524
525
526
527
528 parseProfile(t, prof.Bytes(), func(count uintptr, stk []*profile.Location, _ map[string][]string) {
529
530
531 if len(stk) == 2 {
532 name := stk[1].Line[0].Function.Name
533 if name == "runtime._System" || name == "runtime._ExternalCode" || name == "runtime._GC" {
534 return
535 }
536 }
537
538
539
540 if len(stk) == 1 {
541 return
542 }
543
544
545
546 name := stk[0].Line[0].Function.Name
547 if name == "gogo" {
548 var buf bytes.Buffer
549 fprintStack(&buf, stk)
550 t.Fatalf("found profile entry for gogo:\n%s", buf.String())
551 }
552 })
553 }
554 }
555
556 func fprintStack(w io.Writer, stk []*profile.Location) {
557 for _, loc := range stk {
558 fmt.Fprintf(w, " %#x", loc.Address)
559 fmt.Fprintf(w, " (")
560 for i, line := range loc.Line {
561 if i > 0 {
562 fmt.Fprintf(w, " ")
563 }
564 fmt.Fprintf(w, "%s:%d", line.Function.Name, line.Line)
565 }
566 fmt.Fprintf(w, ")")
567 }
568 fmt.Fprintf(w, "\n")
569 }
570
571
572 func TestMathBigDivide(t *testing.T) {
573 testCPUProfile(t, nil, nil, nil, func(duration time.Duration) {
574 t := time.After(duration)
575 pi := new(big.Int)
576 for {
577 for i := 0; i < 100; i++ {
578 n := big.NewInt(2646693125139304345)
579 d := big.NewInt(842468587426513207)
580 pi.Div(n, d)
581 }
582 select {
583 case <-t:
584 return
585 default:
586 }
587 }
588 })
589 }
590
591
592 func stackContainsAll(spec string, count uintptr, stk []*profile.Location, labels map[string][]string) bool {
593 for _, f := range strings.Split(spec, ",") {
594 if !stackContains(f, count, stk, labels) {
595 return false
596 }
597 }
598 return true
599 }
600
601 func TestMorestack(t *testing.T) {
602 testCPUProfile(t, stackContainsAll, []string{"runtime.newstack,runtime/pprof.growstack"}, avoidFunctions(), func(duration time.Duration) {
603 t := time.After(duration)
604 c := make(chan bool)
605 for {
606 go func() {
607 growstack1()
608 c <- true
609 }()
610 select {
611 case <-t:
612 return
613 case <-c:
614 }
615 }
616 })
617 }
618
619
620 func growstack1() {
621 growstack(10)
622 }
623
624
625 func growstack(n int) {
626 var buf [8 << 18]byte
627 use(buf)
628 if n > 0 {
629 growstack(n - 1)
630 }
631 }
632
633
634 func use(x [8 << 18]byte) {}
635
636 func TestBlockProfile(t *testing.T) {
637 type TestCase struct {
638 name string
639 f func()
640 stk []string
641 re string
642 }
643 tests := [...]TestCase{
644 {
645 name: "chan recv",
646 f: blockChanRecv,
647 stk: []string{
648 "runtime.chanrecv1",
649 "runtime/pprof.blockChanRecv",
650 "runtime/pprof.TestBlockProfile",
651 },
652 re: `
653 [0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
654 # 0x[0-9a-f]+ runtime\.chanrecv1\+0x[0-9a-f]+ .*/src/runtime/chan.go:[0-9]+
655 # 0x[0-9a-f]+ runtime/pprof\.blockChanRecv\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
656 # 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
657 `},
658 {
659 name: "chan send",
660 f: blockChanSend,
661 stk: []string{
662 "runtime.chansend1",
663 "runtime/pprof.blockChanSend",
664 "runtime/pprof.TestBlockProfile",
665 },
666 re: `
667 [0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
668 # 0x[0-9a-f]+ runtime\.chansend1\+0x[0-9a-f]+ .*/src/runtime/chan.go:[0-9]+
669 # 0x[0-9a-f]+ runtime/pprof\.blockChanSend\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
670 # 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
671 `},
672 {
673 name: "chan close",
674 f: blockChanClose,
675 stk: []string{
676 "runtime.chanrecv1",
677 "runtime/pprof.blockChanClose",
678 "runtime/pprof.TestBlockProfile",
679 },
680 re: `
681 [0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
682 # 0x[0-9a-f]+ runtime\.chanrecv1\+0x[0-9a-f]+ .*/src/runtime/chan.go:[0-9]+
683 # 0x[0-9a-f]+ runtime/pprof\.blockChanClose\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
684 # 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
685 `},
686 {
687 name: "select recv async",
688 f: blockSelectRecvAsync,
689 stk: []string{
690 "runtime.selectgo",
691 "runtime/pprof.blockSelectRecvAsync",
692 "runtime/pprof.TestBlockProfile",
693 },
694 re: `
695 [0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
696 # 0x[0-9a-f]+ runtime\.selectgo\+0x[0-9a-f]+ .*/src/runtime/select.go:[0-9]+
697 # 0x[0-9a-f]+ runtime/pprof\.blockSelectRecvAsync\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
698 # 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
699 `},
700 {
701 name: "select send sync",
702 f: blockSelectSendSync,
703 stk: []string{
704 "runtime.selectgo",
705 "runtime/pprof.blockSelectSendSync",
706 "runtime/pprof.TestBlockProfile",
707 },
708 re: `
709 [0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
710 # 0x[0-9a-f]+ runtime\.selectgo\+0x[0-9a-f]+ .*/src/runtime/select.go:[0-9]+
711 # 0x[0-9a-f]+ runtime/pprof\.blockSelectSendSync\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
712 # 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
713 `},
714 {
715 name: "mutex",
716 f: blockMutex,
717 stk: []string{
718 "sync.(*Mutex).Lock",
719 "runtime/pprof.blockMutex",
720 "runtime/pprof.TestBlockProfile",
721 },
722 re: `
723 [0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
724 # 0x[0-9a-f]+ sync\.\(\*Mutex\)\.Lock\+0x[0-9a-f]+ .*/src/sync/mutex\.go:[0-9]+
725 # 0x[0-9a-f]+ runtime/pprof\.blockMutex\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
726 # 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
727 `},
728 {
729 name: "cond",
730 f: blockCond,
731 stk: []string{
732 "sync.(*Cond).Wait",
733 "runtime/pprof.blockCond",
734 "runtime/pprof.TestBlockProfile",
735 },
736 re: `
737 [0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
738 # 0x[0-9a-f]+ sync\.\(\*Cond\)\.Wait\+0x[0-9a-f]+ .*/src/sync/cond\.go:[0-9]+
739 # 0x[0-9a-f]+ runtime/pprof\.blockCond\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
740 # 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
741 `},
742 }
743
744
745 runtime.SetBlockProfileRate(1)
746 defer runtime.SetBlockProfileRate(0)
747 for _, test := range tests {
748 test.f()
749 }
750
751 t.Run("debug=1", func(t *testing.T) {
752 var w bytes.Buffer
753 Lookup("block").WriteTo(&w, 1)
754 prof := w.String()
755
756 if !strings.HasPrefix(prof, "--- contention:\ncycles/second=") {
757 t.Fatalf("Bad profile header:\n%v", prof)
758 }
759
760 if strings.HasSuffix(prof, "#\t0x0\n\n") {
761 t.Errorf("Useless 0 suffix:\n%v", prof)
762 }
763
764 for _, test := range tests {
765 if !regexp.MustCompile(strings.ReplaceAll(test.re, "\t", "\t+")).MatchString(prof) {
766 t.Errorf("Bad %v entry, expect:\n%v\ngot:\n%v", test.name, test.re, prof)
767 }
768 }
769 })
770
771 t.Run("proto", func(t *testing.T) {
772
773 var w bytes.Buffer
774 Lookup("block").WriteTo(&w, 0)
775 p, err := profile.Parse(&w)
776 if err != nil {
777 t.Fatalf("failed to parse profile: %v", err)
778 }
779 t.Logf("parsed proto: %s", p)
780 if err := p.CheckValid(); err != nil {
781 t.Fatalf("invalid profile: %v", err)
782 }
783
784 stks := stacks(p)
785 for _, test := range tests {
786 if !containsStack(stks, test.stk) {
787 t.Errorf("No matching stack entry for %v, want %+v", test.name, test.stk)
788 }
789 }
790 })
791
792 }
793
794 func stacks(p *profile.Profile) (res [][]string) {
795 for _, s := range p.Sample {
796 var stk []string
797 for _, l := range s.Location {
798 for _, line := range l.Line {
799 stk = append(stk, line.Function.Name)
800 }
801 }
802 res = append(res, stk)
803 }
804 return res
805 }
806
807 func containsStack(got [][]string, want []string) bool {
808 for _, stk := range got {
809 if len(stk) < len(want) {
810 continue
811 }
812 for i, f := range want {
813 if f != stk[i] {
814 break
815 }
816 if i == len(want)-1 {
817 return true
818 }
819 }
820 }
821 return false
822 }
823
824 const blockDelay = 10 * time.Millisecond
825
826 func blockChanRecv() {
827 c := make(chan bool)
828 go func() {
829 time.Sleep(blockDelay)
830 c <- true
831 }()
832 <-c
833 }
834
835 func blockChanSend() {
836 c := make(chan bool)
837 go func() {
838 time.Sleep(blockDelay)
839 <-c
840 }()
841 c <- true
842 }
843
844 func blockChanClose() {
845 c := make(chan bool)
846 go func() {
847 time.Sleep(blockDelay)
848 close(c)
849 }()
850 <-c
851 }
852
853 func blockSelectRecvAsync() {
854 const numTries = 3
855 c := make(chan bool, 1)
856 c2 := make(chan bool, 1)
857 go func() {
858 for i := 0; i < numTries; i++ {
859 time.Sleep(blockDelay)
860 c <- true
861 }
862 }()
863 for i := 0; i < numTries; i++ {
864 select {
865 case <-c:
866 case <-c2:
867 }
868 }
869 }
870
871 func blockSelectSendSync() {
872 c := make(chan bool)
873 c2 := make(chan bool)
874 go func() {
875 time.Sleep(blockDelay)
876 <-c
877 }()
878 select {
879 case c <- true:
880 case c2 <- true:
881 }
882 }
883
884 func blockMutex() {
885 var mu sync.Mutex
886 mu.Lock()
887 go func() {
888 time.Sleep(blockDelay)
889 mu.Unlock()
890 }()
891
892
893
894
895 mu.Lock()
896 }
897
898 func blockCond() {
899 var mu sync.Mutex
900 c := sync.NewCond(&mu)
901 mu.Lock()
902 go func() {
903 time.Sleep(blockDelay)
904 mu.Lock()
905 c.Signal()
906 mu.Unlock()
907 }()
908 c.Wait()
909 mu.Unlock()
910 }
911
912
913 func TestBlockProfileBias(t *testing.T) {
914 rate := int(1000)
915 runtime.SetBlockProfileRate(rate)
916 defer runtime.SetBlockProfileRate(0)
917
918
919 blockFrequentShort(rate)
920 blockInfrequentLong(rate)
921
922 var w bytes.Buffer
923 Lookup("block").WriteTo(&w, 0)
924 p, err := profile.Parse(&w)
925 if err != nil {
926 t.Fatalf("failed to parse profile: %v", err)
927 }
928 t.Logf("parsed proto: %s", p)
929
930 il := float64(-1)
931 fs := float64(-1)
932 for _, s := range p.Sample {
933 for _, l := range s.Location {
934 for _, line := range l.Line {
935 if len(s.Value) < 2 {
936 t.Fatal("block profile has less than 2 sample types")
937 }
938
939 if line.Function.Name == "runtime/pprof.blockInfrequentLong" {
940 il = float64(s.Value[1])
941 } else if line.Function.Name == "runtime/pprof.blockFrequentShort" {
942 fs = float64(s.Value[1])
943 }
944 }
945 }
946 }
947 if il == -1 || fs == -1 {
948 t.Fatal("block profile is missing expected functions")
949 }
950
951
952 const threshold = 0.2
953 if bias := (il - fs) / il; math.Abs(bias) > threshold {
954 t.Fatalf("bias: abs(%f) > %f", bias, threshold)
955 } else {
956 t.Logf("bias: abs(%f) < %f", bias, threshold)
957 }
958 }
959
960
961
962 func blockFrequentShort(rate int) {
963 for i := 0; i < 100000; i++ {
964 blockevent(int64(rate/10), 1)
965 }
966 }
967
968
969
970 func blockInfrequentLong(rate int) {
971 for i := 0; i < 10000; i++ {
972 blockevent(int64(rate), 1)
973 }
974 }
975
976
977
978 func blockevent(cycles int64, skip int)
979
980 func TestMutexProfile(t *testing.T) {
981
982
983 old := runtime.SetMutexProfileFraction(1)
984 defer runtime.SetMutexProfileFraction(old)
985 if old != 0 {
986 t.Fatalf("need MutexProfileRate 0, got %d", old)
987 }
988
989 blockMutex()
990
991 t.Run("debug=1", func(t *testing.T) {
992 var w bytes.Buffer
993 Lookup("mutex").WriteTo(&w, 1)
994 prof := w.String()
995 t.Logf("received profile: %v", prof)
996
997 if !strings.HasPrefix(prof, "--- mutex:\ncycles/second=") {
998 t.Errorf("Bad profile header:\n%v", prof)
999 }
1000 prof = strings.Trim(prof, "\n")
1001 lines := strings.Split(prof, "\n")
1002 if len(lines) != 6 {
1003 t.Errorf("expected 6 lines, got %d %q\n%s", len(lines), prof, prof)
1004 }
1005 if len(lines) < 6 {
1006 return
1007 }
1008
1009 r2 := `^\d+ \d+ @(?: 0x[[:xdigit:]]+)+`
1010
1011 if ok, err := regexp.MatchString(r2, lines[3]); err != nil || !ok {
1012 t.Errorf("%q didn't match %q", lines[3], r2)
1013 }
1014 r3 := "^#.*runtime/pprof.blockMutex.*$"
1015 if ok, err := regexp.MatchString(r3, lines[5]); err != nil || !ok {
1016 t.Errorf("%q didn't match %q", lines[5], r3)
1017 }
1018 t.Logf(prof)
1019 })
1020 t.Run("proto", func(t *testing.T) {
1021
1022 var w bytes.Buffer
1023 Lookup("mutex").WriteTo(&w, 0)
1024 p, err := profile.Parse(&w)
1025 if err != nil {
1026 t.Fatalf("failed to parse profile: %v", err)
1027 }
1028 t.Logf("parsed proto: %s", p)
1029 if err := p.CheckValid(); err != nil {
1030 t.Fatalf("invalid profile: %v", err)
1031 }
1032
1033 stks := stacks(p)
1034 for _, want := range [][]string{
1035 {"sync.(*Mutex).Unlock", "runtime/pprof.blockMutex.func1"},
1036 } {
1037 if !containsStack(stks, want) {
1038 t.Errorf("No matching stack entry for %+v", want)
1039 }
1040 }
1041 })
1042 }
1043
1044 func func1(c chan int) { <-c }
1045 func func2(c chan int) { <-c }
1046 func func3(c chan int) { <-c }
1047 func func4(c chan int) { <-c }
1048
1049 func TestGoroutineCounts(t *testing.T) {
1050
1051
1052 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
1053
1054 c := make(chan int)
1055 for i := 0; i < 100; i++ {
1056 switch {
1057 case i%10 == 0:
1058 go func1(c)
1059 case i%2 == 0:
1060 go func2(c)
1061 default:
1062 go func3(c)
1063 }
1064
1065 for j := 0; j < 5; j++ {
1066 runtime.Gosched()
1067 }
1068 }
1069 ctx := context.Background()
1070
1071
1072
1073 Do(ctx, Labels("label", "value"), func(context.Context) {
1074 for i := 0; i < 89; i++ {
1075 switch {
1076 case i%10 == 0:
1077 go func1(c)
1078 case i%2 == 0:
1079 go func2(c)
1080 default:
1081 go func3(c)
1082 }
1083
1084 for j := 0; j < 5; j++ {
1085 runtime.Gosched()
1086 }
1087 }
1088 })
1089
1090 var w bytes.Buffer
1091 goroutineProf := Lookup("goroutine")
1092
1093
1094 goroutineProf.WriteTo(&w, 1)
1095 prof := w.String()
1096
1097 labels := labelMap{"label": "value"}
1098 labelStr := "\n# labels: " + labels.String()
1099 if !containsInOrder(prof, "\n50 @ ", "\n44 @", labelStr,
1100 "\n40 @", "\n36 @", labelStr, "\n10 @", "\n9 @", labelStr, "\n1 @") {
1101 t.Errorf("expected sorted goroutine counts with Labels:\n%s", prof)
1102 }
1103
1104
1105 w.Reset()
1106 goroutineProf.WriteTo(&w, 0)
1107 p, err := profile.Parse(&w)
1108 if err != nil {
1109 t.Errorf("error parsing protobuf profile: %v", err)
1110 }
1111 if err := p.CheckValid(); err != nil {
1112 t.Errorf("protobuf profile is invalid: %v", err)
1113 }
1114 expectedLabels := map[int64]map[string]string{
1115 50: map[string]string{},
1116 44: map[string]string{"label": "value"},
1117 40: map[string]string{},
1118 36: map[string]string{"label": "value"},
1119 10: map[string]string{},
1120 9: map[string]string{"label": "value"},
1121 1: map[string]string{},
1122 }
1123 if !containsCountsLabels(p, expectedLabels) {
1124 t.Errorf("expected count profile to contain goroutines with counts and labels %v, got %v",
1125 expectedLabels, p)
1126 }
1127
1128 close(c)
1129
1130 time.Sleep(10 * time.Millisecond)
1131 }
1132
1133 func containsInOrder(s string, all ...string) bool {
1134 for _, t := range all {
1135 i := strings.Index(s, t)
1136 if i < 0 {
1137 return false
1138 }
1139 s = s[i+len(t):]
1140 }
1141 return true
1142 }
1143
1144 func containsCountsLabels(prof *profile.Profile, countLabels map[int64]map[string]string) bool {
1145 m := make(map[int64]int)
1146 type nkey struct {
1147 count int64
1148 key, val string
1149 }
1150 n := make(map[nkey]int)
1151 for c, kv := range countLabels {
1152 m[c]++
1153 for k, v := range kv {
1154 n[nkey{
1155 count: c,
1156 key: k,
1157 val: v,
1158 }]++
1159
1160 }
1161 }
1162 for _, s := range prof.Sample {
1163
1164 if len(s.Value) != 1 {
1165 return false
1166 }
1167 m[s.Value[0]]--
1168 for k, vs := range s.Label {
1169 for _, v := range vs {
1170 n[nkey{
1171 count: s.Value[0],
1172 key: k,
1173 val: v,
1174 }]--
1175 }
1176 }
1177 }
1178 for _, n := range m {
1179 if n > 0 {
1180 return false
1181 }
1182 }
1183 for _, ncnt := range n {
1184 if ncnt != 0 {
1185 return false
1186 }
1187 }
1188 return true
1189 }
1190
1191 var emptyCallStackTestRun int64
1192
1193
1194 func TestEmptyCallStack(t *testing.T) {
1195 name := fmt.Sprintf("test18836_%d", emptyCallStackTestRun)
1196 emptyCallStackTestRun++
1197
1198 t.Parallel()
1199 var buf bytes.Buffer
1200 p := NewProfile(name)
1201
1202 p.Add("foo", 47674)
1203 p.WriteTo(&buf, 1)
1204 p.Remove("foo")
1205 got := buf.String()
1206 prefix := name + " profile: total 1\n"
1207 if !strings.HasPrefix(got, prefix) {
1208 t.Fatalf("got:\n\t%q\nwant prefix:\n\t%q\n", got, prefix)
1209 }
1210 lostevent := "lostProfileEvent"
1211 if !strings.Contains(got, lostevent) {
1212 t.Fatalf("got:\n\t%q\ndoes not contain:\n\t%q\n", got, lostevent)
1213 }
1214 }
1215
1216
1217
1218 func stackContainsLabeled(spec string, count uintptr, stk []*profile.Location, labels map[string][]string) bool {
1219 semi := strings.Index(spec, ";")
1220 if semi == -1 {
1221 panic("no semicolon in key/value spec")
1222 }
1223 kv := strings.SplitN(spec[semi+1:], "=", 2)
1224 if len(kv) != 2 {
1225 panic("missing = in key/value spec")
1226 }
1227 if !contains(labels[kv[0]], kv[1]) {
1228 return false
1229 }
1230 return stackContains(spec[:semi], count, stk, labels)
1231 }
1232
1233 func TestCPUProfileLabel(t *testing.T) {
1234 testCPUProfile(t, stackContainsLabeled, []string{"runtime/pprof.cpuHogger;key=value"}, avoidFunctions(), func(dur time.Duration) {
1235 Do(context.Background(), Labels("key", "value"), func(context.Context) {
1236 cpuHogger(cpuHog1, &salt1, dur)
1237 })
1238 })
1239 }
1240
1241 func TestLabelRace(t *testing.T) {
1242
1243
1244
1245 testCPUProfile(t, stackContainsLabeled, []string{"runtime/pprof.cpuHogger;key=value"}, nil, func(dur time.Duration) {
1246 start := time.Now()
1247 var wg sync.WaitGroup
1248 for time.Since(start) < dur {
1249 var salts [10]int
1250 for i := 0; i < 10; i++ {
1251 wg.Add(1)
1252 go func(j int) {
1253 Do(context.Background(), Labels("key", "value"), func(context.Context) {
1254 cpuHogger(cpuHog1, &salts[j], time.Millisecond)
1255 })
1256 wg.Done()
1257 }(i)
1258 }
1259 wg.Wait()
1260 }
1261 })
1262 }
1263
1264
1265
1266 func TestAtomicLoadStore64(t *testing.T) {
1267 f, err := os.CreateTemp("", "profatomic")
1268 if err != nil {
1269 t.Fatalf("TempFile: %v", err)
1270 }
1271 defer os.Remove(f.Name())
1272 defer f.Close()
1273
1274 if err := StartCPUProfile(f); err != nil {
1275 t.Fatal(err)
1276 }
1277 defer StopCPUProfile()
1278
1279 var flag uint64
1280 done := make(chan bool, 1)
1281
1282 go func() {
1283 for atomic.LoadUint64(&flag) == 0 {
1284 runtime.Gosched()
1285 }
1286 done <- true
1287 }()
1288 time.Sleep(50 * time.Millisecond)
1289 atomic.StoreUint64(&flag, 1)
1290 <-done
1291 }
1292
1293 func TestTracebackAll(t *testing.T) {
1294
1295
1296 f, err := os.CreateTemp("", "proftraceback")
1297 if err != nil {
1298 t.Fatalf("TempFile: %v", err)
1299 }
1300 defer os.Remove(f.Name())
1301 defer f.Close()
1302
1303 if err := StartCPUProfile(f); err != nil {
1304 t.Fatal(err)
1305 }
1306 defer StopCPUProfile()
1307
1308 ch := make(chan int)
1309 defer close(ch)
1310
1311 count := 10
1312 for i := 0; i < count; i++ {
1313 go func() {
1314 <-ch
1315 }()
1316 }
1317
1318 N := 10000
1319 if testing.Short() {
1320 N = 500
1321 }
1322 buf := make([]byte, 10*1024)
1323 for i := 0; i < N; i++ {
1324 runtime.Stack(buf, true)
1325 }
1326 }
1327
1328
1329
1330
1331
1332
1333
1334 func TestTryAdd(t *testing.T) {
1335 if _, found := findInlinedCall(inlinedCallerDump, 4<<10); !found {
1336 t.Skip("Can't determine whether anything was inlined into inlinedCallerDump.")
1337 }
1338
1339
1340
1341 pcs := make([]uintptr, 2)
1342 inlinedCallerDump(pcs)
1343 inlinedCallerStack := make([]uint64, 2)
1344 for i := range pcs {
1345 inlinedCallerStack[i] = uint64(pcs[i])
1346 }
1347
1348 if _, found := findInlinedCall(recursionChainBottom, 4<<10); !found {
1349 t.Skip("Can't determine whether anything was inlined into recursionChainBottom.")
1350 }
1351
1352
1353
1354
1355
1356
1357
1358 pcs = make([]uintptr, 6)
1359 recursionChainTop(1, pcs)
1360 recursionStack := make([]uint64, len(pcs))
1361 for i := range pcs {
1362 recursionStack[i] = uint64(pcs[i])
1363 }
1364
1365 period := int64(2000 * 1000)
1366
1367 testCases := []struct {
1368 name string
1369 input []uint64
1370 wantLocs [][]string
1371 wantSamples []*profile.Sample
1372 }{{
1373
1374 name: "full_stack_trace",
1375 input: []uint64{
1376 3, 0, 500,
1377 5, 0, 50, inlinedCallerStack[0], inlinedCallerStack[1],
1378 },
1379 wantLocs: [][]string{
1380 {"runtime/pprof.inlinedCalleeDump", "runtime/pprof.inlinedCallerDump"},
1381 },
1382 wantSamples: []*profile.Sample{
1383 {Value: []int64{50, 50 * period}, Location: []*profile.Location{{ID: 1}}},
1384 },
1385 }, {
1386 name: "bug35538",
1387 input: []uint64{
1388 3, 0, 500,
1389
1390
1391
1392 7, 0, 10, inlinedCallerStack[0], inlinedCallerStack[1], inlinedCallerStack[0], inlinedCallerStack[1],
1393 5, 0, 20, inlinedCallerStack[0], inlinedCallerStack[1],
1394 },
1395 wantLocs: [][]string{{"runtime/pprof.inlinedCalleeDump", "runtime/pprof.inlinedCallerDump"}},
1396 wantSamples: []*profile.Sample{
1397 {Value: []int64{10, 10 * period}, Location: []*profile.Location{{ID: 1}, {ID: 1}}},
1398 {Value: []int64{20, 20 * period}, Location: []*profile.Location{{ID: 1}}},
1399 },
1400 }, {
1401 name: "bug38096",
1402 input: []uint64{
1403 3, 0, 500,
1404
1405
1406 4, 0, 0, 4242,
1407 },
1408 wantLocs: [][]string{{"runtime/pprof.lostProfileEvent"}},
1409 wantSamples: []*profile.Sample{
1410 {Value: []int64{4242, 4242 * period}, Location: []*profile.Location{{ID: 1}}},
1411 },
1412 }, {
1413
1414
1415
1416
1417
1418
1419
1420 name: "directly_recursive_func_is_not_inlined",
1421 input: []uint64{
1422 3, 0, 500,
1423 5, 0, 30, inlinedCallerStack[0], inlinedCallerStack[0],
1424 4, 0, 40, inlinedCallerStack[0],
1425 },
1426
1427
1428 wantLocs: [][]string{{"runtime/pprof.inlinedCalleeDump"}, {"runtime/pprof.inlinedCallerDump"}},
1429 wantSamples: []*profile.Sample{
1430 {Value: []int64{30, 30 * period}, Location: []*profile.Location{{ID: 1}, {ID: 1}, {ID: 2}}},
1431 {Value: []int64{40, 40 * period}, Location: []*profile.Location{{ID: 1}, {ID: 2}}},
1432 },
1433 }, {
1434 name: "recursion_chain_inline",
1435 input: []uint64{
1436 3, 0, 500,
1437 9, 0, 10, recursionStack[0], recursionStack[1], recursionStack[2], recursionStack[3], recursionStack[4], recursionStack[5],
1438 },
1439 wantLocs: [][]string{
1440 {"runtime/pprof.recursionChainBottom"},
1441 {
1442 "runtime/pprof.recursionChainMiddle",
1443 "runtime/pprof.recursionChainTop",
1444 "runtime/pprof.recursionChainBottom",
1445 },
1446 {
1447 "runtime/pprof.recursionChainMiddle",
1448 "runtime/pprof.recursionChainTop",
1449 "runtime/pprof.TestTryAdd",
1450 },
1451 },
1452 wantSamples: []*profile.Sample{
1453 {Value: []int64{10, 10 * period}, Location: []*profile.Location{{ID: 1}, {ID: 2}, {ID: 3}}},
1454 },
1455 }, {
1456 name: "truncated_stack_trace_later",
1457 input: []uint64{
1458 3, 0, 500,
1459 5, 0, 50, inlinedCallerStack[0], inlinedCallerStack[1],
1460 4, 0, 60, inlinedCallerStack[0],
1461 },
1462 wantLocs: [][]string{{"runtime/pprof.inlinedCalleeDump", "runtime/pprof.inlinedCallerDump"}},
1463 wantSamples: []*profile.Sample{
1464 {Value: []int64{50, 50 * period}, Location: []*profile.Location{{ID: 1}}},
1465 {Value: []int64{60, 60 * period}, Location: []*profile.Location{{ID: 1}}},
1466 },
1467 }, {
1468 name: "truncated_stack_trace_first",
1469 input: []uint64{
1470 3, 0, 500,
1471 4, 0, 70, inlinedCallerStack[0],
1472 5, 0, 80, inlinedCallerStack[0], inlinedCallerStack[1],
1473 },
1474 wantLocs: [][]string{{"runtime/pprof.inlinedCalleeDump", "runtime/pprof.inlinedCallerDump"}},
1475 wantSamples: []*profile.Sample{
1476 {Value: []int64{70, 70 * period}, Location: []*profile.Location{{ID: 1}}},
1477 {Value: []int64{80, 80 * period}, Location: []*profile.Location{{ID: 1}}},
1478 },
1479 }, {
1480
1481 name: "truncated_stack_trace_only",
1482 input: []uint64{
1483 3, 0, 500,
1484 4, 0, 70, inlinedCallerStack[0],
1485 },
1486 wantLocs: [][]string{{"runtime/pprof.inlinedCalleeDump", "runtime/pprof.inlinedCallerDump"}},
1487 wantSamples: []*profile.Sample{
1488 {Value: []int64{70, 70 * period}, Location: []*profile.Location{{ID: 1}}},
1489 },
1490 }, {
1491
1492 name: "truncated_stack_trace_twice",
1493 input: []uint64{
1494 3, 0, 500,
1495 4, 0, 70, inlinedCallerStack[0],
1496
1497
1498
1499 5, 0, 80, inlinedCallerStack[1], inlinedCallerStack[0],
1500 },
1501 wantLocs: [][]string{
1502 {"runtime/pprof.inlinedCalleeDump", "runtime/pprof.inlinedCallerDump"},
1503 {"runtime/pprof.inlinedCallerDump"},
1504 },
1505 wantSamples: []*profile.Sample{
1506 {Value: []int64{70, 70 * period}, Location: []*profile.Location{{ID: 1}}},
1507 {Value: []int64{80, 80 * period}, Location: []*profile.Location{{ID: 2}, {ID: 1}}},
1508 },
1509 }}
1510
1511 for _, tc := range testCases {
1512 t.Run(tc.name, func(t *testing.T) {
1513 p, err := translateCPUProfile(tc.input)
1514 if err != nil {
1515 t.Fatalf("translating profile: %v", err)
1516 }
1517 t.Logf("Profile: %v\n", p)
1518
1519
1520 var gotLoc [][]string
1521 for _, loc := range p.Location {
1522 var names []string
1523 for _, line := range loc.Line {
1524 names = append(names, line.Function.Name)
1525 }
1526 gotLoc = append(gotLoc, names)
1527 }
1528 if got, want := fmtJSON(gotLoc), fmtJSON(tc.wantLocs); got != want {
1529 t.Errorf("Got Location = %+v\n\twant %+v", got, want)
1530 }
1531
1532 var gotSamples []*profile.Sample
1533 for _, sample := range p.Sample {
1534 var locs []*profile.Location
1535 for _, loc := range sample.Location {
1536 locs = append(locs, &profile.Location{ID: loc.ID})
1537 }
1538 gotSamples = append(gotSamples, &profile.Sample{Value: sample.Value, Location: locs})
1539 }
1540 if got, want := fmtJSON(gotSamples), fmtJSON(tc.wantSamples); got != want {
1541 t.Errorf("Got Samples = %+v\n\twant %+v", got, want)
1542 }
1543 })
1544 }
1545 }
1546
1547 func TestTimeVDSO(t *testing.T) {
1548
1549
1550
1551 if runtime.GOOS == "android" {
1552
1553 testenv.SkipFlaky(t, 48655)
1554 }
1555
1556 p := testCPUProfile(t, stackContains, []string{"time.now"}, avoidFunctions(), func(dur time.Duration) {
1557 t0 := time.Now()
1558 for {
1559 t := time.Now()
1560 if t.Sub(t0) >= dur {
1561 return
1562 }
1563 }
1564 })
1565
1566
1567 for _, sample := range p.Sample {
1568 var seenNow bool
1569 for _, loc := range sample.Location {
1570 for _, line := range loc.Line {
1571 if line.Function.Name == "time.now" {
1572 if seenNow {
1573 t.Fatalf("unexpected recursive time.now")
1574 }
1575 seenNow = true
1576 }
1577 }
1578 }
1579 }
1580 }
1581
View as plain text