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

View as plain text