1
2
3
4
5
6
7 package pprof
8
9 import (
10 "bytes"
11 "context"
12 "fmt"
13 "internal/abi"
14 "internal/profile"
15 "internal/syscall/unix"
16 "internal/testenv"
17 "io"
18 "math"
19 "math/big"
20 "os"
21 "regexp"
22 "runtime"
23 "runtime/debug"
24 "strconv"
25 "strings"
26 "sync"
27 "sync/atomic"
28 "testing"
29 "time"
30 _ "unsafe"
31 )
32
33 func cpuHogger(f func(x int) int, y *int, dur time.Duration) {
34
35
36
37
38
39 t0 := time.Now()
40 accum := *y
41 for i := 0; i < 500 || time.Since(t0) < dur; i++ {
42 accum = f(accum)
43 }
44 *y = accum
45 }
46
47 var (
48 salt1 = 0
49 salt2 = 0
50 )
51
52
53
54
55 func cpuHog1(x int) int {
56 return cpuHog0(x, 1e5)
57 }
58
59 func cpuHog0(x, n int) int {
60 foo := x
61 for i := 0; i < n; i++ {
62 if foo > 0 {
63 foo *= foo
64 } else {
65 foo *= foo + 1
66 }
67 }
68 return foo
69 }
70
71 func cpuHog2(x int) int {
72 foo := x
73 for i := 0; i < 1e5; i++ {
74 if foo > 0 {
75 foo *= foo
76 } else {
77 foo *= foo + 2
78 }
79 }
80 return foo
81 }
82
83
84
85 func avoidFunctions() []string {
86 if runtime.Compiler == "gccgo" {
87 return []string{"runtime.sigprof"}
88 }
89 return nil
90 }
91
92 func TestCPUProfile(t *testing.T) {
93 matches := matchAndAvoidStacks(stackContains, []string{"runtime/pprof.cpuHog1"}, avoidFunctions())
94 testCPUProfile(t, matches, func(dur time.Duration) {
95 cpuHogger(cpuHog1, &salt1, dur)
96 })
97 }
98
99 func TestCPUProfileMultithreaded(t *testing.T) {
100 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2))
101 matches := matchAndAvoidStacks(stackContains, []string{"runtime/pprof.cpuHog1", "runtime/pprof.cpuHog2"}, avoidFunctions())
102 testCPUProfile(t, matches, func(dur time.Duration) {
103 c := make(chan int)
104 go func() {
105 cpuHogger(cpuHog1, &salt1, dur)
106 c <- 1
107 }()
108 cpuHogger(cpuHog2, &salt2, dur)
109 <-c
110 })
111 }
112
113 func TestCPUProfileMultithreadMagnitude(t *testing.T) {
114 if runtime.GOOS != "linux" {
115 t.Skip("issue 35057 is only confirmed on Linux")
116 }
117
118
119
120 major, minor := unix.KernelVersion()
121 t.Logf("Running on Linux %d.%d", major, minor)
122 defer func() {
123 if t.Failed() {
124 t.Logf("Failure of this test may indicate that your system suffers from a known Linux kernel bug fixed on newer kernels. See https://golang.org/issue/49065.")
125 }
126 }()
127
128
129
130
131 if testenv.Builder() != "" && (runtime.GOARCH == "386" || runtime.GOARCH == "amd64") {
132 have59 := major > 5 || (major == 5 && minor >= 9)
133 have516 := major > 5 || (major == 5 && minor >= 16)
134 if have59 && !have516 {
135 testenv.SkipFlaky(t, 49065)
136 }
137 }
138
139
140
141
142
143
144
145
146
147
148
149
150 maxDiff := 0.10
151 if testing.Short() {
152 maxDiff = 0.40
153 }
154
155 compare := func(a, b time.Duration, maxDiff float64) error {
156 if a <= 0 || b <= 0 {
157 return fmt.Errorf("Expected both time reports to be positive")
158 }
159
160 if a < b {
161 a, b = b, a
162 }
163
164 diff := float64(a-b) / float64(a)
165 if diff > maxDiff {
166 return fmt.Errorf("CPU usage reports are too different (limit -%.1f%%, got -%.1f%%)", maxDiff*100, diff*100)
167 }
168
169 return nil
170 }
171
172 for _, tc := range []struct {
173 name string
174 workers int
175 }{
176 {
177 name: "serial",
178 workers: 1,
179 },
180 {
181 name: "parallel",
182 workers: runtime.GOMAXPROCS(0),
183 },
184 } {
185
186 t.Run(tc.name, func(t *testing.T) {
187 t.Logf("Running with %d workers", tc.workers)
188
189 var userTime, systemTime time.Duration
190 matches := matchAndAvoidStacks(stackContains, []string{"runtime/pprof.cpuHog1"}, avoidFunctions())
191 acceptProfile := func(t *testing.T, p *profile.Profile) bool {
192 if !matches(t, p) {
193 return false
194 }
195
196 ok := true
197 for i, unit := range []string{"count", "nanoseconds"} {
198 if have, want := p.SampleType[i].Unit, unit; have != want {
199 t.Logf("pN SampleType[%d]; %q != %q", i, have, want)
200 ok = false
201 }
202 }
203
204
205
206
207
208
209 var value time.Duration
210 for _, sample := range p.Sample {
211 value += time.Duration(sample.Value[1]) * time.Nanosecond
212 }
213
214 totalTime := userTime + systemTime
215 t.Logf("compare %s user + %s system = %s vs %s", userTime, systemTime, totalTime, value)
216 if err := compare(totalTime, value, maxDiff); err != nil {
217 t.Logf("compare got %v want nil", err)
218 ok = false
219 }
220
221 return ok
222 }
223
224 testCPUProfile(t, acceptProfile, func(dur time.Duration) {
225 userTime, systemTime = diffCPUTime(t, func() {
226 var wg sync.WaitGroup
227 var once sync.Once
228 for i := 0; i < tc.workers; i++ {
229 wg.Add(1)
230 go func() {
231 defer wg.Done()
232 var salt = 0
233 cpuHogger(cpuHog1, &salt, dur)
234 once.Do(func() { salt1 = salt })
235 }()
236 }
237 wg.Wait()
238 })
239 })
240 })
241 }
242 }
243
244
245
246 func containsInlinedCall(f any, maxBytes int) bool {
247 _, found := findInlinedCall(f, maxBytes)
248 return found
249 }
250
251
252
253 func findInlinedCall(f any, maxBytes int) (pc uint64, found bool) {
254 fFunc := runtime.FuncForPC(uintptr(abi.FuncPCABIInternal(f)))
255 if fFunc == nil || fFunc.Entry() == 0 {
256 panic("failed to locate function entry")
257 }
258
259 for offset := 0; offset < maxBytes; offset++ {
260 innerPC := fFunc.Entry() + uintptr(offset)
261 inner := runtime.FuncForPC(innerPC)
262 if inner == nil {
263
264
265 continue
266 }
267 if inner.Entry() != fFunc.Entry() {
268
269 break
270 }
271 if inner.Name() != fFunc.Name() {
272
273
274 return uint64(innerPC), true
275 }
276 }
277
278 return 0, false
279 }
280
281 func TestCPUProfileInlining(t *testing.T) {
282 if !containsInlinedCall(inlinedCaller, 4<<10) {
283 t.Skip("Can't determine whether inlinedCallee was inlined into inlinedCaller.")
284 }
285
286 matches := matchAndAvoidStacks(stackContains, []string{"runtime/pprof.inlinedCallee", "runtime/pprof.inlinedCaller"}, avoidFunctions())
287 p := testCPUProfile(t, matches, func(dur time.Duration) {
288 cpuHogger(inlinedCaller, &salt1, dur)
289 })
290
291
292 for _, loc := range p.Location {
293 hasInlinedCallerAfterInlinedCallee, hasInlinedCallee := false, false
294 for _, line := range loc.Line {
295 if line.Function.Name == "runtime/pprof.inlinedCallee" {
296 hasInlinedCallee = true
297 }
298 if hasInlinedCallee && line.Function.Name == "runtime/pprof.inlinedCaller" {
299 hasInlinedCallerAfterInlinedCallee = true
300 }
301 }
302 if hasInlinedCallee != hasInlinedCallerAfterInlinedCallee {
303 t.Fatalf("want inlinedCallee followed by inlinedCaller, got separate Location entries:\n%v", p)
304 }
305 }
306 }
307
308 func inlinedCaller(x int) int {
309 x = inlinedCallee(x, 1e5)
310 return x
311 }
312
313 func inlinedCallee(x, n int) int {
314 return cpuHog0(x, n)
315 }
316
317
318 func dumpCallers(pcs []uintptr) {
319 if pcs == nil {
320 return
321 }
322
323 skip := 2
324 runtime.Callers(skip, pcs)
325 }
326
327
328 func inlinedCallerDump(pcs []uintptr) {
329 inlinedCalleeDump(pcs)
330 }
331
332 func inlinedCalleeDump(pcs []uintptr) {
333 dumpCallers(pcs)
334 }
335
336 type inlineWrapperInterface interface {
337 dump(stack []uintptr)
338 }
339
340 type inlineWrapper struct {
341 }
342
343 func (h inlineWrapper) dump(pcs []uintptr) {
344 dumpCallers(pcs)
345 }
346
347 func inlinedWrapperCallerDump(pcs []uintptr) {
348 var h inlineWrapperInterface
349 h = &inlineWrapper{}
350 h.dump(pcs)
351 }
352
353 func TestCPUProfileRecursion(t *testing.T) {
354 matches := matchAndAvoidStacks(stackContains, []string{"runtime/pprof.inlinedCallee", "runtime/pprof.recursionCallee", "runtime/pprof.recursionCaller"}, avoidFunctions())
355 p := testCPUProfile(t, matches, func(dur time.Duration) {
356 cpuHogger(recursionCaller, &salt1, dur)
357 })
358
359
360 for i, loc := range p.Location {
361 recursionFunc := 0
362 for _, line := range loc.Line {
363 if name := line.Function.Name; name == "runtime/pprof.recursionCaller" || name == "runtime/pprof.recursionCallee" {
364 recursionFunc++
365 }
366 }
367 if recursionFunc > 1 {
368 t.Fatalf("want at most one recursionCaller or recursionCallee in one Location, got a violating Location (index: %d):\n%v", i, p)
369 }
370 }
371 }
372
373 func recursionCaller(x int) int {
374 y := recursionCallee(3, x)
375 return y
376 }
377
378 func recursionCallee(n, x int) int {
379 if n == 0 {
380 return 1
381 }
382 y := inlinedCallee(x, 1e4)
383 return y * recursionCallee(n-1, x)
384 }
385
386 func recursionChainTop(x int, pcs []uintptr) {
387 if x < 0 {
388 return
389 }
390 recursionChainMiddle(x, pcs)
391 }
392
393 func recursionChainMiddle(x int, pcs []uintptr) {
394 recursionChainBottom(x, pcs)
395 }
396
397 func recursionChainBottom(x int, pcs []uintptr) {
398
399
400 dumpCallers(pcs)
401
402 recursionChainTop(x-1, pcs)
403 }
404
405 func parseProfile(t *testing.T, valBytes []byte, f func(uintptr, []*profile.Location, map[string][]string)) *profile.Profile {
406 p, err := profile.Parse(bytes.NewReader(valBytes))
407 if err != nil {
408 t.Fatal(err)
409 }
410 for _, sample := range p.Sample {
411 count := uintptr(sample.Value[0])
412 f(count, sample.Location, sample.Label)
413 }
414 return p
415 }
416
417 func cpuProfilingBroken() bool {
418 switch runtime.GOOS {
419 case "plan9":
420
421 return true
422 case "aix":
423
424 return true
425 case "ios", "dragonfly", "netbsd", "illumos", "solaris":
426
427 return true
428 case "openbsd":
429 if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" {
430
431 return true
432 }
433 }
434
435 return false
436 }
437
438
439
440 func testCPUProfile(t *testing.T, matches profileMatchFunc, f func(dur time.Duration)) *profile.Profile {
441 switch runtime.GOOS {
442 case "darwin":
443 out, err := testenv.Command(t, "uname", "-a").CombinedOutput()
444 if err != nil {
445 t.Fatal(err)
446 }
447 vers := string(out)
448 t.Logf("uname -a: %v", vers)
449 case "plan9":
450 t.Skip("skipping on plan9")
451 case "wasip1":
452 t.Skip("skipping on wasip1")
453 }
454
455 broken := cpuProfilingBroken()
456
457 deadline, ok := t.Deadline()
458 if broken || !ok {
459 if broken && testing.Short() {
460
461 deadline = time.Now().Add(1 * time.Second)
462 } else {
463 deadline = time.Now().Add(10 * time.Second)
464 }
465 }
466
467
468
469 duration := 5 * time.Second
470 if testing.Short() {
471 duration = 100 * time.Millisecond
472 }
473
474
475
476
477
478
479 for {
480 var prof bytes.Buffer
481 if err := StartCPUProfile(&prof); err != nil {
482 t.Fatal(err)
483 }
484 f(duration)
485 StopCPUProfile()
486
487 if p, ok := profileOk(t, matches, prof, duration); ok {
488 return p
489 }
490
491 duration *= 2
492 if time.Until(deadline) < duration {
493 break
494 }
495 t.Logf("retrying with %s duration", duration)
496 }
497
498 if broken {
499 t.Skipf("ignoring failure on %s/%s; see golang.org/issue/13841", runtime.GOOS, runtime.GOARCH)
500 }
501
502
503
504
505
506 if os.Getenv("IN_QEMU") == "1" {
507 t.Skip("ignore the failure in QEMU; see golang.org/issue/9605")
508 }
509 t.FailNow()
510 return nil
511 }
512
513 var diffCPUTimeImpl func(f func()) (user, system time.Duration)
514
515 func diffCPUTime(t *testing.T, f func()) (user, system time.Duration) {
516 if fn := diffCPUTimeImpl; fn != nil {
517 return fn(f)
518 }
519 t.Fatalf("cannot measure CPU time on GOOS=%s GOARCH=%s", runtime.GOOS, runtime.GOARCH)
520 return 0, 0
521 }
522
523 func contains(slice []string, s string) bool {
524 for i := range slice {
525 if slice[i] == s {
526 return true
527 }
528 }
529 return false
530 }
531
532
533 func stackContains(spec string, count uintptr, stk []*profile.Location, labels map[string][]string) bool {
534 for _, loc := range stk {
535 for _, line := range loc.Line {
536 if strings.Contains(line.Function.Name, spec) {
537 return true
538 }
539 }
540 }
541 return false
542 }
543
544 type sampleMatchFunc func(spec string, count uintptr, stk []*profile.Location, labels map[string][]string) bool
545
546 func profileOk(t *testing.T, matches profileMatchFunc, prof bytes.Buffer, duration time.Duration) (_ *profile.Profile, ok bool) {
547 ok = true
548
549 var samples uintptr
550 var buf strings.Builder
551 p := parseProfile(t, prof.Bytes(), func(count uintptr, stk []*profile.Location, labels map[string][]string) {
552 fmt.Fprintf(&buf, "%d:", count)
553 fprintStack(&buf, stk)
554 fmt.Fprintf(&buf, " labels: %v\n", labels)
555 samples += count
556 fmt.Fprintf(&buf, "\n")
557 })
558 t.Logf("total %d CPU profile samples collected:\n%s", samples, buf.String())
559
560 if samples < 10 && runtime.GOOS == "windows" {
561
562
563
564 t.Log("too few samples on Windows (golang.org/issue/10842)")
565 return p, false
566 }
567
568
569
570
571
572
573 if ideal := uintptr(duration * 100 / time.Second); samples == 0 || (samples < ideal/4 && samples < 10) {
574 t.Logf("too few samples; got %d, want at least %d, ideally %d", samples, ideal/4, ideal)
575 ok = false
576 }
577
578 if matches != nil && !matches(t, p) {
579 ok = false
580 }
581
582 return p, ok
583 }
584
585 type profileMatchFunc func(*testing.T, *profile.Profile) bool
586
587 func matchAndAvoidStacks(matches sampleMatchFunc, need []string, avoid []string) profileMatchFunc {
588 return func(t *testing.T, p *profile.Profile) (ok bool) {
589 ok = true
590
591
592
593 have := make([]uintptr, len(need))
594 avoidSamples := make([]uintptr, len(avoid))
595
596 for _, sample := range p.Sample {
597 count := uintptr(sample.Value[0])
598 for i, spec := range need {
599 if matches(spec, count, sample.Location, sample.Label) {
600 have[i] += count
601 }
602 }
603 for i, name := range avoid {
604 for _, loc := range sample.Location {
605 for _, line := range loc.Line {
606 if strings.Contains(line.Function.Name, name) {
607 avoidSamples[i] += count
608 }
609 }
610 }
611 }
612 }
613
614 for i, name := range avoid {
615 bad := avoidSamples[i]
616 if bad != 0 {
617 t.Logf("found %d samples in avoid-function %s\n", bad, name)
618 ok = false
619 }
620 }
621
622 if len(need) == 0 {
623 return
624 }
625
626 var total uintptr
627 for i, name := range need {
628 total += have[i]
629 t.Logf("found %d samples in expected function %s\n", have[i], name)
630 }
631 if total == 0 {
632 t.Logf("no samples in expected functions")
633 ok = false
634 }
635
636
637
638
639
640 min := uintptr(1)
641 for i, name := range need {
642 if have[i] < min {
643 t.Logf("%s has %d samples out of %d, want at least %d, ideally %d", name, have[i], total, min, total/uintptr(len(have)))
644 ok = false
645 }
646 }
647 return
648 }
649 }
650
651
652
653 func TestCPUProfileWithFork(t *testing.T) {
654 testenv.MustHaveExec(t)
655
656 exe, err := os.Executable()
657 if err != nil {
658 t.Fatal(err)
659 }
660
661 heap := 1 << 30
662 if runtime.GOOS == "android" {
663
664 heap = 100 << 20
665 }
666 if runtime.GOOS == "windows" && runtime.GOARCH == "arm" {
667
668 heap = 100 << 20
669 }
670 if testing.Short() {
671 heap = 100 << 20
672 }
673
674 garbage := make([]byte, heap)
675
676 done := make(chan bool)
677 go func() {
678 for i := range garbage {
679 garbage[i] = 42
680 }
681 done <- true
682 }()
683 <-done
684
685 var prof bytes.Buffer
686 if err := StartCPUProfile(&prof); err != nil {
687 t.Fatal(err)
688 }
689 defer StopCPUProfile()
690
691 for i := 0; i < 10; i++ {
692 testenv.Command(t, exe, "-h").CombinedOutput()
693 }
694 }
695
696
697
698
699 func TestGoroutineSwitch(t *testing.T) {
700 if runtime.Compiler == "gccgo" {
701 t.Skip("not applicable for gccgo")
702 }
703
704
705
706 tries := 10
707 count := 1000000
708 if testing.Short() {
709 tries = 1
710 }
711 for try := 0; try < tries; try++ {
712 var prof bytes.Buffer
713 if err := StartCPUProfile(&prof); err != nil {
714 t.Fatal(err)
715 }
716 for i := 0; i < count; i++ {
717 runtime.Gosched()
718 }
719 StopCPUProfile()
720
721
722
723
724
725 parseProfile(t, prof.Bytes(), func(count uintptr, stk []*profile.Location, _ map[string][]string) {
726
727
728 if len(stk) == 2 {
729 name := stk[1].Line[0].Function.Name
730 if name == "runtime._System" || name == "runtime._ExternalCode" || name == "runtime._GC" {
731 return
732 }
733 }
734
735
736
737 if len(stk) == 1 {
738 return
739 }
740
741
742
743 name := stk[0].Line[0].Function.Name
744 if name == "gogo" {
745 var buf strings.Builder
746 fprintStack(&buf, stk)
747 t.Fatalf("found profile entry for gogo:\n%s", buf.String())
748 }
749 })
750 }
751 }
752
753 func fprintStack(w io.Writer, stk []*profile.Location) {
754 if len(stk) == 0 {
755 fmt.Fprintf(w, " (stack empty)")
756 }
757 for _, loc := range stk {
758 fmt.Fprintf(w, " %#x", loc.Address)
759 fmt.Fprintf(w, " (")
760 for i, line := range loc.Line {
761 if i > 0 {
762 fmt.Fprintf(w, " ")
763 }
764 fmt.Fprintf(w, "%s:%d", line.Function.Name, line.Line)
765 }
766 fmt.Fprintf(w, ")")
767 }
768 }
769
770
771 func TestMathBigDivide(t *testing.T) {
772 testCPUProfile(t, nil, func(duration time.Duration) {
773 t := time.After(duration)
774 pi := new(big.Int)
775 for {
776 for i := 0; i < 100; i++ {
777 n := big.NewInt(2646693125139304345)
778 d := big.NewInt(842468587426513207)
779 pi.Div(n, d)
780 }
781 select {
782 case <-t:
783 return
784 default:
785 }
786 }
787 })
788 }
789
790
791 func stackContainsAll(spec string, count uintptr, stk []*profile.Location, labels map[string][]string) bool {
792 for _, f := range strings.Split(spec, ",") {
793 if !stackContains(f, count, stk, labels) {
794 return false
795 }
796 }
797 return true
798 }
799
800 func TestMorestack(t *testing.T) {
801 matches := matchAndAvoidStacks(stackContainsAll, []string{"runtime.newstack,runtime/pprof.growstack"}, avoidFunctions())
802 testCPUProfile(t, matches, func(duration time.Duration) {
803 t := time.After(duration)
804 c := make(chan bool)
805 for {
806 go func() {
807 growstack1()
808 c <- true
809 }()
810 select {
811 case <-t:
812 return
813 case <-c:
814 }
815 }
816 })
817 }
818
819
820 func growstack1() {
821 growstack(10)
822 }
823
824
825 func growstack(n int) {
826 var buf [8 << 18]byte
827 use(buf)
828 if n > 0 {
829 growstack(n - 1)
830 }
831 }
832
833
834 func use(x [8 << 18]byte) {}
835
836 func TestBlockProfile(t *testing.T) {
837 type TestCase struct {
838 name string
839 f func(*testing.T)
840 stk []string
841 re string
842 }
843 tests := [...]TestCase{
844 {
845 name: "chan recv",
846 f: blockChanRecv,
847 stk: []string{
848 "runtime.chanrecv1",
849 "runtime/pprof.blockChanRecv",
850 "runtime/pprof.TestBlockProfile",
851 },
852 re: `
853 [0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
854 # 0x[0-9a-f]+ runtime\.chanrecv1\+0x[0-9a-f]+ .*runtime/chan.go:[0-9]+
855 # 0x[0-9a-f]+ runtime/pprof\.blockChanRecv\+0x[0-9a-f]+ .*runtime/pprof/pprof_test.go:[0-9]+
856 # 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*runtime/pprof/pprof_test.go:[0-9]+
857 `},
858 {
859 name: "chan send",
860 f: blockChanSend,
861 stk: []string{
862 "runtime.chansend1",
863 "runtime/pprof.blockChanSend",
864 "runtime/pprof.TestBlockProfile",
865 },
866 re: `
867 [0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
868 # 0x[0-9a-f]+ runtime\.chansend1\+0x[0-9a-f]+ .*runtime/chan.go:[0-9]+
869 # 0x[0-9a-f]+ runtime/pprof\.blockChanSend\+0x[0-9a-f]+ .*runtime/pprof/pprof_test.go:[0-9]+
870 # 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*runtime/pprof/pprof_test.go:[0-9]+
871 `},
872 {
873 name: "chan close",
874 f: blockChanClose,
875 stk: []string{
876 "runtime.chanrecv1",
877 "runtime/pprof.blockChanClose",
878 "runtime/pprof.TestBlockProfile",
879 },
880 re: `
881 [0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
882 # 0x[0-9a-f]+ runtime\.chanrecv1\+0x[0-9a-f]+ .*runtime/chan.go:[0-9]+
883 # 0x[0-9a-f]+ runtime/pprof\.blockChanClose\+0x[0-9a-f]+ .*runtime/pprof/pprof_test.go:[0-9]+
884 # 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*runtime/pprof/pprof_test.go:[0-9]+
885 `},
886 {
887 name: "select recv async",
888 f: blockSelectRecvAsync,
889 stk: []string{
890 "runtime.selectgo",
891 "runtime/pprof.blockSelectRecvAsync",
892 "runtime/pprof.TestBlockProfile",
893 },
894 re: `
895 [0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
896 # 0x[0-9a-f]+ runtime\.selectgo\+0x[0-9a-f]+ .*runtime/select.go:[0-9]+
897 # 0x[0-9a-f]+ runtime/pprof\.blockSelectRecvAsync\+0x[0-9a-f]+ .*runtime/pprof/pprof_test.go:[0-9]+
898 # 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*runtime/pprof/pprof_test.go:[0-9]+
899 `},
900 {
901 name: "select send sync",
902 f: blockSelectSendSync,
903 stk: []string{
904 "runtime.selectgo",
905 "runtime/pprof.blockSelectSendSync",
906 "runtime/pprof.TestBlockProfile",
907 },
908 re: `
909 [0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
910 # 0x[0-9a-f]+ runtime\.selectgo\+0x[0-9a-f]+ .*runtime/select.go:[0-9]+
911 # 0x[0-9a-f]+ runtime/pprof\.blockSelectSendSync\+0x[0-9a-f]+ .*runtime/pprof/pprof_test.go:[0-9]+
912 # 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*runtime/pprof/pprof_test.go:[0-9]+
913 `},
914 {
915 name: "mutex",
916 f: blockMutex,
917 stk: []string{
918 "sync.(*Mutex).Lock",
919 "runtime/pprof.blockMutex",
920 "runtime/pprof.TestBlockProfile",
921 },
922 re: `
923 [0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
924 # 0x[0-9a-f]+ sync\.\(\*Mutex\)\.Lock\+0x[0-9a-f]+ .*sync/mutex\.go:[0-9]+
925 # 0x[0-9a-f]+ runtime/pprof\.blockMutex\+0x[0-9a-f]+ .*runtime/pprof/pprof_test.go:[0-9]+
926 # 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*runtime/pprof/pprof_test.go:[0-9]+
927 `},
928 {
929 name: "cond",
930 f: blockCond,
931 stk: []string{
932 "sync.(*Cond).Wait",
933 "runtime/pprof.blockCond",
934 "runtime/pprof.TestBlockProfile",
935 },
936 re: `
937 [0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
938 # 0x[0-9a-f]+ sync\.\(\*Cond\)\.Wait\+0x[0-9a-f]+ .*sync/cond\.go:[0-9]+
939 # 0x[0-9a-f]+ runtime/pprof\.blockCond\+0x[0-9a-f]+ .*runtime/pprof/pprof_test.go:[0-9]+
940 # 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*runtime/pprof/pprof_test.go:[0-9]+
941 `},
942 }
943
944
945 runtime.SetBlockProfileRate(1)
946 defer runtime.SetBlockProfileRate(0)
947 for _, test := range tests {
948 test.f(t)
949 }
950
951 t.Run("debug=1", func(t *testing.T) {
952 var w strings.Builder
953 Lookup("block").WriteTo(&w, 1)
954 prof := w.String()
955
956 if !strings.HasPrefix(prof, "--- contention:\ncycles/second=") {
957 t.Fatalf("Bad profile header:\n%v", prof)
958 }
959
960 if strings.HasSuffix(prof, "#\t0x0\n\n") {
961 t.Errorf("Useless 0 suffix:\n%v", prof)
962 }
963
964 for _, test := range tests {
965 if !regexp.MustCompile(strings.ReplaceAll(test.re, "\t", "\t+")).MatchString(prof) {
966 t.Errorf("Bad %v entry, expect:\n%v\ngot:\n%v", test.name, test.re, prof)
967 }
968 }
969 })
970
971 t.Run("proto", func(t *testing.T) {
972
973 var w bytes.Buffer
974 Lookup("block").WriteTo(&w, 0)
975 p, err := profile.Parse(&w)
976 if err != nil {
977 t.Fatalf("failed to parse profile: %v", err)
978 }
979 t.Logf("parsed proto: %s", p)
980 if err := p.CheckValid(); err != nil {
981 t.Fatalf("invalid profile: %v", err)
982 }
983
984 stks := stacks(p)
985 for _, test := range tests {
986 if !containsStack(stks, test.stk) {
987 t.Errorf("No matching stack entry for %v, want %+v", test.name, test.stk)
988 }
989 }
990 })
991
992 }
993
994 func stacks(p *profile.Profile) (res [][]string) {
995 for _, s := range p.Sample {
996 var stk []string
997 for _, l := range s.Location {
998 for _, line := range l.Line {
999 stk = append(stk, line.Function.Name)
1000 }
1001 }
1002 res = append(res, stk)
1003 }
1004 return res
1005 }
1006
1007 func containsStack(got [][]string, want []string) bool {
1008 for _, stk := range got {
1009 if len(stk) < len(want) {
1010 continue
1011 }
1012 for i, f := range want {
1013 if f != stk[i] {
1014 break
1015 }
1016 if i == len(want)-1 {
1017 return true
1018 }
1019 }
1020 }
1021 return false
1022 }
1023
1024
1025
1026
1027 func awaitBlockedGoroutine(t *testing.T, state, fName string, count int) {
1028 re := fmt.Sprintf(`(?m)^goroutine \d+ \[%s\]:\n(?:.+\n\t.+\n)*runtime/pprof\.%s`, regexp.QuoteMeta(state), fName)
1029 r := regexp.MustCompile(re)
1030
1031 if deadline, ok := t.Deadline(); ok {
1032 if d := time.Until(deadline); d > 1*time.Second {
1033 timer := time.AfterFunc(d-1*time.Second, func() {
1034 debug.SetTraceback("all")
1035 panic(fmt.Sprintf("timed out waiting for %#q", re))
1036 })
1037 defer timer.Stop()
1038 }
1039 }
1040
1041 buf := make([]byte, 64<<10)
1042 for {
1043 runtime.Gosched()
1044 n := runtime.Stack(buf, true)
1045 if n == len(buf) {
1046
1047
1048 buf = make([]byte, 2*len(buf))
1049 continue
1050 }
1051 if len(r.FindAll(buf[:n], -1)) >= count {
1052 return
1053 }
1054 }
1055 }
1056
1057 func blockChanRecv(t *testing.T) {
1058 c := make(chan bool)
1059 go func() {
1060 awaitBlockedGoroutine(t, "chan receive", "blockChanRecv", 1)
1061 c <- true
1062 }()
1063 <-c
1064 }
1065
1066 func blockChanSend(t *testing.T) {
1067 c := make(chan bool)
1068 go func() {
1069 awaitBlockedGoroutine(t, "chan send", "blockChanSend", 1)
1070 <-c
1071 }()
1072 c <- true
1073 }
1074
1075 func blockChanClose(t *testing.T) {
1076 c := make(chan bool)
1077 go func() {
1078 awaitBlockedGoroutine(t, "chan receive", "blockChanClose", 1)
1079 close(c)
1080 }()
1081 <-c
1082 }
1083
1084 func blockSelectRecvAsync(t *testing.T) {
1085 const numTries = 3
1086 c := make(chan bool, 1)
1087 c2 := make(chan bool, 1)
1088 go func() {
1089 for i := 0; i < numTries; i++ {
1090 awaitBlockedGoroutine(t, "select", "blockSelectRecvAsync", 1)
1091 c <- true
1092 }
1093 }()
1094 for i := 0; i < numTries; i++ {
1095 select {
1096 case <-c:
1097 case <-c2:
1098 }
1099 }
1100 }
1101
1102 func blockSelectSendSync(t *testing.T) {
1103 c := make(chan bool)
1104 c2 := make(chan bool)
1105 go func() {
1106 awaitBlockedGoroutine(t, "select", "blockSelectSendSync", 1)
1107 <-c
1108 }()
1109 select {
1110 case c <- true:
1111 case c2 <- true:
1112 }
1113 }
1114
1115 func blockMutex(t *testing.T) {
1116 var mu sync.Mutex
1117 mu.Lock()
1118 go func() {
1119 awaitBlockedGoroutine(t, "sync.Mutex.Lock", "blockMutex", 1)
1120 mu.Unlock()
1121 }()
1122
1123
1124
1125
1126 mu.Lock()
1127 }
1128
1129 func blockMutexN(t *testing.T, n int, d time.Duration) {
1130 var wg sync.WaitGroup
1131 var mu sync.Mutex
1132 mu.Lock()
1133 go func() {
1134 awaitBlockedGoroutine(t, "sync.Mutex.Lock", "blockMutex", n)
1135 time.Sleep(d)
1136 mu.Unlock()
1137 }()
1138
1139
1140
1141
1142 for i := 0; i < n; i++ {
1143 wg.Add(1)
1144 go func() {
1145 defer wg.Done()
1146 mu.Lock()
1147 mu.Unlock()
1148 }()
1149 }
1150 wg.Wait()
1151 }
1152
1153 func blockCond(t *testing.T) {
1154 var mu sync.Mutex
1155 c := sync.NewCond(&mu)
1156 mu.Lock()
1157 go func() {
1158 awaitBlockedGoroutine(t, "sync.Cond.Wait", "blockCond", 1)
1159 mu.Lock()
1160 c.Signal()
1161 mu.Unlock()
1162 }()
1163 c.Wait()
1164 mu.Unlock()
1165 }
1166
1167
1168 func TestBlockProfileBias(t *testing.T) {
1169 rate := int(1000)
1170 runtime.SetBlockProfileRate(rate)
1171 defer runtime.SetBlockProfileRate(0)
1172
1173
1174 blockFrequentShort(rate)
1175 blockInfrequentLong(rate)
1176
1177 var w bytes.Buffer
1178 Lookup("block").WriteTo(&w, 0)
1179 p, err := profile.Parse(&w)
1180 if err != nil {
1181 t.Fatalf("failed to parse profile: %v", err)
1182 }
1183 t.Logf("parsed proto: %s", p)
1184
1185 il := float64(-1)
1186 fs := float64(-1)
1187 for _, s := range p.Sample {
1188 for _, l := range s.Location {
1189 for _, line := range l.Line {
1190 if len(s.Value) < 2 {
1191 t.Fatal("block profile has less than 2 sample types")
1192 }
1193
1194 if line.Function.Name == "runtime/pprof.blockInfrequentLong" {
1195 il = float64(s.Value[1])
1196 } else if line.Function.Name == "runtime/pprof.blockFrequentShort" {
1197 fs = float64(s.Value[1])
1198 }
1199 }
1200 }
1201 }
1202 if il == -1 || fs == -1 {
1203 t.Fatal("block profile is missing expected functions")
1204 }
1205
1206
1207 const threshold = 0.2
1208 if bias := (il - fs) / il; math.Abs(bias) > threshold {
1209 t.Fatalf("bias: abs(%f) > %f", bias, threshold)
1210 } else {
1211 t.Logf("bias: abs(%f) < %f", bias, threshold)
1212 }
1213 }
1214
1215
1216
1217 func blockFrequentShort(rate int) {
1218 for i := 0; i < 100000; i++ {
1219 blockevent(int64(rate/10), 1)
1220 }
1221 }
1222
1223
1224
1225 func blockInfrequentLong(rate int) {
1226 for i := 0; i < 10000; i++ {
1227 blockevent(int64(rate), 1)
1228 }
1229 }
1230
1231
1232
1233
1234 func blockevent(cycles int64, skip int)
1235
1236 func TestMutexProfile(t *testing.T) {
1237
1238
1239 old := runtime.SetMutexProfileFraction(1)
1240 defer runtime.SetMutexProfileFraction(old)
1241 if old != 0 {
1242 t.Fatalf("need MutexProfileRate 0, got %d", old)
1243 }
1244
1245 const (
1246 N = 100
1247 D = 100 * time.Millisecond
1248 )
1249 start := time.Now()
1250 blockMutexN(t, N, D)
1251 blockMutexNTime := time.Since(start)
1252
1253 t.Run("debug=1", func(t *testing.T) {
1254 var w strings.Builder
1255 Lookup("mutex").WriteTo(&w, 1)
1256 prof := w.String()
1257 t.Logf("received profile: %v", prof)
1258
1259 if !strings.HasPrefix(prof, "--- mutex:\ncycles/second=") {
1260 t.Errorf("Bad profile header:\n%v", prof)
1261 }
1262 prof = strings.Trim(prof, "\n")
1263 lines := strings.Split(prof, "\n")
1264 if len(lines) < 6 {
1265 t.Fatalf("expected >=6 lines, got %d %q\n%s", len(lines), prof, prof)
1266 }
1267
1268 r2 := `^\d+ \d+ @(?: 0x[[:xdigit:]]+)+`
1269 if ok, err := regexp.MatchString(r2, lines[3]); err != nil || !ok {
1270 t.Errorf("%q didn't match %q", lines[3], r2)
1271 }
1272 r3 := "^#.*runtime/pprof.blockMutex.*$"
1273 if ok, err := regexp.MatchString(r3, lines[5]); err != nil || !ok {
1274 t.Errorf("%q didn't match %q", lines[5], r3)
1275 }
1276 t.Log(prof)
1277 })
1278 t.Run("proto", func(t *testing.T) {
1279
1280 var w bytes.Buffer
1281 Lookup("mutex").WriteTo(&w, 0)
1282 p, err := profile.Parse(&w)
1283 if err != nil {
1284 t.Fatalf("failed to parse profile: %v", err)
1285 }
1286 t.Logf("parsed proto: %s", p)
1287 if err := p.CheckValid(); err != nil {
1288 t.Fatalf("invalid profile: %v", err)
1289 }
1290
1291 stks := stacks(p)
1292 for _, want := range [][]string{
1293 {"sync.(*Mutex).Unlock", "runtime/pprof.blockMutexN.func1"},
1294 } {
1295 if !containsStack(stks, want) {
1296 t.Errorf("No matching stack entry for %+v", want)
1297 }
1298 }
1299
1300 i := 0
1301 for ; i < len(p.SampleType); i++ {
1302 if p.SampleType[i].Unit == "nanoseconds" {
1303 break
1304 }
1305 }
1306 if i >= len(p.SampleType) {
1307 t.Fatalf("profile did not contain nanoseconds sample")
1308 }
1309 total := int64(0)
1310 for _, s := range p.Sample {
1311 total += s.Value[i]
1312 }
1313
1314
1315
1316
1317
1318
1319
1320
1321 d := time.Duration(total)
1322 lo := time.Duration(N * D * 9 / 10)
1323 hi := time.Duration(N) * blockMutexNTime * 11 / 10
1324 if d < lo || d > hi {
1325 for _, s := range p.Sample {
1326 t.Logf("sample: %s", time.Duration(s.Value[i]))
1327 }
1328 t.Fatalf("profile samples total %v, want within range [%v, %v] (target: %v)", d, lo, hi, N*D)
1329 }
1330 })
1331 }
1332
1333 func TestMutexProfileRateAdjust(t *testing.T) {
1334 old := runtime.SetMutexProfileFraction(1)
1335 defer runtime.SetMutexProfileFraction(old)
1336 if old != 0 {
1337 t.Fatalf("need MutexProfileRate 0, got %d", old)
1338 }
1339
1340 readProfile := func() (contentions int64, delay int64) {
1341 var w bytes.Buffer
1342 Lookup("mutex").WriteTo(&w, 0)
1343 p, err := profile.Parse(&w)
1344 if err != nil {
1345 t.Fatalf("failed to parse profile: %v", err)
1346 }
1347 t.Logf("parsed proto: %s", p)
1348 if err := p.CheckValid(); err != nil {
1349 t.Fatalf("invalid profile: %v", err)
1350 }
1351
1352 for _, s := range p.Sample {
1353 var match, runtimeInternal bool
1354 for _, l := range s.Location {
1355 for _, line := range l.Line {
1356 if line.Function.Name == "runtime/pprof.blockMutex.func1" {
1357 match = true
1358 }
1359 if line.Function.Name == "runtime.unlock" {
1360 runtimeInternal = true
1361 }
1362 }
1363 }
1364 if match && !runtimeInternal {
1365 contentions += s.Value[0]
1366 delay += s.Value[1]
1367 }
1368 }
1369 return
1370 }
1371
1372 blockMutex(t)
1373 contentions, delay := readProfile()
1374 if contentions == 0 || delay == 0 {
1375 t.Fatal("did not see expected function in profile")
1376 }
1377 runtime.SetMutexProfileFraction(0)
1378 newContentions, newDelay := readProfile()
1379 if newContentions != contentions || newDelay != delay {
1380 t.Fatalf("sample value changed: got [%d, %d], want [%d, %d]", newContentions, newDelay, contentions, delay)
1381 }
1382 }
1383
1384 func func1(c chan int) { <-c }
1385 func func2(c chan int) { <-c }
1386 func func3(c chan int) { <-c }
1387 func func4(c chan int) { <-c }
1388
1389 func TestGoroutineCounts(t *testing.T) {
1390
1391
1392 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
1393
1394 c := make(chan int)
1395 for i := 0; i < 100; i++ {
1396 switch {
1397 case i%10 == 0:
1398 go func1(c)
1399 case i%2 == 0:
1400 go func2(c)
1401 default:
1402 go func3(c)
1403 }
1404
1405 for j := 0; j < 5; j++ {
1406 runtime.Gosched()
1407 }
1408 }
1409 ctx := context.Background()
1410
1411
1412
1413 Do(ctx, Labels("label", "value"), func(context.Context) {
1414 for i := 0; i < 89; i++ {
1415 switch {
1416 case i%10 == 0:
1417 go func1(c)
1418 case i%2 == 0:
1419 go func2(c)
1420 default:
1421 go func3(c)
1422 }
1423
1424 for j := 0; j < 5; j++ {
1425 runtime.Gosched()
1426 }
1427 }
1428 })
1429
1430 SetGoroutineLabels(WithLabels(context.Background(), Labels("self-label", "self-value")))
1431 defer SetGoroutineLabels(context.Background())
1432
1433 garbage := new(*int)
1434 fingReady := make(chan struct{})
1435 runtime.SetFinalizer(garbage, func(v **int) {
1436 Do(context.Background(), Labels("fing-label", "fing-value"), func(ctx context.Context) {
1437 close(fingReady)
1438 <-c
1439 })
1440 })
1441 garbage = nil
1442 for i := 0; i < 2; i++ {
1443 runtime.GC()
1444 }
1445 <-fingReady
1446
1447 var w bytes.Buffer
1448 goroutineProf := Lookup("goroutine")
1449
1450
1451 goroutineProf.WriteTo(&w, 1)
1452 prof := w.String()
1453
1454 labels := labelMap{"label": "value"}
1455 labelStr := "\n# labels: " + labels.String()
1456 selfLabel := labelMap{"self-label": "self-value"}
1457 selfLabelStr := "\n# labels: " + selfLabel.String()
1458 fingLabel := labelMap{"fing-label": "fing-value"}
1459 fingLabelStr := "\n# labels: " + fingLabel.String()
1460 orderedPrefix := []string{
1461 "\n50 @ ",
1462 "\n44 @", labelStr,
1463 "\n40 @",
1464 "\n36 @", labelStr,
1465 "\n10 @",
1466 "\n9 @", labelStr,
1467 "\n1 @"}
1468 if !containsInOrder(prof, append(orderedPrefix, selfLabelStr)...) {
1469 t.Errorf("expected sorted goroutine counts with Labels:\n%s", prof)
1470 }
1471 if !containsInOrder(prof, append(orderedPrefix, fingLabelStr)...) {
1472 t.Errorf("expected sorted goroutine counts with Labels:\n%s", prof)
1473 }
1474
1475
1476 w.Reset()
1477 goroutineProf.WriteTo(&w, 0)
1478 p, err := profile.Parse(&w)
1479 if err != nil {
1480 t.Errorf("error parsing protobuf profile: %v", err)
1481 }
1482 if err := p.CheckValid(); err != nil {
1483 t.Errorf("protobuf profile is invalid: %v", err)
1484 }
1485 expectedLabels := map[int64]map[string]string{
1486 50: {},
1487 44: {"label": "value"},
1488 40: {},
1489 36: {"label": "value"},
1490 10: {},
1491 9: {"label": "value"},
1492 1: {"self-label": "self-value", "fing-label": "fing-value"},
1493 }
1494 if !containsCountsLabels(p, expectedLabels) {
1495 t.Errorf("expected count profile to contain goroutines with counts and labels %v, got %v",
1496 expectedLabels, p)
1497 }
1498
1499 close(c)
1500
1501 time.Sleep(10 * time.Millisecond)
1502 }
1503
1504 func containsInOrder(s string, all ...string) bool {
1505 for _, t := range all {
1506 var ok bool
1507 if _, s, ok = strings.Cut(s, t); !ok {
1508 return false
1509 }
1510 }
1511 return true
1512 }
1513
1514 func containsCountsLabels(prof *profile.Profile, countLabels map[int64]map[string]string) bool {
1515 m := make(map[int64]int)
1516 type nkey struct {
1517 count int64
1518 key, val string
1519 }
1520 n := make(map[nkey]int)
1521 for c, kv := range countLabels {
1522 m[c]++
1523 for k, v := range kv {
1524 n[nkey{
1525 count: c,
1526 key: k,
1527 val: v,
1528 }]++
1529
1530 }
1531 }
1532 for _, s := range prof.Sample {
1533
1534 if len(s.Value) != 1 {
1535 return false
1536 }
1537 m[s.Value[0]]--
1538 for k, vs := range s.Label {
1539 for _, v := range vs {
1540 n[nkey{
1541 count: s.Value[0],
1542 key: k,
1543 val: v,
1544 }]--
1545 }
1546 }
1547 }
1548 for _, n := range m {
1549 if n > 0 {
1550 return false
1551 }
1552 }
1553 for _, ncnt := range n {
1554 if ncnt != 0 {
1555 return false
1556 }
1557 }
1558 return true
1559 }
1560
1561 func TestGoroutineProfileConcurrency(t *testing.T) {
1562 testenv.MustHaveParallelism(t)
1563
1564 goroutineProf := Lookup("goroutine")
1565
1566 profilerCalls := func(s string) int {
1567 return strings.Count(s, "\truntime/pprof.runtime_goroutineProfileWithLabels+")
1568 }
1569
1570 includesFinalizer := func(s string) bool {
1571 return strings.Contains(s, "runtime.runfinq")
1572 }
1573
1574
1575
1576 t.Run("overlapping profile requests", func(t *testing.T) {
1577 ctx := context.Background()
1578 ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
1579 defer cancel()
1580
1581 var wg sync.WaitGroup
1582 for i := 0; i < 2; i++ {
1583 wg.Add(1)
1584 Do(ctx, Labels("i", fmt.Sprint(i)), func(context.Context) {
1585 go func() {
1586 defer wg.Done()
1587 for ctx.Err() == nil {
1588 var w strings.Builder
1589 goroutineProf.WriteTo(&w, 1)
1590 prof := w.String()
1591 count := profilerCalls(prof)
1592 if count >= 2 {
1593 t.Logf("prof %d\n%s", count, prof)
1594 cancel()
1595 }
1596 }
1597 }()
1598 })
1599 }
1600 wg.Wait()
1601 })
1602
1603
1604
1605 t.Run("finalizer not present", func(t *testing.T) {
1606 var w strings.Builder
1607 goroutineProf.WriteTo(&w, 1)
1608 prof := w.String()
1609 if includesFinalizer(prof) {
1610 t.Errorf("profile includes finalizer (but finalizer should be marked as system):\n%s", prof)
1611 }
1612 })
1613
1614
1615 t.Run("finalizer present", func(t *testing.T) {
1616
1617
1618
1619 type T *byte
1620 obj := new(T)
1621 ch1, ch2 := make(chan int), make(chan int)
1622 defer close(ch2)
1623 runtime.SetFinalizer(obj, func(_ interface{}) {
1624 close(ch1)
1625 <-ch2
1626 })
1627 obj = nil
1628 for i := 10; i >= 0; i-- {
1629 select {
1630 case <-ch1:
1631 default:
1632 if i == 0 {
1633 t.Fatalf("finalizer did not run")
1634 }
1635 runtime.GC()
1636 }
1637 }
1638 var w strings.Builder
1639 goroutineProf.WriteTo(&w, 1)
1640 prof := w.String()
1641 if !includesFinalizer(prof) {
1642 t.Errorf("profile does not include finalizer (and it should be marked as user):\n%s", prof)
1643 }
1644 })
1645
1646
1647 testLaunches := func(t *testing.T) {
1648 var done sync.WaitGroup
1649 defer done.Wait()
1650
1651 ctx := context.Background()
1652 ctx, cancel := context.WithCancel(ctx)
1653 defer cancel()
1654
1655 ch := make(chan int)
1656 defer close(ch)
1657
1658 var ready sync.WaitGroup
1659
1660
1661
1662
1663 ready.Add(1)
1664 done.Add(1)
1665 go func() {
1666 defer done.Done()
1667 for i := 0; ctx.Err() == nil; i++ {
1668
1669
1670 SetGoroutineLabels(WithLabels(ctx, Labels(t.Name()+"-loop-i", fmt.Sprint(i))))
1671 done.Add(1)
1672 go func() {
1673 <-ch
1674 done.Done()
1675 }()
1676 for j := 0; j < i; j++ {
1677
1678
1679
1680
1681
1682
1683 runtime.Gosched()
1684 }
1685 if i == 0 {
1686 ready.Done()
1687 }
1688 }
1689 }()
1690
1691
1692
1693
1694 ready.Add(1)
1695 var churn func(i int)
1696 churn = func(i int) {
1697 SetGoroutineLabels(WithLabels(ctx, Labels(t.Name()+"-churn-i", fmt.Sprint(i))))
1698 if i == 0 {
1699 ready.Done()
1700 } else if i%16 == 0 {
1701
1702
1703 runtime.Gosched()
1704 }
1705 if ctx.Err() == nil {
1706 go churn(i + 1)
1707 }
1708 }
1709 go func() {
1710 churn(0)
1711 }()
1712
1713 ready.Wait()
1714
1715 var w [3]bytes.Buffer
1716 for i := range w {
1717 goroutineProf.WriteTo(&w[i], 0)
1718 }
1719 for i := range w {
1720 p, err := profile.Parse(bytes.NewReader(w[i].Bytes()))
1721 if err != nil {
1722 t.Errorf("error parsing protobuf profile: %v", err)
1723 }
1724
1725
1726
1727 counts := make(map[string]int)
1728 for _, s := range p.Sample {
1729 label := s.Label[t.Name()+"-loop-i"]
1730 if len(label) > 0 {
1731 counts[label[0]]++
1732 }
1733 }
1734 for j, max := 0, len(counts)-1; j <= max; j++ {
1735 n := counts[fmt.Sprint(j)]
1736 if n == 1 || (n == 2 && j == max) {
1737 continue
1738 }
1739 t.Errorf("profile #%d's goroutines with label loop-i:%d; %d != 1 (or 2 for the last entry, %d)",
1740 i+1, j, n, max)
1741 t.Logf("counts %v", counts)
1742 break
1743 }
1744 }
1745 }
1746
1747 runs := 100
1748 if testing.Short() {
1749 runs = 5
1750 }
1751 for i := 0; i < runs; i++ {
1752
1753 t.Run("goroutine launches", testLaunches)
1754 }
1755 }
1756
1757 func BenchmarkGoroutine(b *testing.B) {
1758 withIdle := func(n int, fn func(b *testing.B)) func(b *testing.B) {
1759 return func(b *testing.B) {
1760 c := make(chan int)
1761 var ready, done sync.WaitGroup
1762 defer func() {
1763 close(c)
1764 done.Wait()
1765 }()
1766
1767 for i := 0; i < n; i++ {
1768 ready.Add(1)
1769 done.Add(1)
1770 go func() {
1771 ready.Done()
1772 <-c
1773 done.Done()
1774 }()
1775 }
1776
1777 ready.Wait()
1778 for i := 0; i < 5; i++ {
1779 runtime.Gosched()
1780 }
1781
1782 fn(b)
1783 }
1784 }
1785
1786 withChurn := func(fn func(b *testing.B)) func(b *testing.B) {
1787 return func(b *testing.B) {
1788 ctx := context.Background()
1789 ctx, cancel := context.WithCancel(ctx)
1790 defer cancel()
1791
1792 var ready sync.WaitGroup
1793 ready.Add(1)
1794 var count int64
1795 var churn func(i int)
1796 churn = func(i int) {
1797 SetGoroutineLabels(WithLabels(ctx, Labels("churn-i", fmt.Sprint(i))))
1798 atomic.AddInt64(&count, 1)
1799 if i == 0 {
1800 ready.Done()
1801 }
1802 if ctx.Err() == nil {
1803 go churn(i + 1)
1804 }
1805 }
1806 go func() {
1807 churn(0)
1808 }()
1809 ready.Wait()
1810
1811 fn(b)
1812 b.ReportMetric(float64(atomic.LoadInt64(&count))/float64(b.N), "concurrent_launches/op")
1813 }
1814 }
1815
1816 benchWriteTo := func(b *testing.B) {
1817 goroutineProf := Lookup("goroutine")
1818 b.ResetTimer()
1819 for i := 0; i < b.N; i++ {
1820 goroutineProf.WriteTo(io.Discard, 0)
1821 }
1822 b.StopTimer()
1823 }
1824
1825 benchGoroutineProfile := func(b *testing.B) {
1826 p := make([]runtime.StackRecord, 10000)
1827 b.ResetTimer()
1828 for i := 0; i < b.N; i++ {
1829 runtime.GoroutineProfile(p)
1830 }
1831 b.StopTimer()
1832 }
1833
1834
1835
1836
1837 for _, n := range []int{50, 500, 5000} {
1838 b.Run(fmt.Sprintf("Profile.WriteTo idle %d", n), withIdle(n, benchWriteTo))
1839 b.Run(fmt.Sprintf("Profile.WriteTo churn %d", n), withIdle(n, withChurn(benchWriteTo)))
1840 b.Run(fmt.Sprintf("runtime.GoroutineProfile churn %d", n), withIdle(n, withChurn(benchGoroutineProfile)))
1841 }
1842 }
1843
1844 var emptyCallStackTestRun int64
1845
1846
1847 func TestEmptyCallStack(t *testing.T) {
1848 name := fmt.Sprintf("test18836_%d", emptyCallStackTestRun)
1849 emptyCallStackTestRun++
1850
1851 t.Parallel()
1852 var buf strings.Builder
1853 p := NewProfile(name)
1854
1855 p.Add("foo", 47674)
1856 p.WriteTo(&buf, 1)
1857 p.Remove("foo")
1858 got := buf.String()
1859 prefix := name + " profile: total 1\n"
1860 if !strings.HasPrefix(got, prefix) {
1861 t.Fatalf("got:\n\t%q\nwant prefix:\n\t%q\n", got, prefix)
1862 }
1863 lostevent := "lostProfileEvent"
1864 if !strings.Contains(got, lostevent) {
1865 t.Fatalf("got:\n\t%q\ndoes not contain:\n\t%q\n", got, lostevent)
1866 }
1867 }
1868
1869
1870
1871 func stackContainsLabeled(spec string, count uintptr, stk []*profile.Location, labels map[string][]string) bool {
1872 base, kv, ok := strings.Cut(spec, ";")
1873 if !ok {
1874 panic("no semicolon in key/value spec")
1875 }
1876 k, v, ok := strings.Cut(kv, "=")
1877 if !ok {
1878 panic("missing = in key/value spec")
1879 }
1880 if !contains(labels[k], v) {
1881 return false
1882 }
1883 return stackContains(base, count, stk, labels)
1884 }
1885
1886 func TestCPUProfileLabel(t *testing.T) {
1887 matches := matchAndAvoidStacks(stackContainsLabeled, []string{"runtime/pprof.cpuHogger;key=value"}, avoidFunctions())
1888 testCPUProfile(t, matches, func(dur time.Duration) {
1889 Do(context.Background(), Labels("key", "value"), func(context.Context) {
1890 cpuHogger(cpuHog1, &salt1, dur)
1891 })
1892 })
1893 }
1894
1895 func TestLabelRace(t *testing.T) {
1896 testenv.MustHaveParallelism(t)
1897
1898
1899
1900 matches := matchAndAvoidStacks(stackContainsLabeled, []string{"runtime/pprof.cpuHogger;key=value"}, nil)
1901 testCPUProfile(t, matches, func(dur time.Duration) {
1902 start := time.Now()
1903 var wg sync.WaitGroup
1904 for time.Since(start) < dur {
1905 var salts [10]int
1906 for i := 0; i < 10; i++ {
1907 wg.Add(1)
1908 go func(j int) {
1909 Do(context.Background(), Labels("key", "value"), func(context.Context) {
1910 cpuHogger(cpuHog1, &salts[j], time.Millisecond)
1911 })
1912 wg.Done()
1913 }(i)
1914 }
1915 wg.Wait()
1916 }
1917 })
1918 }
1919
1920 func TestGoroutineProfileLabelRace(t *testing.T) {
1921 testenv.MustHaveParallelism(t)
1922
1923
1924
1925
1926 t.Run("reset", func(t *testing.T) {
1927 ctx := context.Background()
1928 ctx, cancel := context.WithCancel(ctx)
1929 defer cancel()
1930
1931 go func() {
1932 goroutineProf := Lookup("goroutine")
1933 for ctx.Err() == nil {
1934 var w strings.Builder
1935 goroutineProf.WriteTo(&w, 1)
1936 prof := w.String()
1937 if strings.Contains(prof, "loop-i") {
1938 cancel()
1939 }
1940 }
1941 }()
1942
1943 for i := 0; ctx.Err() == nil; i++ {
1944 Do(ctx, Labels("loop-i", fmt.Sprint(i)), func(ctx context.Context) {
1945 })
1946 }
1947 })
1948
1949 t.Run("churn", func(t *testing.T) {
1950 ctx := context.Background()
1951 ctx, cancel := context.WithCancel(ctx)
1952 defer cancel()
1953
1954 var ready sync.WaitGroup
1955 ready.Add(1)
1956 var churn func(i int)
1957 churn = func(i int) {
1958 SetGoroutineLabels(WithLabels(ctx, Labels("churn-i", fmt.Sprint(i))))
1959 if i == 0 {
1960 ready.Done()
1961 }
1962 if ctx.Err() == nil {
1963 go churn(i + 1)
1964 }
1965 }
1966 go func() {
1967 churn(0)
1968 }()
1969 ready.Wait()
1970
1971 goroutineProf := Lookup("goroutine")
1972 for i := 0; i < 10; i++ {
1973 goroutineProf.WriteTo(io.Discard, 1)
1974 }
1975 })
1976 }
1977
1978
1979
1980 func TestLabelSystemstack(t *testing.T) {
1981
1982
1983 gogc := debug.SetGCPercent(100)
1984 debug.SetGCPercent(gogc)
1985
1986 matches := matchAndAvoidStacks(stackContainsLabeled, []string{"runtime.systemstack;key=value"}, avoidFunctions())
1987 p := testCPUProfile(t, matches, func(dur time.Duration) {
1988 Do(context.Background(), Labels("key", "value"), func(ctx context.Context) {
1989 parallelLabelHog(ctx, dur, gogc)
1990 })
1991 })
1992
1993
1994
1995
1996 for _, s := range p.Sample {
1997 isLabeled := s.Label != nil && contains(s.Label["key"], "value")
1998 var (
1999 mayBeLabeled bool
2000 mustBeLabeled string
2001 mustNotBeLabeled string
2002 )
2003 for _, loc := range s.Location {
2004 for _, l := range loc.Line {
2005 switch l.Function.Name {
2006 case "runtime/pprof.labelHog", "runtime/pprof.parallelLabelHog", "runtime/pprof.parallelLabelHog.func1":
2007 mustBeLabeled = l.Function.Name
2008 case "runtime/pprof.Do":
2009
2010
2011
2012
2013 mayBeLabeled = true
2014 case "runtime.bgsweep", "runtime.bgscavenge", "runtime.forcegchelper", "runtime.gcBgMarkWorker", "runtime.runfinq", "runtime.sysmon":
2015
2016
2017
2018
2019 mustNotBeLabeled = l.Function.Name
2020 case "gogo", "gosave_systemstack_switch", "racecall":
2021
2022
2023
2024
2025
2026
2027 mayBeLabeled = true
2028 }
2029
2030 if strings.HasPrefix(l.Function.Name, "runtime.") {
2031
2032
2033
2034
2035
2036
2037 mayBeLabeled = true
2038 }
2039 }
2040 }
2041 errorStack := func(f string, args ...any) {
2042 var buf strings.Builder
2043 fprintStack(&buf, s.Location)
2044 t.Errorf("%s: %s", fmt.Sprintf(f, args...), buf.String())
2045 }
2046 if mustBeLabeled != "" && mustNotBeLabeled != "" {
2047 errorStack("sample contains both %s, which must be labeled, and %s, which must not be labeled", mustBeLabeled, mustNotBeLabeled)
2048 continue
2049 }
2050 if mustBeLabeled != "" || mustNotBeLabeled != "" {
2051
2052 mayBeLabeled = false
2053 }
2054 if mayBeLabeled {
2055
2056 continue
2057 }
2058 if mustBeLabeled != "" && !isLabeled {
2059 errorStack("sample must be labeled because of %s, but is not", mustBeLabeled)
2060 }
2061 if mustNotBeLabeled != "" && isLabeled {
2062 errorStack("sample must not be labeled because of %s, but is", mustNotBeLabeled)
2063 }
2064 }
2065 }
2066
2067
2068
2069 func labelHog(stop chan struct{}, gogc int) {
2070
2071
2072 runtime.GC()
2073
2074 for i := 0; ; i++ {
2075 select {
2076 case <-stop:
2077 return
2078 default:
2079 debug.SetGCPercent(gogc)
2080 }
2081 }
2082 }
2083
2084
2085 func parallelLabelHog(ctx context.Context, dur time.Duration, gogc int) {
2086 var wg sync.WaitGroup
2087 stop := make(chan struct{})
2088 for i := 0; i < runtime.GOMAXPROCS(0); i++ {
2089 wg.Add(1)
2090 go func() {
2091 defer wg.Done()
2092 labelHog(stop, gogc)
2093 }()
2094 }
2095
2096 time.Sleep(dur)
2097 close(stop)
2098 wg.Wait()
2099 }
2100
2101
2102
2103 func TestAtomicLoadStore64(t *testing.T) {
2104 f, err := os.CreateTemp("", "profatomic")
2105 if err != nil {
2106 t.Fatalf("TempFile: %v", err)
2107 }
2108 defer os.Remove(f.Name())
2109 defer f.Close()
2110
2111 if err := StartCPUProfile(f); err != nil {
2112 t.Fatal(err)
2113 }
2114 defer StopCPUProfile()
2115
2116 var flag uint64
2117 done := make(chan bool, 1)
2118
2119 go func() {
2120 for atomic.LoadUint64(&flag) == 0 {
2121 runtime.Gosched()
2122 }
2123 done <- true
2124 }()
2125 time.Sleep(50 * time.Millisecond)
2126 atomic.StoreUint64(&flag, 1)
2127 <-done
2128 }
2129
2130 func TestTracebackAll(t *testing.T) {
2131
2132
2133 f, err := os.CreateTemp("", "proftraceback")
2134 if err != nil {
2135 t.Fatalf("TempFile: %v", err)
2136 }
2137 defer os.Remove(f.Name())
2138 defer f.Close()
2139
2140 if err := StartCPUProfile(f); err != nil {
2141 t.Fatal(err)
2142 }
2143 defer StopCPUProfile()
2144
2145 ch := make(chan int)
2146 defer close(ch)
2147
2148 count := 10
2149 for i := 0; i < count; i++ {
2150 go func() {
2151 <-ch
2152 }()
2153 }
2154
2155 N := 10000
2156 if testing.Short() {
2157 N = 500
2158 }
2159 buf := make([]byte, 10*1024)
2160 for i := 0; i < N; i++ {
2161 runtime.Stack(buf, true)
2162 }
2163 }
2164
2165
2166
2167
2168
2169
2170
2171 func TestTryAdd(t *testing.T) {
2172 if _, found := findInlinedCall(inlinedCallerDump, 4<<10); !found {
2173 t.Skip("Can't determine whether anything was inlined into inlinedCallerDump.")
2174 }
2175
2176
2177
2178 pcs := make([]uintptr, 2)
2179 inlinedCallerDump(pcs)
2180 inlinedCallerStack := make([]uint64, 2)
2181 for i := range pcs {
2182 inlinedCallerStack[i] = uint64(pcs[i])
2183 }
2184 wrapperPCs := make([]uintptr, 1)
2185 inlinedWrapperCallerDump(wrapperPCs)
2186
2187 if _, found := findInlinedCall(recursionChainBottom, 4<<10); !found {
2188 t.Skip("Can't determine whether anything was inlined into recursionChainBottom.")
2189 }
2190
2191
2192
2193
2194
2195
2196
2197 pcs = make([]uintptr, 6)
2198 recursionChainTop(1, pcs)
2199 recursionStack := make([]uint64, len(pcs))
2200 for i := range pcs {
2201 recursionStack[i] = uint64(pcs[i])
2202 }
2203
2204 period := int64(2000 * 1000)
2205
2206 testCases := []struct {
2207 name string
2208 input []uint64
2209 count int
2210 wantLocs [][]string
2211 wantSamples []*profile.Sample
2212 }{{
2213
2214 name: "full_stack_trace",
2215 input: []uint64{
2216 3, 0, 500,
2217 5, 0, 50, inlinedCallerStack[0], inlinedCallerStack[1],
2218 },
2219 count: 2,
2220 wantLocs: [][]string{
2221 {"runtime/pprof.inlinedCalleeDump", "runtime/pprof.inlinedCallerDump"},
2222 },
2223 wantSamples: []*profile.Sample{
2224 {Value: []int64{50, 50 * period}, Location: []*profile.Location{{ID: 1}}},
2225 },
2226 }, {
2227 name: "bug35538",
2228 input: []uint64{
2229 3, 0, 500,
2230
2231
2232
2233 7, 0, 10, inlinedCallerStack[0], inlinedCallerStack[1], inlinedCallerStack[0], inlinedCallerStack[1],
2234 5, 0, 20, inlinedCallerStack[0], inlinedCallerStack[1],
2235 },
2236 count: 3,
2237 wantLocs: [][]string{{"runtime/pprof.inlinedCalleeDump", "runtime/pprof.inlinedCallerDump"}},
2238 wantSamples: []*profile.Sample{
2239 {Value: []int64{10, 10 * period}, Location: []*profile.Location{{ID: 1}, {ID: 1}}},
2240 {Value: []int64{20, 20 * period}, Location: []*profile.Location{{ID: 1}}},
2241 },
2242 }, {
2243 name: "bug38096",
2244 input: []uint64{
2245 3, 0, 500,
2246
2247
2248 4, 0, 0, 4242,
2249 },
2250 count: 2,
2251 wantLocs: [][]string{{"runtime/pprof.lostProfileEvent"}},
2252 wantSamples: []*profile.Sample{
2253 {Value: []int64{4242, 4242 * period}, Location: []*profile.Location{{ID: 1}}},
2254 },
2255 }, {
2256
2257
2258
2259
2260
2261
2262
2263 name: "directly_recursive_func_is_not_inlined",
2264 input: []uint64{
2265 3, 0, 500,
2266 5, 0, 30, inlinedCallerStack[0], inlinedCallerStack[0],
2267 4, 0, 40, inlinedCallerStack[0],
2268 },
2269 count: 3,
2270
2271
2272 wantLocs: [][]string{{"runtime/pprof.inlinedCalleeDump"}, {"runtime/pprof.inlinedCallerDump"}},
2273 wantSamples: []*profile.Sample{
2274 {Value: []int64{30, 30 * period}, Location: []*profile.Location{{ID: 1}, {ID: 1}, {ID: 2}}},
2275 {Value: []int64{40, 40 * period}, Location: []*profile.Location{{ID: 1}, {ID: 2}}},
2276 },
2277 }, {
2278 name: "recursion_chain_inline",
2279 input: []uint64{
2280 3, 0, 500,
2281 9, 0, 10, recursionStack[0], recursionStack[1], recursionStack[2], recursionStack[3], recursionStack[4], recursionStack[5],
2282 },
2283 count: 2,
2284 wantLocs: [][]string{
2285 {"runtime/pprof.recursionChainBottom"},
2286 {
2287 "runtime/pprof.recursionChainMiddle",
2288 "runtime/pprof.recursionChainTop",
2289 "runtime/pprof.recursionChainBottom",
2290 },
2291 {
2292 "runtime/pprof.recursionChainMiddle",
2293 "runtime/pprof.recursionChainTop",
2294 "runtime/pprof.TestTryAdd",
2295 },
2296 },
2297 wantSamples: []*profile.Sample{
2298 {Value: []int64{10, 10 * period}, Location: []*profile.Location{{ID: 1}, {ID: 2}, {ID: 3}}},
2299 },
2300 }, {
2301 name: "truncated_stack_trace_later",
2302 input: []uint64{
2303 3, 0, 500,
2304 5, 0, 50, inlinedCallerStack[0], inlinedCallerStack[1],
2305 4, 0, 60, inlinedCallerStack[0],
2306 },
2307 count: 3,
2308 wantLocs: [][]string{{"runtime/pprof.inlinedCalleeDump", "runtime/pprof.inlinedCallerDump"}},
2309 wantSamples: []*profile.Sample{
2310 {Value: []int64{50, 50 * period}, Location: []*profile.Location{{ID: 1}}},
2311 {Value: []int64{60, 60 * period}, Location: []*profile.Location{{ID: 1}}},
2312 },
2313 }, {
2314 name: "truncated_stack_trace_first",
2315 input: []uint64{
2316 3, 0, 500,
2317 4, 0, 70, inlinedCallerStack[0],
2318 5, 0, 80, inlinedCallerStack[0], inlinedCallerStack[1],
2319 },
2320 count: 3,
2321 wantLocs: [][]string{{"runtime/pprof.inlinedCalleeDump", "runtime/pprof.inlinedCallerDump"}},
2322 wantSamples: []*profile.Sample{
2323 {Value: []int64{70, 70 * period}, Location: []*profile.Location{{ID: 1}}},
2324 {Value: []int64{80, 80 * period}, Location: []*profile.Location{{ID: 1}}},
2325 },
2326 }, {
2327
2328 name: "truncated_stack_trace_only",
2329 input: []uint64{
2330 3, 0, 500,
2331 4, 0, 70, inlinedCallerStack[0],
2332 },
2333 count: 2,
2334 wantLocs: [][]string{{"runtime/pprof.inlinedCalleeDump", "runtime/pprof.inlinedCallerDump"}},
2335 wantSamples: []*profile.Sample{
2336 {Value: []int64{70, 70 * period}, Location: []*profile.Location{{ID: 1}}},
2337 },
2338 }, {
2339
2340 name: "truncated_stack_trace_twice",
2341 input: []uint64{
2342 3, 0, 500,
2343 4, 0, 70, inlinedCallerStack[0],
2344
2345
2346
2347 5, 0, 80, inlinedCallerStack[1], inlinedCallerStack[0],
2348 },
2349 count: 3,
2350 wantLocs: [][]string{
2351 {"runtime/pprof.inlinedCalleeDump", "runtime/pprof.inlinedCallerDump"},
2352 {"runtime/pprof.inlinedCallerDump"},
2353 },
2354 wantSamples: []*profile.Sample{
2355 {Value: []int64{70, 70 * period}, Location: []*profile.Location{{ID: 1}}},
2356 {Value: []int64{80, 80 * period}, Location: []*profile.Location{{ID: 2}, {ID: 1}}},
2357 },
2358 }, {
2359 name: "expand_wrapper_function",
2360 input: []uint64{
2361 3, 0, 500,
2362 4, 0, 50, uint64(wrapperPCs[0]),
2363 },
2364 count: 2,
2365 wantLocs: [][]string{{"runtime/pprof.inlineWrapper.dump"}},
2366 wantSamples: []*profile.Sample{
2367 {Value: []int64{50, 50 * period}, Location: []*profile.Location{{ID: 1}}},
2368 },
2369 }}
2370
2371 for _, tc := range testCases {
2372 t.Run(tc.name, func(t *testing.T) {
2373 p, err := translateCPUProfile(tc.input, tc.count)
2374 if err != nil {
2375 t.Fatalf("translating profile: %v", err)
2376 }
2377 t.Logf("Profile: %v\n", p)
2378
2379
2380 var gotLoc [][]string
2381 for _, loc := range p.Location {
2382 var names []string
2383 for _, line := range loc.Line {
2384 names = append(names, line.Function.Name)
2385 }
2386 gotLoc = append(gotLoc, names)
2387 }
2388 if got, want := fmtJSON(gotLoc), fmtJSON(tc.wantLocs); got != want {
2389 t.Errorf("Got Location = %+v\n\twant %+v", got, want)
2390 }
2391
2392 var gotSamples []*profile.Sample
2393 for _, sample := range p.Sample {
2394 var locs []*profile.Location
2395 for _, loc := range sample.Location {
2396 locs = append(locs, &profile.Location{ID: loc.ID})
2397 }
2398 gotSamples = append(gotSamples, &profile.Sample{Value: sample.Value, Location: locs})
2399 }
2400 if got, want := fmtJSON(gotSamples), fmtJSON(tc.wantSamples); got != want {
2401 t.Errorf("Got Samples = %+v\n\twant %+v", got, want)
2402 }
2403 })
2404 }
2405 }
2406
2407 func TestTimeVDSO(t *testing.T) {
2408
2409
2410
2411 if runtime.GOOS == "android" {
2412
2413 testenv.SkipFlaky(t, 48655)
2414 }
2415
2416 matches := matchAndAvoidStacks(stackContains, []string{"time.now"}, avoidFunctions())
2417 p := testCPUProfile(t, matches, func(dur time.Duration) {
2418 t0 := time.Now()
2419 for {
2420 t := time.Now()
2421 if t.Sub(t0) >= dur {
2422 return
2423 }
2424 }
2425 })
2426
2427
2428 for _, sample := range p.Sample {
2429 var seenNow bool
2430 for _, loc := range sample.Location {
2431 for _, line := range loc.Line {
2432 if line.Function.Name == "time.now" {
2433 if seenNow {
2434 t.Fatalf("unexpected recursive time.now")
2435 }
2436 seenNow = true
2437 }
2438 }
2439 }
2440 }
2441 }
2442
2443 func TestProfilerStackDepth(t *testing.T) {
2444
2445 oldMemRate := runtime.MemProfileRate
2446 runtime.MemProfileRate = 1
2447 runtime.SetBlockProfileRate(1)
2448 oldMutexRate := runtime.SetMutexProfileFraction(1)
2449 t.Cleanup(func() {
2450 runtime.MemProfileRate = oldMemRate
2451 runtime.SetBlockProfileRate(0)
2452 runtime.SetMutexProfileFraction(oldMutexRate)
2453 })
2454
2455 const depth = 128
2456 go produceProfileEvents(t, depth)
2457 awaitBlockedGoroutine(t, "chan receive", "goroutineDeep", 1)
2458
2459 tests := []struct {
2460 profiler string
2461 prefix []string
2462 }{
2463 {"heap", []string{"runtime/pprof.allocDeep"}},
2464 {"block", []string{"runtime.chanrecv1", "runtime/pprof.blockChanDeep"}},
2465 {"mutex", []string{"sync.(*Mutex).Unlock", "runtime/pprof.blockMutexDeep"}},
2466 {"goroutine", []string{"runtime.gopark", "runtime.chanrecv", "runtime.chanrecv1", "runtime/pprof.goroutineDeep"}},
2467 }
2468
2469 for _, test := range tests {
2470 t.Run(test.profiler, func(t *testing.T) {
2471 var buf bytes.Buffer
2472 if err := Lookup(test.profiler).WriteTo(&buf, 0); err != nil {
2473 t.Fatalf("failed to write heap profile: %v", err)
2474 }
2475 p, err := profile.Parse(&buf)
2476 if err != nil {
2477 t.Fatalf("failed to parse heap profile: %v", err)
2478 }
2479 t.Logf("Profile = %v", p)
2480
2481 stks := stacks(p)
2482 var stk []string
2483 for _, s := range stks {
2484 if hasPrefix(s, test.prefix) {
2485 stk = s
2486 break
2487 }
2488 }
2489 if len(stk) != depth {
2490 t.Fatalf("want stack depth = %d, got %d", depth, len(stk))
2491 }
2492
2493 if rootFn, wantFn := stk[depth-1], "runtime/pprof.produceProfileEvents"; rootFn != wantFn {
2494 t.Fatalf("want stack stack root %s, got %v", wantFn, rootFn)
2495 }
2496 })
2497 }
2498 }
2499
2500 func hasPrefix(stk []string, prefix []string) bool {
2501 if len(prefix) > len(stk) {
2502 return false
2503 }
2504 for i := range prefix {
2505 if stk[i] != prefix[i] {
2506 return false
2507 }
2508 }
2509 return true
2510 }
2511
2512
2513 var _ = map[runtime.MemProfileRecord]struct{}{}
2514 var _ = map[runtime.StackRecord]struct{}{}
2515
2516
2517 func allocDeep(n int) {
2518 if n > 1 {
2519 allocDeep(n - 1)
2520 return
2521 }
2522 memSink = make([]byte, 1<<20)
2523 }
2524
2525
2526
2527 func blockChanDeep(t *testing.T, n int) {
2528 if n > 1 {
2529 blockChanDeep(t, n-1)
2530 return
2531 }
2532 ch := make(chan struct{})
2533 go func() {
2534 awaitBlockedGoroutine(t, "chan receive", "blockChanDeep", 1)
2535 ch <- struct{}{}
2536 }()
2537 <-ch
2538 }
2539
2540
2541
2542 func blockMutexDeep(t *testing.T, n int) {
2543 if n > 1 {
2544 blockMutexDeep(t, n-1)
2545 return
2546 }
2547 var mu sync.Mutex
2548 go func() {
2549 mu.Lock()
2550 mu.Lock()
2551 }()
2552 awaitBlockedGoroutine(t, "sync.Mutex.Lock", "blockMutexDeep", 1)
2553 mu.Unlock()
2554 }
2555
2556
2557
2558 func goroutineDeep(t *testing.T, n int) {
2559 if n > 1 {
2560 goroutineDeep(t, n-1)
2561 return
2562 }
2563 wait := make(chan struct{}, 1)
2564 t.Cleanup(func() {
2565 wait <- struct{}{}
2566 })
2567 <-wait
2568 }
2569
2570
2571
2572
2573
2574 func produceProfileEvents(t *testing.T, depth int) {
2575 allocDeep(depth - 1)
2576 blockChanDeep(t, depth-2)
2577 blockMutexDeep(t, depth-2)
2578 memSink = nil
2579 runtime.GC()
2580 goroutineDeep(t, depth-4)
2581 }
2582
2583 func getProfileStacks(collect func([]runtime.BlockProfileRecord) (int, bool), fileLine bool) []string {
2584 var n int
2585 var ok bool
2586 var p []runtime.BlockProfileRecord
2587 for {
2588 p = make([]runtime.BlockProfileRecord, n)
2589 n, ok = collect(p)
2590 if ok {
2591 p = p[:n]
2592 break
2593 }
2594 }
2595 var stacks []string
2596 for _, r := range p {
2597 var stack strings.Builder
2598 for i, pc := range r.Stack() {
2599 if i > 0 {
2600 stack.WriteByte('\n')
2601 }
2602
2603
2604
2605
2606
2607 f := runtime.FuncForPC(pc - 1)
2608 stack.WriteString(f.Name())
2609 if fileLine {
2610 stack.WriteByte(' ')
2611 file, line := f.FileLine(pc - 1)
2612 stack.WriteString(file)
2613 stack.WriteByte(':')
2614 stack.WriteString(strconv.Itoa(line))
2615 }
2616 }
2617 stacks = append(stacks, stack.String())
2618 }
2619 return stacks
2620 }
2621
2622 func TestMutexBlockFullAggregation(t *testing.T) {
2623
2624
2625
2626
2627 var m sync.Mutex
2628
2629 prev := runtime.SetMutexProfileFraction(-1)
2630 defer runtime.SetMutexProfileFraction(prev)
2631
2632 const fraction = 1
2633 const iters = 100
2634 const workers = 2
2635
2636 runtime.SetMutexProfileFraction(fraction)
2637 runtime.SetBlockProfileRate(1)
2638 defer runtime.SetBlockProfileRate(0)
2639
2640 wg := sync.WaitGroup{}
2641 wg.Add(workers)
2642 for j := 0; j < workers; j++ {
2643 go func() {
2644 for i := 0; i < iters; i++ {
2645 m.Lock()
2646
2647
2648 time.Sleep(time.Millisecond)
2649 m.Unlock()
2650 }
2651 wg.Done()
2652 }()
2653 }
2654 wg.Wait()
2655
2656 assertNoDuplicates := func(name string, collect func([]runtime.BlockProfileRecord) (int, bool)) {
2657 stacks := getProfileStacks(collect, true)
2658 seen := make(map[string]struct{})
2659 for _, s := range stacks {
2660 if _, ok := seen[s]; ok {
2661 t.Errorf("saw duplicate entry in %s profile with stack:\n%s", name, s)
2662 }
2663 seen[s] = struct{}{}
2664 }
2665 if len(seen) == 0 {
2666 t.Errorf("did not see any samples in %s profile for this test", name)
2667 }
2668 }
2669 t.Run("mutex", func(t *testing.T) {
2670 assertNoDuplicates("mutex", runtime.MutexProfile)
2671 })
2672 t.Run("block", func(t *testing.T) {
2673 assertNoDuplicates("block", runtime.BlockProfile)
2674 })
2675 }
2676
2677 func inlineA(mu *sync.Mutex, wg *sync.WaitGroup) { inlineB(mu, wg) }
2678 func inlineB(mu *sync.Mutex, wg *sync.WaitGroup) { inlineC(mu, wg) }
2679 func inlineC(mu *sync.Mutex, wg *sync.WaitGroup) {
2680 defer wg.Done()
2681 mu.Lock()
2682 mu.Unlock()
2683 }
2684
2685 func inlineD(mu *sync.Mutex, wg *sync.WaitGroup) { inlineE(mu, wg) }
2686 func inlineE(mu *sync.Mutex, wg *sync.WaitGroup) { inlineF(mu, wg) }
2687 func inlineF(mu *sync.Mutex, wg *sync.WaitGroup) {
2688 defer wg.Done()
2689 mu.Unlock()
2690 }
2691
2692 func TestBlockMutexProfileInlineExpansion(t *testing.T) {
2693 runtime.SetBlockProfileRate(1)
2694 defer runtime.SetBlockProfileRate(0)
2695 prev := runtime.SetMutexProfileFraction(1)
2696 defer runtime.SetMutexProfileFraction(prev)
2697
2698 var mu sync.Mutex
2699 var wg sync.WaitGroup
2700 wg.Add(2)
2701 mu.Lock()
2702 go inlineA(&mu, &wg)
2703 awaitBlockedGoroutine(t, "sync.Mutex.Lock", "inlineC", 1)
2704
2705 go inlineD(&mu, &wg)
2706 wg.Wait()
2707
2708 tcs := []struct {
2709 Name string
2710 Collect func([]runtime.BlockProfileRecord) (int, bool)
2711 SubStack string
2712 }{
2713 {
2714 Name: "mutex",
2715 Collect: runtime.MutexProfile,
2716 SubStack: `sync.(*Mutex).Unlock
2717 runtime/pprof.inlineF
2718 runtime/pprof.inlineE
2719 runtime/pprof.inlineD`,
2720 },
2721 {
2722 Name: "block",
2723 Collect: runtime.BlockProfile,
2724 SubStack: `sync.(*Mutex).Lock
2725 runtime/pprof.inlineC
2726 runtime/pprof.inlineB
2727 runtime/pprof.inlineA`,
2728 },
2729 }
2730
2731 for _, tc := range tcs {
2732 t.Run(tc.Name, func(t *testing.T) {
2733 stacks := getProfileStacks(tc.Collect, false)
2734 for _, s := range stacks {
2735 if strings.Contains(s, tc.SubStack) {
2736 return
2737 }
2738 }
2739 t.Error("did not see expected stack")
2740 t.Logf("wanted:\n%s", tc.SubStack)
2741 t.Logf("got: %s", stacks)
2742 })
2743 }
2744 }
2745
View as plain text