// 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 profileimport ()// Profile is an in-memory representation of profile.proto.typeProfilestruct { SampleType []*ValueType DefaultSampleType string Sample []*Sample Mapping []*Mapping Location []*Location Function []*Function Comments []string DocURL string DropFrames string KeepFrames string TimeNanos int64 DurationNanos int64 PeriodType *ValueType Period int64// The following fields are modified during encoding and copying, // so are protected by a Mutex. encodeMu sync.Mutex commentX []int64 docURLX int64 dropFramesX int64 keepFramesX int64 stringTable []string defaultSampleTypeX int64}// ValueType corresponds to Profile.ValueTypetypeValueTypestruct { Type string// cpu, wall, inuse_space, etc Unit string// seconds, nanoseconds, bytes, etc typeX int64 unitX int64}// Sample corresponds to Profile.SampletypeSamplestruct { Location []*Location Value []int64// Label is a per-label-key map to values for string labels. // // In general, having multiple values for the given label key is strongly // discouraged - see docs for the sample label field in profile.proto. The // main reason this unlikely state is tracked here is to make the // decoding->encoding roundtrip not lossy. But we expect that the value // slices present in this map are always of length 1. Label map[string][]string// NumLabel is a per-label-key map to values for numeric labels. See a note // above on handling multiple values for a label. NumLabel map[string][]int64// NumUnit is a per-label-key map to the unit names of corresponding numeric // label values. The unit info may be missing even if the label is in // NumLabel, see the docs in profile.proto for details. When the value is // slice is present and not nil, its length must be equal to the length of // the corresponding value slice in NumLabel. NumUnit map[string][]string locationIDX []uint64 labelX []label}// label corresponds to Profile.Labeltype label struct { keyX int64// Exactly one of the two following values must be set strX int64 numX int64// Integer value for this label// can be set if numX has value unitX int64}// Mapping corresponds to Profile.MappingtypeMappingstruct { ID uint64 Start uint64 Limit uint64 Offset uint64 File string BuildID string HasFunctions bool HasFilenames bool HasLineNumbers bool HasInlineFrames bool fileX int64 buildIDX int64// Name of the kernel relocation symbol ("_text" or "_stext"), extracted from File. // For linux kernel mappings generated by some tools, correct symbolization depends // on knowing which of the two possible relocation symbols was used for `Start`. // This is given to us as a suffix in `File` (e.g. "[kernel.kallsyms]_stext"). // // Note, this public field is not persisted in the proto. For the purposes of // copying / merging / hashing profiles, it is considered subsumed by `File`. KernelRelocationSymbol string}// Location corresponds to Profile.LocationtypeLocationstruct { ID uint64 Mapping *Mapping Address uint64 Line []Line IsFolded bool mappingIDX uint64}// Line corresponds to Profile.LinetypeLinestruct { Function *Function Line int64 Column int64 functionIDX uint64}// Function corresponds to Profile.FunctiontypeFunctionstruct { ID uint64 Name string SystemName string Filename string StartLine int64 nameX int64 systemNameX int64 filenameX int64}// 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) { , := io.ReadAll()if != nil {returnnil, }returnParseData()}// ParseData parses a profile from a buffer and checks for its// validity.func ( []byte) (*Profile, error) {var *Profilevarerroriflen() >= 2 && [0] == 0x1f && [1] == 0x8b { , := gzip.NewReader(bytes.NewBuffer())if == nil { , = io.ReadAll() }if != nil {returnnil, fmt.Errorf("decompressing profile: %v", ) } }if , = ParseUncompressed(); != nil && != errNoData && != errConcatProfile { , = parseLegacy() }if != nil {returnnil, fmt.Errorf("parsing profile: %v", ) }if := .CheckValid(); != nil {returnnil, 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 parseLegacy( []byte) (*Profile, error) { := []func([]byte) (*Profile, error){parseCPU,parseHeap,parseGoCount, // goroutine, threadcreateparseThread,parseContention,parseJavaProfile, }for , := range { , := ()if == nil { .addLegacyFrameInfo()return , nil }if != errUnrecognized {returnnil, } }returnnil, errUnrecognized}// ParseUncompressed parses an uncompressed protobuf into a profile.func ( []byte) (*Profile, error) {iflen() == 0 {returnnil, errNoData } := &Profile{}if := unmarshal(, ); != nil {returnnil, }if := .postDecode(); != nil {returnnil, }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.func ( *Profile) () {// Merge adjacent regions with matching names, checking that the offsets matchiflen(.Mapping) > 1 { := []*Mapping{.Mapping[0]}for , := range .Mapping[1:] { := [len()-1]ifadjacent(, ) { .Limit = .Limitif .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 mappingsfor , := range .Mapping { := strings.TrimSpace(strings.Replace(.File, "(deleted)", "", -1))iflen() == 0 {continue }iflen(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 sortedfor , := 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 adjacent(, *Mapping) bool {if .File != "" && .File != "" {if .File != .File {returnfalse } }if .BuildID != "" && .BuildID != "" {if .BuildID != .BuildID {returnfalse } }if .Limit != .Start {returnfalse }if .Offset != 0 && .Offset != 0 { := .Offset + (.Limit - .Start)if != .Offset {returnfalse } }returntrue}func ( *Profile) (, *Mapping) {for , := range .Location {if .Mapping == { .Mapping = } }}func serialize( *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.Locationfunc ( *Profile) () error {// Check that sample values are consistent := len(.SampleType)if == 0 && len(.Sample) != 0 {returnfmt.Errorf("missing sample type information") }for , := range .Sample {if == nil {returnfmt.Errorf("profile has nil sample") }iflen(.Value) != {returnfmt.Errorf("mismatch: sample has %d values vs. %d types", len(.Value), len(.SampleType)) }for , := range .Location {if == nil {returnfmt.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 {returnfmt.Errorf("profile has nil mapping") }if .ID == 0 {returnfmt.Errorf("found mapping with reserved ID=0") }if [.ID] != nil {returnfmt.Errorf("multiple mappings with same id: %d", .ID) } [.ID] = } := make(map[uint64]*Function, len(.Function))for , := range .Function {if == nil {returnfmt.Errorf("profile has nil function") }if .ID == 0 {returnfmt.Errorf("found function with reserved ID=0") }if [.ID] != nil {returnfmt.Errorf("multiple functions with same id: %d", .ID) } [.ID] = } := make(map[uint64]*Location, len(.Location))for , := range .Location {if == nil {returnfmt.Errorf("profile has nil location") }if .ID == 0 {returnfmt.Errorf("found location with reserved id=0") }if [.ID] != nil {returnfmt.Errorf("multiple locations with same id: %d", .ID) } [.ID] = if := .Mapping; != nil {if .ID == 0 || [.ID] != {returnfmt.Errorf("inconsistent mapping %p: %d", , .ID) } }for , := range .Line { := .Functionif == nil {returnfmt.Errorf("location id: %d has a line with nil function", .ID) }if .ID == 0 || [.ID] != {returnfmt.Errorf("inconsistent function %p: %d", , .ID) } } }returnnil}// 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 functionsif ! || ! {for , := range .Function {if ! { .Name = "" .SystemName = "" }if ! { .Filename = "" } } }// Aggregate locationsif ! || ! || ! || ! {for , := range .Location {if ! && len(.Line) > 1 { .Line = .Line[len(.Line)-1:] }if ! {for := range .Line { .Line[].Line = 0 .Line[].Column = 0 } }if ! {for := range .Line { .Line[].Column = 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 { [] = truefor , := range .NumUnit[] {if == "" {continue }if , := []; ! { [] = } elseif != {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()) := 0for := 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 := .DocURL; != "" { = append(, fmt.Sprintf("Doc: %s", )) }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:")varstringfor , := 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()) }returnstrings.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]" }returnfmt.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] " }iflen(.Line) == 0 { = append(, ) }for := range .Line { := "??"if := .Line[].Function; != nil { = fmt.Sprintf("%s %s:%d:%d s=%d", .Name, .Filename, .Line[].Line, .Line[].Column, .StartLine)if .Name != .SystemName { = + "(" + .SystemName + ")" } } = append(, +)// Do not print location details past the first line = " " }returnstrings.Join(, "\n")}// string dumps a text representation of a sample. Intended mainly// for debugging purposes.func ( *Sample) () string { := []string{}varstringfor , := range .Value { = fmt.Sprintf("%s %10d", , ) } = + ": "for , := range .Location { = + fmt.Sprintf("%d ", .ID) } = append(, )const = " "iflen(.Label) > 0 { = append(, +labelsToString(.Label)) }iflen(.NumLabel) > 0 { = append(, +numLabelsToString(.NumLabel, .NumUnit)) }returnstrings.Join(, "\n")}// labelsToString returns a string representation of a// map representing labels.func labelsToString( map[string][]string) string { := []string{}for , := range { = append(, fmt.Sprintf("%s:%v", , )) }sort.Strings()returnstrings.Join(, " ")}// numLabelsToString returns a string representation of a map// representing numeric labels.func numLabelsToString( map[string][]int64, map[string][]string) string { := []string{}for , := range { := []varstringiflen() == len() { := make([]string, len())for , := range { [] = fmt.Sprintf("%d %s", , []) } = fmt.Sprintf("%s:%v", , ) } else { = fmt.Sprintf("%s:%v", , ) } = append(, ) }sort.Strings()returnstrings.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 == {returntrue } }returnfalse}// SetNumLabel sets the specified key to the specified value for all samples in the// profile. "unit" is a slice that describes the units that each corresponding member// of "values" is measured in (e.g. bytes or seconds). If there is no relevant// unit for a given value, that member of "unit" should be the empty string.// "unit" must either have the same length as "value", or be nil.func ( *Profile) ( string, []int64, []string) {for , := range .Sample {if .NumLabel == nil { .NumLabel = map[string][]int64{: } } else { .NumLabel[] = }if .NumUnit == nil { .NumUnit = map[string][]string{: } } else { .NumUnit[] = } }}// RemoveNumLabel removes all numerical labels associated with the specified key for all// samples in the profile.func ( *Profile) ( string) {for , := range .Sample {delete(.NumLabel, )delete(.NumUnit, ) }}// 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 and keeps// only samples that have at least one non-zero value.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// and keeps only samples that have at least one non-zero value.func ( *Profile) ( []float64) error {iflen(.SampleType) != len() {returnfmt.Errorf("mismatched scale ratios, got %d, want %d", len(), len(.SampleType)) } := truefor , := range {if != 1 { = falsebreak } }if {returnnil } := 0for , := range .Sample { := falsefor , := range .Value {if [] != 1 { := int64(math.Round(float64() * [])) .Value[] = = || != 0 } }if { .Sample[] = ++ } } .Sample = .Sample[:]returnnil}// HasFunctions determines if all locations in this profile have// symbolized function information.func ( *Profile) () bool {for , := range .Location {if .Mapping != nil && !.Mapping.HasFunctions {returnfalse } }returntrue}// 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) {returnfalse } }returntrue}// 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)returnstrings.HasPrefix(, "[") || strings.HasPrefix(, "linux-vdso") || strings.HasPrefix(.File, "/dev/dri/") || .File == "//anon"}// Copy makes a fully independent copy of a profile.func ( *Profile) () *Profile { := &Profile{}if := unmarshal(serialize(), ); != nil {panic() }if := .postDecode(); != nil {panic() }return}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.