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