github.com/guyezi/gofrontend@v0.0.0-20200228202240-7a62a49e62c0/libgo/go/runtime/pprof/proto_test.go (about) 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 }