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