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