github.com/geraldss/go/src@v0.0.0-20210511222824-ac7d0ebfc235/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/profile" 12 "internal/testenv" 13 "os" 14 "os/exec" 15 "reflect" 16 "runtime" 17 "strings" 18 "testing" 19 ) 20 21 // translateCPUProfile parses binary CPU profiling stack trace data 22 // generated by runtime.CPUProfile() into a profile struct. 23 // This is only used for testing. Real conversions stream the 24 // data into the profileBuilder as it becomes available. 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 // fmtJSON returns a pretty-printed JSON form for x. 36 // It works reasonbly well for printing protocol-buffer 37 // data structures like profile.Profile. 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 // A test server with mock cpu profile data. 45 var buf bytes.Buffer 46 47 b := []uint64{3, 0, 500} // empty profile at 500 Hz (2ms sample period) 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 // Expected PeriodType and SampleType. 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 // testPCs returns two PCs and two corresponding memory mappings 75 // to use in test profiles. 76 func testPCs(t *testing.T) (addr1, addr2 uint64, map1, map2 *profile.Mapping) { 77 switch runtime.GOOS { 78 case "linux", "android", "netbsd": 79 // Figure out two addresses from /proc/self/maps. 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 // It is possible for a binary to only have 1 executable 90 // region of memory. 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 // Fake mapping - HasFunctions will be true because two PCs from Go 106 // will be fully symbolized. 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, // hz = 500 118 5, 0, 10, uint64(addr1 + 1), uint64(addr1 + 2), // 10 samples in addr1 119 5, 0, 40, uint64(addr2 + 1), uint64(addr2 + 2), // 40 samples in addr2 120 5, 0, 10, uint64(addr1 + 1), uint64(addr1 + 2), // 10 samples in addr1 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 // Clear line info since it is not in the expected samples. 161 // If we used f1 and f2 above, then the samples will have line info. 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) { // ignore unexported fields 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 // TestMapping checks the mapping section of CPU profiles 304 // has the HasFunctions field set correctly. If all PCs included 305 // in the samples are successfully symbolized, the corresponding 306 // mapping entry (in this test case, only one entry) should have 307 // its HasFunctions field set true. 308 // The test generates a CPU profile that includes PCs from C side 309 // that the runtime can't symbolize. See ./testdata/mappingtest. 310 func TestMapping(t *testing.T) { 311 testenv.MustHaveGoRun(t) 312 testenv.MustHaveCGO(t) 313 314 prog := "./testdata/mappingtest/main.go" 315 316 // GoOnly includes only Go symbols that runtime will symbolize. 317 // Go+C includes C symbols that runtime will not symbolize. 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 // The test code was arranged to have PCs from C and 363 // they are not symbolized. 364 // Check no Location containing those unsymbolized PCs contains multiple lines. 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 // TestFakeMapping tests if at least one mapping exists 388 // (including a fake mapping), and their HasFunctions bits 389 // are set correctly. 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 // Make sure the profiler can handle an empty stack trace. 426 // See issue 37967. 427 func TestEmptyStack(t *testing.T) { 428 b := []uint64{ 429 3, 0, 500, // hz = 500 430 3, 0, 10, // 10 samples with an empty stack trace 431 } 432 _, err := translateCPUProfile(b) 433 if err != nil { 434 t.Fatalf("translating profile: %v", err) 435 } 436 }