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