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