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.
Package profile provides a representation of profile.proto and methods to encode/decode profiles in this format.
package profile

import (
	
	
	
	
	
	
	
	
	
	
	
)
The following fields are modified during encoding and copying, so are protected by a Mutex.
ValueType corresponds to Profile.ValueType
type ValueType struct {
	Type string // cpu, wall, inuse_space, etc
	Unit string // seconds, nanoseconds, bytes, etc

	typeX int64
	unitX int64
}
Sample corresponds to Profile.Sample
label corresponds to Profile.Label
type label struct {
Exactly one of the two following values must be set
can be set if numX has value
Location corresponds to Profile.Location
Line corresponds to Profile.Line
Function corresponds to Profile.Function
Parse parses a profile and checks for its validity. The input may be a gzip-compressed encoded protobuf or one of many legacy profile formats which may be unsupported in the future.
func ( io.Reader) (*Profile, error) {
	,  := ioutil.ReadAll()
	if  != nil {
		return nil, 
	}
	return ParseData()
}
ParseData parses a profile from a buffer and checks for its validity.
func ( []byte) (*Profile, error) {
	var  *Profile
	var  error
	if len() >= 2 && [0] == 0x1f && [1] == 0x8b {
		,  := gzip.NewReader(bytes.NewBuffer())
		if  == nil {
			,  = ioutil.ReadAll()
		}
		if  != nil {
			return nil, fmt.Errorf("decompressing profile: %v", )
		}
	}
	if ,  = ParseUncompressed();  != nil &&  != errNoData &&  != errConcatProfile {
		,  = parseLegacy()
	}

	if  != nil {
		return nil, fmt.Errorf("parsing profile: %v", )
	}

	if  := .CheckValid();  != nil {
		return nil, fmt.Errorf("malformed profile: %v", )
	}
	return , nil
}

var errUnrecognized = fmt.Errorf("unrecognized profile format")
var errMalformed = fmt.Errorf("malformed profile format")
var errNoData = fmt.Errorf("empty input file")
var errConcatProfile = fmt.Errorf("concatenated profiles detected")

func ( []byte) (*Profile, error) {
	 := []func([]byte) (*Profile, error){
		parseCPU,
		parseHeap,
		parseGoCount, // goroutine, threadcreate
		parseThread,
		parseContention,
		parseJavaProfile,
	}

	for ,  := range  {
		,  := ()
		if  == nil {
			.addLegacyFrameInfo()
			return , nil
		}
		if  != errUnrecognized {
			return nil, 
		}
	}
	return nil, errUnrecognized
}
ParseUncompressed parses an uncompressed protobuf into a profile.
func ( []byte) (*Profile, error) {
	if len() == 0 {
		return nil, errNoData
	}
	 := &Profile{}
	if  := unmarshal(, );  != nil {
		return nil, 
	}

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

	return , nil
}

var libRx = regexp.MustCompile(`([.]so$|[.]so[._][0-9]+)`)
massageMappings applies heuristic-based changes to the profile mappings to account for quirks of some environments.
Merge adjacent regions with matching names, checking that the offsets match
	if len(.Mapping) > 1 {
		 := []*Mapping{.Mapping[0]}
		for ,  := range .Mapping[1:] {
			 := [len()-1]
			if adjacent(, ) {
				.Limit = .Limit
				if .File != "" {
					.File = .File
				}
				if .BuildID != "" {
					.BuildID = .BuildID
				}
				.updateLocationMapping(, )
				continue
			}
			 = append(, )
		}
		.Mapping = 
	}
Use heuristics to identify main binary and move it to the top of the list of mappings
	for ,  := range .Mapping {
		 := strings.TrimSpace(strings.Replace(.File, "(deleted)", "", -1))
		if len() == 0 {
			continue
		}
		if len(libRx.FindStringSubmatch()) > 0 {
			continue
		}
		if [0] == '[' {
			continue
Swap what we guess is main to position 0.
		.Mapping[0], .Mapping[] = .Mapping[], .Mapping[0]
		break
	}
Keep the mapping IDs neatly sorted
	for ,  := range .Mapping {
		.ID = uint64( + 1)
	}
}
adjacent returns whether two mapping entries represent the same mapping that has been split into two. Check that their addresses are adjacent, and if the offsets match, if they are available.
func (,  *Mapping) bool {
	if .File != "" && .File != "" {
		if .File != .File {
			return false
		}
	}
	if .BuildID != "" && .BuildID != "" {
		if .BuildID != .BuildID {
			return false
		}
	}
	if .Limit != .Start {
		return false
	}
	if .Offset != 0 && .Offset != 0 {
		 := .Offset + (.Limit - .Start)
		if  != .Offset {
			return false
		}
	}
	return true
}

func ( *Profile) (,  *Mapping) {
	for ,  := range .Location {
		if .Mapping ==  {
			.Mapping = 
		}
	}
}

func ( *Profile) []byte {
	.encodeMu.Lock()
	.preEncode()
	 := marshal()
	.encodeMu.Unlock()
	return 
}
Write writes the profile as a gzip-compressed marshaled protobuf.
func ( *Profile) ( io.Writer) error {
	 := gzip.NewWriter()
	defer .Close()
	,  := .Write(serialize())
	return 
}
WriteUncompressed writes the profile as a marshaled protobuf.
func ( *Profile) ( io.Writer) error {
	,  := .Write(serialize())
	return 
}
CheckValid tests whether the profile is valid. Checks include, but are not limited to: - len(Profile.Sample[n].value) == len(Profile.value_unit) - Sample.id has a corresponding Profile.Location
Check that sample values are consistent
	 := len(.SampleType)
	if  == 0 && len(.Sample) != 0 {
		return fmt.Errorf("missing sample type information")
	}
	for ,  := range .Sample {
		if  == nil {
			return fmt.Errorf("profile has nil sample")
		}
		if len(.Value) !=  {
			return fmt.Errorf("mismatch: sample has %d values vs. %d types", len(.Value), len(.SampleType))
		}
		for ,  := range .Location {
			if  == nil {
				return fmt.Errorf("sample has nil location")
			}
		}
	}
Check that all mappings/locations/functions are in the tables Check that there are no duplicate ids
	 := make(map[uint64]*Mapping, len(.Mapping))
	for ,  := range .Mapping {
		if  == nil {
			return fmt.Errorf("profile has nil mapping")
		}
		if .ID == 0 {
			return fmt.Errorf("found mapping with reserved ID=0")
		}
		if [.ID] != nil {
			return fmt.Errorf("multiple mappings with same id: %d", .ID)
		}
		[.ID] = 
	}
	 := make(map[uint64]*Function, len(.Function))
	for ,  := range .Function {
		if  == nil {
			return fmt.Errorf("profile has nil function")
		}
		if .ID == 0 {
			return fmt.Errorf("found function with reserved ID=0")
		}
		if [.ID] != nil {
			return fmt.Errorf("multiple functions with same id: %d", .ID)
		}
		[.ID] = 
	}
	 := make(map[uint64]*Location, len(.Location))
	for ,  := range .Location {
		if  == nil {
			return fmt.Errorf("profile has nil location")
		}
		if .ID == 0 {
			return fmt.Errorf("found location with reserved id=0")
		}
		if [.ID] != nil {
			return fmt.Errorf("multiple locations with same id: %d", .ID)
		}
		[.ID] = 
		if  := .Mapping;  != nil {
			if .ID == 0 || [.ID] !=  {
				return fmt.Errorf("inconsistent mapping %p: %d", , .ID)
			}
		}
		for ,  := range .Line {
			 := .Function
			if  == nil {
				return fmt.Errorf("location id: %d has a line with nil function", .ID)
			}
			if .ID == 0 || [.ID] !=  {
				return fmt.Errorf("inconsistent function %p: %d", , .ID)
			}
		}
	}
	return nil
}
Aggregate merges the locations in the profile into equivalence classes preserving the request attributes. It also updates the samples to point to the merged locations.
func ( *Profile) (, , , ,  bool) error {
	for ,  := range .Mapping {
		.HasInlineFrames = .HasInlineFrames && 
		.HasFunctions = .HasFunctions && 
		.HasFilenames = .HasFilenames && 
		.HasLineNumbers = .HasLineNumbers && 
	}
Aggregate functions
	if ! || ! {
		for ,  := range .Function {
			if ! {
				.Name = ""
				.SystemName = ""
			}
			if ! {
				.Filename = ""
			}
		}
	}
Aggregate locations
	if ! || ! || ! {
		for ,  := range .Location {
			if ! && len(.Line) > 1 {
				.Line = .Line[len(.Line)-1:]
			}
			if ! {
				for  := range .Line {
					.Line[].Line = 0
				}
			}
			if ! {
				.Address = 0
			}
		}
	}

	return .CheckValid()
}
NumLabelUnits returns a map of numeric label keys to the units associated with those keys and a map of those keys to any units that were encountered but not used. Unit for a given key is the first encountered unit for that key. If multiple units are encountered for values paired with a particular key, then the first unit encountered is used and all other units are returned in sorted order in map of ignored units. If no units are encountered for a particular key, the unit is then inferred based on the key.
func ( *Profile) () (map[string]string, map[string][]string) {
	 := map[string]string{}
	 := map[string]map[string]bool{}
	 := map[string]bool{}
Determine units based on numeric tags for each sample.
	for ,  := range .Sample {
		for  := range .NumLabel {
			[] = true
			for ,  := range .NumUnit[] {
				if  == "" {
					continue
				}
				if ,  := []; ! {
					[] = 
				} else if  !=  {
					if ,  := [];  {
						[] = true
					} else {
						[] = map[string]bool{: true}
					}
				}
			}
		}
Infer units for keys without any units associated with numeric tag values.
	for  := range  {
		 := []
		if  == "" {
			switch  {
			case "alignment", "request":
				[] = "bytes"
			default:
				[] = 
			}
		}
	}
Copy ignored units into more readable format
	 := make(map[string][]string, len())
	for ,  := range  {
		 := make([]string, len())
		 := 0
		for  := range  {
			[] = 
			++
		}
		sort.Strings()
		[] = 
	}

	return , 
}
String dumps a text representation of a profile. Intended mainly for debugging purposes.
func ( *Profile) () string {
	 := make([]string, 0, len(.Comments)+len(.Sample)+len(.Mapping)+len(.Location))
	for ,  := range .Comments {
		 = append(, "Comment: "+)
	}
	if  := .PeriodType;  != nil {
		 = append(, fmt.Sprintf("PeriodType: %s %s", .Type, .Unit))
	}
	 = append(, fmt.Sprintf("Period: %d", .Period))
	if .TimeNanos != 0 {
		 = append(, fmt.Sprintf("Time: %v", time.Unix(0, .TimeNanos)))
	}
	if .DurationNanos != 0 {
		 = append(, fmt.Sprintf("Duration: %.4v", time.Duration(.DurationNanos)))
	}

	 = append(, "Samples:")
	var  string
	for ,  := range .SampleType {
		 := ""
		if .Type == .DefaultSampleType {
			 = "[dflt]"
		}
		 =  + fmt.Sprintf("%s/%s%s ", .Type, .Unit, )
	}
	 = append(, strings.TrimSpace())
	for ,  := range .Sample {
		 = append(, .string())
	}

	 = append(, "Locations")
	for ,  := range .Location {
		 = append(, .string())
	}

	 = append(, "Mappings")
	for ,  := range .Mapping {
		 = append(, .string())
	}

	return strings.Join(, "\n") + "\n"
}
string dumps a text representation of a mapping. Intended mainly for debugging purposes.
func ( *Mapping) () string {
	 := ""
	if .HasFunctions {
		 =  + "[FN]"
	}
	if .HasFilenames {
		 =  + "[FL]"
	}
	if .HasLineNumbers {
		 =  + "[LN]"
	}
	if .HasInlineFrames {
		 =  + "[IN]"
	}
	return fmt.Sprintf("%d: %#x/%#x/%#x %s %s %s",
		.ID,
		.Start, .Limit, .Offset,
		.File,
		.BuildID,
		)
}
string dumps a text representation of a location. Intended mainly for debugging purposes.
func ( *Location) () string {
	 := []string{}
	 := fmt.Sprintf("%6d: %#x ", .ID, .Address)
	if  := .Mapping;  != nil {
		 =  + fmt.Sprintf("M=%d ", .ID)
	}
	if .IsFolded {
		 =  + "[F] "
	}
	if len(.Line) == 0 {
		 = append(, )
	}
	for  := range .Line {
		 := "??"
		if  := .Line[].Function;  != nil {
			 = fmt.Sprintf("%s %s:%d s=%d",
				.Name,
				.Filename,
				.Line[].Line,
				.StartLine)
			if .Name != .SystemName {
				 =  + "(" + .SystemName + ")"
			}
		}
Do not print location details past the first line
		 = "             "
	}
	return strings.Join(, "\n")
}
string dumps a text representation of a sample. Intended mainly for debugging purposes.
func ( *Sample) () string {
	 := []string{}
	var  string
	for ,  := range .Value {
		 = fmt.Sprintf("%s %10d", , )
	}
	 =  + ": "
	for ,  := range .Location {
		 =  + fmt.Sprintf("%d ", .ID)
	}
	 = append(, )
	const  = "                "
	if len(.Label) > 0 {
		 = append(, +labelsToString(.Label))
	}
	if len(.NumLabel) > 0 {
		 = append(, +numLabelsToString(.NumLabel, .NumUnit))
	}
	return strings.Join(, "\n")
}
labelsToString returns a string representation of a map representing labels.
func ( map[string][]string) string {
	 := []string{}
	for ,  := range  {
		 = append(, fmt.Sprintf("%s:%v", , ))
	}
	sort.Strings()
	return strings.Join(, " ")
}
numLabelsToString returns a string representation of a map representing numeric labels.
func ( map[string][]int64,  map[string][]string) string {
	 := []string{}
	for ,  := range  {
		 := []
		var  string
		if len() == len() {
			 := make([]string, len())
			for ,  := range  {
				[] = fmt.Sprintf("%d %s", , [])
			}
			 = fmt.Sprintf("%s:%v", , )
		} else {
			 = fmt.Sprintf("%s:%v", , )
		}
		 = append(, )
	}
	sort.Strings()
	return strings.Join(, " ")
}
SetLabel sets the specified key to the specified value for all samples in the profile.
func ( *Profile) ( string,  []string) {
	for ,  := range .Sample {
		if .Label == nil {
			.Label = map[string][]string{: }
		} else {
			.Label[] = 
		}
	}
}
RemoveLabel removes all labels associated with the specified key for all samples in the profile.
func ( *Profile) ( string) {
	for ,  := range .Sample {
		delete(.Label, )
	}
}
HasLabel returns true if a sample has a label with indicated key and value.
func ( *Sample) (,  string) bool {
	for ,  := range .Label[] {
		if  ==  {
			return true
		}
	}
	return false
}
DiffBaseSample returns true if a sample belongs to the diff base and false otherwise.
func ( *Sample) () bool {
	return .HasLabel("pprof::base", "true")
}
Scale multiplies all sample values in a profile by a constant.
func ( *Profile) ( float64) {
	if  == 1 {
		return
	}
	 := make([]float64, len(.SampleType))
	for  := range .SampleType {
		[] = 
	}
	.ScaleN()
}
ScaleN multiplies each sample values in a sample by a different amount.
func ( *Profile) ( []float64) error {
	if len(.SampleType) != len() {
		return fmt.Errorf("mismatched scale ratios, got %d, want %d", len(), len(.SampleType))
	}
	 := true
	for ,  := range  {
		if  != 1 {
			 = false
			break
		}
	}
	if  {
		return nil
	}
	for ,  := range .Sample {
		for ,  := range .Value {
			if [] != 1 {
				.Value[] = int64(float64() * [])
			}
		}
	}
	return nil
}
HasFunctions determines if all locations in this profile have symbolized function information.
func ( *Profile) () bool {
	for ,  := range .Location {
		if .Mapping != nil && !.Mapping.HasFunctions {
			return false
		}
	}
	return true
}
HasFileLines determines if all locations in this profile have symbolized file and line number information.
func ( *Profile) () bool {
	for ,  := range .Location {
		if .Mapping != nil && (!.Mapping.HasFilenames || !.Mapping.HasLineNumbers) {
			return false
		}
	}
	return true
}
Unsymbolizable returns true if a mapping points to a binary for which locations can't be symbolized in principle, at least now. Examples are "[vdso]", [vsyscall]" and some others, see the code.
func ( *Mapping) () bool {
	 := filepath.Base(.File)
	return strings.HasPrefix(, "[") || strings.HasPrefix(, "linux-vdso") || strings.HasPrefix(.File, "/dev/dri/")
}
Copy makes a fully independent copy of a profile.
func ( *Profile) () *Profile {
	 := &Profile{}
	if  := unmarshal(serialize(), );  != nil {
		panic()
	}
	if  := .postDecode();  != nil {
		panic()
	}

	return