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