Source file src/cmd/vendor/github.com/google/pprof/profile/profile.go

     1  // Copyright 2014 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package profile provides a representation of profile.proto and
    16  // methods to encode/decode profiles in this format.
    17  package profile
    18  
    19  import (
    20  	"bytes"
    21  	"compress/gzip"
    22  	"fmt"
    23  	"io"
    24  	"math"
    25  	"path/filepath"
    26  	"regexp"
    27  	"sort"
    28  	"strings"
    29  	"sync"
    30  	"time"
    31  )
    32  
    33  // Profile is an in-memory representation of profile.proto.
    34  type Profile struct {
    35  	SampleType        []*ValueType
    36  	DefaultSampleType string
    37  	Sample            []*Sample
    38  	Mapping           []*Mapping
    39  	Location          []*Location
    40  	Function          []*Function
    41  	Comments          []string
    42  
    43  	DropFrames string
    44  	KeepFrames string
    45  
    46  	TimeNanos     int64
    47  	DurationNanos int64
    48  	PeriodType    *ValueType
    49  	Period        int64
    50  
    51  	// The following fields are modified during encoding and copying,
    52  	// so are protected by a Mutex.
    53  	encodeMu sync.Mutex
    54  
    55  	commentX           []int64
    56  	dropFramesX        int64
    57  	keepFramesX        int64
    58  	stringTable        []string
    59  	defaultSampleTypeX int64
    60  }
    61  
    62  // ValueType corresponds to Profile.ValueType
    63  type ValueType struct {
    64  	Type string // cpu, wall, inuse_space, etc
    65  	Unit string // seconds, nanoseconds, bytes, etc
    66  
    67  	typeX int64
    68  	unitX int64
    69  }
    70  
    71  // Sample corresponds to Profile.Sample
    72  type Sample struct {
    73  	Location []*Location
    74  	Value    []int64
    75  	// Label is a per-label-key map to values for string labels.
    76  	//
    77  	// In general, having multiple values for the given label key is strongly
    78  	// discouraged - see docs for the sample label field in profile.proto.  The
    79  	// main reason this unlikely state is tracked here is to make the
    80  	// decoding->encoding roundtrip not lossy. But we expect that the value
    81  	// slices present in this map are always of length 1.
    82  	Label map[string][]string
    83  	// NumLabel is a per-label-key map to values for numeric labels. See a note
    84  	// above on handling multiple values for a label.
    85  	NumLabel map[string][]int64
    86  	// NumUnit is a per-label-key map to the unit names of corresponding numeric
    87  	// label values. The unit info may be missing even if the label is in
    88  	// NumLabel, see the docs in profile.proto for details. When the value is
    89  	// slice is present and not nil, its length must be equal to the length of
    90  	// the corresponding value slice in NumLabel.
    91  	NumUnit map[string][]string
    92  
    93  	locationIDX []uint64
    94  	labelX      []label
    95  }
    96  
    97  // label corresponds to Profile.Label
    98  type label struct {
    99  	keyX int64
   100  	// Exactly one of the two following values must be set
   101  	strX int64
   102  	numX int64 // Integer value for this label
   103  	// can be set if numX has value
   104  	unitX int64
   105  }
   106  
   107  // Mapping corresponds to Profile.Mapping
   108  type Mapping struct {
   109  	ID              uint64
   110  	Start           uint64
   111  	Limit           uint64
   112  	Offset          uint64
   113  	File            string
   114  	BuildID         string
   115  	HasFunctions    bool
   116  	HasFilenames    bool
   117  	HasLineNumbers  bool
   118  	HasInlineFrames bool
   119  
   120  	fileX    int64
   121  	buildIDX int64
   122  
   123  	// Name of the kernel relocation symbol ("_text" or "_stext"), extracted from File.
   124  	// For linux kernel mappings generated by some tools, correct symbolization depends
   125  	// on knowing which of the two possible relocation symbols was used for `Start`.
   126  	// This is given to us as a suffix in `File` (e.g. "[kernel.kallsyms]_stext").
   127  	//
   128  	// Note, this public field is not persisted in the proto. For the purposes of
   129  	// copying / merging / hashing profiles, it is considered subsumed by `File`.
   130  	KernelRelocationSymbol string
   131  }
   132  
   133  // Location corresponds to Profile.Location
   134  type Location struct {
   135  	ID       uint64
   136  	Mapping  *Mapping
   137  	Address  uint64
   138  	Line     []Line
   139  	IsFolded bool
   140  
   141  	mappingIDX uint64
   142  }
   143  
   144  // Line corresponds to Profile.Line
   145  type Line struct {
   146  	Function *Function
   147  	Line     int64
   148  
   149  	functionIDX uint64
   150  }
   151  
   152  // Function corresponds to Profile.Function
   153  type Function struct {
   154  	ID         uint64
   155  	Name       string
   156  	SystemName string
   157  	Filename   string
   158  	StartLine  int64
   159  
   160  	nameX       int64
   161  	systemNameX int64
   162  	filenameX   int64
   163  }
   164  
   165  // Parse parses a profile and checks for its validity. The input
   166  // may be a gzip-compressed encoded protobuf or one of many legacy
   167  // profile formats which may be unsupported in the future.
   168  func Parse(r io.Reader) (*Profile, error) {
   169  	data, err := io.ReadAll(r)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  	return ParseData(data)
   174  }
   175  
   176  // ParseData parses a profile from a buffer and checks for its
   177  // validity.
   178  func ParseData(data []byte) (*Profile, error) {
   179  	var p *Profile
   180  	var err error
   181  	if len(data) >= 2 && data[0] == 0x1f && data[1] == 0x8b {
   182  		gz, err := gzip.NewReader(bytes.NewBuffer(data))
   183  		if err == nil {
   184  			data, err = io.ReadAll(gz)
   185  		}
   186  		if err != nil {
   187  			return nil, fmt.Errorf("decompressing profile: %v", err)
   188  		}
   189  	}
   190  	if p, err = ParseUncompressed(data); err != nil && err != errNoData && err != errConcatProfile {
   191  		p, err = parseLegacy(data)
   192  	}
   193  
   194  	if err != nil {
   195  		return nil, fmt.Errorf("parsing profile: %v", err)
   196  	}
   197  
   198  	if err := p.CheckValid(); err != nil {
   199  		return nil, fmt.Errorf("malformed profile: %v", err)
   200  	}
   201  	return p, nil
   202  }
   203  
   204  var errUnrecognized = fmt.Errorf("unrecognized profile format")
   205  var errMalformed = fmt.Errorf("malformed profile format")
   206  var errNoData = fmt.Errorf("empty input file")
   207  var errConcatProfile = fmt.Errorf("concatenated profiles detected")
   208  
   209  func parseLegacy(data []byte) (*Profile, error) {
   210  	parsers := []func([]byte) (*Profile, error){
   211  		parseCPU,
   212  		parseHeap,
   213  		parseGoCount, // goroutine, threadcreate
   214  		parseThread,
   215  		parseContention,
   216  		parseJavaProfile,
   217  	}
   218  
   219  	for _, parser := range parsers {
   220  		p, err := parser(data)
   221  		if err == nil {
   222  			p.addLegacyFrameInfo()
   223  			return p, nil
   224  		}
   225  		if err != errUnrecognized {
   226  			return nil, err
   227  		}
   228  	}
   229  	return nil, errUnrecognized
   230  }
   231  
   232  // ParseUncompressed parses an uncompressed protobuf into a profile.
   233  func ParseUncompressed(data []byte) (*Profile, error) {
   234  	if len(data) == 0 {
   235  		return nil, errNoData
   236  	}
   237  	p := &Profile{}
   238  	if err := unmarshal(data, p); err != nil {
   239  		return nil, err
   240  	}
   241  
   242  	if err := p.postDecode(); err != nil {
   243  		return nil, err
   244  	}
   245  
   246  	return p, nil
   247  }
   248  
   249  var libRx = regexp.MustCompile(`([.]so$|[.]so[._][0-9]+)`)
   250  
   251  // massageMappings applies heuristic-based changes to the profile
   252  // mappings to account for quirks of some environments.
   253  func (p *Profile) massageMappings() {
   254  	// Merge adjacent regions with matching names, checking that the offsets match
   255  	if len(p.Mapping) > 1 {
   256  		mappings := []*Mapping{p.Mapping[0]}
   257  		for _, m := range p.Mapping[1:] {
   258  			lm := mappings[len(mappings)-1]
   259  			if adjacent(lm, m) {
   260  				lm.Limit = m.Limit
   261  				if m.File != "" {
   262  					lm.File = m.File
   263  				}
   264  				if m.BuildID != "" {
   265  					lm.BuildID = m.BuildID
   266  				}
   267  				p.updateLocationMapping(m, lm)
   268  				continue
   269  			}
   270  			mappings = append(mappings, m)
   271  		}
   272  		p.Mapping = mappings
   273  	}
   274  
   275  	// Use heuristics to identify main binary and move it to the top of the list of mappings
   276  	for i, m := range p.Mapping {
   277  		file := strings.TrimSpace(strings.Replace(m.File, "(deleted)", "", -1))
   278  		if len(file) == 0 {
   279  			continue
   280  		}
   281  		if len(libRx.FindStringSubmatch(file)) > 0 {
   282  			continue
   283  		}
   284  		if file[0] == '[' {
   285  			continue
   286  		}
   287  		// Swap what we guess is main to position 0.
   288  		p.Mapping[0], p.Mapping[i] = p.Mapping[i], p.Mapping[0]
   289  		break
   290  	}
   291  
   292  	// Keep the mapping IDs neatly sorted
   293  	for i, m := range p.Mapping {
   294  		m.ID = uint64(i + 1)
   295  	}
   296  }
   297  
   298  // adjacent returns whether two mapping entries represent the same
   299  // mapping that has been split into two. Check that their addresses are adjacent,
   300  // and if the offsets match, if they are available.
   301  func adjacent(m1, m2 *Mapping) bool {
   302  	if m1.File != "" && m2.File != "" {
   303  		if m1.File != m2.File {
   304  			return false
   305  		}
   306  	}
   307  	if m1.BuildID != "" && m2.BuildID != "" {
   308  		if m1.BuildID != m2.BuildID {
   309  			return false
   310  		}
   311  	}
   312  	if m1.Limit != m2.Start {
   313  		return false
   314  	}
   315  	if m1.Offset != 0 && m2.Offset != 0 {
   316  		offset := m1.Offset + (m1.Limit - m1.Start)
   317  		if offset != m2.Offset {
   318  			return false
   319  		}
   320  	}
   321  	return true
   322  }
   323  
   324  func (p *Profile) updateLocationMapping(from, to *Mapping) {
   325  	for _, l := range p.Location {
   326  		if l.Mapping == from {
   327  			l.Mapping = to
   328  		}
   329  	}
   330  }
   331  
   332  func serialize(p *Profile) []byte {
   333  	p.encodeMu.Lock()
   334  	p.preEncode()
   335  	b := marshal(p)
   336  	p.encodeMu.Unlock()
   337  	return b
   338  }
   339  
   340  // Write writes the profile as a gzip-compressed marshaled protobuf.
   341  func (p *Profile) Write(w io.Writer) error {
   342  	zw := gzip.NewWriter(w)
   343  	defer zw.Close()
   344  	_, err := zw.Write(serialize(p))
   345  	return err
   346  }
   347  
   348  // WriteUncompressed writes the profile as a marshaled protobuf.
   349  func (p *Profile) WriteUncompressed(w io.Writer) error {
   350  	_, err := w.Write(serialize(p))
   351  	return err
   352  }
   353  
   354  // CheckValid tests whether the profile is valid. Checks include, but are
   355  // not limited to:
   356  //   - len(Profile.Sample[n].value) == len(Profile.value_unit)
   357  //   - Sample.id has a corresponding Profile.Location
   358  func (p *Profile) CheckValid() error {
   359  	// Check that sample values are consistent
   360  	sampleLen := len(p.SampleType)
   361  	if sampleLen == 0 && len(p.Sample) != 0 {
   362  		return fmt.Errorf("missing sample type information")
   363  	}
   364  	for _, s := range p.Sample {
   365  		if s == nil {
   366  			return fmt.Errorf("profile has nil sample")
   367  		}
   368  		if len(s.Value) != sampleLen {
   369  			return fmt.Errorf("mismatch: sample has %d values vs. %d types", len(s.Value), len(p.SampleType))
   370  		}
   371  		for _, l := range s.Location {
   372  			if l == nil {
   373  				return fmt.Errorf("sample has nil location")
   374  			}
   375  		}
   376  	}
   377  
   378  	// Check that all mappings/locations/functions are in the tables
   379  	// Check that there are no duplicate ids
   380  	mappings := make(map[uint64]*Mapping, len(p.Mapping))
   381  	for _, m := range p.Mapping {
   382  		if m == nil {
   383  			return fmt.Errorf("profile has nil mapping")
   384  		}
   385  		if m.ID == 0 {
   386  			return fmt.Errorf("found mapping with reserved ID=0")
   387  		}
   388  		if mappings[m.ID] != nil {
   389  			return fmt.Errorf("multiple mappings with same id: %d", m.ID)
   390  		}
   391  		mappings[m.ID] = m
   392  	}
   393  	functions := make(map[uint64]*Function, len(p.Function))
   394  	for _, f := range p.Function {
   395  		if f == nil {
   396  			return fmt.Errorf("profile has nil function")
   397  		}
   398  		if f.ID == 0 {
   399  			return fmt.Errorf("found function with reserved ID=0")
   400  		}
   401  		if functions[f.ID] != nil {
   402  			return fmt.Errorf("multiple functions with same id: %d", f.ID)
   403  		}
   404  		functions[f.ID] = f
   405  	}
   406  	locations := make(map[uint64]*Location, len(p.Location))
   407  	for _, l := range p.Location {
   408  		if l == nil {
   409  			return fmt.Errorf("profile has nil location")
   410  		}
   411  		if l.ID == 0 {
   412  			return fmt.Errorf("found location with reserved id=0")
   413  		}
   414  		if locations[l.ID] != nil {
   415  			return fmt.Errorf("multiple locations with same id: %d", l.ID)
   416  		}
   417  		locations[l.ID] = l
   418  		if m := l.Mapping; m != nil {
   419  			if m.ID == 0 || mappings[m.ID] != m {
   420  				return fmt.Errorf("inconsistent mapping %p: %d", m, m.ID)
   421  			}
   422  		}
   423  		for _, ln := range l.Line {
   424  			f := ln.Function
   425  			if f == nil {
   426  				return fmt.Errorf("location id: %d has a line with nil function", l.ID)
   427  			}
   428  			if f.ID == 0 || functions[f.ID] != f {
   429  				return fmt.Errorf("inconsistent function %p: %d", f, f.ID)
   430  			}
   431  		}
   432  	}
   433  	return nil
   434  }
   435  
   436  // Aggregate merges the locations in the profile into equivalence
   437  // classes preserving the request attributes. It also updates the
   438  // samples to point to the merged locations.
   439  func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, address bool) error {
   440  	for _, m := range p.Mapping {
   441  		m.HasInlineFrames = m.HasInlineFrames && inlineFrame
   442  		m.HasFunctions = m.HasFunctions && function
   443  		m.HasFilenames = m.HasFilenames && filename
   444  		m.HasLineNumbers = m.HasLineNumbers && linenumber
   445  	}
   446  
   447  	// Aggregate functions
   448  	if !function || !filename {
   449  		for _, f := range p.Function {
   450  			if !function {
   451  				f.Name = ""
   452  				f.SystemName = ""
   453  			}
   454  			if !filename {
   455  				f.Filename = ""
   456  			}
   457  		}
   458  	}
   459  
   460  	// Aggregate locations
   461  	if !inlineFrame || !address || !linenumber {
   462  		for _, l := range p.Location {
   463  			if !inlineFrame && len(l.Line) > 1 {
   464  				l.Line = l.Line[len(l.Line)-1:]
   465  			}
   466  			if !linenumber {
   467  				for i := range l.Line {
   468  					l.Line[i].Line = 0
   469  				}
   470  			}
   471  			if !address {
   472  				l.Address = 0
   473  			}
   474  		}
   475  	}
   476  
   477  	return p.CheckValid()
   478  }
   479  
   480  // NumLabelUnits returns a map of numeric label keys to the units
   481  // associated with those keys and a map of those keys to any units
   482  // that were encountered but not used.
   483  // Unit for a given key is the first encountered unit for that key. If multiple
   484  // units are encountered for values paired with a particular key, then the first
   485  // unit encountered is used and all other units are returned in sorted order
   486  // in map of ignored units.
   487  // If no units are encountered for a particular key, the unit is then inferred
   488  // based on the key.
   489  func (p *Profile) NumLabelUnits() (map[string]string, map[string][]string) {
   490  	numLabelUnits := map[string]string{}
   491  	ignoredUnits := map[string]map[string]bool{}
   492  	encounteredKeys := map[string]bool{}
   493  
   494  	// Determine units based on numeric tags for each sample.
   495  	for _, s := range p.Sample {
   496  		for k := range s.NumLabel {
   497  			encounteredKeys[k] = true
   498  			for _, unit := range s.NumUnit[k] {
   499  				if unit == "" {
   500  					continue
   501  				}
   502  				if wantUnit, ok := numLabelUnits[k]; !ok {
   503  					numLabelUnits[k] = unit
   504  				} else if wantUnit != unit {
   505  					if v, ok := ignoredUnits[k]; ok {
   506  						v[unit] = true
   507  					} else {
   508  						ignoredUnits[k] = map[string]bool{unit: true}
   509  					}
   510  				}
   511  			}
   512  		}
   513  	}
   514  	// Infer units for keys without any units associated with
   515  	// numeric tag values.
   516  	for key := range encounteredKeys {
   517  		unit := numLabelUnits[key]
   518  		if unit == "" {
   519  			switch key {
   520  			case "alignment", "request":
   521  				numLabelUnits[key] = "bytes"
   522  			default:
   523  				numLabelUnits[key] = key
   524  			}
   525  		}
   526  	}
   527  
   528  	// Copy ignored units into more readable format
   529  	unitsIgnored := make(map[string][]string, len(ignoredUnits))
   530  	for key, values := range ignoredUnits {
   531  		units := make([]string, len(values))
   532  		i := 0
   533  		for unit := range values {
   534  			units[i] = unit
   535  			i++
   536  		}
   537  		sort.Strings(units)
   538  		unitsIgnored[key] = units
   539  	}
   540  
   541  	return numLabelUnits, unitsIgnored
   542  }
   543  
   544  // String dumps a text representation of a profile. Intended mainly
   545  // for debugging purposes.
   546  func (p *Profile) String() string {
   547  	ss := make([]string, 0, len(p.Comments)+len(p.Sample)+len(p.Mapping)+len(p.Location))
   548  	for _, c := range p.Comments {
   549  		ss = append(ss, "Comment: "+c)
   550  	}
   551  	if pt := p.PeriodType; pt != nil {
   552  		ss = append(ss, fmt.Sprintf("PeriodType: %s %s", pt.Type, pt.Unit))
   553  	}
   554  	ss = append(ss, fmt.Sprintf("Period: %d", p.Period))
   555  	if p.TimeNanos != 0 {
   556  		ss = append(ss, fmt.Sprintf("Time: %v", time.Unix(0, p.TimeNanos)))
   557  	}
   558  	if p.DurationNanos != 0 {
   559  		ss = append(ss, fmt.Sprintf("Duration: %.4v", time.Duration(p.DurationNanos)))
   560  	}
   561  
   562  	ss = append(ss, "Samples:")
   563  	var sh1 string
   564  	for _, s := range p.SampleType {
   565  		dflt := ""
   566  		if s.Type == p.DefaultSampleType {
   567  			dflt = "[dflt]"
   568  		}
   569  		sh1 = sh1 + fmt.Sprintf("%s/%s%s ", s.Type, s.Unit, dflt)
   570  	}
   571  	ss = append(ss, strings.TrimSpace(sh1))
   572  	for _, s := range p.Sample {
   573  		ss = append(ss, s.string())
   574  	}
   575  
   576  	ss = append(ss, "Locations")
   577  	for _, l := range p.Location {
   578  		ss = append(ss, l.string())
   579  	}
   580  
   581  	ss = append(ss, "Mappings")
   582  	for _, m := range p.Mapping {
   583  		ss = append(ss, m.string())
   584  	}
   585  
   586  	return strings.Join(ss, "\n") + "\n"
   587  }
   588  
   589  // string dumps a text representation of a mapping. Intended mainly
   590  // for debugging purposes.
   591  func (m *Mapping) string() string {
   592  	bits := ""
   593  	if m.HasFunctions {
   594  		bits = bits + "[FN]"
   595  	}
   596  	if m.HasFilenames {
   597  		bits = bits + "[FL]"
   598  	}
   599  	if m.HasLineNumbers {
   600  		bits = bits + "[LN]"
   601  	}
   602  	if m.HasInlineFrames {
   603  		bits = bits + "[IN]"
   604  	}
   605  	return fmt.Sprintf("%d: %#x/%#x/%#x %s %s %s",
   606  		m.ID,
   607  		m.Start, m.Limit, m.Offset,
   608  		m.File,
   609  		m.BuildID,
   610  		bits)
   611  }
   612  
   613  // string dumps a text representation of a location. Intended mainly
   614  // for debugging purposes.
   615  func (l *Location) string() string {
   616  	ss := []string{}
   617  	locStr := fmt.Sprintf("%6d: %#x ", l.ID, l.Address)
   618  	if m := l.Mapping; m != nil {
   619  		locStr = locStr + fmt.Sprintf("M=%d ", m.ID)
   620  	}
   621  	if l.IsFolded {
   622  		locStr = locStr + "[F] "
   623  	}
   624  	if len(l.Line) == 0 {
   625  		ss = append(ss, locStr)
   626  	}
   627  	for li := range l.Line {
   628  		lnStr := "??"
   629  		if fn := l.Line[li].Function; fn != nil {
   630  			lnStr = fmt.Sprintf("%s %s:%d s=%d",
   631  				fn.Name,
   632  				fn.Filename,
   633  				l.Line[li].Line,
   634  				fn.StartLine)
   635  			if fn.Name != fn.SystemName {
   636  				lnStr = lnStr + "(" + fn.SystemName + ")"
   637  			}
   638  		}
   639  		ss = append(ss, locStr+lnStr)
   640  		// Do not print location details past the first line
   641  		locStr = "             "
   642  	}
   643  	return strings.Join(ss, "\n")
   644  }
   645  
   646  // string dumps a text representation of a sample. Intended mainly
   647  // for debugging purposes.
   648  func (s *Sample) string() string {
   649  	ss := []string{}
   650  	var sv string
   651  	for _, v := range s.Value {
   652  		sv = fmt.Sprintf("%s %10d", sv, v)
   653  	}
   654  	sv = sv + ": "
   655  	for _, l := range s.Location {
   656  		sv = sv + fmt.Sprintf("%d ", l.ID)
   657  	}
   658  	ss = append(ss, sv)
   659  	const labelHeader = "                "
   660  	if len(s.Label) > 0 {
   661  		ss = append(ss, labelHeader+labelsToString(s.Label))
   662  	}
   663  	if len(s.NumLabel) > 0 {
   664  		ss = append(ss, labelHeader+numLabelsToString(s.NumLabel, s.NumUnit))
   665  	}
   666  	return strings.Join(ss, "\n")
   667  }
   668  
   669  // labelsToString returns a string representation of a
   670  // map representing labels.
   671  func labelsToString(labels map[string][]string) string {
   672  	ls := []string{}
   673  	for k, v := range labels {
   674  		ls = append(ls, fmt.Sprintf("%s:%v", k, v))
   675  	}
   676  	sort.Strings(ls)
   677  	return strings.Join(ls, " ")
   678  }
   679  
   680  // numLabelsToString returns a string representation of a map
   681  // representing numeric labels.
   682  func numLabelsToString(numLabels map[string][]int64, numUnits map[string][]string) string {
   683  	ls := []string{}
   684  	for k, v := range numLabels {
   685  		units := numUnits[k]
   686  		var labelString string
   687  		if len(units) == len(v) {
   688  			values := make([]string, len(v))
   689  			for i, vv := range v {
   690  				values[i] = fmt.Sprintf("%d %s", vv, units[i])
   691  			}
   692  			labelString = fmt.Sprintf("%s:%v", k, values)
   693  		} else {
   694  			labelString = fmt.Sprintf("%s:%v", k, v)
   695  		}
   696  		ls = append(ls, labelString)
   697  	}
   698  	sort.Strings(ls)
   699  	return strings.Join(ls, " ")
   700  }
   701  
   702  // SetLabel sets the specified key to the specified value for all samples in the
   703  // profile.
   704  func (p *Profile) SetLabel(key string, value []string) {
   705  	for _, sample := range p.Sample {
   706  		if sample.Label == nil {
   707  			sample.Label = map[string][]string{key: value}
   708  		} else {
   709  			sample.Label[key] = value
   710  		}
   711  	}
   712  }
   713  
   714  // RemoveLabel removes all labels associated with the specified key for all
   715  // samples in the profile.
   716  func (p *Profile) RemoveLabel(key string) {
   717  	for _, sample := range p.Sample {
   718  		delete(sample.Label, key)
   719  	}
   720  }
   721  
   722  // HasLabel returns true if a sample has a label with indicated key and value.
   723  func (s *Sample) HasLabel(key, value string) bool {
   724  	for _, v := range s.Label[key] {
   725  		if v == value {
   726  			return true
   727  		}
   728  	}
   729  	return false
   730  }
   731  
   732  // SetNumLabel sets the specified key to the specified value for all samples in the
   733  // profile. "unit" is a slice that describes the units that each corresponding member
   734  // of "values" is measured in (e.g. bytes or seconds).  If there is no relevant
   735  // unit for a given value, that member of "unit" should be the empty string.
   736  // "unit" must either have the same length as "value", or be nil.
   737  func (p *Profile) SetNumLabel(key string, value []int64, unit []string) {
   738  	for _, sample := range p.Sample {
   739  		if sample.NumLabel == nil {
   740  			sample.NumLabel = map[string][]int64{key: value}
   741  		} else {
   742  			sample.NumLabel[key] = value
   743  		}
   744  		if sample.NumUnit == nil {
   745  			sample.NumUnit = map[string][]string{key: unit}
   746  		} else {
   747  			sample.NumUnit[key] = unit
   748  		}
   749  	}
   750  }
   751  
   752  // RemoveNumLabel removes all numerical labels associated with the specified key for all
   753  // samples in the profile.
   754  func (p *Profile) RemoveNumLabel(key string) {
   755  	for _, sample := range p.Sample {
   756  		delete(sample.NumLabel, key)
   757  		delete(sample.NumUnit, key)
   758  	}
   759  }
   760  
   761  // DiffBaseSample returns true if a sample belongs to the diff base and false
   762  // otherwise.
   763  func (s *Sample) DiffBaseSample() bool {
   764  	return s.HasLabel("pprof::base", "true")
   765  }
   766  
   767  // Scale multiplies all sample values in a profile by a constant and keeps
   768  // only samples that have at least one non-zero value.
   769  func (p *Profile) Scale(ratio float64) {
   770  	if ratio == 1 {
   771  		return
   772  	}
   773  	ratios := make([]float64, len(p.SampleType))
   774  	for i := range p.SampleType {
   775  		ratios[i] = ratio
   776  	}
   777  	p.ScaleN(ratios)
   778  }
   779  
   780  // ScaleN multiplies each sample values in a sample by a different amount
   781  // and keeps only samples that have at least one non-zero value.
   782  func (p *Profile) ScaleN(ratios []float64) error {
   783  	if len(p.SampleType) != len(ratios) {
   784  		return fmt.Errorf("mismatched scale ratios, got %d, want %d", len(ratios), len(p.SampleType))
   785  	}
   786  	allOnes := true
   787  	for _, r := range ratios {
   788  		if r != 1 {
   789  			allOnes = false
   790  			break
   791  		}
   792  	}
   793  	if allOnes {
   794  		return nil
   795  	}
   796  	fillIdx := 0
   797  	for _, s := range p.Sample {
   798  		keepSample := false
   799  		for i, v := range s.Value {
   800  			if ratios[i] != 1 {
   801  				val := int64(math.Round(float64(v) * ratios[i]))
   802  				s.Value[i] = val
   803  				keepSample = keepSample || val != 0
   804  			}
   805  		}
   806  		if keepSample {
   807  			p.Sample[fillIdx] = s
   808  			fillIdx++
   809  		}
   810  	}
   811  	p.Sample = p.Sample[:fillIdx]
   812  	return nil
   813  }
   814  
   815  // HasFunctions determines if all locations in this profile have
   816  // symbolized function information.
   817  func (p *Profile) HasFunctions() bool {
   818  	for _, l := range p.Location {
   819  		if l.Mapping != nil && !l.Mapping.HasFunctions {
   820  			return false
   821  		}
   822  	}
   823  	return true
   824  }
   825  
   826  // HasFileLines determines if all locations in this profile have
   827  // symbolized file and line number information.
   828  func (p *Profile) HasFileLines() bool {
   829  	for _, l := range p.Location {
   830  		if l.Mapping != nil && (!l.Mapping.HasFilenames || !l.Mapping.HasLineNumbers) {
   831  			return false
   832  		}
   833  	}
   834  	return true
   835  }
   836  
   837  // Unsymbolizable returns true if a mapping points to a binary for which
   838  // locations can't be symbolized in principle, at least now. Examples are
   839  // "[vdso]", [vsyscall]" and some others, see the code.
   840  func (m *Mapping) Unsymbolizable() bool {
   841  	name := filepath.Base(m.File)
   842  	return strings.HasPrefix(name, "[") || strings.HasPrefix(name, "linux-vdso") || strings.HasPrefix(m.File, "/dev/dri/")
   843  }
   844  
   845  // Copy makes a fully independent copy of a profile.
   846  func (p *Profile) Copy() *Profile {
   847  	pp := &Profile{}
   848  	if err := unmarshal(serialize(p), pp); err != nil {
   849  		panic(err)
   850  	}
   851  	if err := pp.postDecode(); err != nil {
   852  		panic(err)
   853  	}
   854  
   855  	return pp
   856  }
   857  

View as plain text