// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file implements parsers to convert legacy profiles into the
// profile.proto format.

package profile

import (
	
	
	
	
	
	
	
	
)

var (
	countStartRE = lazyregexp.New(`\A(\w+) profile: total \d+\n\z`)
	countRE      = lazyregexp.New(`\A(\d+) @(( 0x[0-9a-f]+)+)\n\z`)

	heapHeaderRE = lazyregexp.New(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] *@ *(heap[_a-z0-9]*)/?(\d*)`)
	heapSampleRE = lazyregexp.New(`(-?\d+): *(-?\d+) *\[ *(\d+): *(\d+) *] @([ x0-9a-f]*)`)

	contentionSampleRE = lazyregexp.New(`(\d+) *(\d+) @([ x0-9a-f]*)`)

	hexNumberRE = lazyregexp.New(`0x[0-9a-f]+`)

	growthHeaderRE = lazyregexp.New(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] @ growthz`)

	fragmentationHeaderRE = lazyregexp.New(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] @ fragmentationz`)

	threadzStartRE = lazyregexp.New(`--- threadz \d+ ---`)
	threadStartRE  = lazyregexp.New(`--- Thread ([[:xdigit:]]+) \(name: (.*)/(\d+)\) stack: ---`)

	procMapsRE = lazyregexp.New(`([[:xdigit:]]+)-([[:xdigit:]]+)\s+([-rwxp]+)\s+([[:xdigit:]]+)\s+([[:xdigit:]]+):([[:xdigit:]]+)\s+([[:digit:]]+)\s*(\S+)?`)

	briefMapsRE = lazyregexp.New(`\s*([[:xdigit:]]+)-([[:xdigit:]]+):\s*(\S+)(\s.*@)?([[:xdigit:]]+)?`)

	// LegacyHeapAllocated instructs the heapz parsers to use the
	// allocated memory stats instead of the default in-use memory. Note
	// that tcmalloc doesn't provide all allocated memory, only in-use
	// stats.
	LegacyHeapAllocated bool
)

func isSpaceOrComment( 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 parseGoCount( []byte) (*Profile, error) {
	 := bytes.NewBuffer()

	var  string
	var  error
	for {
		// Skip past comments and empty lines seeking a real header.
		,  = .ReadString('\n')
		if  != nil {
			return nil, 
		}
		if !isSpaceOrComment() {
			break
		}
	}

	 := countStartRE.FindStringSubmatch()
	if  == nil {
		return nil, errUnrecognized
	}
	 := [1]
	 := &Profile{
		PeriodType: &ValueType{Type: , Unit: "count"},
		Period:     1,
		SampleType: []*ValueType{{Type: , Unit: "count"}},
	}
	 := make(map[uint64]*Location)
	for {
		,  = .ReadString('\n')
		if  != nil {
			if  == io.EOF {
				break
			}
			return nil, 
		}
		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 the call instruction.
			--
			 := []
			if  == nil {
				 = &Location{
					Address: ,
				}
				[] = 
				.Location = append(.Location, )
			}
			 = append(, )
		}
		.Sample = append(.Sample, &Sample{
			Location: ,
			Value:    []int64{},
		})
	}

	if  = parseAdditionalSections(strings.TrimSpace(), , );  != 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)).
func ( *Profile) () {
	if len(.Mapping) == 0 {
		return
	}

	// 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:]
		}
	}

	for ,  := range .Location {
		if  := .Address;  != 0 {
			for ,  := range .Mapping {
				if .Start <=  &&  < .Limit {
					.Mapping = 
					break
				}
			}
		}
	}

	// 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) {
	if len() < 4 {
		return 0, nil
	}
	return uint64([0]) | uint64([1])<<8 | uint64([2])<<16 | uint64([3])<<24, [4:]
}

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

func get64l( []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 get64b( []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:]
}

// ParseTracebacks parses a set of tracebacks and returns a newly
// populated profile. It will accept any text file and generate a
// Profile out of it with any hex addresses it can identify, including
// a process map if it can recognize one. Each sample will include a
// tag "source" with the addresses recognized in string format.
func ( []byte) (*Profile, error) {
	 := bytes.NewBuffer()

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

	var  []string
	var  []*Location

	 := make(map[uint64]*Location)
	for {
		,  := .ReadString('\n')
		if  != nil {
			if  != io.EOF {
				return nil, 
			}
			if  == "" {
				break
			}
		}
		if sectionTrigger() == memoryMapSection {
			break
		}
		if ,  := extractHexAddresses(); len() > 0 {
			for ,  := 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(, )
			}

			 = append(, ...)
		} else {
			if len() > 0 || len() > 0 {
				addTracebackSample(, , )
				,  = nil, nil
			}
		}
	}

	// Add final sample to save any leftover data.
	if len() > 0 || len() > 0 {
		addTracebackSample(, , )
	}

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

func addTracebackSample( []*Location,  []string,  *Profile) {
	.Sample = append(.Sample,
		&Sample{
			Value:    []int64{1},
			Location: ,
			Label:    map[string][]string{"source": },
		})
}

// 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) {
	var  func([]byte) (uint64, []byte)
	var , , , ,  uint64
	for _,  = range cpuInts {
		var  []byte
		,  = ()
		,  = ()
		,  = ()
		,  = ()
		,  = ()

		if  != nil &&  == 0 &&  == 3 &&  == 0 &&  > 0 &&  == 0 {
			 = 
			return cpuProfile(, 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 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"},
		},
	}
	var  error
	if , _,  = parseCPUSamples(, , true, );  != nil {
		return nil, 
	}

	// If all 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.
	if len(.Sample) > 1 && len(.Sample[0].Location) > 1 {
		 := true
		 := .Sample[0].Location[1].Address
		for ,  := range .Sample {
			if len(.Location) < 2 ||  != .Location[1].Address {
				 = false
				break
			}
		}
		if  {
			for ,  := range .Sample {
				.Location = append(.Location[:1], .Location[2:]...)
			}
		}
	}

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

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

		if  == 0 &&  == 1 && [0] == 0 {
			// 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 parseHeap( []byte) ( *Profile,  error) {
	 := bytes.NewBuffer()
	,  := .ReadString('\n')
	if  != nil {
		return nil, errUnrecognized
	}

	 := ""

	if  := heapHeaderRE.FindStringSubmatch();  != nil {
		 = &Profile{
			SampleType: []*ValueType{
				{Type: "objects", Unit: "count"},
				{Type: "space", Unit: "bytes"},
			},
			PeriodType: &ValueType{Type: "objects", Unit: "bytes"},
		}

		var  int64
		if len([6]) > 0 {
			if ,  = strconv.ParseInt([6], 10, 64);  != nil {
				return nil, errUnrecognized
			}
		}

		switch [5] {
		case "heapz_v2", "heap_v2":
			, .Period = "v2", 
		case "heapprofile":
			, .Period = "", 1
		case "heap":
			, .Period = "v2", /2
		default:
			return nil, errUnrecognized
		}
	} else if  = growthHeaderRE.FindStringSubmatch();  != nil {
		 = &Profile{
			SampleType: []*ValueType{
				{Type: "objects", Unit: "count"},
				{Type: "space", Unit: "bytes"},
			},
			PeriodType: &ValueType{Type: "heapgrowth", Unit: "count"},
			Period:     1,
		}
	} else if  = fragmentationHeaderRE.FindStringSubmatch();  != nil {
		 = &Profile{
			SampleType: []*ValueType{
				{Type: "objects", Unit: "count"},
				{Type: "space", Unit: "bytes"},
			},
			PeriodType: &ValueType{Type: "allocations", Unit: "count"},
			Period:     1,
		}
	} else {
		return nil, errUnrecognized
	}

	if LegacyHeapAllocated {
		for ,  := range .SampleType {
			.Type = "alloc_" + .Type
		}
	} else {
		for ,  := range .SampleType {
			.Type = "inuse_" + .Type
		}
	}

	 := make(map[uint64]*Location)
	for {
		,  = .ReadString('\n')
		if  != nil {
			if  != io.EOF {
				return nil, 
			}

			if  == "" {
				break
			}
		}

		if isSpaceOrComment() {
			continue
		}
		 = strings.TrimSpace()

		if sectionTrigger() != unrecognizedSection {
			break
		}

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

// parseHeapSample parses a single row from a heap profile into a new Sample.
func parseHeapSample( string,  int64,  string) ( []int64,  int64,  []uint64,  error) {
	 := heapSampleRE.FindStringSubmatch()
	if len() != 6 {
		return , , , fmt.Errorf("unexpected number of sample values: got %d, want 6", len())
	}

	// Use first two values by default; tcmalloc sampling generates the
	// same value for both, only the older heap-profile collect separate
	// stats for in-use and allocated objects.
	 := 1
	if LegacyHeapAllocated {
		 = 3
	}

	var ,  int64
	if ,  = strconv.ParseInt([], 10, 64);  != nil {
		return , , , fmt.Errorf("malformed sample: %s: %v", , )
	}
	if ,  = strconv.ParseInt([+1], 10, 64);  != nil {
		return , , , fmt.Errorf("malformed sample: %s: %v", , )
	}

	if  == 0 {
		if  != 0 {
			return , , , fmt.Errorf("allocation count was 0 but allocation bytes was %d", )
		}
	} else {
		 =  / 
		if  == "v2" {
			,  = scaleHeapSample(, , )
		}
	}

	 = []int64{, }
	 = parseHexAddresses([5])

	return , , , nil
}

// extractHexAddresses extracts hex numbers from a string and returns
// them, together with their numeric value, in a slice.
func extractHexAddresses( string) ([]string, []uint64) {
	 := hexNumberRE.FindAllString(, -1)
	var  []uint64
	for ,  := range  {
		if ,  := strconv.ParseUint(, 0, 64);  == nil {
			 = append(, )
		} else {
			// Do not expect any parsing failures due to the regexp matching.
			panic("failed to parse hex value:" + )
		}
	}
	return , 
}

// parseHexAddresses parses hex numbers from a string and returns them
// in a slice.
func parseHexAddresses( string) []uint64 {
	,  := extractHexAddresses()
	return 
}

// 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 {
		return 0, 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()))

	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.
// This code converts the text output from runtime into a *Profile. (In the future
// the runtime might write a serialized Profile directly making this unnecessary.)
func parseContention( []byte) (*Profile, error) {
	 := bytes.NewBuffer()
	var  string
	var  error
	for {
		// Skip past comments and empty lines seeking a real header.
		,  = .ReadString('\n')
		if  != nil {
			return nil, 
		}
		if !isSpaceOrComment() {
			break
		}
	}

	if strings.HasPrefix(, "--- contentionz ") {
		return parseCppContention()
	} else if strings.HasPrefix(, "--- mutex:") {
		return parseCppContention()
	} else if strings.HasPrefix(, "--- contention:") {
		return parseCppContention()
	}
	return nil, errUnrecognized
}

// parseCppContention parses the output from synchronization_profiling.cc
// for backward compatibility, and the compatible (non-debug) block profile
// output from the Go runtime.
func parseCppContention( *bytes.Buffer) (*Profile, error) {
	 := &Profile{
		PeriodType: &ValueType{Type: "contentions", Unit: "count"},
		Period:     1,
		SampleType: []*ValueType{
			{Type: "contentions", Unit: "count"},
			{Type: "delay", Unit: "nanoseconds"},
		},
	}

	var  int64
	var  string
	var  error
	// Parse text of the form "attribute = value" before the samples.
	const  = '='
	for {
		,  = .ReadString('\n')
		if  != nil {
			if  != io.EOF {
				return nil, 
			}

			if  == "" {
				break
			}
		}
		if isSpaceOrComment() {
			continue
		}

		if  = strings.TrimSpace();  == "" {
			continue
		}

		if strings.HasPrefix(, "---") {
			break
		}

		 := strings.IndexByte(, )
		if  < 0 {
			break
		}
		 := [:]
		 := [+1:]

		,  = strings.TrimSpace(), strings.TrimSpace()
		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
		case "format":
			// CPP contentionz profiles don't have format.
			return nil, errUnrecognized
		case "resolution":
			// CPP contentionz profiles don't have resolution.
			return nil, errUnrecognized
		case "discarded samples":
		default:
			return nil, errUnrecognized
		}
	}

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

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

	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 {
		return , , errUnrecognized
	}

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

	return , , nil
}

// parseThread parses a Threadz profile and returns a new Profile.
func parseThread( []byte) (*Profile, error) {
	 := bytes.NewBuffer()

	var  string
	var  error
	for {
		// Skip past comments and empty lines seeking a real header.
		,  = .ReadString('\n')
		if  != nil {
			return nil, 
		}
		if !isSpaceOrComment() {
			break
		}
	}

	if  := threadzStartRE.FindStringSubmatch();  != nil {
		// Advance over initial comments until first stack trace.
		for {
			,  = .ReadString('\n')
			if  != nil {
				if  != io.EOF {
					return nil, 
				}

				if  == "" {
					break
				}
			}
			if sectionTrigger() != unrecognizedSection || [0] == '-' {
				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,
	}

	 := make(map[uint64]*Location)
	// Recognize each thread and populate profile samples.
	for sectionTrigger() == unrecognizedSection {
		if strings.HasPrefix(, "---- no stack trace for") {
			 = ""
			break
		}
		if  := threadStartRE.FindStringSubmatch(); len() != 4 {
			return nil, errUnrecognized
		}

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

		var  []*Location
		for ,  := 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:    []int64{1},
			Location: ,
		})
	}

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

	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( *bytes.Buffer) ( string,  []uint64,  error) {
	var  string
	 := false
	for {
		if ,  = .ReadString('\n');  != nil {
			if  != io.EOF {
				return "", nil, 
			}
			if  == "" {
				break
			}
		}
		if  = strings.TrimSpace();  == "" {
			continue
		}

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

		 = append(, parseHexAddresses()...)
	}

	if  {
		return , nil, nil
	}
	return , , nil
}

// parseAdditionalSections parses any additional sections in the
// profile, ignoring any unrecognized sections.
func parseAdditionalSections( string,  *bytes.Buffer,  *Profile) ( error) {
	for {
		if sectionTrigger() == memoryMapSection {
			break
		}
		// Ignore any unrecognized sections.
		if ,  := .ReadString('\n');  != nil {
			if  != io.EOF {
				return 
			}
			if  == "" {
				break
			}
		}
	}
	return .ParseMemoryMap()
}

// 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 {
	 := bufio.NewReader()

	var  []string
	var  *strings.Replacer
	const  = '='
	for {
		,  := .ReadString('\n')
		if  != nil {
			if  != io.EOF {
				return 
			}
			if  == "" {
				break
			}
		}
		if  = strings.TrimSpace();  == "" {
			continue
		}

		if  != nil {
			 = .Replace()
		}
		,  := parseMappingEntry()
		if  != nil {
			if  == errUnrecognized {
				// Recognize assignments of the form: attr=value, and replace
				// $attr with value on subsequent mappings.
				 := strings.IndexByte(, )
				if  >= 0 {
					 := [:]
					 := [+1:]
					 = append(, "$"+strings.TrimSpace(), strings.TrimSpace())
					 = strings.NewReplacer(...)
				}
				// Ignore any unrecognized entries
				continue
			}
			return 
		}
		if  == nil || (.File == "" && len(.Mapping) != 0) {
			// In some cases the first entry may include the address range
			// but not the name of the file. It should be followed by
			// another entry with the name.
			continue
		}
		if len(.Mapping) == 1 && .Mapping[0].File == "" {
			// Update the name if this is the entry following that empty one.
			.Mapping[0].File = .File
			continue
		}
		.Mapping = append(.Mapping, )
	}
	.remapLocationIDs()
	.remapFunctionIDs()
	.remapMappingIDs()
	return nil
}

func parseMappingEntry( string) (*Mapping, error) {
	 := &Mapping{}
	var  error
	if  := procMapsRE.FindStringSubmatch(); len() == 9 {
		if !strings.Contains([3], "x") {
			// Skip non-executable entries.
			return nil, nil
		}
		if .Start,  = strconv.ParseUint([1], 16, 64);  != nil {
			return nil, errUnrecognized
		}
		if .Limit,  = strconv.ParseUint([2], 16, 64);  != nil {
			return nil, errUnrecognized
		}
		if [4] != "" {
			if .Offset,  = strconv.ParseUint([4], 16, 64);  != nil {
				return nil, errUnrecognized
			}
		}
		.File = [8]
		return , nil
	}

	if  := briefMapsRE.FindStringSubmatch(); len() == 6 {
		if .Start,  = strconv.ParseUint([1], 16, 64);  != nil {
			return nil, errUnrecognized
		}
		if .Limit,  = strconv.ParseUint([2], 16, 64);  != nil {
			return nil, errUnrecognized
		}
		.File = [3]
		if [5] != "" {
			if .Offset,  = strconv.ParseUint([5], 16, 64);  != nil {
				return nil, errUnrecognized
			}
		}
		return , nil
	}

	return nil, errUnrecognized
}

type sectionType int

const (
	unrecognizedSection sectionType = iota
	memoryMapSection
)

var memoryMapTriggers = []string{
	"--- Memory map: ---",
	"MAPPED_LIBRARIES:",
}

func sectionTrigger( string) sectionType {
	for ,  := range memoryMapTriggers {
		if strings.Contains(, ) {
			return memoryMapSection
		}
	}
	return unrecognizedSection
}

func ( *Profile) () {
	switch {
	case isProfileType(, heapzSampleTypes) ||
		isProfileType(, heapzInUseSampleTypes) ||
		isProfileType(, heapzAllocSampleTypes):
		.DropFrames, .KeepFrames = allocRxStr, allocSkipRxStr
	case isProfileType(, contentionzSampleTypes):
		.DropFrames, .KeepFrames = lockRxStr, ""
	default:
		.DropFrames, .KeepFrames = cpuProfilerRxStr, ""
	}
}

var heapzSampleTypes = []string{"allocations", "size"} // early Go pprof profiles
var heapzInUseSampleTypes = []string{"inuse_objects", "inuse_space"}
var heapzAllocSampleTypes = []string{"alloc_objects", "alloc_space"}
var contentionzSampleTypes = []string{"contentions", "delay"}

func isProfileType( *Profile,  []string) bool {
	 := .SampleType
	if len() != len() {
		return false
	}

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

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.*`,
	`(Mutex::)?AwaitCommon.*`,
	`(Mutex::)?Unlock.*`,
	`(Mutex::)?UnlockSlow.*`,
	`(Mutex::)?ReaderUnlock.*`,
	`(MutexLock::)?~MutexLock.*`,
	`(SpinLock::)?Unlock.*`,
	`(SpinLock::)?SlowUnlock.*`,
	`(SpinLockHolder::)?~SpinLockHolder.*`,
}, `|`)