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