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