github.com/JimmyHuang454/JLS-go@v0.0.0-20230831150107-90d536585ba0/internal/profile/legacy_profile.go (about) 1 // Copyright 2014 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 // This file implements parsers to convert legacy profiles into the 6 // profile.proto format. 7 8 package profile 9 10 import ( 11 "bufio" 12 "bytes" 13 "fmt" 14 "io" 15 "math" 16 "regexp" 17 "strconv" 18 "strings" 19 ) 20 21 var ( 22 countStartRE = regexp.MustCompile(`\A(\w+) profile: total \d+\n\z`) 23 countRE = regexp.MustCompile(`\A(\d+) @(( 0x[0-9a-f]+)+)\n\z`) 24 25 heapHeaderRE = regexp.MustCompile(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] *@ *(heap[_a-z0-9]*)/?(\d*)`) 26 heapSampleRE = regexp.MustCompile(`(-?\d+): *(-?\d+) *\[ *(\d+): *(\d+) *] @([ x0-9a-f]*)`) 27 28 contentionSampleRE = regexp.MustCompile(`(\d+) *(\d+) @([ x0-9a-f]*)`) 29 30 hexNumberRE = regexp.MustCompile(`0x[0-9a-f]+`) 31 32 growthHeaderRE = regexp.MustCompile(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] @ growthz`) 33 34 fragmentationHeaderRE = regexp.MustCompile(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] @ fragmentationz`) 35 36 threadzStartRE = regexp.MustCompile(`--- threadz \d+ ---`) 37 threadStartRE = regexp.MustCompile(`--- Thread ([[:xdigit:]]+) \(name: (.*)/(\d+)\) stack: ---`) 38 39 procMapsRE = regexp.MustCompile(`([[:xdigit:]]+)-([[:xdigit:]]+)\s+([-rwxp]+)\s+([[:xdigit:]]+)\s+([[:xdigit:]]+):([[:xdigit:]]+)\s+([[:digit:]]+)\s*(\S+)?`) 40 41 briefMapsRE = regexp.MustCompile(`\s*([[:xdigit:]]+)-([[:xdigit:]]+):\s*(\S+)(\s.*@)?([[:xdigit:]]+)?`) 42 43 // LegacyHeapAllocated instructs the heapz parsers to use the 44 // allocated memory stats instead of the default in-use memory. Note 45 // that tcmalloc doesn't provide all allocated memory, only in-use 46 // stats. 47 LegacyHeapAllocated bool 48 ) 49 50 func isSpaceOrComment(line string) bool { 51 trimmed := strings.TrimSpace(line) 52 return len(trimmed) == 0 || trimmed[0] == '#' 53 } 54 55 // parseGoCount parses a Go count profile (e.g., threadcreate or 56 // goroutine) and returns a new Profile. 57 func parseGoCount(b []byte) (*Profile, error) { 58 r := bytes.NewBuffer(b) 59 60 var line string 61 var err error 62 for { 63 // Skip past comments and empty lines seeking a real header. 64 line, err = r.ReadString('\n') 65 if err != nil { 66 return nil, err 67 } 68 if !isSpaceOrComment(line) { 69 break 70 } 71 } 72 73 m := countStartRE.FindStringSubmatch(line) 74 if m == nil { 75 return nil, errUnrecognized 76 } 77 profileType := m[1] 78 p := &Profile{ 79 PeriodType: &ValueType{Type: profileType, Unit: "count"}, 80 Period: 1, 81 SampleType: []*ValueType{{Type: profileType, Unit: "count"}}, 82 } 83 locations := make(map[uint64]*Location) 84 for { 85 line, err = r.ReadString('\n') 86 if err != nil { 87 if err == io.EOF { 88 break 89 } 90 return nil, err 91 } 92 if isSpaceOrComment(line) { 93 continue 94 } 95 if strings.HasPrefix(line, "---") { 96 break 97 } 98 m := countRE.FindStringSubmatch(line) 99 if m == nil { 100 return nil, errMalformed 101 } 102 n, err := strconv.ParseInt(m[1], 0, 64) 103 if err != nil { 104 return nil, errMalformed 105 } 106 fields := strings.Fields(m[2]) 107 locs := make([]*Location, 0, len(fields)) 108 for _, stk := range fields { 109 addr, err := strconv.ParseUint(stk, 0, 64) 110 if err != nil { 111 return nil, errMalformed 112 } 113 // Adjust all frames by -1 to land on the call instruction. 114 addr-- 115 loc := locations[addr] 116 if loc == nil { 117 loc = &Location{ 118 Address: addr, 119 } 120 locations[addr] = loc 121 p.Location = append(p.Location, loc) 122 } 123 locs = append(locs, loc) 124 } 125 p.Sample = append(p.Sample, &Sample{ 126 Location: locs, 127 Value: []int64{n}, 128 }) 129 } 130 131 if err = parseAdditionalSections(strings.TrimSpace(line), r, p); err != nil { 132 return nil, err 133 } 134 return p, nil 135 } 136 137 // remapLocationIDs ensures there is a location for each address 138 // referenced by a sample, and remaps the samples to point to the new 139 // location ids. 140 func (p *Profile) remapLocationIDs() { 141 seen := make(map[*Location]bool, len(p.Location)) 142 var locs []*Location 143 144 for _, s := range p.Sample { 145 for _, l := range s.Location { 146 if seen[l] { 147 continue 148 } 149 l.ID = uint64(len(locs) + 1) 150 locs = append(locs, l) 151 seen[l] = true 152 } 153 } 154 p.Location = locs 155 } 156 157 func (p *Profile) remapFunctionIDs() { 158 seen := make(map[*Function]bool, len(p.Function)) 159 var fns []*Function 160 161 for _, l := range p.Location { 162 for _, ln := range l.Line { 163 fn := ln.Function 164 if fn == nil || seen[fn] { 165 continue 166 } 167 fn.ID = uint64(len(fns) + 1) 168 fns = append(fns, fn) 169 seen[fn] = true 170 } 171 } 172 p.Function = fns 173 } 174 175 // remapMappingIDs matches location addresses with existing mappings 176 // and updates them appropriately. This is O(N*M), if this ever shows 177 // up as a bottleneck, evaluate sorting the mappings and doing a 178 // binary search, which would make it O(N*log(M)). 179 func (p *Profile) remapMappingIDs() { 180 if len(p.Mapping) == 0 { 181 return 182 } 183 184 // Some profile handlers will incorrectly set regions for the main 185 // executable if its section is remapped. Fix them through heuristics. 186 187 // Remove the initial mapping if named '/anon_hugepage' and has a 188 // consecutive adjacent mapping. 189 if m := p.Mapping[0]; strings.HasPrefix(m.File, "/anon_hugepage") { 190 if len(p.Mapping) > 1 && m.Limit == p.Mapping[1].Start { 191 p.Mapping = p.Mapping[1:] 192 } 193 } 194 195 for _, l := range p.Location { 196 if a := l.Address; a != 0 { 197 for _, m := range p.Mapping { 198 if m.Start <= a && a < m.Limit { 199 l.Mapping = m 200 break 201 } 202 } 203 } 204 } 205 206 // Reset all mapping IDs. 207 for i, m := range p.Mapping { 208 m.ID = uint64(i + 1) 209 } 210 } 211 212 var cpuInts = []func([]byte) (uint64, []byte){ 213 get32l, 214 get32b, 215 get64l, 216 get64b, 217 } 218 219 func get32l(b []byte) (uint64, []byte) { 220 if len(b) < 4 { 221 return 0, nil 222 } 223 return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24, b[4:] 224 } 225 226 func get32b(b []byte) (uint64, []byte) { 227 if len(b) < 4 { 228 return 0, nil 229 } 230 return uint64(b[3]) | uint64(b[2])<<8 | uint64(b[1])<<16 | uint64(b[0])<<24, b[4:] 231 } 232 233 func get64l(b []byte) (uint64, []byte) { 234 if len(b) < 8 { 235 return 0, nil 236 } 237 return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56, b[8:] 238 } 239 240 func get64b(b []byte) (uint64, []byte) { 241 if len(b) < 8 { 242 return 0, nil 243 } 244 return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56, b[8:] 245 } 246 247 // ParseTracebacks parses a set of tracebacks and returns a newly 248 // populated profile. It will accept any text file and generate a 249 // Profile out of it with any hex addresses it can identify, including 250 // a process map if it can recognize one. Each sample will include a 251 // tag "source" with the addresses recognized in string format. 252 func ParseTracebacks(b []byte) (*Profile, error) { 253 r := bytes.NewBuffer(b) 254 255 p := &Profile{ 256 PeriodType: &ValueType{Type: "trace", Unit: "count"}, 257 Period: 1, 258 SampleType: []*ValueType{ 259 {Type: "trace", Unit: "count"}, 260 }, 261 } 262 263 var sources []string 264 var sloc []*Location 265 266 locs := make(map[uint64]*Location) 267 for { 268 l, err := r.ReadString('\n') 269 if err != nil { 270 if err != io.EOF { 271 return nil, err 272 } 273 if l == "" { 274 break 275 } 276 } 277 if sectionTrigger(l) == memoryMapSection { 278 break 279 } 280 if s, addrs := extractHexAddresses(l); len(s) > 0 { 281 for _, addr := range addrs { 282 // Addresses from stack traces point to the next instruction after 283 // each call. Adjust by -1 to land somewhere on the actual call. 284 addr-- 285 loc := locs[addr] 286 if locs[addr] == nil { 287 loc = &Location{ 288 Address: addr, 289 } 290 p.Location = append(p.Location, loc) 291 locs[addr] = loc 292 } 293 sloc = append(sloc, loc) 294 } 295 296 sources = append(sources, s...) 297 } else { 298 if len(sources) > 0 || len(sloc) > 0 { 299 addTracebackSample(sloc, sources, p) 300 sloc, sources = nil, nil 301 } 302 } 303 } 304 305 // Add final sample to save any leftover data. 306 if len(sources) > 0 || len(sloc) > 0 { 307 addTracebackSample(sloc, sources, p) 308 } 309 310 if err := p.ParseMemoryMap(r); err != nil { 311 return nil, err 312 } 313 return p, nil 314 } 315 316 func addTracebackSample(l []*Location, s []string, p *Profile) { 317 p.Sample = append(p.Sample, 318 &Sample{ 319 Value: []int64{1}, 320 Location: l, 321 Label: map[string][]string{"source": s}, 322 }) 323 } 324 325 // parseCPU parses a profilez legacy profile and returns a newly 326 // populated Profile. 327 // 328 // The general format for profilez samples is a sequence of words in 329 // binary format. The first words are a header with the following data: 330 // 331 // 1st word -- 0 332 // 2nd word -- 3 333 // 3rd word -- 0 if a c++ application, 1 if a java application. 334 // 4th word -- Sampling period (in microseconds). 335 // 5th word -- Padding. 336 func parseCPU(b []byte) (*Profile, error) { 337 var parse func([]byte) (uint64, []byte) 338 var n1, n2, n3, n4, n5 uint64 339 for _, parse = range cpuInts { 340 var tmp []byte 341 n1, tmp = parse(b) 342 n2, tmp = parse(tmp) 343 n3, tmp = parse(tmp) 344 n4, tmp = parse(tmp) 345 n5, tmp = parse(tmp) 346 347 if tmp != nil && n1 == 0 && n2 == 3 && n3 == 0 && n4 > 0 && n5 == 0 { 348 b = tmp 349 return cpuProfile(b, int64(n4), parse) 350 } 351 } 352 return nil, errUnrecognized 353 } 354 355 // cpuProfile returns a new Profile from C++ profilez data. 356 // b is the profile bytes after the header, period is the profiling 357 // period, and parse is a function to parse 8-byte chunks from the 358 // profile in its native endianness. 359 func cpuProfile(b []byte, period int64, parse func(b []byte) (uint64, []byte)) (*Profile, error) { 360 p := &Profile{ 361 Period: period * 1000, 362 PeriodType: &ValueType{Type: "cpu", Unit: "nanoseconds"}, 363 SampleType: []*ValueType{ 364 {Type: "samples", Unit: "count"}, 365 {Type: "cpu", Unit: "nanoseconds"}, 366 }, 367 } 368 var err error 369 if b, _, err = parseCPUSamples(b, parse, true, p); err != nil { 370 return nil, err 371 } 372 373 // If all samples have the same second-to-the-bottom frame, it 374 // strongly suggests that it is an uninteresting artifact of 375 // measurement -- a stack frame pushed by the signal handler. The 376 // bottom frame is always correct as it is picked up from the signal 377 // structure, not the stack. Check if this is the case and if so, 378 // remove. 379 if len(p.Sample) > 1 && len(p.Sample[0].Location) > 1 { 380 allSame := true 381 id1 := p.Sample[0].Location[1].Address 382 for _, s := range p.Sample { 383 if len(s.Location) < 2 || id1 != s.Location[1].Address { 384 allSame = false 385 break 386 } 387 } 388 if allSame { 389 for _, s := range p.Sample { 390 s.Location = append(s.Location[:1], s.Location[2:]...) 391 } 392 } 393 } 394 395 if err := p.ParseMemoryMap(bytes.NewBuffer(b)); err != nil { 396 return nil, err 397 } 398 return p, nil 399 } 400 401 // parseCPUSamples parses a collection of profilez samples from a 402 // profile. 403 // 404 // profilez samples are a repeated sequence of stack frames of the 405 // form: 406 // 407 // 1st word -- The number of times this stack was encountered. 408 // 2nd word -- The size of the stack (StackSize). 409 // 3rd word -- The first address on the stack. 410 // ... 411 // StackSize + 2 -- The last address on the stack 412 // 413 // The last stack trace is of the form: 414 // 415 // 1st word -- 0 416 // 2nd word -- 1 417 // 3rd word -- 0 418 // 419 // Addresses from stack traces may point to the next instruction after 420 // each call. Optionally adjust by -1 to land somewhere on the actual 421 // call (except for the leaf, which is not a call). 422 func parseCPUSamples(b []byte, parse func(b []byte) (uint64, []byte), adjust bool, p *Profile) ([]byte, map[uint64]*Location, error) { 423 locs := make(map[uint64]*Location) 424 for len(b) > 0 { 425 var count, nstk uint64 426 count, b = parse(b) 427 nstk, b = parse(b) 428 if b == nil || nstk > uint64(len(b)/4) { 429 return nil, nil, errUnrecognized 430 } 431 var sloc []*Location 432 addrs := make([]uint64, nstk) 433 for i := 0; i < int(nstk); i++ { 434 addrs[i], b = parse(b) 435 } 436 437 if count == 0 && nstk == 1 && addrs[0] == 0 { 438 // End of data marker 439 break 440 } 441 for i, addr := range addrs { 442 if adjust && i > 0 { 443 addr-- 444 } 445 loc := locs[addr] 446 if loc == nil { 447 loc = &Location{ 448 Address: addr, 449 } 450 locs[addr] = loc 451 p.Location = append(p.Location, loc) 452 } 453 sloc = append(sloc, loc) 454 } 455 p.Sample = append(p.Sample, 456 &Sample{ 457 Value: []int64{int64(count), int64(count) * p.Period}, 458 Location: sloc, 459 }) 460 } 461 // Reached the end without finding the EOD marker. 462 return b, locs, nil 463 } 464 465 // parseHeap parses a heapz legacy or a growthz profile and 466 // returns a newly populated Profile. 467 func parseHeap(b []byte) (p *Profile, err error) { 468 r := bytes.NewBuffer(b) 469 l, err := r.ReadString('\n') 470 if err != nil { 471 return nil, errUnrecognized 472 } 473 474 sampling := "" 475 476 if header := heapHeaderRE.FindStringSubmatch(l); header != nil { 477 p = &Profile{ 478 SampleType: []*ValueType{ 479 {Type: "objects", Unit: "count"}, 480 {Type: "space", Unit: "bytes"}, 481 }, 482 PeriodType: &ValueType{Type: "objects", Unit: "bytes"}, 483 } 484 485 var period int64 486 if len(header[6]) > 0 { 487 if period, err = strconv.ParseInt(header[6], 10, 64); err != nil { 488 return nil, errUnrecognized 489 } 490 } 491 492 switch header[5] { 493 case "heapz_v2", "heap_v2": 494 sampling, p.Period = "v2", period 495 case "heapprofile": 496 sampling, p.Period = "", 1 497 case "heap": 498 sampling, p.Period = "v2", period/2 499 default: 500 return nil, errUnrecognized 501 } 502 } else if header = growthHeaderRE.FindStringSubmatch(l); header != nil { 503 p = &Profile{ 504 SampleType: []*ValueType{ 505 {Type: "objects", Unit: "count"}, 506 {Type: "space", Unit: "bytes"}, 507 }, 508 PeriodType: &ValueType{Type: "heapgrowth", Unit: "count"}, 509 Period: 1, 510 } 511 } else if header = fragmentationHeaderRE.FindStringSubmatch(l); header != nil { 512 p = &Profile{ 513 SampleType: []*ValueType{ 514 {Type: "objects", Unit: "count"}, 515 {Type: "space", Unit: "bytes"}, 516 }, 517 PeriodType: &ValueType{Type: "allocations", Unit: "count"}, 518 Period: 1, 519 } 520 } else { 521 return nil, errUnrecognized 522 } 523 524 if LegacyHeapAllocated { 525 for _, st := range p.SampleType { 526 st.Type = "alloc_" + st.Type 527 } 528 } else { 529 for _, st := range p.SampleType { 530 st.Type = "inuse_" + st.Type 531 } 532 } 533 534 locs := make(map[uint64]*Location) 535 for { 536 l, err = r.ReadString('\n') 537 if err != nil { 538 if err != io.EOF { 539 return nil, err 540 } 541 542 if l == "" { 543 break 544 } 545 } 546 547 if isSpaceOrComment(l) { 548 continue 549 } 550 l = strings.TrimSpace(l) 551 552 if sectionTrigger(l) != unrecognizedSection { 553 break 554 } 555 556 value, blocksize, addrs, err := parseHeapSample(l, p.Period, sampling) 557 if err != nil { 558 return nil, err 559 } 560 var sloc []*Location 561 for _, addr := range addrs { 562 // Addresses from stack traces point to the next instruction after 563 // each call. Adjust by -1 to land somewhere on the actual call. 564 addr-- 565 loc := locs[addr] 566 if locs[addr] == nil { 567 loc = &Location{ 568 Address: addr, 569 } 570 p.Location = append(p.Location, loc) 571 locs[addr] = loc 572 } 573 sloc = append(sloc, loc) 574 } 575 576 p.Sample = append(p.Sample, &Sample{ 577 Value: value, 578 Location: sloc, 579 NumLabel: map[string][]int64{"bytes": {blocksize}}, 580 }) 581 } 582 583 if err = parseAdditionalSections(l, r, p); err != nil { 584 return nil, err 585 } 586 return p, nil 587 } 588 589 // parseHeapSample parses a single row from a heap profile into a new Sample. 590 func parseHeapSample(line string, rate int64, sampling string) (value []int64, blocksize int64, addrs []uint64, err error) { 591 sampleData := heapSampleRE.FindStringSubmatch(line) 592 if len(sampleData) != 6 { 593 return value, blocksize, addrs, fmt.Errorf("unexpected number of sample values: got %d, want 6", len(sampleData)) 594 } 595 596 // Use first two values by default; tcmalloc sampling generates the 597 // same value for both, only the older heap-profile collect separate 598 // stats for in-use and allocated objects. 599 valueIndex := 1 600 if LegacyHeapAllocated { 601 valueIndex = 3 602 } 603 604 var v1, v2 int64 605 if v1, err = strconv.ParseInt(sampleData[valueIndex], 10, 64); err != nil { 606 return value, blocksize, addrs, fmt.Errorf("malformed sample: %s: %v", line, err) 607 } 608 if v2, err = strconv.ParseInt(sampleData[valueIndex+1], 10, 64); err != nil { 609 return value, blocksize, addrs, fmt.Errorf("malformed sample: %s: %v", line, err) 610 } 611 612 if v1 == 0 { 613 if v2 != 0 { 614 return value, blocksize, addrs, fmt.Errorf("allocation count was 0 but allocation bytes was %d", v2) 615 } 616 } else { 617 blocksize = v2 / v1 618 if sampling == "v2" { 619 v1, v2 = scaleHeapSample(v1, v2, rate) 620 } 621 } 622 623 value = []int64{v1, v2} 624 addrs = parseHexAddresses(sampleData[5]) 625 626 return value, blocksize, addrs, nil 627 } 628 629 // extractHexAddresses extracts hex numbers from a string and returns 630 // them, together with their numeric value, in a slice. 631 func extractHexAddresses(s string) ([]string, []uint64) { 632 hexStrings := hexNumberRE.FindAllString(s, -1) 633 var ids []uint64 634 for _, s := range hexStrings { 635 if id, err := strconv.ParseUint(s, 0, 64); err == nil { 636 ids = append(ids, id) 637 } else { 638 // Do not expect any parsing failures due to the regexp matching. 639 panic("failed to parse hex value:" + s) 640 } 641 } 642 return hexStrings, ids 643 } 644 645 // parseHexAddresses parses hex numbers from a string and returns them 646 // in a slice. 647 func parseHexAddresses(s string) []uint64 { 648 _, ids := extractHexAddresses(s) 649 return ids 650 } 651 652 // scaleHeapSample adjusts the data from a heapz Sample to 653 // account for its probability of appearing in the collected 654 // data. heapz profiles are a sampling of the memory allocations 655 // requests in a program. We estimate the unsampled value by dividing 656 // each collected sample by its probability of appearing in the 657 // profile. heapz v2 profiles rely on a poisson process to determine 658 // which samples to collect, based on the desired average collection 659 // rate R. The probability of a sample of size S to appear in that 660 // profile is 1-exp(-S/R). 661 func scaleHeapSample(count, size, rate int64) (int64, int64) { 662 if count == 0 || size == 0 { 663 return 0, 0 664 } 665 666 if rate <= 1 { 667 // if rate==1 all samples were collected so no adjustment is needed. 668 // if rate<1 treat as unknown and skip scaling. 669 return count, size 670 } 671 672 avgSize := float64(size) / float64(count) 673 scale := 1 / (1 - math.Exp(-avgSize/float64(rate))) 674 675 return int64(float64(count) * scale), int64(float64(size) * scale) 676 } 677 678 // parseContention parses a mutex or contention profile. There are 2 cases: 679 // "--- contentionz " for legacy C++ profiles (and backwards compatibility) 680 // "--- mutex:" or "--- contention:" for profiles generated by the Go runtime. 681 // This code converts the text output from runtime into a *Profile. (In the future 682 // the runtime might write a serialized Profile directly making this unnecessary.) 683 func parseContention(b []byte) (*Profile, error) { 684 r := bytes.NewBuffer(b) 685 var l string 686 var err error 687 for { 688 // Skip past comments and empty lines seeking a real header. 689 l, err = r.ReadString('\n') 690 if err != nil { 691 return nil, err 692 } 693 if !isSpaceOrComment(l) { 694 break 695 } 696 } 697 698 if strings.HasPrefix(l, "--- contentionz ") { 699 return parseCppContention(r) 700 } else if strings.HasPrefix(l, "--- mutex:") { 701 return parseCppContention(r) 702 } else if strings.HasPrefix(l, "--- contention:") { 703 return parseCppContention(r) 704 } 705 return nil, errUnrecognized 706 } 707 708 // parseCppContention parses the output from synchronization_profiling.cc 709 // for backward compatibility, and the compatible (non-debug) block profile 710 // output from the Go runtime. 711 func parseCppContention(r *bytes.Buffer) (*Profile, error) { 712 p := &Profile{ 713 PeriodType: &ValueType{Type: "contentions", Unit: "count"}, 714 Period: 1, 715 SampleType: []*ValueType{ 716 {Type: "contentions", Unit: "count"}, 717 {Type: "delay", Unit: "nanoseconds"}, 718 }, 719 } 720 721 var cpuHz int64 722 var l string 723 var err error 724 // Parse text of the form "attribute = value" before the samples. 725 const delimiter = '=' 726 for { 727 l, err = r.ReadString('\n') 728 if err != nil { 729 if err != io.EOF { 730 return nil, err 731 } 732 733 if l == "" { 734 break 735 } 736 } 737 if isSpaceOrComment(l) { 738 continue 739 } 740 741 if l = strings.TrimSpace(l); l == "" { 742 continue 743 } 744 745 if strings.HasPrefix(l, "---") { 746 break 747 } 748 749 index := strings.IndexByte(l, delimiter) 750 if index < 0 { 751 break 752 } 753 key := l[:index] 754 val := l[index+1:] 755 756 key, val = strings.TrimSpace(key), strings.TrimSpace(val) 757 var err error 758 switch key { 759 case "cycles/second": 760 if cpuHz, err = strconv.ParseInt(val, 0, 64); err != nil { 761 return nil, errUnrecognized 762 } 763 case "sampling period": 764 if p.Period, err = strconv.ParseInt(val, 0, 64); err != nil { 765 return nil, errUnrecognized 766 } 767 case "ms since reset": 768 ms, err := strconv.ParseInt(val, 0, 64) 769 if err != nil { 770 return nil, errUnrecognized 771 } 772 p.DurationNanos = ms * 1000 * 1000 773 case "format": 774 // CPP contentionz profiles don't have format. 775 return nil, errUnrecognized 776 case "resolution": 777 // CPP contentionz profiles don't have resolution. 778 return nil, errUnrecognized 779 case "discarded samples": 780 default: 781 return nil, errUnrecognized 782 } 783 } 784 785 locs := make(map[uint64]*Location) 786 for { 787 if !isSpaceOrComment(l) { 788 if l = strings.TrimSpace(l); strings.HasPrefix(l, "---") { 789 break 790 } 791 value, addrs, err := parseContentionSample(l, p.Period, cpuHz) 792 if err != nil { 793 return nil, err 794 } 795 var sloc []*Location 796 for _, addr := range addrs { 797 // Addresses from stack traces point to the next instruction after 798 // each call. Adjust by -1 to land somewhere on the actual call. 799 addr-- 800 loc := locs[addr] 801 if locs[addr] == nil { 802 loc = &Location{ 803 Address: addr, 804 } 805 p.Location = append(p.Location, loc) 806 locs[addr] = loc 807 } 808 sloc = append(sloc, loc) 809 } 810 p.Sample = append(p.Sample, &Sample{ 811 Value: value, 812 Location: sloc, 813 }) 814 } 815 816 if l, err = r.ReadString('\n'); err != nil { 817 if err != io.EOF { 818 return nil, err 819 } 820 if l == "" { 821 break 822 } 823 } 824 } 825 826 if err = parseAdditionalSections(l, r, p); err != nil { 827 return nil, err 828 } 829 830 return p, nil 831 } 832 833 // parseContentionSample parses a single row from a contention profile 834 // into a new Sample. 835 func parseContentionSample(line string, period, cpuHz int64) (value []int64, addrs []uint64, err error) { 836 sampleData := contentionSampleRE.FindStringSubmatch(line) 837 if sampleData == nil { 838 return value, addrs, errUnrecognized 839 } 840 841 v1, err := strconv.ParseInt(sampleData[1], 10, 64) 842 if err != nil { 843 return value, addrs, fmt.Errorf("malformed sample: %s: %v", line, err) 844 } 845 v2, err := strconv.ParseInt(sampleData[2], 10, 64) 846 if err != nil { 847 return value, addrs, fmt.Errorf("malformed sample: %s: %v", line, err) 848 } 849 850 // Unsample values if period and cpuHz are available. 851 // - Delays are scaled to cycles and then to nanoseconds. 852 // - Contentions are scaled to cycles. 853 if period > 0 { 854 if cpuHz > 0 { 855 cpuGHz := float64(cpuHz) / 1e9 856 v1 = int64(float64(v1) * float64(period) / cpuGHz) 857 } 858 v2 = v2 * period 859 } 860 861 value = []int64{v2, v1} 862 addrs = parseHexAddresses(sampleData[3]) 863 864 return value, addrs, nil 865 } 866 867 // parseThread parses a Threadz profile and returns a new Profile. 868 func parseThread(b []byte) (*Profile, error) { 869 r := bytes.NewBuffer(b) 870 871 var line string 872 var err error 873 for { 874 // Skip past comments and empty lines seeking a real header. 875 line, err = r.ReadString('\n') 876 if err != nil { 877 return nil, err 878 } 879 if !isSpaceOrComment(line) { 880 break 881 } 882 } 883 884 if m := threadzStartRE.FindStringSubmatch(line); m != nil { 885 // Advance over initial comments until first stack trace. 886 for { 887 line, err = r.ReadString('\n') 888 if err != nil { 889 if err != io.EOF { 890 return nil, err 891 } 892 893 if line == "" { 894 break 895 } 896 } 897 if sectionTrigger(line) != unrecognizedSection || line[0] == '-' { 898 break 899 } 900 } 901 } else if t := threadStartRE.FindStringSubmatch(line); len(t) != 4 { 902 return nil, errUnrecognized 903 } 904 905 p := &Profile{ 906 SampleType: []*ValueType{{Type: "thread", Unit: "count"}}, 907 PeriodType: &ValueType{Type: "thread", Unit: "count"}, 908 Period: 1, 909 } 910 911 locs := make(map[uint64]*Location) 912 // Recognize each thread and populate profile samples. 913 for sectionTrigger(line) == unrecognizedSection { 914 if strings.HasPrefix(line, "---- no stack trace for") { 915 line = "" 916 break 917 } 918 if t := threadStartRE.FindStringSubmatch(line); len(t) != 4 { 919 return nil, errUnrecognized 920 } 921 922 var addrs []uint64 923 line, addrs, err = parseThreadSample(r) 924 if err != nil { 925 return nil, errUnrecognized 926 } 927 if len(addrs) == 0 { 928 // We got a --same as previous threads--. Bump counters. 929 if len(p.Sample) > 0 { 930 s := p.Sample[len(p.Sample)-1] 931 s.Value[0]++ 932 } 933 continue 934 } 935 936 var sloc []*Location 937 for _, addr := range addrs { 938 // Addresses from stack traces point to the next instruction after 939 // each call. Adjust by -1 to land somewhere on the actual call. 940 addr-- 941 loc := locs[addr] 942 if locs[addr] == nil { 943 loc = &Location{ 944 Address: addr, 945 } 946 p.Location = append(p.Location, loc) 947 locs[addr] = loc 948 } 949 sloc = append(sloc, loc) 950 } 951 952 p.Sample = append(p.Sample, &Sample{ 953 Value: []int64{1}, 954 Location: sloc, 955 }) 956 } 957 958 if err = parseAdditionalSections(line, r, p); err != nil { 959 return nil, err 960 } 961 962 return p, nil 963 } 964 965 // parseThreadSample parses a symbolized or unsymbolized stack trace. 966 // Returns the first line after the traceback, the sample (or nil if 967 // it hits a 'same-as-previous' marker) and an error. 968 func parseThreadSample(b *bytes.Buffer) (nextl string, addrs []uint64, err error) { 969 var l string 970 sameAsPrevious := false 971 for { 972 if l, err = b.ReadString('\n'); err != nil { 973 if err != io.EOF { 974 return "", nil, err 975 } 976 if l == "" { 977 break 978 } 979 } 980 if l = strings.TrimSpace(l); l == "" { 981 continue 982 } 983 984 if strings.HasPrefix(l, "---") { 985 break 986 } 987 if strings.Contains(l, "same as previous thread") { 988 sameAsPrevious = true 989 continue 990 } 991 992 addrs = append(addrs, parseHexAddresses(l)...) 993 } 994 995 if sameAsPrevious { 996 return l, nil, nil 997 } 998 return l, addrs, nil 999 } 1000 1001 // parseAdditionalSections parses any additional sections in the 1002 // profile, ignoring any unrecognized sections. 1003 func parseAdditionalSections(l string, b *bytes.Buffer, p *Profile) (err error) { 1004 for { 1005 if sectionTrigger(l) == memoryMapSection { 1006 break 1007 } 1008 // Ignore any unrecognized sections. 1009 if l, err := b.ReadString('\n'); err != nil { 1010 if err != io.EOF { 1011 return err 1012 } 1013 if l == "" { 1014 break 1015 } 1016 } 1017 } 1018 return p.ParseMemoryMap(b) 1019 } 1020 1021 // ParseMemoryMap parses a memory map in the format of 1022 // /proc/self/maps, and overrides the mappings in the current profile. 1023 // It renumbers the samples and locations in the profile correspondingly. 1024 func (p *Profile) ParseMemoryMap(rd io.Reader) error { 1025 b := bufio.NewReader(rd) 1026 1027 var attrs []string 1028 var r *strings.Replacer 1029 const delimiter = '=' 1030 for { 1031 l, err := b.ReadString('\n') 1032 if err != nil { 1033 if err != io.EOF { 1034 return err 1035 } 1036 if l == "" { 1037 break 1038 } 1039 } 1040 if l = strings.TrimSpace(l); l == "" { 1041 continue 1042 } 1043 1044 if r != nil { 1045 l = r.Replace(l) 1046 } 1047 m, err := parseMappingEntry(l) 1048 if err != nil { 1049 if err == errUnrecognized { 1050 // Recognize assignments of the form: attr=value, and replace 1051 // $attr with value on subsequent mappings. 1052 idx := strings.IndexByte(l, delimiter) 1053 if idx >= 0 { 1054 attr := l[:idx] 1055 value := l[idx+1:] 1056 attrs = append(attrs, "$"+strings.TrimSpace(attr), strings.TrimSpace(value)) 1057 r = strings.NewReplacer(attrs...) 1058 } 1059 // Ignore any unrecognized entries 1060 continue 1061 } 1062 return err 1063 } 1064 if m == nil || (m.File == "" && len(p.Mapping) != 0) { 1065 // In some cases the first entry may include the address range 1066 // but not the name of the file. It should be followed by 1067 // another entry with the name. 1068 continue 1069 } 1070 if len(p.Mapping) == 1 && p.Mapping[0].File == "" { 1071 // Update the name if this is the entry following that empty one. 1072 p.Mapping[0].File = m.File 1073 continue 1074 } 1075 p.Mapping = append(p.Mapping, m) 1076 } 1077 p.remapLocationIDs() 1078 p.remapFunctionIDs() 1079 p.remapMappingIDs() 1080 return nil 1081 } 1082 1083 func parseMappingEntry(l string) (*Mapping, error) { 1084 mapping := &Mapping{} 1085 var err error 1086 if me := procMapsRE.FindStringSubmatch(l); len(me) == 9 { 1087 if !strings.Contains(me[3], "x") { 1088 // Skip non-executable entries. 1089 return nil, nil 1090 } 1091 if mapping.Start, err = strconv.ParseUint(me[1], 16, 64); err != nil { 1092 return nil, errUnrecognized 1093 } 1094 if mapping.Limit, err = strconv.ParseUint(me[2], 16, 64); err != nil { 1095 return nil, errUnrecognized 1096 } 1097 if me[4] != "" { 1098 if mapping.Offset, err = strconv.ParseUint(me[4], 16, 64); err != nil { 1099 return nil, errUnrecognized 1100 } 1101 } 1102 mapping.File = me[8] 1103 return mapping, nil 1104 } 1105 1106 if me := briefMapsRE.FindStringSubmatch(l); len(me) == 6 { 1107 if mapping.Start, err = strconv.ParseUint(me[1], 16, 64); err != nil { 1108 return nil, errUnrecognized 1109 } 1110 if mapping.Limit, err = strconv.ParseUint(me[2], 16, 64); err != nil { 1111 return nil, errUnrecognized 1112 } 1113 mapping.File = me[3] 1114 if me[5] != "" { 1115 if mapping.Offset, err = strconv.ParseUint(me[5], 16, 64); err != nil { 1116 return nil, errUnrecognized 1117 } 1118 } 1119 return mapping, nil 1120 } 1121 1122 return nil, errUnrecognized 1123 } 1124 1125 type sectionType int 1126 1127 const ( 1128 unrecognizedSection sectionType = iota 1129 memoryMapSection 1130 ) 1131 1132 var memoryMapTriggers = []string{ 1133 "--- Memory map: ---", 1134 "MAPPED_LIBRARIES:", 1135 } 1136 1137 func sectionTrigger(line string) sectionType { 1138 for _, trigger := range memoryMapTriggers { 1139 if strings.Contains(line, trigger) { 1140 return memoryMapSection 1141 } 1142 } 1143 return unrecognizedSection 1144 } 1145 1146 func (p *Profile) addLegacyFrameInfo() { 1147 switch { 1148 case isProfileType(p, heapzSampleTypes) || 1149 isProfileType(p, heapzInUseSampleTypes) || 1150 isProfileType(p, heapzAllocSampleTypes): 1151 p.DropFrames, p.KeepFrames = allocRxStr, allocSkipRxStr 1152 case isProfileType(p, contentionzSampleTypes): 1153 p.DropFrames, p.KeepFrames = lockRxStr, "" 1154 default: 1155 p.DropFrames, p.KeepFrames = cpuProfilerRxStr, "" 1156 } 1157 } 1158 1159 var heapzSampleTypes = []string{"allocations", "size"} // early Go pprof profiles 1160 var heapzInUseSampleTypes = []string{"inuse_objects", "inuse_space"} 1161 var heapzAllocSampleTypes = []string{"alloc_objects", "alloc_space"} 1162 var contentionzSampleTypes = []string{"contentions", "delay"} 1163 1164 func isProfileType(p *Profile, t []string) bool { 1165 st := p.SampleType 1166 if len(st) != len(t) { 1167 return false 1168 } 1169 1170 for i := range st { 1171 if st[i].Type != t[i] { 1172 return false 1173 } 1174 } 1175 return true 1176 } 1177 1178 var allocRxStr = strings.Join([]string{ 1179 // POSIX entry points. 1180 `calloc`, 1181 `cfree`, 1182 `malloc`, 1183 `free`, 1184 `memalign`, 1185 `do_memalign`, 1186 `(__)?posix_memalign`, 1187 `pvalloc`, 1188 `valloc`, 1189 `realloc`, 1190 1191 // TC malloc. 1192 `tcmalloc::.*`, 1193 `tc_calloc`, 1194 `tc_cfree`, 1195 `tc_malloc`, 1196 `tc_free`, 1197 `tc_memalign`, 1198 `tc_posix_memalign`, 1199 `tc_pvalloc`, 1200 `tc_valloc`, 1201 `tc_realloc`, 1202 `tc_new`, 1203 `tc_delete`, 1204 `tc_newarray`, 1205 `tc_deletearray`, 1206 `tc_new_nothrow`, 1207 `tc_newarray_nothrow`, 1208 1209 // Memory-allocation routines on OS X. 1210 `malloc_zone_malloc`, 1211 `malloc_zone_calloc`, 1212 `malloc_zone_valloc`, 1213 `malloc_zone_realloc`, 1214 `malloc_zone_memalign`, 1215 `malloc_zone_free`, 1216 1217 // Go runtime 1218 `runtime\..*`, 1219 1220 // Other misc. memory allocation routines 1221 `BaseArena::.*`, 1222 `(::)?do_malloc_no_errno`, 1223 `(::)?do_malloc_pages`, 1224 `(::)?do_malloc`, 1225 `DoSampledAllocation`, 1226 `MallocedMemBlock::MallocedMemBlock`, 1227 `_M_allocate`, 1228 `__builtin_(vec_)?delete`, 1229 `__builtin_(vec_)?new`, 1230 `__gnu_cxx::new_allocator::allocate`, 1231 `__libc_malloc`, 1232 `__malloc_alloc_template::allocate`, 1233 `allocate`, 1234 `cpp_alloc`, 1235 `operator new(\[\])?`, 1236 `simple_alloc::allocate`, 1237 }, `|`) 1238 1239 var allocSkipRxStr = strings.Join([]string{ 1240 // Preserve Go runtime frames that appear in the middle/bottom of 1241 // the stack. 1242 `runtime\.panic`, 1243 `runtime\.reflectcall`, 1244 `runtime\.call[0-9]*`, 1245 }, `|`) 1246 1247 var cpuProfilerRxStr = strings.Join([]string{ 1248 `ProfileData::Add`, 1249 `ProfileData::prof_handler`, 1250 `CpuProfiler::prof_handler`, 1251 `__pthread_sighandler`, 1252 `__restore`, 1253 }, `|`) 1254 1255 var lockRxStr = strings.Join([]string{ 1256 `RecordLockProfileData`, 1257 `(base::)?RecordLockProfileData.*`, 1258 `(base::)?SubmitMutexProfileData.*`, 1259 `(base::)?SubmitSpinLockProfileData.*`, 1260 `(Mutex::)?AwaitCommon.*`, 1261 `(Mutex::)?Unlock.*`, 1262 `(Mutex::)?UnlockSlow.*`, 1263 `(Mutex::)?ReaderUnlock.*`, 1264 `(MutexLock::)?~MutexLock.*`, 1265 `(SpinLock::)?Unlock.*`, 1266 `(SpinLock::)?SlowUnlock.*`, 1267 `(SpinLockHolder::)?~SpinLockHolder.*`, 1268 }, `|`)