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

View as plain text