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