1
2
3
4
5
6
7 package pprof
8
9 import (
10 "bytes"
11 "fmt"
12 "internal/profile"
13 "reflect"
14 "regexp"
15 "runtime"
16 "testing"
17 "unsafe"
18 )
19
20 var memSink any
21
22 func allocateTransient1M() {
23 for i := 0; i < 1024; i++ {
24 memSink = &struct{ x [1024]byte }{}
25 }
26 }
27
28
29 func allocateTransient2M() {
30 memSink = make([]byte, 2<<20)
31 }
32
33 func allocateTransient2MInline() {
34 memSink = make([]byte, 2<<20)
35 }
36
37 type Obj32 struct {
38 link *Obj32
39 pad [32 - unsafe.Sizeof(uintptr(0))]byte
40 }
41
42 var persistentMemSink *Obj32
43
44 func allocatePersistent1K() {
45 for i := 0; i < 32; i++ {
46
47 obj := &Obj32{link: persistentMemSink}
48 persistentMemSink = obj
49 }
50 }
51
52
53
54 func allocateReflectTransient() {
55 memSink = make([]byte, 2<<20)
56 }
57
58 func allocateReflect() {
59 rv := reflect.ValueOf(allocateReflectTransient)
60 rv.Call(nil)
61 }
62
63 var memoryProfilerRun = 0
64
65 func TestMemoryProfiler(t *testing.T) {
66
67 oldRate := runtime.MemProfileRate
68 runtime.MemProfileRate = 1
69 defer func() {
70 runtime.MemProfileRate = oldRate
71 }()
72
73
74 for i := 0; i < 1024; i++ {
75 memSink = make([]byte, 1024)
76 }
77
78
79 allocateTransient1M()
80 allocateTransient2M()
81 allocateTransient2MInline()
82 allocatePersistent1K()
83 allocateReflect()
84 memSink = nil
85
86 runtime.GC()
87
88 memoryProfilerRun++
89
90 tests := []struct {
91 stk []string
92 legacy string
93 }{{
94 stk: []string{"runtime/pprof.allocatePersistent1K", "runtime/pprof.TestMemoryProfiler"},
95 legacy: fmt.Sprintf(`%v: %v \[%v: %v\] @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+
96 # 0x[0-9,a-f]+ runtime/pprof\.allocatePersistent1K\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test\.go:47
97 # 0x[0-9,a-f]+ runtime/pprof\.TestMemoryProfiler\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test\.go:82
98 `, 32*memoryProfilerRun, 1024*memoryProfilerRun, 32*memoryProfilerRun, 1024*memoryProfilerRun),
99 }, {
100 stk: []string{"runtime/pprof.allocateTransient1M", "runtime/pprof.TestMemoryProfiler"},
101 legacy: fmt.Sprintf(`0: 0 \[%v: %v\] @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+
102 # 0x[0-9,a-f]+ runtime/pprof\.allocateTransient1M\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:24
103 # 0x[0-9,a-f]+ runtime/pprof\.TestMemoryProfiler\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:79
104 `, (1<<10)*memoryProfilerRun, (1<<20)*memoryProfilerRun),
105 }, {
106 stk: []string{"runtime/pprof.allocateTransient2M", "runtime/pprof.TestMemoryProfiler"},
107 legacy: fmt.Sprintf(`0: 0 \[%v: %v\] @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+
108 # 0x[0-9,a-f]+ runtime/pprof\.allocateTransient2M\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:30
109 # 0x[0-9,a-f]+ runtime/pprof\.TestMemoryProfiler\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:80
110 `, memoryProfilerRun, (2<<20)*memoryProfilerRun),
111 }, {
112 stk: []string{"runtime/pprof.allocateTransient2MInline", "runtime/pprof.TestMemoryProfiler"},
113 legacy: fmt.Sprintf(`0: 0 \[%v: %v\] @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+
114 # 0x[0-9,a-f]+ runtime/pprof\.allocateTransient2MInline\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:34
115 # 0x[0-9,a-f]+ runtime/pprof\.TestMemoryProfiler\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:81
116 `, memoryProfilerRun, (2<<20)*memoryProfilerRun),
117 }, {
118 stk: []string{"runtime/pprof.allocateReflectTransient"},
119 legacy: fmt.Sprintf(`0: 0 \[%v: %v\] @( 0x[0-9,a-f]+)+
120 # 0x[0-9,a-f]+ runtime/pprof\.allocateReflectTransient\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:55
121 `, memoryProfilerRun, (2<<20)*memoryProfilerRun),
122 }}
123
124 t.Run("debug=1", func(t *testing.T) {
125 var buf bytes.Buffer
126 if err := Lookup("heap").WriteTo(&buf, 1); err != nil {
127 t.Fatalf("failed to write heap profile: %v", err)
128 }
129
130 for _, test := range tests {
131 if !regexp.MustCompile(test.legacy).Match(buf.Bytes()) {
132 t.Fatalf("The entry did not match:\n%v\n\nProfile:\n%v\n", test.legacy, buf.String())
133 }
134 }
135 })
136
137 t.Run("proto", func(t *testing.T) {
138 var buf bytes.Buffer
139 if err := Lookup("heap").WriteTo(&buf, 0); err != nil {
140 t.Fatalf("failed to write heap profile: %v", err)
141 }
142 p, err := profile.Parse(&buf)
143 if err != nil {
144 t.Fatalf("failed to parse heap profile: %v", err)
145 }
146 t.Logf("Profile = %v", p)
147
148 stks := profileStacks(p)
149 for _, test := range tests {
150 if !containsStack(stks, test.stk) {
151 t.Fatalf("No matching stack entry for %q\n\nProfile:\n%v\n", test.stk, p)
152 }
153 }
154
155 if !containsInlinedCall(TestMemoryProfiler, 4<<10) {
156 t.Logf("Can't determine whether allocateTransient2MInline was inlined into TestMemoryProfiler.")
157 return
158 }
159
160
161 for _, loc := range p.Location {
162 inlinedCaller, inlinedCallee := false, false
163 for _, line := range loc.Line {
164 if line.Function.Name == "runtime/pprof.allocateTransient2MInline" {
165 inlinedCallee = true
166 }
167 if inlinedCallee && line.Function.Name == "runtime/pprof.TestMemoryProfiler" {
168 inlinedCaller = true
169 }
170 }
171 if inlinedCallee != inlinedCaller {
172 t.Errorf("want allocateTransient2MInline after TestMemoryProfiler in one location, got separate location entries:\n%v", loc)
173 }
174 }
175 })
176 }
177
View as plain text