1
2
3
4
5 package pprof
6
7 import (
8 "bytes"
9 "encoding/json"
10 "fmt"
11 "internal/abi"
12 "internal/profile"
13 "internal/testenv"
14 "os"
15 "os/exec"
16 "reflect"
17 "runtime"
18 "strings"
19 "testing"
20 "unsafe"
21 )
22
23
24
25
26
27
28
29 func translateCPUProfile(data []uint64, count int) (*profile.Profile, error) {
30 var buf bytes.Buffer
31 b := newProfileBuilder(&buf)
32 tags := make([]unsafe.Pointer, count)
33 if err := b.addCPUData(data, tags); err != nil {
34 return nil, err
35 }
36 b.build()
37 return profile.Parse(&buf)
38 }
39
40
41
42
43 func fmtJSON(x any) string {
44 js, _ := json.MarshalIndent(x, "", "\t")
45 return string(js)
46 }
47
48 func TestConvertCPUProfileEmpty(t *testing.T) {
49
50 var buf bytes.Buffer
51
52 b := []uint64{3, 0, 500}
53 p, err := translateCPUProfile(b, 1)
54 if err != nil {
55 t.Fatalf("translateCPUProfile: %v", err)
56 }
57 if err := p.Write(&buf); err != nil {
58 t.Fatalf("writing profile: %v", err)
59 }
60
61 p, err = profile.Parse(&buf)
62 if err != nil {
63 t.Fatalf("profile.Parse: %v", err)
64 }
65
66
67 periodType := &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}
68 sampleType := []*profile.ValueType{
69 {Type: "samples", Unit: "count"},
70 {Type: "cpu", Unit: "nanoseconds"},
71 }
72
73 checkProfile(t, p, 2000*1000, periodType, sampleType, nil, "")
74 }
75
76 func f1() { f1() }
77 func f2() { f2() }
78
79
80
81 func testPCs(t *testing.T) (addr1, addr2 uint64, map1, map2 *profile.Mapping) {
82 switch runtime.GOOS {
83 case "linux", "android", "netbsd":
84
85 mmap, err := os.ReadFile("/proc/self/maps")
86 if err != nil {
87 t.Fatal(err)
88 }
89 mprof := &profile.Profile{}
90 if err = mprof.ParseMemoryMap(bytes.NewReader(mmap)); err != nil {
91 t.Fatalf("parsing /proc/self/maps: %v", err)
92 }
93 if len(mprof.Mapping) < 2 {
94
95
96 t.Skipf("need 2 or more mappings, got %v", len(mprof.Mapping))
97 }
98 addr1 = mprof.Mapping[0].Start
99 map1 = mprof.Mapping[0]
100 map1.BuildID, _ = elfBuildID(map1.File)
101 addr2 = mprof.Mapping[1].Start
102 map2 = mprof.Mapping[1]
103 map2.BuildID, _ = elfBuildID(map2.File)
104 case "js":
105 addr1 = uint64(abi.FuncPCABIInternal(f1))
106 addr2 = uint64(abi.FuncPCABIInternal(f2))
107 default:
108 addr1 = uint64(abi.FuncPCABIInternal(f1))
109 addr2 = uint64(abi.FuncPCABIInternal(f2))
110
111
112 fake := &profile.Mapping{ID: 1, HasFunctions: true}
113 map1, map2 = fake, fake
114 }
115 return
116 }
117
118 func TestConvertCPUProfile(t *testing.T) {
119 addr1, addr2, map1, map2 := testPCs(t)
120
121 b := []uint64{
122 3, 0, 500,
123 5, 0, 10, uint64(addr1 + 1), uint64(addr1 + 2),
124 5, 0, 40, uint64(addr2 + 1), uint64(addr2 + 2),
125 5, 0, 10, uint64(addr1 + 1), uint64(addr1 + 2),
126 }
127 p, err := translateCPUProfile(b, 4)
128 if err != nil {
129 t.Fatalf("translating profile: %v", err)
130 }
131 period := int64(2000 * 1000)
132 periodType := &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}
133 sampleType := []*profile.ValueType{
134 {Type: "samples", Unit: "count"},
135 {Type: "cpu", Unit: "nanoseconds"},
136 }
137 samples := []*profile.Sample{
138 {Value: []int64{20, 20 * 2000 * 1000}, Location: []*profile.Location{
139 {ID: 1, Mapping: map1, Address: addr1},
140 {ID: 2, Mapping: map1, Address: addr1 + 1},
141 }},
142 {Value: []int64{40, 40 * 2000 * 1000}, Location: []*profile.Location{
143 {ID: 3, Mapping: map2, Address: addr2},
144 {ID: 4, Mapping: map2, Address: addr2 + 1},
145 }},
146 }
147 checkProfile(t, p, period, periodType, sampleType, samples, "")
148 }
149
150 func checkProfile(t *testing.T, p *profile.Profile, period int64, periodType *profile.ValueType, sampleType []*profile.ValueType, samples []*profile.Sample, defaultSampleType string) {
151 t.Helper()
152
153 if p.Period != period {
154 t.Errorf("p.Period = %d, want %d", p.Period, period)
155 }
156 if !reflect.DeepEqual(p.PeriodType, periodType) {
157 t.Errorf("p.PeriodType = %v\nwant = %v", fmtJSON(p.PeriodType), fmtJSON(periodType))
158 }
159 if !reflect.DeepEqual(p.SampleType, sampleType) {
160 t.Errorf("p.SampleType = %v\nwant = %v", fmtJSON(p.SampleType), fmtJSON(sampleType))
161 }
162 if defaultSampleType != p.DefaultSampleType {
163 t.Errorf("p.DefaultSampleType = %v\nwant = %v", p.DefaultSampleType, defaultSampleType)
164 }
165
166
167 for _, s := range p.Sample {
168 for _, l := range s.Location {
169 l.Line = nil
170 }
171 }
172 if fmtJSON(p.Sample) != fmtJSON(samples) {
173 if len(p.Sample) == len(samples) {
174 for i := range p.Sample {
175 if !reflect.DeepEqual(p.Sample[i], samples[i]) {
176 t.Errorf("sample %d = %v\nwant = %v\n", i, fmtJSON(p.Sample[i]), fmtJSON(samples[i]))
177 }
178 }
179 if t.Failed() {
180 t.FailNow()
181 }
182 }
183 t.Fatalf("p.Sample = %v\nwant = %v", fmtJSON(p.Sample), fmtJSON(samples))
184 }
185 }
186
187 var profSelfMapsTests = `
188 00400000-0040b000 r-xp 00000000 fc:01 787766 /bin/cat
189 0060a000-0060b000 r--p 0000a000 fc:01 787766 /bin/cat
190 0060b000-0060c000 rw-p 0000b000 fc:01 787766 /bin/cat
191 014ab000-014cc000 rw-p 00000000 00:00 0 [heap]
192 7f7d76af8000-7f7d7797c000 r--p 00000000 fc:01 1318064 /usr/lib/locale/locale-archive
193 7f7d7797c000-7f7d77b36000 r-xp 00000000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so
194 7f7d77b36000-7f7d77d36000 ---p 001ba000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so
195 7f7d77d36000-7f7d77d3a000 r--p 001ba000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so
196 7f7d77d3a000-7f7d77d3c000 rw-p 001be000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so
197 7f7d77d3c000-7f7d77d41000 rw-p 00000000 00:00 0
198 7f7d77d41000-7f7d77d64000 r-xp 00000000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so
199 7f7d77f3f000-7f7d77f42000 rw-p 00000000 00:00 0
200 7f7d77f61000-7f7d77f63000 rw-p 00000000 00:00 0
201 7f7d77f63000-7f7d77f64000 r--p 00022000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so
202 7f7d77f64000-7f7d77f65000 rw-p 00023000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so
203 7f7d77f65000-7f7d77f66000 rw-p 00000000 00:00 0
204 7ffc342a2000-7ffc342c3000 rw-p 00000000 00:00 0 [stack]
205 7ffc34343000-7ffc34345000 r-xp 00000000 00:00 0 [vdso]
206 ffffffffff600000-ffffffffff601000 r-xp 00000090 00:00 0 [vsyscall]
207 ->
208 00400000 0040b000 00000000 /bin/cat
209 7f7d7797c000 7f7d77b36000 00000000 /lib/x86_64-linux-gnu/libc-2.19.so
210 7f7d77d41000 7f7d77d64000 00000000 /lib/x86_64-linux-gnu/ld-2.19.so
211 7ffc34343000 7ffc34345000 00000000 [vdso]
212 ffffffffff600000 ffffffffff601000 00000090 [vsyscall]
213
214 00400000-07000000 r-xp 00000000 00:00 0
215 07000000-07093000 r-xp 06c00000 00:2e 536754 /path/to/gobench_server_main
216 07093000-0722d000 rw-p 06c92000 00:2e 536754 /path/to/gobench_server_main
217 0722d000-07b21000 rw-p 00000000 00:00 0
218 c000000000-c000036000 rw-p 00000000 00:00 0
219 ->
220 07000000 07093000 06c00000 /path/to/gobench_server_main
221 `
222
223 var profSelfMapsTestsWithDeleted = `
224 00400000-0040b000 r-xp 00000000 fc:01 787766 /bin/cat (deleted)
225 0060a000-0060b000 r--p 0000a000 fc:01 787766 /bin/cat (deleted)
226 0060b000-0060c000 rw-p 0000b000 fc:01 787766 /bin/cat (deleted)
227 014ab000-014cc000 rw-p 00000000 00:00 0 [heap]
228 7f7d76af8000-7f7d7797c000 r--p 00000000 fc:01 1318064 /usr/lib/locale/locale-archive
229 7f7d7797c000-7f7d77b36000 r-xp 00000000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so
230 7f7d77b36000-7f7d77d36000 ---p 001ba000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so
231 7f7d77d36000-7f7d77d3a000 r--p 001ba000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so
232 7f7d77d3a000-7f7d77d3c000 rw-p 001be000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so
233 7f7d77d3c000-7f7d77d41000 rw-p 00000000 00:00 0
234 7f7d77d41000-7f7d77d64000 r-xp 00000000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so
235 7f7d77f3f000-7f7d77f42000 rw-p 00000000 00:00 0
236 7f7d77f61000-7f7d77f63000 rw-p 00000000 00:00 0
237 7f7d77f63000-7f7d77f64000 r--p 00022000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so
238 7f7d77f64000-7f7d77f65000 rw-p 00023000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so
239 7f7d77f65000-7f7d77f66000 rw-p 00000000 00:00 0
240 7ffc342a2000-7ffc342c3000 rw-p 00000000 00:00 0 [stack]
241 7ffc34343000-7ffc34345000 r-xp 00000000 00:00 0 [vdso]
242 ffffffffff600000-ffffffffff601000 r-xp 00000090 00:00 0 [vsyscall]
243 ->
244 00400000 0040b000 00000000 /bin/cat
245 7f7d7797c000 7f7d77b36000 00000000 /lib/x86_64-linux-gnu/libc-2.19.so
246 7f7d77d41000 7f7d77d64000 00000000 /lib/x86_64-linux-gnu/ld-2.19.so
247 7ffc34343000 7ffc34345000 00000000 [vdso]
248 ffffffffff600000 ffffffffff601000 00000090 [vsyscall]
249
250 00400000-0040b000 r-xp 00000000 fc:01 787766 /bin/cat with space
251 0060a000-0060b000 r--p 0000a000 fc:01 787766 /bin/cat with space
252 0060b000-0060c000 rw-p 0000b000 fc:01 787766 /bin/cat with space
253 014ab000-014cc000 rw-p 00000000 00:00 0 [heap]
254 7f7d76af8000-7f7d7797c000 r--p 00000000 fc:01 1318064 /usr/lib/locale/locale-archive
255 7f7d7797c000-7f7d77b36000 r-xp 00000000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so
256 7f7d77b36000-7f7d77d36000 ---p 001ba000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so
257 7f7d77d36000-7f7d77d3a000 r--p 001ba000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so
258 7f7d77d3a000-7f7d77d3c000 rw-p 001be000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so
259 7f7d77d3c000-7f7d77d41000 rw-p 00000000 00:00 0
260 7f7d77d41000-7f7d77d64000 r-xp 00000000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so
261 7f7d77f3f000-7f7d77f42000 rw-p 00000000 00:00 0
262 7f7d77f61000-7f7d77f63000 rw-p 00000000 00:00 0
263 7f7d77f63000-7f7d77f64000 r--p 00022000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so
264 7f7d77f64000-7f7d77f65000 rw-p 00023000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so
265 7f7d77f65000-7f7d77f66000 rw-p 00000000 00:00 0
266 7ffc342a2000-7ffc342c3000 rw-p 00000000 00:00 0 [stack]
267 7ffc34343000-7ffc34345000 r-xp 00000000 00:00 0 [vdso]
268 ffffffffff600000-ffffffffff601000 r-xp 00000090 00:00 0 [vsyscall]
269 ->
270 00400000 0040b000 00000000 /bin/cat with space
271 7f7d7797c000 7f7d77b36000 00000000 /lib/x86_64-linux-gnu/libc-2.19.so
272 7f7d77d41000 7f7d77d64000 00000000 /lib/x86_64-linux-gnu/ld-2.19.so
273 7ffc34343000 7ffc34345000 00000000 [vdso]
274 ffffffffff600000 ffffffffff601000 00000090 [vsyscall]
275 `
276
277 func TestProcSelfMaps(t *testing.T) {
278
279 f := func(t *testing.T, input string) {
280 for tx, tt := range strings.Split(input, "\n\n") {
281 in, out, ok := strings.Cut(tt, "->\n")
282 if !ok {
283 t.Fatal("malformed test case")
284 }
285 if len(out) > 0 && out[len(out)-1] != '\n' {
286 out += "\n"
287 }
288 var buf bytes.Buffer
289 parseProcSelfMaps([]byte(in), func(lo, hi, offset uint64, file, buildID string) {
290 fmt.Fprintf(&buf, "%08x %08x %08x %s\n", lo, hi, offset, file)
291 })
292 if buf.String() != out {
293 t.Errorf("#%d: have:\n%s\nwant:\n%s\n%q\n%q", tx, buf.String(), out, buf.String(), out)
294 }
295 }
296 }
297
298 t.Run("Normal", func(t *testing.T) {
299 f(t, profSelfMapsTests)
300 })
301
302 t.Run("WithDeletedFile", func(t *testing.T) {
303 f(t, profSelfMapsTestsWithDeleted)
304 })
305 }
306
307
308
309
310
311
312
313
314 func TestMapping(t *testing.T) {
315 testenv.MustHaveGoRun(t)
316 testenv.MustHaveCGO(t)
317
318 prog := "./testdata/mappingtest/main.go"
319
320
321
322 for _, traceback := range []string{"GoOnly", "Go+C"} {
323 t.Run("traceback"+traceback, func(t *testing.T) {
324 cmd := exec.Command(testenv.GoToolPath(t), "run", prog)
325 if traceback != "GoOnly" {
326 cmd.Env = append(os.Environ(), "SETCGOTRACEBACK=1")
327 }
328 cmd.Stderr = new(bytes.Buffer)
329
330 out, err := cmd.Output()
331 if err != nil {
332 t.Fatalf("failed to run the test program %q: %v\n%v", prog, err, cmd.Stderr)
333 }
334
335 prof, err := profile.Parse(bytes.NewReader(out))
336 if err != nil {
337 t.Fatalf("failed to parse the generated profile data: %v", err)
338 }
339 t.Logf("Profile: %s", prof)
340
341 hit := make(map[*profile.Mapping]bool)
342 miss := make(map[*profile.Mapping]bool)
343 for _, loc := range prof.Location {
344 if symbolized(loc) {
345 hit[loc.Mapping] = true
346 } else {
347 miss[loc.Mapping] = true
348 }
349 }
350 if len(miss) == 0 {
351 t.Log("no location with missing symbol info was sampled")
352 }
353
354 for _, m := range prof.Mapping {
355 if miss[m] && m.HasFunctions {
356 t.Errorf("mapping %+v has HasFunctions=true, but contains locations with failed symbolization", m)
357 continue
358 }
359 if !miss[m] && hit[m] && !m.HasFunctions {
360 t.Errorf("mapping %+v has HasFunctions=false, but all referenced locations from this lapping were symbolized successfully", m)
361 continue
362 }
363 }
364
365 if traceback == "Go+C" {
366
367
368
369 for i, loc := range prof.Location {
370 if !symbolized(loc) && len(loc.Line) > 1 {
371 t.Errorf("Location[%d] contains unsymbolized PCs and multiple lines: %v", i, loc)
372 }
373 }
374 }
375 })
376 }
377 }
378
379 func symbolized(loc *profile.Location) bool {
380 if len(loc.Line) == 0 {
381 return false
382 }
383 l := loc.Line[0]
384 f := l.Function
385 if l.Line == 0 || f == nil || f.Name == "" || f.Filename == "" {
386 return false
387 }
388 return true
389 }
390
391
392
393
394 func TestFakeMapping(t *testing.T) {
395 var buf bytes.Buffer
396 if err := Lookup("heap").WriteTo(&buf, 0); err != nil {
397 t.Fatalf("failed to write heap profile: %v", err)
398 }
399 prof, err := profile.Parse(&buf)
400 if err != nil {
401 t.Fatalf("failed to parse the generated profile data: %v", err)
402 }
403 t.Logf("Profile: %s", prof)
404 if len(prof.Mapping) == 0 {
405 t.Fatal("want profile with at least one mapping entry, got 0 mapping")
406 }
407
408 hit := make(map[*profile.Mapping]bool)
409 miss := make(map[*profile.Mapping]bool)
410 for _, loc := range prof.Location {
411 if symbolized(loc) {
412 hit[loc.Mapping] = true
413 } else {
414 miss[loc.Mapping] = true
415 }
416 }
417 for _, m := range prof.Mapping {
418 if miss[m] && m.HasFunctions {
419 t.Errorf("mapping %+v has HasFunctions=true, but contains locations with failed symbolization", m)
420 continue
421 }
422 if !miss[m] && hit[m] && !m.HasFunctions {
423 t.Errorf("mapping %+v has HasFunctions=false, but all referenced locations from this lapping were symbolized successfully", m)
424 continue
425 }
426 }
427 }
428
429
430
431 func TestEmptyStack(t *testing.T) {
432 b := []uint64{
433 3, 0, 500,
434 3, 0, 10,
435 }
436 _, err := translateCPUProfile(b, 2)
437 if err != nil {
438 t.Fatalf("translating profile: %v", err)
439 }
440 }
441
View as plain text