1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73 package pprof
74
75 import (
76 "bufio"
77 "cmp"
78 "fmt"
79 "internal/abi"
80 "internal/profilerecord"
81 "io"
82 "runtime"
83 "slices"
84 "sort"
85 "strings"
86 "sync"
87 "text/tabwriter"
88 "time"
89 "unsafe"
90 )
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175 type Profile struct {
176 name string
177 mu sync.Mutex
178 m map[any][]uintptr
179 count func() int
180 write func(io.Writer, int) error
181 }
182
183
184 var profiles struct {
185 mu sync.Mutex
186 m map[string]*Profile
187 }
188
189 var goroutineProfile = &Profile{
190 name: "goroutine",
191 count: countGoroutine,
192 write: writeGoroutine,
193 }
194
195 var threadcreateProfile = &Profile{
196 name: "threadcreate",
197 count: countThreadCreate,
198 write: writeThreadCreate,
199 }
200
201 var heapProfile = &Profile{
202 name: "heap",
203 count: countHeap,
204 write: writeHeap,
205 }
206
207 var allocsProfile = &Profile{
208 name: "allocs",
209 count: countHeap,
210 write: writeAlloc,
211 }
212
213 var blockProfile = &Profile{
214 name: "block",
215 count: countBlock,
216 write: writeBlock,
217 }
218
219 var mutexProfile = &Profile{
220 name: "mutex",
221 count: countMutex,
222 write: writeMutex,
223 }
224
225 func lockProfiles() {
226 profiles.mu.Lock()
227 if profiles.m == nil {
228
229 profiles.m = map[string]*Profile{
230 "goroutine": goroutineProfile,
231 "threadcreate": threadcreateProfile,
232 "heap": heapProfile,
233 "allocs": allocsProfile,
234 "block": blockProfile,
235 "mutex": mutexProfile,
236 }
237 }
238 }
239
240 func unlockProfiles() {
241 profiles.mu.Unlock()
242 }
243
244
245
246
247
248
249
250 func NewProfile(name string) *Profile {
251 lockProfiles()
252 defer unlockProfiles()
253 if name == "" {
254 panic("pprof: NewProfile with empty name")
255 }
256 if profiles.m[name] != nil {
257 panic("pprof: NewProfile name already in use: " + name)
258 }
259 p := &Profile{
260 name: name,
261 m: map[any][]uintptr{},
262 }
263 profiles.m[name] = p
264 return p
265 }
266
267
268 func Lookup(name string) *Profile {
269 lockProfiles()
270 defer unlockProfiles()
271 return profiles.m[name]
272 }
273
274
275 func Profiles() []*Profile {
276 lockProfiles()
277 defer unlockProfiles()
278
279 all := make([]*Profile, 0, len(profiles.m))
280 for _, p := range profiles.m {
281 all = append(all, p)
282 }
283
284 slices.SortFunc(all, func(a, b *Profile) int {
285 return strings.Compare(a.name, b.name)
286 })
287 return all
288 }
289
290
291 func (p *Profile) Name() string {
292 return p.name
293 }
294
295
296 func (p *Profile) Count() int {
297 p.mu.Lock()
298 defer p.mu.Unlock()
299 if p.count != nil {
300 return p.count()
301 }
302 return len(p.m)
303 }
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322 func (p *Profile) Add(value any, skip int) {
323 if p.name == "" {
324 panic("pprof: use of uninitialized Profile")
325 }
326 if p.write != nil {
327 panic("pprof: Add called on built-in Profile " + p.name)
328 }
329
330 stk := make([]uintptr, 32)
331 n := runtime.Callers(skip+1, stk[:])
332 stk = stk[:n]
333 if len(stk) == 0 {
334
335 stk = []uintptr{abi.FuncPCABIInternal(lostProfileEvent)}
336 }
337
338 p.mu.Lock()
339 defer p.mu.Unlock()
340 if p.m[value] != nil {
341 panic("pprof: Profile.Add of duplicate value")
342 }
343 p.m[value] = stk
344 }
345
346
347
348 func (p *Profile) Remove(value any) {
349 p.mu.Lock()
350 defer p.mu.Unlock()
351 delete(p.m, value)
352 }
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369 func (p *Profile) WriteTo(w io.Writer, debug int) error {
370 if p.name == "" {
371 panic("pprof: use of zero Profile")
372 }
373 if p.write != nil {
374 return p.write(w, debug)
375 }
376
377
378 p.mu.Lock()
379 all := make([][]uintptr, 0, len(p.m))
380 for _, stk := range p.m {
381 all = append(all, stk)
382 }
383 p.mu.Unlock()
384
385
386 slices.SortFunc(all, slices.Compare)
387
388 return printCountProfile(w, debug, p.name, stackProfile(all))
389 }
390
391 type stackProfile [][]uintptr
392
393 func (x stackProfile) Len() int { return len(x) }
394 func (x stackProfile) Stack(i int) []uintptr { return x[i] }
395 func (x stackProfile) Label(i int) *labelMap { return nil }
396
397
398
399
400
401 type countProfile interface {
402 Len() int
403 Stack(i int) []uintptr
404 Label(i int) *labelMap
405 }
406
407
408
409
410 func expandInlinedFrames(dst, pcs []uintptr) int {
411 cf := runtime.CallersFrames(pcs)
412 var n int
413 for n < len(dst) {
414 f, more := cf.Next()
415
416
417 dst[n] = f.PC + 1
418 n++
419 if !more {
420 break
421 }
422 }
423 return n
424 }
425
426
427
428
429
430 func printCountCycleProfile(w io.Writer, countName, cycleName string, records []profilerecord.BlockProfileRecord) error {
431
432 b := newProfileBuilder(w)
433 b.pbValueType(tagProfile_PeriodType, countName, "count")
434 b.pb.int64Opt(tagProfile_Period, 1)
435 b.pbValueType(tagProfile_SampleType, countName, "count")
436 b.pbValueType(tagProfile_SampleType, cycleName, "nanoseconds")
437
438 cpuGHz := float64(pprof_cyclesPerSecond()) / 1e9
439
440 values := []int64{0, 0}
441 var locs []uint64
442 expandedStack := pprof_makeProfStack()
443 for _, r := range records {
444 values[0] = r.Count
445 values[1] = int64(float64(r.Cycles) / cpuGHz)
446
447
448 n := expandInlinedFrames(expandedStack, r.Stack)
449 locs = b.appendLocsForStack(locs[:0], expandedStack[:n])
450 b.pbSample(values, locs, nil)
451 }
452 b.build()
453 return nil
454 }
455
456
457
458 func printCountProfile(w io.Writer, debug int, name string, p countProfile) error {
459
460 var buf strings.Builder
461 key := func(stk []uintptr, lbls *labelMap) string {
462 buf.Reset()
463 fmt.Fprintf(&buf, "@")
464 for _, pc := range stk {
465 fmt.Fprintf(&buf, " %#x", pc)
466 }
467 if lbls != nil {
468 buf.WriteString("\n# labels: ")
469 buf.WriteString(lbls.String())
470 }
471 return buf.String()
472 }
473 count := map[string]int{}
474 index := map[string]int{}
475 var keys []string
476 n := p.Len()
477 for i := 0; i < n; i++ {
478 k := key(p.Stack(i), p.Label(i))
479 if count[k] == 0 {
480 index[k] = i
481 keys = append(keys, k)
482 }
483 count[k]++
484 }
485
486 sort.Sort(&keysByCount{keys, count})
487
488 if debug > 0 {
489
490 tw := tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)
491 fmt.Fprintf(tw, "%s profile: total %d\n", name, p.Len())
492 for _, k := range keys {
493 fmt.Fprintf(tw, "%d %s\n", count[k], k)
494 printStackRecord(tw, p.Stack(index[k]), false)
495 }
496 return tw.Flush()
497 }
498
499
500 b := newProfileBuilder(w)
501 b.pbValueType(tagProfile_PeriodType, name, "count")
502 b.pb.int64Opt(tagProfile_Period, 1)
503 b.pbValueType(tagProfile_SampleType, name, "count")
504
505 values := []int64{0}
506 var locs []uint64
507 for _, k := range keys {
508 values[0] = int64(count[k])
509
510
511 locs = b.appendLocsForStack(locs[:0], p.Stack(index[k]))
512 idx := index[k]
513 var labels func()
514 if p.Label(idx) != nil {
515 labels = func() {
516 for k, v := range *p.Label(idx) {
517 b.pbLabel(tagSample_Label, k, v, 0)
518 }
519 }
520 }
521 b.pbSample(values, locs, labels)
522 }
523 b.build()
524 return nil
525 }
526
527
528 type keysByCount struct {
529 keys []string
530 count map[string]int
531 }
532
533 func (x *keysByCount) Len() int { return len(x.keys) }
534 func (x *keysByCount) Swap(i, j int) { x.keys[i], x.keys[j] = x.keys[j], x.keys[i] }
535 func (x *keysByCount) Less(i, j int) bool {
536 ki, kj := x.keys[i], x.keys[j]
537 ci, cj := x.count[ki], x.count[kj]
538 if ci != cj {
539 return ci > cj
540 }
541 return ki < kj
542 }
543
544
545
546 func printStackRecord(w io.Writer, stk []uintptr, allFrames bool) {
547 show := allFrames
548 frames := runtime.CallersFrames(stk)
549 for {
550 frame, more := frames.Next()
551 name := frame.Function
552 if name == "" {
553 show = true
554 fmt.Fprintf(w, "#\t%#x\n", frame.PC)
555 } else if name != "runtime.goexit" && (show || !strings.HasPrefix(name, "runtime.")) {
556
557
558 show = true
559 fmt.Fprintf(w, "#\t%#x\t%s+%#x\t%s:%d\n", frame.PC, name, frame.PC-frame.Entry, frame.File, frame.Line)
560 }
561 if !more {
562 break
563 }
564 }
565 if !show {
566
567
568 printStackRecord(w, stk, true)
569 return
570 }
571 fmt.Fprintf(w, "\n")
572 }
573
574
575
576
577
578 func WriteHeapProfile(w io.Writer) error {
579 return writeHeap(w, 0)
580 }
581
582
583 func countHeap() int {
584 n, _ := runtime.MemProfile(nil, true)
585 return n
586 }
587
588
589 func writeHeap(w io.Writer, debug int) error {
590 return writeHeapInternal(w, debug, "")
591 }
592
593
594
595 func writeAlloc(w io.Writer, debug int) error {
596 return writeHeapInternal(w, debug, "alloc_space")
597 }
598
599 func writeHeapInternal(w io.Writer, debug int, defaultSampleType string) error {
600 var memStats *runtime.MemStats
601 if debug != 0 {
602
603
604 memStats = new(runtime.MemStats)
605 runtime.ReadMemStats(memStats)
606 }
607
608
609
610
611
612
613
614
615 var p []profilerecord.MemProfileRecord
616 n, ok := pprof_memProfileInternal(nil, true)
617 for {
618
619
620
621 p = make([]profilerecord.MemProfileRecord, n+50)
622 n, ok = pprof_memProfileInternal(p, true)
623 if ok {
624 p = p[0:n]
625 break
626 }
627
628 }
629
630 if debug == 0 {
631 return writeHeapProto(w, p, int64(runtime.MemProfileRate), defaultSampleType)
632 }
633
634 slices.SortFunc(p, func(a, b profilerecord.MemProfileRecord) int {
635 return cmp.Compare(a.InUseBytes(), b.InUseBytes())
636 })
637
638 b := bufio.NewWriter(w)
639 tw := tabwriter.NewWriter(b, 1, 8, 1, '\t', 0)
640 w = tw
641
642 var total runtime.MemProfileRecord
643 for i := range p {
644 r := &p[i]
645 total.AllocBytes += r.AllocBytes
646 total.AllocObjects += r.AllocObjects
647 total.FreeBytes += r.FreeBytes
648 total.FreeObjects += r.FreeObjects
649 }
650
651
652
653
654 rate := 2 * runtime.MemProfileRate
655
656
657
658
659
660
661
662 inUseBytes := total.InUseBytes()
663 allocBytes := total.AllocBytes
664 if inUseBytes == allocBytes {
665 allocBytes++
666 }
667
668 fmt.Fprintf(w, "heap profile: %d: %d [%d: %d] @ heap/%d\n",
669 total.InUseObjects(), inUseBytes,
670 total.AllocObjects, allocBytes,
671 rate)
672
673 for i := range p {
674 r := &p[i]
675 fmt.Fprintf(w, "%d: %d [%d: %d] @",
676 r.InUseObjects(), r.InUseBytes(),
677 r.AllocObjects, r.AllocBytes)
678 for _, pc := range r.Stack {
679 fmt.Fprintf(w, " %#x", pc)
680 }
681 fmt.Fprintf(w, "\n")
682 printStackRecord(w, r.Stack, false)
683 }
684
685
686
687 s := memStats
688 fmt.Fprintf(w, "\n# runtime.MemStats\n")
689 fmt.Fprintf(w, "# Alloc = %d\n", s.Alloc)
690 fmt.Fprintf(w, "# TotalAlloc = %d\n", s.TotalAlloc)
691 fmt.Fprintf(w, "# Sys = %d\n", s.Sys)
692 fmt.Fprintf(w, "# Lookups = %d\n", s.Lookups)
693 fmt.Fprintf(w, "# Mallocs = %d\n", s.Mallocs)
694 fmt.Fprintf(w, "# Frees = %d\n", s.Frees)
695
696 fmt.Fprintf(w, "# HeapAlloc = %d\n", s.HeapAlloc)
697 fmt.Fprintf(w, "# HeapSys = %d\n", s.HeapSys)
698 fmt.Fprintf(w, "# HeapIdle = %d\n", s.HeapIdle)
699 fmt.Fprintf(w, "# HeapInuse = %d\n", s.HeapInuse)
700 fmt.Fprintf(w, "# HeapReleased = %d\n", s.HeapReleased)
701 fmt.Fprintf(w, "# HeapObjects = %d\n", s.HeapObjects)
702
703 fmt.Fprintf(w, "# Stack = %d / %d\n", s.StackInuse, s.StackSys)
704 fmt.Fprintf(w, "# MSpan = %d / %d\n", s.MSpanInuse, s.MSpanSys)
705 fmt.Fprintf(w, "# MCache = %d / %d\n", s.MCacheInuse, s.MCacheSys)
706 fmt.Fprintf(w, "# BuckHashSys = %d\n", s.BuckHashSys)
707 fmt.Fprintf(w, "# GCSys = %d\n", s.GCSys)
708 fmt.Fprintf(w, "# OtherSys = %d\n", s.OtherSys)
709
710 fmt.Fprintf(w, "# NextGC = %d\n", s.NextGC)
711 fmt.Fprintf(w, "# LastGC = %d\n", s.LastGC)
712 fmt.Fprintf(w, "# PauseNs = %d\n", s.PauseNs)
713 fmt.Fprintf(w, "# PauseEnd = %d\n", s.PauseEnd)
714 fmt.Fprintf(w, "# NumGC = %d\n", s.NumGC)
715 fmt.Fprintf(w, "# NumForcedGC = %d\n", s.NumForcedGC)
716 fmt.Fprintf(w, "# GCCPUFraction = %v\n", s.GCCPUFraction)
717 fmt.Fprintf(w, "# DebugGC = %v\n", s.DebugGC)
718
719
720 addMaxRSS(w)
721
722 tw.Flush()
723 return b.Flush()
724 }
725
726
727 func countThreadCreate() int {
728 n, _ := runtime.ThreadCreateProfile(nil)
729 return n
730 }
731
732
733 func writeThreadCreate(w io.Writer, debug int) error {
734
735
736
737 return writeRuntimeProfile(w, debug, "threadcreate", func(p []profilerecord.StackRecord, _ []unsafe.Pointer) (n int, ok bool) {
738 return pprof_threadCreateInternal(p)
739 })
740 }
741
742
743 func countGoroutine() int {
744 return runtime.NumGoroutine()
745 }
746
747
748 func writeGoroutine(w io.Writer, debug int) error {
749 if debug >= 2 {
750 return writeGoroutineStacks(w)
751 }
752 return writeRuntimeProfile(w, debug, "goroutine", pprof_goroutineProfileWithLabels)
753 }
754
755 func writeGoroutineStacks(w io.Writer) error {
756
757
758
759 buf := make([]byte, 1<<20)
760 for i := 0; ; i++ {
761 n := runtime.Stack(buf, true)
762 if n < len(buf) {
763 buf = buf[:n]
764 break
765 }
766 if len(buf) >= 64<<20 {
767
768 break
769 }
770 buf = make([]byte, 2*len(buf))
771 }
772 _, err := w.Write(buf)
773 return err
774 }
775
776 func writeRuntimeProfile(w io.Writer, debug int, name string, fetch func([]profilerecord.StackRecord, []unsafe.Pointer) (int, bool)) error {
777
778
779
780
781
782
783 var p []profilerecord.StackRecord
784 var labels []unsafe.Pointer
785 n, ok := fetch(nil, nil)
786
787 for {
788
789
790
791 p = make([]profilerecord.StackRecord, n+10)
792 labels = make([]unsafe.Pointer, n+10)
793 n, ok = fetch(p, labels)
794 if ok {
795 p = p[0:n]
796 break
797 }
798
799 }
800
801 return printCountProfile(w, debug, name, &runtimeProfile{p, labels})
802 }
803
804 type runtimeProfile struct {
805 stk []profilerecord.StackRecord
806 labels []unsafe.Pointer
807 }
808
809 func (p *runtimeProfile) Len() int { return len(p.stk) }
810 func (p *runtimeProfile) Stack(i int) []uintptr { return p.stk[i].Stack }
811 func (p *runtimeProfile) Label(i int) *labelMap { return (*labelMap)(p.labels[i]) }
812
813 var cpu struct {
814 sync.Mutex
815 profiling bool
816 done chan bool
817 }
818
819
820
821
822
823
824
825
826
827
828
829
830 func StartCPUProfile(w io.Writer) error {
831
832
833
834
835
836
837
838
839
840 const hz = 100
841
842 cpu.Lock()
843 defer cpu.Unlock()
844 if cpu.done == nil {
845 cpu.done = make(chan bool)
846 }
847
848 if cpu.profiling {
849 return fmt.Errorf("cpu profiling already in use")
850 }
851 cpu.profiling = true
852 runtime.SetCPUProfileRate(hz)
853 go profileWriter(w)
854 return nil
855 }
856
857
858
859
860
861
862 func readProfile() (data []uint64, tags []unsafe.Pointer, eof bool)
863
864 func profileWriter(w io.Writer) {
865 b := newProfileBuilder(w)
866 var err error
867 for {
868 time.Sleep(100 * time.Millisecond)
869 data, tags, eof := readProfile()
870 if e := b.addCPUData(data, tags); e != nil && err == nil {
871 err = e
872 }
873 if eof {
874 break
875 }
876 }
877 if err != nil {
878
879
880 panic("runtime/pprof: converting profile: " + err.Error())
881 }
882 b.build()
883 cpu.done <- true
884 }
885
886
887
888
889 func StopCPUProfile() {
890 cpu.Lock()
891 defer cpu.Unlock()
892
893 if !cpu.profiling {
894 return
895 }
896 cpu.profiling = false
897 runtime.SetCPUProfileRate(0)
898 <-cpu.done
899 }
900
901
902 func countBlock() int {
903 n, _ := runtime.BlockProfile(nil)
904 return n
905 }
906
907
908 func countMutex() int {
909 n, _ := runtime.MutexProfile(nil)
910 return n
911 }
912
913
914 func writeBlock(w io.Writer, debug int) error {
915 return writeProfileInternal(w, debug, "contention", pprof_blockProfileInternal)
916 }
917
918
919 func writeMutex(w io.Writer, debug int) error {
920 return writeProfileInternal(w, debug, "mutex", pprof_mutexProfileInternal)
921 }
922
923
924 func writeProfileInternal(w io.Writer, debug int, name string, runtimeProfile func([]profilerecord.BlockProfileRecord) (int, bool)) error {
925 var p []profilerecord.BlockProfileRecord
926 n, ok := runtimeProfile(nil)
927 for {
928 p = make([]profilerecord.BlockProfileRecord, n+50)
929 n, ok = runtimeProfile(p)
930 if ok {
931 p = p[:n]
932 break
933 }
934 }
935
936 slices.SortFunc(p, func(a, b profilerecord.BlockProfileRecord) int {
937 return cmp.Compare(b.Cycles, a.Cycles)
938 })
939
940 if debug <= 0 {
941 return printCountCycleProfile(w, "contentions", "delay", p)
942 }
943
944 b := bufio.NewWriter(w)
945 tw := tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)
946 w = tw
947
948 fmt.Fprintf(w, "--- %v:\n", name)
949 fmt.Fprintf(w, "cycles/second=%v\n", pprof_cyclesPerSecond())
950 if name == "mutex" {
951 fmt.Fprintf(w, "sampling period=%d\n", runtime.SetMutexProfileFraction(-1))
952 }
953 expandedStack := pprof_makeProfStack()
954 for i := range p {
955 r := &p[i]
956 fmt.Fprintf(w, "%v %v @", r.Cycles, r.Count)
957 n := expandInlinedFrames(expandedStack, r.Stack)
958 stack := expandedStack[:n]
959 for _, pc := range stack {
960 fmt.Fprintf(w, " %#x", pc)
961 }
962 fmt.Fprint(w, "\n")
963 if debug > 0 {
964 printStackRecord(w, stack, true)
965 }
966 }
967
968 if tw != nil {
969 tw.Flush()
970 }
971 return b.Flush()
972 }
973
974
975 func pprof_goroutineProfileWithLabels(p []profilerecord.StackRecord, labels []unsafe.Pointer) (n int, ok bool)
976
977
978 func pprof_cyclesPerSecond() int64
979
980
981 func pprof_memProfileInternal(p []profilerecord.MemProfileRecord, inuseZero bool) (n int, ok bool)
982
983
984 func pprof_blockProfileInternal(p []profilerecord.BlockProfileRecord) (n int, ok bool)
985
986
987 func pprof_mutexProfileInternal(p []profilerecord.BlockProfileRecord) (n int, ok bool)
988
989
990 func pprof_threadCreateInternal(p []profilerecord.StackRecord) (n int, ok bool)
991
992
993 func pprof_fpunwindExpand(dst, src []uintptr) int
994
995
996 func pprof_makeProfStack() []uintptr
997
View as plain text