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 profile

import (
	
	
	
	
	
	
	
	
)

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:]]+`
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 ( string) bool {
	 := strings.TrimSpace()
	return len() == 0 || [0] == '#'
}
parseGoCount parses a Go count profile (e.g., threadcreate or goroutine) and returns a new Profile.
func ( []byte) (*Profile, error) {
Skip comments at the beginning of the file.
	for .Scan() && isSpaceOrComment(.Text()) {
	}
	if  := .Err();  != nil {
		return nil, 
	}
	 := countStartRE.FindStringSubmatch(.Text())
	if  == nil {
		return nil, errUnrecognized
	}
	 := [1]
	 := &Profile{
		PeriodType: &ValueType{Type: , Unit: "count"},
		Period:     1,
		SampleType: []*ValueType{{Type: , Unit: "count"}},
	}
	 := make(map[uint64]*Location)
	for .Scan() {
		 := .Text()
		if isSpaceOrComment() {
			continue
		}
		if strings.HasPrefix(, "---") {
			break
		}
		 := countRE.FindStringSubmatch()
		if  == nil {
			return nil, errMalformed
		}
		,  := strconv.ParseInt([1], 0, 64)
		if  != nil {
			return nil, errMalformed
		}
		 := strings.Fields([2])
		 := make([]*Location, 0, len())
		for ,  := range  {
			,  := strconv.ParseUint(, 0, 64)
			if  != nil {
				return nil, 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 {
		return nil, 
	}

	if  := parseAdditionalSections(, );  != nil {
		return nil, 
	}
	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  []*Location

	for ,  := range .Sample {
		for ,  := range .Location {
			if [] {
				continue
			}
			.ID = uint64(len() + 1)
			 = append(, )
			[] = true
		}
	}
	.Location = 
}

func ( *Profile) () {
	 := make(map[*Function]bool, len(.Function))
	var  []*Function

	for ,  := range .Location {
		for ,  := range .Line {
			 := .Function
			if  == 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)).
Some profile handlers will incorrectly set regions for the main executable if its section is remapped. Fix them through heuristics.

Remove the initial mapping if named '/anon_hugepage' and has a consecutive adjacent mapping.
		if  := .Mapping[0]; strings.HasPrefix(.File, "/anon_hugepage") {
			if len(.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.
	if len(.Mapping) > 0 {
		const  = 0x400000
		if  := .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 {
		 := .Address
		if .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 ( []byte) (uint64, []byte) {
	if len() < 4 {
		return 0, nil
	}
	return uint64([0]) | uint64([1])<<8 | uint64([2])<<16 | uint64([3])<<24, [4:]
}

func ( []byte) (uint64, []byte) {
	if len() < 4 {
		return 0, nil
	}
	return uint64([3]) | uint64([2])<<8 | uint64([1])<<16 | uint64([0])<<24, [4:]
}

func ( []byte) (uint64, []byte) {
	if len() < 8 {
		return 0, nil
	}
	return uint64([0]) | uint64([1])<<8 | uint64([2])<<16 | uint64([3])<<24 | uint64([4])<<32 | uint64([5])<<40 | uint64([6])<<48 | uint64([7])<<56, [8:]
}

func ( []byte) (uint64, []byte) {
	if len() < 8 {
		return 0, nil
	}
	return uint64([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 ( []byte) (*Profile, error) {
	var  func([]byte) (uint64, []byte)
	var , , , ,  uint64
	for _,  = range cpuInts {
		var  []byte
		,  = ()
		,  = ()
		,  = ()
		,  = ()
		,  = ()

		if  != nil &&  == 0 &&  == 3 &&  == 0 &&  > 0 &&  == 0 {
			 = 
			return cpuProfile(, int64(), )
		}
		if  != nil &&  == 0 &&  == 3 &&  == 1 &&  > 0 &&  == 0 {
			 = 
			return javaCPUProfile(, int64(), )
		}
	}
	return nil, 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 ( []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"},
		},
	}
	var  error
	if , _,  = parseCPUSamples(, , true, );  != nil {
		return nil, 
	}
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.
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 {
			if len(.Location) > 1 {
				 := .Location[1].Address
				[] = [] + 1
			}
		}

		for ,  := range  {
Found uninteresting frame, strip it out from all samples
				for ,  := range .Sample {
					if len(.Location) > 1 && .Location[1].Address ==  {
						.Location = append(.Location[:1], .Location[2:]...)
					}
				}
				break
			}
		}
	}

	if  := .ParseMemoryMap(bytes.NewBuffer());  != nil {
		return nil, 
	}

	cleanupDuplicateLocations()
	return , nil
}

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 {
		if len(.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 ( []byte,  func( []byte) (uint64, []byte),  bool,  *Profile) ([]byte, map[uint64]*Location, error) {
	 := make(map[uint64]*Location)
	for len() > 0 {
		var ,  uint64
		,  = ()
		,  = ()
		if  == nil ||  > uint64(len()/4) {
			return nil, nil, errUnrecognized
		}
		var  []*Location
		 := make([]uint64, )
		for  := 0;  < int(); ++ {
			[],  = ()
		}

End of data marker
			break
		}
		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 ( []byte) ( *Profile,  error) {
	 := bufio.NewScanner(bytes.NewBuffer())
	if !.Scan() {
		if  := .Err();  != nil {
			return nil, 
		}
		return nil, errUnrecognized
	}
	 = &Profile{}

	 := ""
	 := false

	 := .Text()
	.PeriodType = &ValueType{Type: "space", Unit: "bytes"}
	if  := heapHeaderRE.FindStringSubmatch();  != nil {
		, .Period, ,  = parseHeapHeader()
		if  != nil {
			return nil, 
		}
	} else if  = growthHeaderRE.FindStringSubmatch();  != nil {
		.Period = 1
	} else if  = fragmentationHeaderRE.FindStringSubmatch();  != nil {
		.Period = 1
	} else {
		return nil, errUnrecognized
	}

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())

		if isSpaceOrComment() {
			continue
		}

		if isMemoryMapSentinel() {
			break
		}

		, , ,  := parseHeapSample(, .Period, , )
		if  != nil {
			return nil, 
		}

		var  []*Location
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 {
		return nil, 
	}
	if  := parseAdditionalSections(, );  != nil {
		return nil, 
	}
	return , nil
}

func ( string) ( string,  int64,  bool,  error) {
	 := heapHeaderRE.FindStringSubmatch()
	if  == nil {
		return "", 0, false, errUnrecognized
	}

	if len([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", , , nil
	case "heapprofile":
		return "", 1, , nil
	case "heap":
		return "v2",  / 2, , nil
	default:
		return "", 0, false, errUnrecognized
	}
}
parseHeapSample parses a single row from a heap profile into a new Sample.
func ( string,  int64,  string,  bool) ( []int64,  int64,  []uint64,  error) {
	 := heapSampleRE.FindStringSubmatch()
	if len() != 6 {
		return nil, 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 {
			return fmt.Errorf("malformed sample: %s: %v", , )
		}
		,  := strconv.ParseInt(, 10, 64)
		if  != nil {
			return fmt.Errorf("malformed sample: %s: %v", , )
		}
		if  == 0 &&  != 0 {
			return fmt.Errorf("%s count was 0 but %s bytes was %d", , , )
		}
		if  != 0 {
			 =  / 
			if  == "v2" {
				,  = scaleHeapSample(, , )
			}
		}
		 = append(, , )
		return nil
	}

	if  {
		if  := ([3], [4], "allocation");  != nil {
			return nil, 0, nil, 
		}
	}

	if  := ([1], [2], "inuse");  != nil {
		return nil, 0, nil, 
	}

	,  = parseHexAddresses([5])
	if  != nil {
		return nil, 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 ( string) ([]uint64, error) {
	 := hexNumberRE.FindAllString(, -1)
	var  []uint64
	for ,  := range  {
		if ,  := strconv.ParseUint(, 0, 64);  == nil {
			 = append(, )
		} else {
			return nil, 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 (, ,  int64) (int64, int64) {
	if  == 0 ||  == 0 {
		return 0, 0
	}

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()))

	return int64(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 ( []byte) (*Profile, error) {
	 := bufio.NewScanner(bytes.NewBuffer())
	if !.Scan() {
		if  := .Err();  != nil {
			return nil, 
		}
		return nil, errUnrecognized
	}

	switch  := .Text(); {
	case strings.HasPrefix(, "--- contentionz "):
	case strings.HasPrefix(, "--- mutex:"):
	case strings.HasPrefix(, "--- contention:"):
	default:
		return nil, errUnrecognized
	}

	 := &Profile{
		PeriodType: &ValueType{Type: "contentions", Unit: "count"},
		Period:     1,
		SampleType: []*ValueType{
			{Type: "contentions", Unit: "count"},
			{Type: "delay", Unit: "nanoseconds"},
		},
	}

Parse text of the form "attribute = value" before the samples.
	const  = "="
	for .Scan() {
		 := .Text()
		if  = strings.TrimSpace(); isSpaceOrComment() {
			continue
		}
		if strings.HasPrefix(, "---") {
			break
		}
		 := strings.SplitN(, , 2)
		if len() != 2 {
			break
		}
		,  := strings.TrimSpace([0]), strings.TrimSpace([1])
		var  error
		switch  {
		case "cycles/second":
			if ,  = strconv.ParseInt(, 0, 64);  != nil {
				return nil, errUnrecognized
			}
		case "sampling period":
			if .Period,  = strconv.ParseInt(, 0, 64);  != nil {
				return nil, errUnrecognized
			}
		case "ms since reset":
			,  := strconv.ParseInt(, 0, 64)
			if  != nil {
				return nil, errUnrecognized
			}
			.DurationNanos =  * 1000 * 1000
CPP contentionz profiles don't have format.
CPP contentionz profiles don't have resolution.
			return nil, errUnrecognized
		case "discarded samples":
		default:
			return nil, errUnrecognized
		}
	}
	if  := .Err();  != nil {
		return nil, 
	}

	 := make(map[uint64]*Location)
	for {
		 := strings.TrimSpace(.Text())
		if strings.HasPrefix(, "---") {
			break
		}
		if !isSpaceOrComment() {
			, ,  := parseContentionSample(, .Period, )
			if  != nil {
				return nil, 
			}
			var  []*Location
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 {
		return nil, 
	}

	if  := parseAdditionalSections(, );  != nil {
		return nil, 
	}

	return , nil
}
parseContentionSample parses a single row from a contention profile into a new Sample.
func ( string, ,  int64) ( []int64,  []uint64,  error) {
	 := contentionSampleRE.FindStringSubmatch()
	if  == nil {
		return nil, nil, errUnrecognized
	}

	,  := strconv.ParseInt([1], 10, 64)
	if  != nil {
		return nil, nil, fmt.Errorf("malformed sample: %s: %v", , )
	}
	,  := strconv.ParseInt([2], 10, 64)
	if  != nil {
		return nil, 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 {
		return nil, nil, fmt.Errorf("malformed sample: %s: %v", , )
	}

	return , , nil
}
parseThread parses a Threadz profile and returns a new Profile.
func ( []byte) (*Profile, error) {
Skip past comments and empty lines seeking a real header.
	for .Scan() && isSpaceOrComment(.Text()) {
	}

	 := .Text()
Advance over initial comments until first stack trace.
		for .Scan() {
			if  = .Text(); isMemoryMapSentinel() || strings.HasPrefix(, "-") {
				break
			}
		}
	} else if  := threadStartRE.FindStringSubmatch(); len() != 4 {
		return nil, errUnrecognized
	}

	 := &Profile{
		SampleType: []*ValueType{{Type: "thread", Unit: "count"}},
		PeriodType: &ValueType{Type: "thread", Unit: "count"},
		Period:     1,
	}

Recognize each thread and populate profile samples.
	for !isMemoryMapSentinel() {
		if strings.HasPrefix(, "---- no stack trace for") {
			 = ""
			break
		}
		if  := threadStartRE.FindStringSubmatch(); len() != 4 {
			return nil, errUnrecognized
		}

		var  []uint64
		var  error
		, ,  = parseThreadSample()
		if  != nil {
			return nil, 
		}
We got a --same as previous threads--. Bump counters.
			if len(.Sample) > 0 {
				 := .Sample[len(.Sample)-1]
				.Value[0]++
			}
			continue
		}

		var  []*Location
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 {
		return nil, 
	}

	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 ( *bufio.Scanner) ( string,  []uint64,  error) {
	var  string
	 := false
	for .Scan() {
		 = strings.TrimSpace(.Text())
		if  == "" {
			continue
		}

		if strings.HasPrefix(, "---") {
			break
		}
		if strings.Contains(, "same as previous thread") {
			 = true
			continue
		}

		,  := 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 ( *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()
	return parseProcMapsFromScanner()
}

func ( *bufio.Scanner) ([]*Mapping, error) {
	var  []*Mapping

	var  []string
	const  = "="
	 := strings.NewReplacer()
	for .Scan() {
		 := .Replace(removeLoggingInfo(.Text()))
		,  := parseMappingEntry()
		if  != nil {
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 entries
				continue
			}
			return nil, 
		}
		if  == nil {
			continue
		}
		 = append(, )
	}
	if  := .Err();  != nil {
		return nil, 
	}
	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 ( 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.
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()
	return nil
}

func ( string) (*Mapping, error) {
	var , , , , ,  string
	if  := procMapsRE.FindStringSubmatch(); len() == 6 {
		, , , ,  = [1], [2], [3], [4], [5]
	} else if  := briefMapsRE.FindStringSubmatch(); len() == 7 {
		, , , , ,  = [1], [2], [3], [4], [5], [6]
	} else {
		return nil, errUnrecognized
	}

	var  error
	 := &Mapping{
		File:    ,
		BuildID: ,
	}
Skip non-executable entries.
		return nil, nil
	}
	if .Start,  = strconv.ParseUint(, 16, 64);  != nil {
		return nil, errUnrecognized
	}
	if .Limit,  = strconv.ParseUint(, 16, 64);  != nil {
		return nil, errUnrecognized
	}
	if  != "" {
		if .Offset,  = strconv.ParseUint(, 16, 64);  != nil {
			return nil, 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 ( string) bool {
	for ,  := range memoryMapSentinels {
		if strings.Contains(, ) {
			return true
		}
	}
	return false
}

func ( *Profile) () {
	switch {
	case isProfileType(, heapzSampleTypes):
		.DropFrames, .KeepFrames = allocRxStr, allocSkipRxStr
	case isProfileType(, 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 ( *Profile,  [][]string) bool {
	 := .SampleType
:
	for ,  := range  {
		if len() != len() {
			continue
		}

		for  := range  {
			if [].Type != [] {
				continue 
			}
		}
		return true
	}
	return false
}

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`,
}, `|`)

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.*`,