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 stk: []string{"runtime/pprof.allocatePersistent1K", "runtime/pprof.TestMemoryProfiler"},
100 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]+
101 # 0x[0-9,a-f]+ runtime/pprof\.allocatePersistent1K\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test\.go:48
102 # 0x[0-9,a-f]+ runtime/pprof\.TestMemoryProfiler\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test\.go:87
103 `, 32*memoryProfilerRun, 1024*memoryProfilerRun, 32*memoryProfilerRun, 1024*memoryProfilerRun),
104 }, {
105 stk: []string{"runtime/pprof.allocateTransient1M", "runtime/pprof.TestMemoryProfiler"},
106 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]+
107 # 0x[0-9,a-f]+ runtime/pprof\.allocateTransient1M\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:25
108 # 0x[0-9,a-f]+ runtime/pprof\.TestMemoryProfiler\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:84
109 `, (1<<10)*memoryProfilerRun, (1<<20)*memoryProfilerRun),
110 }, {
111 stk: []string{"runtime/pprof.allocateTransient2M", "runtime/pprof.TestMemoryProfiler"},
112 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]+
113 # 0x[0-9,a-f]+ runtime/pprof\.allocateTransient2M\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:31
114 # 0x[0-9,a-f]+ runtime/pprof\.TestMemoryProfiler\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:85
115 `, memoryProfilerRun, (2<<20)*memoryProfilerRun),
116 }, {
117 stk: []string{"runtime/pprof.allocateTransient2MInline", "runtime/pprof.TestMemoryProfiler"},
118 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]+
119 # 0x[0-9,a-f]+ runtime/pprof\.allocateTransient2MInline\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:35
120 # 0x[0-9,a-f]+ runtime/pprof\.TestMemoryProfiler\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:86
121 `, memoryProfilerRun, (2<<20)*memoryProfilerRun),
122 }, {
123 stk: []string{"runtime/pprof.allocateReflectTransient"},
124 legacy: fmt.Sprintf(`0: 0 \[%v: %v\] @( 0x[0-9,a-f]+)+
125 # 0x[0-9,a-f]+ runtime/pprof\.allocateReflectTransient\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:56
126 `, memoryProfilerRun, (2<<20)*memoryProfilerRun),
127 }}
128
129 t.Run("debug=1", func(t *testing.T) {
130 var buf bytes.Buffer
131 if err := Lookup("heap").WriteTo(&buf, 1); err != nil {
132 t.Fatalf("failed to write heap profile: %v", err)
133 }
134
135 for _, test := range tests {
136 if !regexp.MustCompile(test.legacy).Match(buf.Bytes()) {
137 t.Fatalf("The entry did not match:\n%v\n\nProfile:\n%v\n", test.legacy, buf.String())
138 }
139 }
140 })
141
142 t.Run("proto", func(t *testing.T) {
143 var buf bytes.Buffer
144 if err := Lookup("heap").WriteTo(&buf, 0); err != nil {
145 t.Fatalf("failed to write heap profile: %v", err)
146 }
147 p, err := profile.Parse(&buf)
148 if err != nil {
149 t.Fatalf("failed to parse heap profile: %v", err)
150 }
151 t.Logf("Profile = %v", p)
152
153 stks := profileStacks(p)
154 for _, test := range tests {
155 if !containsStack(stks, test.stk) {
156 t.Fatalf("No matching stack entry for %q\n\nProfile:\n%v\n", test.stk, p)
157 }
158 }
159
160 if !containsInlinedCall(TestMemoryProfiler, 4<<10) {
161 t.Logf("Can't determine whether allocateTransient2MInline was inlined into TestMemoryProfiler.")
162 return
163 }
164
165
166 for _, loc := range p.Location {
167 inlinedCaller, inlinedCallee := false, false
168 for _, line := range loc.Line {
169 if line.Function.Name == "runtime/pprof.allocateTransient2MInline" {
170 inlinedCallee = true
171 }
172 if inlinedCallee && line.Function.Name == "runtime/pprof.TestMemoryProfiler" {
173 inlinedCaller = true
174 }
175 }
176 if inlinedCallee != inlinedCaller {
177 t.Errorf("want allocateTransient2MInline after TestMemoryProfiler in one location, got separate location entries:\n%v", loc)
178 }
179 }
180 })
181 }
182
View as plain text