// 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:]]+`
	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()
	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) {
	 := bufio.NewScanner(bytes.NewBuffer())
	// 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)).
func ( *Profile) () {
	// Some profile handlers will incorrectly set regions for the main
	// executable if its section is remapped. Fix them through heuristics.

	if len(.Mapping) > 0 {
		// 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 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:]
}

// 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(), )
		}
		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 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 *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 {
			if len(.Location) > 1 {
				 := .Location[1].Address
				[] = [] + 1
			}
		}

		for ,  := range  {
			if  >= len(.Sample)- {
				// 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
}

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 {
		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 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) {
	 := 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
	}

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

		if isSpaceOrComment() {
			continue
		}

		if isMemoryMapSentinel() {
			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  := .Err();  != nil {
		return nil, 
	}
	if  := parseAdditionalSections(, );  != nil {
		return nil, 
	}
	return , nil
}

func parseHeapHeader( 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 parseHeapSample( 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 parseHexAddresses( 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 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.
func parseContention( []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"},
		},
	}

	var  int64
	// 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
		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
		}
	}
	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
			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 !.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 parseContentionSample( 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 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
			}
		}
	} 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 !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, 
		}
		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
			// (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 parseThreadSample( *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 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()
	return parseProcMapsFromScanner()
}

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

	var  []string
	const  = "="
	 := 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 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 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()
	return nil
}

func parseMappingEntry( 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: ,
	}
	if  != "" && !strings.Contains(, "x") {
		// 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 isMemoryMapSentinel( 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 isProfileType( *Profile,  [][]string) bool {
	 := .SampleType
:
	for ,  := range  {
		if len() != len() {
			continue
		}

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

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