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  	Column   int64
   149  
   150  	functionIDX uint64
   151  }
   152  
   153  // Function corresponds to Profile.Function
   154  type Function struct {
   155  	ID         uint64
   156  	Name       string
   157  	SystemName string
   158  	Filename   string
   159  	StartLine  int64
   160  
   161  	nameX       int64
   162  	systemNameX int64
   163  	filenameX   int64
   164  }
   165  
   166  // Parse parses a profile and checks for its validity. The input
   167  // may be a gzip-compressed encoded protobuf or one of many legacy
   168  // profile formats which may be unsupported in the future.
   169  func Parse(r io.Reader) (*Profile, error) {
   170  	data, err := io.ReadAll(r)
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  	return ParseData(data)
   175  }
   176  
   177  // ParseData parses a profile from a buffer and checks for its
   178  // validity.
   179  func ParseData(data []byte) (*Profile, error) {
   180  	var p *Profile
   181  	var err error
   182  	if len(data) >= 2 && data[0] == 0x1f && data[1] == 0x8b {
   183  		gz, err := gzip.NewReader(bytes.NewBuffer(data))
   184  		if err == nil {
   185  			data, err = io.ReadAll(gz)
   186  		}
   187  		if err != nil {
   188  			return nil, fmt.Errorf("decompressing profile: %v", err)
   189  		}
   190  	}
   191  	if p, err = ParseUncompressed(data); err != nil && err != errNoData && err != errConcatProfile {
   192  		p, err = parseLegacy(data)
   193  	}
   194  
   195  	if err != nil {
   196  		return nil, fmt.Errorf("parsing profile: %v", err)
   197  	}
   198  
   199  	if err := p.CheckValid(); err != nil {
   200  		return nil, fmt.Errorf("malformed profile: %v", err)
   201  	}
   202  	return p, nil
   203  }
   204  
   205  var errUnrecognized = fmt.Errorf("unrecognized profile format")
   206  var errMalformed = fmt.Errorf("malformed profile format")
   207  var errNoData = fmt.Errorf("empty input file")
   208  var errConcatProfile = fmt.Errorf("concatenated profiles detected")
   209  
   210  func parseLegacy(data []byte) (*Profile, error) {
   211  	parsers := []func([]byte) (*Profile, error){
   212  		parseCPU,
   213  		parseHeap,
   214  		parseGoCount, // goroutine, threadcreate
   215  		parseThread,
   216  		parseContention,
   217  		parseJavaProfile,
   218  	}
   219  
   220  	for _, parser := range parsers {
   221  		p, err := parser(data)
   222  		if err == nil {
   223  			p.addLegacyFrameInfo()
   224  			return p, nil
   225  		}
   226  		if err != errUnrecognized {
   227  			return nil, err
   228  		}
   229  	}
   230  	return nil, errUnrecognized
   231  }
   232  
   233  // ParseUncompressed parses an uncompressed protobuf into a profile.
   234  func ParseUncompressed(data []byte) (*Profile, error) {
   235  	if len(data) == 0 {
   236  		return nil, errNoData
   237  	}
   238  	p := &Profile{}
   239  	if err := unmarshal(data, p); err != nil {
   240  		return nil, err
   241  	}
   242  
   243  	if err := p.postDecode(); err != nil {
   244  		return nil, err
   245  	}
   246  
   247  	return p, nil
   248  }
   249  
   250  var libRx = regexp.MustCompile(`([.]so$|[.]so[._][0-9]+)`)
   251  
   252  // massageMappings applies heuristic-based changes to the profile
   253  // mappings to account for quirks of some environments.
   254  func (p *Profile) massageMappings() {
   255  	// Merge adjacent regions with matching names, checking that the offsets match
   256  	if len(p.Mapping) > 1 {
   257  		mappings := []*Mapping{p.Mapping[0]}
   258  		for _, m := range p.Mapping[1:] {
   259  			lm := mappings[len(mappings)-1]
   260  			if adjacent(lm, m) {
   261  				lm.Limit = m.Limit
   262  				if m.File != "" {
   263  					lm.File = m.File
   264  				}
   265  				if m.BuildID != "" {
   266  					lm.BuildID = m.BuildID
   267  				}
   268  				p.updateLocationMapping(m, lm)
   269  				continue
   270  			}
   271  			mappings = append(mappings, m)
   272  		}
   273  		p.Mapping = mappings
   274  	}
   275  
   276  	// Use heuristics to identify main binary and move it to the top of the list of mappings
   277  	for i, m := range p.Mapping {
   278  		file := strings.TrimSpace(strings.Replace(m.File, "(deleted)", "", -1))
   279  		if len(file) == 0 {
   280  			continue
   281  		}
   282  		if len(libRx.FindStringSubmatch(file)) > 0 {
   283  			continue
   284  		}
   285  		if file[0] == '[' {
   286  			continue
   287  		}
   288  		// Swap what we guess is main to position 0.
   289  		p.Mapping[0], p.Mapping[i] = p.Mapping[i], p.Mapping[0]
   290  		break
   291  	}
   292  
   293  	// Keep the mapping IDs neatly sorted
   294  	for i, m := range p.Mapping {
   295  		m.ID = uint64(i + 1)
   296  	}
   297  }
   298  
   299  // adjacent returns whether two mapping entries represent the same
   300  // mapping that has been split into two. Check that their addresses are adjacent,
   301  // and if the offsets match, if they are available.
   302  func adjacent(m1, m2 *Mapping) bool {
   303  	if m1.File != "" && m2.File != "" {
   304  		if m1.File != m2.File {
   305  			return false
   306  		}
   307  	}
   308  	if m1.BuildID != "" && m2.BuildID != "" {
   309  		if m1.BuildID != m2.BuildID {
   310  			return false
   311  		}
   312  	}
   313  	if m1.Limit != m2.Start {
   314  		return false
   315  	}
   316  	if m1.Offset != 0 && m2.Offset != 0 {
   317  		offset := m1.Offset + (m1.Limit - m1.Start)
   318  		if offset != m2.Offset {
   319  			return false
   320  		}
   321  	}
   322  	return true
   323  }
   324  
   325  func (p *Profile) updateLocationMapping(from, to *Mapping) {
   326  	for _, l := range p.Location {
   327  		if l.Mapping == from {
   328  			l.Mapping = to
   329  		}
   330  	}
   331  }
   332  
   333  func serialize(p *Profile) []byte {
   334  	p.encodeMu.Lock()
   335  	p.preEncode()
   336  	b := marshal(p)
   337  	p.encodeMu.Unlock()
   338  	return b
   339  }
   340  
   341  // Write writes the profile as a gzip-compressed marshaled protobuf.
   342  func (p *Profile) Write(w io.Writer) error {
   343  	zw := gzip.NewWriter(w)
   344  	defer zw.Close()
   345  	_, err := zw.Write(serialize(p))
   346  	return err
   347  }
   348  
   349  // WriteUncompressed writes the profile as a marshaled protobuf.
   350  func (p *Profile) WriteUncompressed(w io.Writer) error {
   351  	_, err := w.Write(serialize(p))
   352  	return err
   353  }
   354  
   355  // CheckValid tests whether the profile is valid. Checks include, but are
   356  // not limited to:
   357  //   - len(Profile.Sample[n].value) == len(Profile.value_unit)
   358  //   - Sample.id has a corresponding Profile.Location
   359  func (p *Profile) CheckValid() error {
   360  	// Check that sample values are consistent
   361  	sampleLen := len(p.SampleType)
   362  	if sampleLen == 0 && len(p.Sample) != 0 {
   363  		return fmt.Errorf("missing sample type information")
   364  	}
   365  	for _, s := range p.Sample {
   366  		if s == nil {
   367  			return fmt.Errorf("profile has nil sample")
   368  		}
   369  		if len(s.Value) != sampleLen {
   370  			return fmt.Errorf("mismatch: sample has %d values vs. %d types", len(s.Value), len(p.SampleType))
   371  		}
   372  		for _, l := range s.Location {
   373  			if l == nil {
   374  				return fmt.Errorf("sample has nil location")
   375  			}
   376  		}
   377  	}
   378  
   379  	// Check that all mappings/locations/functions are in the tables
   380  	// Check that there are no duplicate ids
   381  	mappings := make(map[uint64]*Mapping, len(p.Mapping))
   382  	for _, m := range p.Mapping {
   383  		if m == nil {
   384  			return fmt.Errorf("profile has nil mapping")
   385  		}
   386  		if m.ID == 0 {
   387  			return fmt.Errorf("found mapping with reserved ID=0")
   388  		}
   389  		if mappings[m.ID] != nil {
   390  			return fmt.Errorf("multiple mappings with same id: %d", m.ID)
   391  		}
   392  		mappings[m.ID] = m
   393  	}
   394  	functions := make(map[uint64]*Function, len(p.Function))
   395  	for _, f := range p.Function {
   396  		if f == nil {
   397  			return fmt.Errorf("profile has nil function")
   398  		}
   399  		if f.ID == 0 {
   400  			return fmt.Errorf("found function with reserved ID=0")
   401  		}
   402  		if functions[f.ID] != nil {
   403  			return fmt.Errorf("multiple functions with same id: %d", f.ID)
   404  		}
   405  		functions[f.ID] = f
   406  	}
   407  	locations := make(map[uint64]*Location, len(p.Location))
   408  	for _, l := range p.Location {
   409  		if l == nil {
   410  			return fmt.Errorf("profile has nil location")
   411  		}
   412  		if l.ID == 0 {
   413  			return fmt.Errorf("found location with reserved id=0")
   414  		}
   415  		if locations[l.ID] != nil {
   416  			return fmt.Errorf("multiple locations with same id: %d", l.ID)
   417  		}
   418  		locations[l.ID] = l
   419  		if m := l.Mapping; m != nil {
   420  			if m.ID == 0 || mappings[m.ID] != m {
   421  				return fmt.Errorf("inconsistent mapping %p: %d", m, m.ID)
   422  			}
   423  		}
   424  		for _, ln := range l.Line {
   425  			f := ln.Function
   426  			if f == nil {
   427  				return fmt.Errorf("location id: %d has a line with nil function", l.ID)
   428  			}
   429  			if f.ID == 0 || functions[f.ID] != f {
   430  				return fmt.Errorf("inconsistent function %p: %d", f, f.ID)
   431  			}
   432  		}
   433  	}
   434  	return nil
   435  }
   436  
   437  // Aggregate merges the locations in the profile into equivalence
   438  // classes preserving the request attributes. It also updates the
   439  // samples to point to the merged locations.
   440  func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, columnnumber, address bool) error {
   441  	for _, m := range p.Mapping {
   442  		m.HasInlineFrames = m.HasInlineFrames && inlineFrame
   443  		m.HasFunctions = m.HasFunctions && function
   444  		m.HasFilenames = m.HasFilenames && filename
   445  		m.HasLineNumbers = m.HasLineNumbers && linenumber
   446  	}
   447  
   448  	// Aggregate functions
   449  	if !function || !filename {
   450  		for _, f := range p.Function {
   451  			if !function {
   452  				f.Name = ""
   453  				f.SystemName = ""
   454  			}
   455  			if !filename {
   456  				f.Filename = ""
   457  			}
   458  		}
   459  	}
   460  
   461  	// Aggregate locations
   462  	if !inlineFrame || !address || !linenumber || !columnnumber {
   463  		for _, l := range p.Location {
   464  			if !inlineFrame && len(l.Line) > 1 {
   465  				l.Line = l.Line[len(l.Line)-1:]
   466  			}
   467  			if !linenumber {
   468  				for i := range l.Line {
   469  					l.Line[i].Line = 0
   470  					l.Line[i].Column = 0
   471  				}
   472  			}
   473  			if !columnnumber {
   474  				for i := range l.Line {
   475  					l.Line[i].Column = 0
   476  				}
   477  			}
   478  			if !address {
   479  				l.Address = 0
   480  			}
   481  		}
   482  	}
   483  
   484  	return p.CheckValid()
   485  }
   486  
   487  // NumLabelUnits returns a map of numeric label keys to the units
   488  // associated with those keys and a map of those keys to any units
   489  // that were encountered but not used.
   490  // Unit for a given key is the first encountered unit for that key. If multiple
   491  // units are encountered for values paired with a particular key, then the first
   492  // unit encountered is used and all other units are returned in sorted order
   493  // in map of ignored units.
   494  // If no units are encountered for a particular key, the unit is then inferred
   495  // based on the key.
   496  func (p *Profile) NumLabelUnits() (map[string]string, map[string][]string) {
   497  	numLabelUnits := map[string]string{}
   498  	ignoredUnits := map[string]map[string]bool{}
   499  	encounteredKeys := map[string]bool{}
   500  
   501  	// Determine units based on numeric tags for each sample.
   502  	for _, s := range p.Sample {
   503  		for k := range s.NumLabel {
   504  			encounteredKeys[k] = true
   505  			for _, unit := range s.NumUnit[k] {
   506  				if unit == "" {
   507  					continue
   508  				}
   509  				if wantUnit, ok := numLabelUnits[k]; !ok {
   510  					numLabelUnits[k] = unit
   511  				} else if wantUnit != unit {
   512  					if v, ok := ignoredUnits[k]; ok {
   513  						v[unit] = true
   514  					} else {
   515  						ignoredUnits[k] = map[string]bool{unit: true}
   516  					}
   517  				}
   518  			}
   519  		}
   520  	}
   521  	// Infer units for keys without any units associated with
   522  	// numeric tag values.
   523  	for key := range encounteredKeys {
   524  		unit := numLabelUnits[key]
   525  		if unit == "" {
   526  			switch key {
   527  			case "alignment", "request":
   528  				numLabelUnits[key] = "bytes"
   529  			default:
   530  				numLabelUnits[key] = key
   531  			}
   532  		}
   533  	}
   534  
   535  	// Copy ignored units into more readable format
   536  	unitsIgnored := make(map[string][]string, len(ignoredUnits))
   537  	for key, values := range ignoredUnits {
   538  		units := make([]string, len(values))
   539  		i := 0
   540  		for unit := range values {
   541  			units[i] = unit
   542  			i++
   543  		}
   544  		sort.Strings(units)
   545  		unitsIgnored[key] = units
   546  	}
   547  
   548  	return numLabelUnits, unitsIgnored
   549  }
   550  
   551  // String dumps a text representation of a profile. Intended mainly
   552  // for debugging purposes.
   553  func (p *Profile) String() string {
   554  	ss := make([]string, 0, len(p.Comments)+len(p.Sample)+len(p.Mapping)+len(p.Location))
   555  	for _, c := range p.Comments {
   556  		ss = append(ss, "Comment: "+c)
   557  	}
   558  	if pt := p.PeriodType; pt != nil {
   559  		ss = append(ss, fmt.Sprintf("PeriodType: %s %s", pt.Type, pt.Unit))
   560  	}
   561  	ss = append(ss, fmt.Sprintf("Period: %d", p.Period))
   562  	if p.TimeNanos != 0 {
   563  		ss = append(ss, fmt.Sprintf("Time: %v", time.Unix(0, p.TimeNanos)))
   564  	}
   565  	if p.DurationNanos != 0 {
   566  		ss = append(ss, fmt.Sprintf("Duration: %.4v", time.Duration(p.DurationNanos)))
   567  	}
   568  
   569  	ss = append(ss, "Samples:")
   570  	var sh1 string
   571  	for _, s := range p.SampleType {
   572  		dflt := ""
   573  		if s.Type == p.DefaultSampleType {
   574  			dflt = "[dflt]"
   575  		}
   576  		sh1 = sh1 + fmt.Sprintf("%s/%s%s ", s.Type, s.Unit, dflt)
   577  	}
   578  	ss = append(ss, strings.TrimSpace(sh1))
   579  	for _, s := range p.Sample {
   580  		ss = append(ss, s.string())
   581  	}
   582  
   583  	ss = append(ss, "Locations")
   584  	for _, l := range p.Location {
   585  		ss = append(ss, l.string())
   586  	}
   587  
   588  	ss = append(ss, "Mappings")
   589  	for _, m := range p.Mapping {
   590  		ss = append(ss, m.string())
   591  	}
   592  
   593  	return strings.Join(ss, "\n") + "\n"
   594  }
   595  
   596  // string dumps a text representation of a mapping. Intended mainly
   597  // for debugging purposes.
   598  func (m *Mapping) string() string {
   599  	bits := ""
   600  	if m.HasFunctions {
   601  		bits = bits + "[FN]"
   602  	}
   603  	if m.HasFilenames {
   604  		bits = bits + "[FL]"
   605  	}
   606  	if m.HasLineNumbers {
   607  		bits = bits + "[LN]"
   608  	}
   609  	if m.HasInlineFrames {
   610  		bits = bits + "[IN]"
   611  	}
   612  	return fmt.Sprintf("%d: %#x/%#x/%#x %s %s %s",
   613  		m.ID,
   614  		m.Start, m.Limit, m.Offset,
   615  		m.File,
   616  		m.BuildID,
   617  		bits)
   618  }
   619  
   620  // string dumps a text representation of a location. Intended mainly
   621  // for debugging purposes.
   622  func (l *Location) string() string {
   623  	ss := []string{}
   624  	locStr := fmt.Sprintf("%6d: %#x ", l.ID, l.Address)
   625  	if m := l.Mapping; m != nil {
   626  		locStr = locStr + fmt.Sprintf("M=%d ", m.ID)
   627  	}
   628  	if l.IsFolded {
   629  		locStr = locStr + "[F] "
   630  	}
   631  	if len(l.Line) == 0 {
   632  		ss = append(ss, locStr)
   633  	}
   634  	for li := range l.Line {
   635  		lnStr := "??"
   636  		if fn := l.Line[li].Function; fn != nil {
   637  			lnStr = fmt.Sprintf("%s %s:%d:%d s=%d",
   638  				fn.Name,
   639  				fn.Filename,
   640  				l.Line[li].Line,
   641  				l.Line[li].Column,
   642  				fn.StartLine)
   643  			if fn.Name != fn.SystemName {
   644  				lnStr = lnStr + "(" + fn.SystemName + ")"
   645  			}
   646  		}
   647  		ss = append(ss, locStr+lnStr)
   648  		// Do not print location details past the first line
   649  		locStr = "             "
   650  	}
   651  	return strings.Join(ss, "\n")
   652  }
   653  
   654  // string dumps a text representation of a sample. Intended mainly
   655  // for debugging purposes.
   656  func (s *Sample) string() string {
   657  	ss := []string{}
   658  	var sv string
   659  	for _, v := range s.Value {
   660  		sv = fmt.Sprintf("%s %10d", sv, v)
   661  	}
   662  	sv = sv + ": "
   663  	for _, l := range s.Location {
   664  		sv = sv + fmt.Sprintf("%d ", l.ID)
   665  	}
   666  	ss = append(ss, sv)
   667  	const labelHeader = "                "
   668  	if len(s.Label) > 0 {
   669  		ss = append(ss, labelHeader+labelsToString(s.Label))
   670  	}
   671  	if len(s.NumLabel) > 0 {
   672  		ss = append(ss, labelHeader+numLabelsToString(s.NumLabel, s.NumUnit))
   673  	}
   674  	return strings.Join(ss, "\n")
   675  }
   676  
   677  // labelsToString returns a string representation of a
   678  // map representing labels.
   679  func labelsToString(labels map[string][]string) string {
   680  	ls := []string{}
   681  	for k, v := range labels {
   682  		ls = append(ls, fmt.Sprintf("%s:%v", k, v))
   683  	}
   684  	sort.Strings(ls)
   685  	return strings.Join(ls, " ")
   686  }
   687  
   688  // numLabelsToString returns a string representation of a map
   689  // representing numeric labels.
   690  func numLabelsToString(numLabels map[string][]int64, numUnits map[string][]string) string {
   691  	ls := []string{}
   692  	for k, v := range numLabels {
   693  		units := numUnits[k]
   694  		var labelString string
   695  		if len(units) == len(v) {
   696  			values := make([]string, len(v))
   697  			for i, vv := range v {
   698  				values[i] = fmt.Sprintf("%d %s", vv, units[i])
   699  			}
   700  			labelString = fmt.Sprintf("%s:%v", k, values)
   701  		} else {
   702  			labelString = fmt.Sprintf("%s:%v", k, v)
   703  		}
   704  		ls = append(ls, labelString)
   705  	}
   706  	sort.Strings(ls)
   707  	return strings.Join(ls, " ")
   708  }
   709  
   710  // SetLabel sets the specified key to the specified value for all samples in the
   711  // profile.
   712  func (p *Profile) SetLabel(key string, value []string) {
   713  	for _, sample := range p.Sample {
   714  		if sample.Label == nil {
   715  			sample.Label = map[string][]string{key: value}
   716  		} else {
   717  			sample.Label[key] = value
   718  		}
   719  	}
   720  }
   721  
   722  // RemoveLabel removes all labels associated with the specified key for all
   723  // samples in the profile.
   724  func (p *Profile) RemoveLabel(key string) {
   725  	for _, sample := range p.Sample {
   726  		delete(sample.Label, key)
   727  	}
   728  }
   729  
   730  // HasLabel returns true if a sample has a label with indicated key and value.
   731  func (s *Sample) HasLabel(key, value string) bool {
   732  	for _, v := range s.Label[key] {
   733  		if v == value {
   734  			return true
   735  		}
   736  	}
   737  	return false
   738  }
   739  
   740  // SetNumLabel sets the specified key to the specified value for all samples in the
   741  // profile. "unit" is a slice that describes the units that each corresponding member
   742  // of "values" is measured in (e.g. bytes or seconds).  If there is no relevant
   743  // unit for a given value, that member of "unit" should be the empty string.
   744  // "unit" must either have the same length as "value", or be nil.
   745  func (p *Profile) SetNumLabel(key string, value []int64, unit []string) {
   746  	for _, sample := range p.Sample {
   747  		if sample.NumLabel == nil {
   748  			sample.NumLabel = map[string][]int64{key: value}
   749  		} else {
   750  			sample.NumLabel[key] = value
   751  		}
   752  		if sample.NumUnit == nil {
   753  			sample.NumUnit = map[string][]string{key: unit}
   754  		} else {
   755  			sample.NumUnit[key] = unit
   756  		}
   757  	}
   758  }
   759  
   760  // RemoveNumLabel removes all numerical labels associated with the specified key for all
   761  // samples in the profile.
   762  func (p *Profile) RemoveNumLabel(key string) {
   763  	for _, sample := range p.Sample {
   764  		delete(sample.NumLabel, key)
   765  		delete(sample.NumUnit, key)
   766  	}
   767  }
   768  
   769  // DiffBaseSample returns true if a sample belongs to the diff base and false
   770  // otherwise.
   771  func (s *Sample) DiffBaseSample() bool {
   772  	return s.HasLabel("pprof::base", "true")
   773  }
   774  
   775  // Scale multiplies all sample values in a profile by a constant and keeps
   776  // only samples that have at least one non-zero value.
   777  func (p *Profile) Scale(ratio float64) {
   778  	if ratio == 1 {
   779  		return
   780  	}
   781  	ratios := make([]float64, len(p.SampleType))
   782  	for i := range p.SampleType {
   783  		ratios[i] = ratio
   784  	}
   785  	p.ScaleN(ratios)
   786  }
   787  
   788  // ScaleN multiplies each sample values in a sample by a different amount
   789  // and keeps only samples that have at least one non-zero value.
   790  func (p *Profile) ScaleN(ratios []float64) error {
   791  	if len(p.SampleType) != len(ratios) {
   792  		return fmt.Errorf("mismatched scale ratios, got %d, want %d", len(ratios), len(p.SampleType))
   793  	}
   794  	allOnes := true
   795  	for _, r := range ratios {
   796  		if r != 1 {
   797  			allOnes = false
   798  			break
   799  		}
   800  	}
   801  	if allOnes {
   802  		return nil
   803  	}
   804  	fillIdx := 0
   805  	for _, s := range p.Sample {
   806  		keepSample := false
   807  		for i, v := range s.Value {
   808  			if ratios[i] != 1 {
   809  				val := int64(math.Round(float64(v) * ratios[i]))
   810  				s.Value[i] = val
   811  				keepSample = keepSample || val != 0
   812  			}
   813  		}
   814  		if keepSample {
   815  			p.Sample[fillIdx] = s
   816  			fillIdx++
   817  		}
   818  	}
   819  	p.Sample = p.Sample[:fillIdx]
   820  	return nil
   821  }
   822  
   823  // HasFunctions determines if all locations in this profile have
   824  // symbolized function information.
   825  func (p *Profile) HasFunctions() bool {
   826  	for _, l := range p.Location {
   827  		if l.Mapping != nil && !l.Mapping.HasFunctions {
   828  			return false
   829  		}
   830  	}
   831  	return true
   832  }
   833  
   834  // HasFileLines determines if all locations in this profile have
   835  // symbolized file and line number information.
   836  func (p *Profile) HasFileLines() bool {
   837  	for _, l := range p.Location {
   838  		if l.Mapping != nil && (!l.Mapping.HasFilenames || !l.Mapping.HasLineNumbers) {
   839  			return false
   840  		}
   841  	}
   842  	return true
   843  }
   844  
   845  // Unsymbolizable returns true if a mapping points to a binary for which
   846  // locations can't be symbolized in principle, at least now. Examples are
   847  // "[vdso]", [vsyscall]" and some others, see the code.
   848  func (m *Mapping) Unsymbolizable() bool {
   849  	name := filepath.Base(m.File)
   850  	return strings.HasPrefix(name, "[") || strings.HasPrefix(name, "linux-vdso") || strings.HasPrefix(m.File, "/dev/dri/") || m.File == "//anon"
   851  }
   852  
   853  // Copy makes a fully independent copy of a profile.
   854  func (p *Profile) Copy() *Profile {
   855  	pp := &Profile{}
   856  	if err := unmarshal(serialize(p), pp); err != nil {
   857  		panic(err)
   858  	}
   859  	if err := pp.postDecode(); err != nil {
   860  		panic(err)
   861  	}
   862  
   863  	return pp
   864  }
   865  

View as plain text