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