// Copyright 2014 Google Inc. All Rights Reserved.//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.// This file implements parsers to convert legacy profiles into the// profile.proto format.package profileimport ()var ( countStartRE = regexp.MustCompile(`\A(\S+) profile: total \d+\z`) countRE = regexp.MustCompile(`\A(\d+) @(( 0x[0-9a-f]+)+)\z`) heapHeaderRE = regexp.MustCompile(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] *@ *(heap[_a-z0-9]*)/?(\d*)`) heapSampleRE = regexp.MustCompile(`(-?\d+): *(-?\d+) *\[ *(\d+): *(\d+) *] @([ x0-9a-f]*)`) contentionSampleRE = regexp.MustCompile(`(\d+) *(\d+) @([ x0-9a-f]*)`) hexNumberRE = regexp.MustCompile(`0x[0-9a-f]+`) growthHeaderRE = regexp.MustCompile(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] @ growthz?`) fragmentationHeaderRE = regexp.MustCompile(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] @ fragmentationz?`) threadzStartRE = regexp.MustCompile(`--- threadz \d+ ---`) threadStartRE = regexp.MustCompile(`--- Thread ([[:xdigit:]]+) \(name: (.*)/(\d+)\) stack: ---`)// Regular expressions to parse process mappings. Support the format used by Linux /proc/.../maps and other tools. // Recommended format: // Start End object file name offset(optional) linker build id // 0x40000-0x80000 /path/to/binary (@FF00) abc123456 spaceDigits = `\s+[[:digit:]]+` hexPair = `\s+[[:xdigit:]]+:[[:xdigit:]]+` oSpace = `\s*`// Capturing expressions. cHex = `(?:0x)?([[:xdigit:]]+)` cHexRange = `\s*` + cHex + `[\s-]?` + oSpace + cHex + `:?` cSpaceString = `(?:\s+(\S+))?` cSpaceHex = `(?:\s+([[:xdigit:]]+))?` cSpaceAtOffset = `(?:\s+\(@([[:xdigit:]]+)\))?` cPerm = `(?:\s+([-rwxp]+))?` procMapsRE = regexp.MustCompile(`^` + cHexRange + cPerm + cSpaceHex + hexPair + spaceDigits + cSpaceString) briefMapsRE = regexp.MustCompile(`^` + cHexRange + cPerm + cSpaceString + cSpaceAtOffset + cSpaceHex)// Regular expression to parse log data, of the form: // ... file:line] msg... logInfoRE = regexp.MustCompile(`^[^\[\]]+:[0-9]+]\s`))func isSpaceOrComment( string) bool { := strings.TrimSpace()returnlen() == 0 || [0] == '#'}// parseGoCount parses a Go count profile (e.g., threadcreate or// goroutine) and returns a new Profile.func parseGoCount( []byte) (*Profile, error) { := bufio.NewScanner(bytes.NewBuffer())// Skip comments at the beginning of the file.for .Scan() && isSpaceOrComment(.Text()) { }if := .Err(); != nil {returnnil, } := countStartRE.FindStringSubmatch(.Text())if == nil {returnnil, errUnrecognized } := [1] := &Profile{PeriodType: &ValueType{Type: , Unit: "count"},Period: 1,SampleType: []*ValueType{{Type: , Unit: "count"}}, } := make(map[uint64]*Location)for .Scan() { := .Text()ifisSpaceOrComment() {continue }ifstrings.HasPrefix(, "---") {break } := countRE.FindStringSubmatch()if == nil {returnnil, errMalformed } , := strconv.ParseInt([1], 0, 64)if != nil {returnnil, errMalformed } := strings.Fields([2]) := make([]*Location, 0, len())for , := range { , := strconv.ParseUint(, 0, 64)if != nil {returnnil, errMalformed }// Adjust all frames by -1 to land on top of the call instruction. -- := []if == nil { = &Location{Address: , } [] = .Location = append(.Location, ) } = append(, ) } .Sample = append(.Sample, &Sample{Location: ,Value: []int64{}, }) }if := .Err(); != nil {returnnil, }if := parseAdditionalSections(, ); != nil {returnnil, }return , nil}// remapLocationIDs ensures there is a location for each address// referenced by a sample, and remaps the samples to point to the new// location ids.func ( *Profile) () { := make(map[*Location]bool, len(.Location))var []*Locationfor , := range .Sample {for , := range .Location {if [] {continue } .ID = uint64(len() + 1) = append(, ) [] = true } } .Location = }func ( *Profile) () { := make(map[*Function]bool, len(.Function))var []*Functionfor , := range .Location {for , := range .Line { := .Functionif == nil || [] {continue } .ID = uint64(len() + 1) = append(, ) [] = true } } .Function = }// remapMappingIDs matches location addresses with existing mappings// and updates them appropriately. This is O(N*M), if this ever shows// up as a bottleneck, evaluate sorting the mappings and doing a// binary search, which would make it O(N*log(M)).func ( *Profile) () {// Some profile handlers will incorrectly set regions for the main // executable if its section is remapped. Fix them through heuristics.iflen(.Mapping) > 0 {// Remove the initial mapping if named '/anon_hugepage' and has a // consecutive adjacent mapping.if := .Mapping[0]; strings.HasPrefix(.File, "/anon_hugepage") {iflen(.Mapping) > 1 && .Limit == .Mapping[1].Start { .Mapping = .Mapping[1:] } } }// Subtract the offset from the start of the main mapping if it // ends up at a recognizable start address.iflen(.Mapping) > 0 {const = 0x400000if := .Mapping[0]; .Start-.Offset == { .Start = .Offset = 0 } }// Associate each location with an address to the corresponding // mapping. Create fake mapping if a suitable one isn't found.var *Mapping:for , := range .Location { := .Addressif .Mapping != nil || == 0 {continue }for , := range .Mapping {if .Start <= && < .Limit { .Mapping = continue } }// Work around legacy handlers failing to encode the first // part of mappings split into adjacent ranges.for , := range .Mapping {if .Offset != 0 && .Start-.Offset <= && < .Start { .Start -= .Offset .Offset = 0 .Mapping = continue } }// If there is still no mapping, create a fake one. // This is important for the Go legacy handler, which produced // no mappings.if == nil { = &Mapping{ID: 1,Limit: ^uint64(0), } .Mapping = append(.Mapping, ) } .Mapping = }// Reset all mapping IDs.for , := range .Mapping { .ID = uint64( + 1) }}var cpuInts = []func([]byte) (uint64, []byte){get32l,get32b,get64l,get64b,}func get32l( []byte) (uint64, []byte) {iflen() < 4 {return0, nil }returnuint64([0]) | uint64([1])<<8 | uint64([2])<<16 | uint64([3])<<24, [4:]}func get32b( []byte) (uint64, []byte) {iflen() < 4 {return0, nil }returnuint64([3]) | uint64([2])<<8 | uint64([1])<<16 | uint64([0])<<24, [4:]}func get64l( []byte) (uint64, []byte) {iflen() < 8 {return0, nil }returnuint64([0]) | uint64([1])<<8 | uint64([2])<<16 | uint64([3])<<24 | uint64([4])<<32 | uint64([5])<<40 | uint64([6])<<48 | uint64([7])<<56, [8:]}func get64b( []byte) (uint64, []byte) {iflen() < 8 {return0, nil }returnuint64([7]) | uint64([6])<<8 | uint64([5])<<16 | uint64([4])<<24 | uint64([3])<<32 | uint64([2])<<40 | uint64([1])<<48 | uint64([0])<<56, [8:]}// parseCPU parses a profilez legacy profile and returns a newly// populated Profile.//// The general format for profilez samples is a sequence of words in// binary format. The first words are a header with the following data://// 1st word -- 0// 2nd word -- 3// 3rd word -- 0 if a c++ application, 1 if a java application.// 4th word -- Sampling period (in microseconds).// 5th word -- Padding.func parseCPU( []byte) (*Profile, error) {varfunc([]byte) (uint64, []byte)var , , , , uint64for _, = rangecpuInts {var []byte , = () , = () , = () , = () , = ()if != nil && == 0 && == 3 && == 0 && > 0 && == 0 { = returncpuProfile(, int64(), ) }if != nil && == 0 && == 3 && == 1 && > 0 && == 0 { = returnjavaCPUProfile(, int64(), ) } }returnnil, errUnrecognized}// cpuProfile returns a new Profile from C++ profilez data.// b is the profile bytes after the header, period is the profiling// period, and parse is a function to parse 8-byte chunks from the// profile in its native endianness.func cpuProfile( []byte, int64, func( []byte) (uint64, []byte)) (*Profile, error) { := &Profile{Period: * 1000,PeriodType: &ValueType{Type: "cpu", Unit: "nanoseconds"},SampleType: []*ValueType{ {Type: "samples", Unit: "count"}, {Type: "cpu", Unit: "nanoseconds"}, }, }varerrorif , _, = parseCPUSamples(, , true, ); != nil {returnnil, }// If *most* samples have the same second-to-the-bottom frame, it // strongly suggests that it is an uninteresting artifact of // measurement -- a stack frame pushed by the signal handler. The // bottom frame is always correct as it is picked up from the signal // structure, not the stack. Check if this is the case and if so, // remove.// Remove up to two frames. := 2// Allow one different sample for this many samples with the same // second-to-last frame. := 32 := len(.Sample) / for := 0; < ; ++ { := make(map[uint64]int)for , := range .Sample {iflen(.Location) > 1 { := .Location[1].Address [] = [] + 1 } }for , := range {if >= len(.Sample)- {// Found uninteresting frame, strip it out from all samplesfor , := range .Sample {iflen(.Location) > 1 && .Location[1].Address == { .Location = append(.Location[:1], .Location[2:]...) } }break } } }if := .ParseMemoryMap(bytes.NewBuffer()); != nil {returnnil, }cleanupDuplicateLocations()return , nil}func cleanupDuplicateLocations( *Profile) {// The profile handler may duplicate the leaf frame, because it gets // its address both from stack unwinding and from the signal // context. Detect this and delete the duplicate, which has been // adjusted by -1. The leaf address should not be adjusted as it is // not a call.for , := range .Sample {iflen(.Location) > 1 && .Location[0].Address == .Location[1].Address+1 { .Location = append(.Location[:1], .Location[2:]...) } }}// parseCPUSamples parses a collection of profilez samples from a// profile.//// profilez samples are a repeated sequence of stack frames of the// form://// 1st word -- The number of times this stack was encountered.// 2nd word -- The size of the stack (StackSize).// 3rd word -- The first address on the stack.// ...// StackSize + 2 -- The last address on the stack//// The last stack trace is of the form://// 1st word -- 0// 2nd word -- 1// 3rd word -- 0//// Addresses from stack traces may point to the next instruction after// each call. Optionally adjust by -1 to land somewhere on the actual// call (except for the leaf, which is not a call).func parseCPUSamples( []byte, func( []byte) (uint64, []byte), bool, *Profile) ([]byte, map[uint64]*Location, error) { := make(map[uint64]*Location)forlen() > 0 {var , uint64 , = () , = ()if == nil || > uint64(len()/4) {returnnil, nil, errUnrecognized }var []*Location := make([]uint64, )for := 0; < int(); ++ { [], = () }if == 0 && == 1 && [0] == 0 {// End of data markerbreak }for , := range {if && > 0 { -- } := []if == nil { = &Location{Address: , } [] = .Location = append(.Location, ) } = append(, ) } .Sample = append(.Sample, &Sample{Value: []int64{int64(), int64() * .Period},Location: , }) }// Reached the end without finding the EOD marker.return , , nil}// parseHeap parses a heapz legacy or a growthz profile and// returns a newly populated Profile.func parseHeap( []byte) ( *Profile, error) { := bufio.NewScanner(bytes.NewBuffer())if !.Scan() {if := .Err(); != nil {returnnil, }returnnil, errUnrecognized } = &Profile{} := "" := false := .Text() .PeriodType = &ValueType{Type: "space", Unit: "bytes"}if := heapHeaderRE.FindStringSubmatch(); != nil { , .Period, , = parseHeapHeader()if != nil {returnnil, } } elseif = growthHeaderRE.FindStringSubmatch(); != nil { .Period = 1 } elseif = fragmentationHeaderRE.FindStringSubmatch(); != nil { .Period = 1 } else {returnnil, errUnrecognized }if {// Put alloc before inuse so that default pprof selection // will prefer inuse_space. .SampleType = []*ValueType{ {Type: "alloc_objects", Unit: "count"}, {Type: "alloc_space", Unit: "bytes"}, {Type: "inuse_objects", Unit: "count"}, {Type: "inuse_space", Unit: "bytes"}, } } else { .SampleType = []*ValueType{ {Type: "objects", Unit: "count"}, {Type: "space", Unit: "bytes"}, } } := make(map[uint64]*Location)for .Scan() { := strings.TrimSpace(.Text())ifisSpaceOrComment() {continue }ifisMemoryMapSentinel() {break } , , , := parseHeapSample(, .Period, , )if != nil {returnnil, }var []*Locationfor , := range {// Addresses from stack traces point to the next instruction after // each call. Adjust by -1 to land somewhere on the actual call. -- := []if [] == nil { = &Location{Address: , } .Location = append(.Location, ) [] = } = append(, ) } .Sample = append(.Sample, &Sample{Value: ,Location: ,NumLabel: map[string][]int64{"bytes": {}}, }) }if := .Err(); != nil {returnnil, }if := parseAdditionalSections(, ); != nil {returnnil, }return , nil}func parseHeapHeader( string) ( string, int64, bool, error) { := heapHeaderRE.FindStringSubmatch()if == nil {return"", 0, false, errUnrecognized }iflen([6]) > 0 {if , = strconv.ParseInt([6], 10, 64); != nil {return"", 0, false, errUnrecognized } }if ([3] != [1] && [3] != "0") || ([4] != [2] && [4] != "0") { = true }switch [5] {case"heapz_v2", "heap_v2":return"v2", , , nilcase"heapprofile":return"", 1, , nilcase"heap":return"v2", / 2, , nildefault:return"", 0, false, errUnrecognized }}// parseHeapSample parses a single row from a heap profile into a new Sample.func parseHeapSample( string, int64, string, bool) ( []int64, int64, []uint64, error) { := heapSampleRE.FindStringSubmatch()iflen() != 6 {returnnil, 0, nil, fmt.Errorf("unexpected number of sample values: got %d, want 6", len()) }// This is a local-scoped helper function to avoid needing to pass // around rate, sampling and many return parameters. := func(, string, string) error { , := strconv.ParseInt(, 10, 64)if != nil {returnfmt.Errorf("malformed sample: %s: %v", , ) } , := strconv.ParseInt(, 10, 64)if != nil {returnfmt.Errorf("malformed sample: %s: %v", , ) }if == 0 && != 0 {returnfmt.Errorf("%s count was 0 but %s bytes was %d", , , ) }if != 0 { = / if == "v2" { , = scaleHeapSample(, , ) } } = append(, , )returnnil }if {if := ([3], [4], "allocation"); != nil {returnnil, 0, nil, } }if := ([1], [2], "inuse"); != nil {returnnil, 0, nil, } , = parseHexAddresses([5])if != nil {returnnil, 0, nil, fmt.Errorf("malformed sample: %s: %v", , ) }return , , , nil}// parseHexAddresses extracts hex numbers from a string, attempts to convert// each to an unsigned 64-bit number and returns the resulting numbers as a// slice, or an error if the string contains hex numbers which are too large to// handle (which means a malformed profile).func parseHexAddresses( string) ([]uint64, error) { := hexNumberRE.FindAllString(, -1)var []uint64for , := range {if , := strconv.ParseUint(, 0, 64); == nil { = append(, ) } else {returnnil, fmt.Errorf("failed to parse as hex 64-bit number: %s", ) } }return , nil}// scaleHeapSample adjusts the data from a heapz Sample to// account for its probability of appearing in the collected// data. heapz profiles are a sampling of the memory allocations// requests in a program. We estimate the unsampled value by dividing// each collected sample by its probability of appearing in the// profile. heapz v2 profiles rely on a poisson process to determine// which samples to collect, based on the desired average collection// rate R. The probability of a sample of size S to appear in that// profile is 1-exp(-S/R).func scaleHeapSample(, , int64) (int64, int64) {if == 0 || == 0 {return0, 0 }if <= 1 {// if rate==1 all samples were collected so no adjustment is needed. // if rate<1 treat as unknown and skip scaling.return , } := float64() / float64() := 1 / (1 - math.Exp(-/float64()))returnint64(float64() * ), int64(float64() * )}// parseContention parses a mutex or contention profile. There are 2 cases:// "--- contentionz " for legacy C++ profiles (and backwards compatibility)// "--- mutex:" or "--- contention:" for profiles generated by the Go runtime.func parseContention( []byte) (*Profile, error) { := bufio.NewScanner(bytes.NewBuffer())if !.Scan() {if := .Err(); != nil {returnnil, }returnnil, errUnrecognized }switch := .Text(); {casestrings.HasPrefix(, "--- contentionz "):casestrings.HasPrefix(, "--- mutex:"):casestrings.HasPrefix(, "--- contention:"):default:returnnil, errUnrecognized } := &Profile{PeriodType: &ValueType{Type: "contentions", Unit: "count"},Period: 1,SampleType: []*ValueType{ {Type: "contentions", Unit: "count"}, {Type: "delay", Unit: "nanoseconds"}, }, }varint64// Parse text of the form "attribute = value" before the samples.const = "="for .Scan() { := .Text()if = strings.TrimSpace(); isSpaceOrComment() {continue }ifstrings.HasPrefix(, "---") {break } := strings.SplitN(, , 2)iflen() != 2 {break } , := strings.TrimSpace([0]), strings.TrimSpace([1])varerrorswitch {case"cycles/second":if , = strconv.ParseInt(, 0, 64); != nil {returnnil, errUnrecognized }case"sampling period":if .Period, = strconv.ParseInt(, 0, 64); != nil {returnnil, errUnrecognized }case"ms since reset": , := strconv.ParseInt(, 0, 64)if != nil {returnnil, errUnrecognized } .DurationNanos = * 1000 * 1000case"format":// CPP contentionz profiles don't have format.returnnil, errUnrecognizedcase"resolution":// CPP contentionz profiles don't have resolution.returnnil, errUnrecognizedcase"discarded samples":default:returnnil, errUnrecognized } }if := .Err(); != nil {returnnil, } := make(map[uint64]*Location)for { := strings.TrimSpace(.Text())ifstrings.HasPrefix(, "---") {break }if !isSpaceOrComment() { , , := parseContentionSample(, .Period, )if != nil {returnnil, }var []*Locationfor , := range {// Addresses from stack traces point to the next instruction after // each call. Adjust by -1 to land somewhere on the actual call. -- := []if [] == nil { = &Location{Address: , } .Location = append(.Location, ) [] = } = append(, ) } .Sample = append(.Sample, &Sample{Value: ,Location: , }) }if !.Scan() {break } }if := .Err(); != nil {returnnil, }if := parseAdditionalSections(, ); != nil {returnnil, }return , nil}// parseContentionSample parses a single row from a contention profile// into a new Sample.func parseContentionSample( string, , int64) ( []int64, []uint64, error) { := contentionSampleRE.FindStringSubmatch()if == nil {returnnil, nil, errUnrecognized } , := strconv.ParseInt([1], 10, 64)if != nil {returnnil, nil, fmt.Errorf("malformed sample: %s: %v", , ) } , := strconv.ParseInt([2], 10, 64)if != nil {returnnil, nil, fmt.Errorf("malformed sample: %s: %v", , ) }// Unsample values if period and cpuHz are available. // - Delays are scaled to cycles and then to nanoseconds. // - Contentions are scaled to cycles.if > 0 {if > 0 { := float64() / 1e9 = int64(float64() * float64() / ) } = * } = []int64{, } , = parseHexAddresses([3])if != nil {returnnil, nil, fmt.Errorf("malformed sample: %s: %v", , ) }return , , nil}// parseThread parses a Threadz profile and returns a new Profile.func parseThread( []byte) (*Profile, error) { := bufio.NewScanner(bytes.NewBuffer())// Skip past comments and empty lines seeking a real header.for .Scan() && isSpaceOrComment(.Text()) { } := .Text()if := threadzStartRE.FindStringSubmatch(); != nil {// Advance over initial comments until first stack trace.for .Scan() {if = .Text(); isMemoryMapSentinel() || strings.HasPrefix(, "-") {break } } } elseif := threadStartRE.FindStringSubmatch(); len() != 4 {returnnil, errUnrecognized } := &Profile{SampleType: []*ValueType{{Type: "thread", Unit: "count"}},PeriodType: &ValueType{Type: "thread", Unit: "count"},Period: 1, } := make(map[uint64]*Location)// Recognize each thread and populate profile samples.for !isMemoryMapSentinel() {ifstrings.HasPrefix(, "---- no stack trace for") {break }if := threadStartRE.FindStringSubmatch(); len() != 4 {returnnil, errUnrecognized }var []uint64varerror , , = parseThreadSample()if != nil {returnnil, }iflen() == 0 {// We got a --same as previous threads--. Bump counters.iflen(.Sample) > 0 { := .Sample[len(.Sample)-1] .Value[0]++ }continue }var []*Locationfor , := range {// Addresses from stack traces point to the next instruction after // each call. Adjust by -1 to land somewhere on the actual call // (except for the leaf, which is not a call).if > 0 { -- } := []if [] == nil { = &Location{Address: , } .Location = append(.Location, ) [] = } = append(, ) } .Sample = append(.Sample, &Sample{Value: []int64{1},Location: , }) }if := parseAdditionalSections(, ); != nil {returnnil, }cleanupDuplicateLocations()return , nil}// parseThreadSample parses a symbolized or unsymbolized stack trace.// Returns the first line after the traceback, the sample (or nil if// it hits a 'same-as-previous' marker) and an error.func parseThreadSample( *bufio.Scanner) ( string, []uint64, error) {varstring := falsefor .Scan() { = strings.TrimSpace(.Text())if == "" {continue }ifstrings.HasPrefix(, "---") {break }ifstrings.Contains(, "same as previous thread") { = truecontinue } , := parseHexAddresses()if != nil {return"", nil, fmt.Errorf("malformed sample: %s: %v", , ) } = append(, ...) }if := .Err(); != nil {return"", nil, }if {return , nil, nil }return , , nil}// parseAdditionalSections parses any additional sections in the// profile, ignoring any unrecognized sections.func parseAdditionalSections( *bufio.Scanner, *Profile) error {for !isMemoryMapSentinel(.Text()) && .Scan() { }if := .Err(); != nil {return }return .ParseMemoryMapFromScanner()}// ParseProcMaps parses a memory map in the format of /proc/self/maps.// ParseMemoryMap should be called after setting on a profile to// associate locations to the corresponding mapping based on their// address.func ( io.Reader) ([]*Mapping, error) { := bufio.NewScanner()returnparseProcMapsFromScanner()}func parseProcMapsFromScanner( *bufio.Scanner) ([]*Mapping, error) {var []*Mappingvar []stringconst = "=" := strings.NewReplacer()for .Scan() { := .Replace(removeLoggingInfo(.Text())) , := parseMappingEntry()if != nil {if == errUnrecognized {// Recognize assignments of the form: attr=value, and replace // $attr with value on subsequent mappings.if := strings.SplitN(, , 2); len() == 2 { = append(, "$"+strings.TrimSpace([0]), strings.TrimSpace([1])) = strings.NewReplacer(...) }// Ignore any unrecognized entriescontinue }returnnil, }if == nil {continue } = append(, ) }if := .Err(); != nil {returnnil, }return , nil}// removeLoggingInfo detects and removes log prefix entries generated// by the glog package. If no logging prefix is detected, the string// is returned unmodified.func removeLoggingInfo( string) string {if := logInfoRE.FindStringIndex(); != nil {return [[1]:] }return}// ParseMemoryMap parses a memory map in the format of// /proc/self/maps, and overrides the mappings in the current profile.// It renumbers the samples and locations in the profile correspondingly.func ( *Profile) ( io.Reader) error {return .ParseMemoryMapFromScanner(bufio.NewScanner())}// ParseMemoryMapFromScanner parses a memory map in the format of// /proc/self/maps or a variety of legacy format, and overrides the// mappings in the current profile. It renumbers the samples and// locations in the profile correspondingly.func ( *Profile) ( *bufio.Scanner) error { , := parseProcMapsFromScanner()if != nil {return } .Mapping = append(.Mapping, ...) .massageMappings() .remapLocationIDs() .remapFunctionIDs() .remapMappingIDs()returnnil}func parseMappingEntry( string) (*Mapping, error) {var , , , , , stringif := procMapsRE.FindStringSubmatch(); len() == 6 { , , , , = [1], [2], [3], [4], [5] } elseif := briefMapsRE.FindStringSubmatch(); len() == 7 { , , , , , = [1], [2], [3], [4], [5], [6] } else {returnnil, errUnrecognized }varerror := &Mapping{File: ,BuildID: , }if != "" && !strings.Contains(, "x") {// Skip non-executable entries.returnnil, nil }if .Start, = strconv.ParseUint(, 16, 64); != nil {returnnil, errUnrecognized }if .Limit, = strconv.ParseUint(, 16, 64); != nil {returnnil, errUnrecognized }if != "" {if .Offset, = strconv.ParseUint(, 16, 64); != nil {returnnil, errUnrecognized } }return , nil}var memoryMapSentinels = []string{"--- Memory map: ---","MAPPED_LIBRARIES:",}// isMemoryMapSentinel returns true if the string contains one of the// known sentinels for memory map information.func isMemoryMapSentinel( string) bool {for , := rangememoryMapSentinels {ifstrings.Contains(, ) {returntrue } }returnfalse}func ( *Profile) () {switch {caseisProfileType(, heapzSampleTypes): .DropFrames, .KeepFrames = allocRxStr, allocSkipRxStrcaseisProfileType(, contentionzSampleTypes): .DropFrames, .KeepFrames = lockRxStr, ""default: .DropFrames, .KeepFrames = cpuProfilerRxStr, "" }}var heapzSampleTypes = [][]string{ {"allocations", "size"}, // early Go pprof profiles {"objects", "space"}, {"inuse_objects", "inuse_space"}, {"alloc_objects", "alloc_space"}, {"alloc_objects", "alloc_space", "inuse_objects", "inuse_space"}, // Go pprof legacy profiles}var contentionzSampleTypes = [][]string{ {"contentions", "delay"},}func isProfileType( *Profile, [][]string) bool { := .SampleType:for , := range {iflen() != len() {continue }for := range {if [].Type != [] {continue } }returntrue }returnfalse}var allocRxStr = strings.Join([]string{// POSIX entry points.`calloc`,`cfree`,`malloc`,`free`,`memalign`,`do_memalign`,`(__)?posix_memalign`,`pvalloc`,`valloc`,`realloc`,// TC malloc.`tcmalloc::.*`,`tc_calloc`,`tc_cfree`,`tc_malloc`,`tc_free`,`tc_memalign`,`tc_posix_memalign`,`tc_pvalloc`,`tc_valloc`,`tc_realloc`,`tc_new`,`tc_delete`,`tc_newarray`,`tc_deletearray`,`tc_new_nothrow`,`tc_newarray_nothrow`,// Memory-allocation routines on OS X.`malloc_zone_malloc`,`malloc_zone_calloc`,`malloc_zone_valloc`,`malloc_zone_realloc`,`malloc_zone_memalign`,`malloc_zone_free`,// Go runtime`runtime\..*`,// Other misc. memory allocation routines`BaseArena::.*`,`(::)?do_malloc_no_errno`,`(::)?do_malloc_pages`,`(::)?do_malloc`,`DoSampledAllocation`,`MallocedMemBlock::MallocedMemBlock`,`_M_allocate`,`__builtin_(vec_)?delete`,`__builtin_(vec_)?new`,`__gnu_cxx::new_allocator::allocate`,`__libc_malloc`,`__malloc_alloc_template::allocate`,`allocate`,`cpp_alloc`,`operator new(\[\])?`,`simple_alloc::allocate`,}, `|`)var allocSkipRxStr = strings.Join([]string{// Preserve Go runtime frames that appear in the middle/bottom of // the stack.`runtime\.panic`,`runtime\.reflectcall`,`runtime\.call[0-9]*`,}, `|`)var cpuProfilerRxStr = strings.Join([]string{`ProfileData::Add`,`ProfileData::prof_handler`,`CpuProfiler::prof_handler`,`__pthread_sighandler`,`__restore`,}, `|`)var lockRxStr = strings.Join([]string{`RecordLockProfileData`,`(base::)?RecordLockProfileData.*`,`(base::)?SubmitMutexProfileData.*`,`(base::)?SubmitSpinLockProfileData.*`,`(base::Mutex::)?AwaitCommon.*`,`(base::Mutex::)?Unlock.*`,`(base::Mutex::)?UnlockSlow.*`,`(base::Mutex::)?ReaderUnlock.*`,`(base::MutexLock::)?~MutexLock.*`,`(Mutex::)?AwaitCommon.*`,`(Mutex::)?Unlock.*`,`(Mutex::)?UnlockSlow.*`,`(Mutex::)?ReaderUnlock.*`,`(MutexLock::)?~MutexLock.*`,`(SpinLock::)?Unlock.*`,`(SpinLock::)?SlowUnlock.*`,`(SpinLockHolder::)?~SpinLockHolder.*`,}, `|`)
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.