golang.org/toolchain@v0.0.1-go1.9rc2.windows-amd64/src/cmd/vendor/github.com/google/pprof/profile/profile.go (about)

     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  	"path/filepath"
    26  	"regexp"
    27  	"sort"
    28  	"strings"
    29  	"time"
    30  )
    31  
    32  // Profile is an in-memory representation of profile.proto.
    33  type Profile struct {
    34  	SampleType        []*ValueType
    35  	DefaultSampleType string
    36  	Sample            []*Sample
    37  	Mapping           []*Mapping
    38  	Location          []*Location
    39  	Function          []*Function
    40  	Comments          []string
    41  
    42  	DropFrames string
    43  	KeepFrames string
    44  
    45  	TimeNanos     int64
    46  	DurationNanos int64
    47  	PeriodType    *ValueType
    48  	Period        int64
    49  
    50  	commentX           []int64
    51  	dropFramesX        int64
    52  	keepFramesX        int64
    53  	stringTable        []string
    54  	defaultSampleTypeX int64
    55  }
    56  
    57  // ValueType corresponds to Profile.ValueType
    58  type ValueType struct {
    59  	Type string // cpu, wall, inuse_space, etc
    60  	Unit string // seconds, nanoseconds, bytes, etc
    61  
    62  	typeX int64
    63  	unitX int64
    64  }
    65  
    66  // Sample corresponds to Profile.Sample
    67  type Sample struct {
    68  	Location []*Location
    69  	Value    []int64
    70  	Label    map[string][]string
    71  	NumLabel map[string][]int64
    72  
    73  	locationIDX []uint64
    74  	labelX      []label
    75  }
    76  
    77  // label corresponds to Profile.Label
    78  type label struct {
    79  	keyX int64
    80  	// Exactly one of the two following values must be set
    81  	strX int64
    82  	numX int64 // Integer value for this label
    83  }
    84  
    85  // Mapping corresponds to Profile.Mapping
    86  type Mapping struct {
    87  	ID              uint64
    88  	Start           uint64
    89  	Limit           uint64
    90  	Offset          uint64
    91  	File            string
    92  	BuildID         string
    93  	HasFunctions    bool
    94  	HasFilenames    bool
    95  	HasLineNumbers  bool
    96  	HasInlineFrames bool
    97  
    98  	fileX    int64
    99  	buildIDX int64
   100  }
   101  
   102  // Location corresponds to Profile.Location
   103  type Location struct {
   104  	ID      uint64
   105  	Mapping *Mapping
   106  	Address uint64
   107  	Line    []Line
   108  
   109  	mappingIDX uint64
   110  }
   111  
   112  // Line corresponds to Profile.Line
   113  type Line struct {
   114  	Function *Function
   115  	Line     int64
   116  
   117  	functionIDX uint64
   118  }
   119  
   120  // Function corresponds to Profile.Function
   121  type Function struct {
   122  	ID         uint64
   123  	Name       string
   124  	SystemName string
   125  	Filename   string
   126  	StartLine  int64
   127  
   128  	nameX       int64
   129  	systemNameX int64
   130  	filenameX   int64
   131  }
   132  
   133  // Parse parses a profile and checks for its validity. The input
   134  // may be a gzip-compressed encoded protobuf or one of many legacy
   135  // profile formats which may be unsupported in the future.
   136  func Parse(r io.Reader) (*Profile, error) {
   137  	data, err := ioutil.ReadAll(r)
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  	return ParseData(data)
   142  }
   143  
   144  // ParseData parses a profile from a buffer and checks for its
   145  // validity.
   146  func ParseData(data []byte) (*Profile, error) {
   147  	var p *Profile
   148  	var err error
   149  	if len(data) >= 2 && data[0] == 0x1f && data[1] == 0x8b {
   150  		gz, err := gzip.NewReader(bytes.NewBuffer(data))
   151  		if err == nil {
   152  			data, err = ioutil.ReadAll(gz)
   153  		}
   154  		if err != nil {
   155  			return nil, fmt.Errorf("decompressing profile: %v", err)
   156  		}
   157  	}
   158  	if p, err = ParseUncompressed(data); err != nil && err != errNoData {
   159  		p, err = parseLegacy(data)
   160  	}
   161  
   162  	if err != nil {
   163  		return nil, fmt.Errorf("parsing profile: %v", err)
   164  	}
   165  
   166  	if err := p.CheckValid(); err != nil {
   167  		return nil, fmt.Errorf("malformed profile: %v", err)
   168  	}
   169  	return p, nil
   170  }
   171  
   172  var errUnrecognized = fmt.Errorf("unrecognized profile format")
   173  var errMalformed = fmt.Errorf("malformed profile format")
   174  var errNoData = fmt.Errorf("empty input file")
   175  
   176  func parseLegacy(data []byte) (*Profile, error) {
   177  	parsers := []func([]byte) (*Profile, error){
   178  		parseCPU,
   179  		parseHeap,
   180  		parseGoCount, // goroutine, threadcreate
   181  		parseThread,
   182  		parseContention,
   183  		parseJavaProfile,
   184  	}
   185  
   186  	for _, parser := range parsers {
   187  		p, err := parser(data)
   188  		if err == nil {
   189  			p.addLegacyFrameInfo()
   190  			return p, nil
   191  		}
   192  		if err != errUnrecognized {
   193  			return nil, err
   194  		}
   195  	}
   196  	return nil, errUnrecognized
   197  }
   198  
   199  // ParseUncompressed parses an uncompressed protobuf into a profile.
   200  func ParseUncompressed(data []byte) (*Profile, error) {
   201  	if len(data) == 0 {
   202  		return nil, errNoData
   203  	}
   204  	p := &Profile{}
   205  	if err := unmarshal(data, p); err != nil {
   206  		return nil, err
   207  	}
   208  
   209  	if err := p.postDecode(); err != nil {
   210  		return nil, err
   211  	}
   212  
   213  	return p, nil
   214  }
   215  
   216  var libRx = regexp.MustCompile(`([.]so$|[.]so[._][0-9]+)`)
   217  
   218  // massageMappings applies heuristic-based changes to the profile
   219  // mappings to account for quirks of some environments.
   220  func (p *Profile) massageMappings() {
   221  	// Merge adjacent regions with matching names, checking that the offsets match
   222  	if len(p.Mapping) > 1 {
   223  		mappings := []*Mapping{p.Mapping[0]}
   224  		for _, m := range p.Mapping[1:] {
   225  			lm := mappings[len(mappings)-1]
   226  			if adjacent(lm, m) {
   227  				lm.Limit = m.Limit
   228  				if m.File != "" {
   229  					lm.File = m.File
   230  				}
   231  				if m.BuildID != "" {
   232  					lm.BuildID = m.BuildID
   233  				}
   234  				p.updateLocationMapping(m, lm)
   235  				continue
   236  			}
   237  			mappings = append(mappings, m)
   238  		}
   239  		p.Mapping = mappings
   240  	}
   241  
   242  	// Use heuristics to identify main binary and move it to the top of the list of mappings
   243  	for i, m := range p.Mapping {
   244  		file := strings.TrimSpace(strings.Replace(m.File, "(deleted)", "", -1))
   245  		if len(file) == 0 {
   246  			continue
   247  		}
   248  		if len(libRx.FindStringSubmatch(file)) > 0 {
   249  			continue
   250  		}
   251  		if file[0] == '[' {
   252  			continue
   253  		}
   254  		// Swap what we guess is main to position 0.
   255  		p.Mapping[0], p.Mapping[i] = p.Mapping[i], p.Mapping[0]
   256  		break
   257  	}
   258  
   259  	// Keep the mapping IDs neatly sorted
   260  	for i, m := range p.Mapping {
   261  		m.ID = uint64(i + 1)
   262  	}
   263  }
   264  
   265  // adjacent returns whether two mapping entries represent the same
   266  // mapping that has been split into two. Check that their addresses are adjacent,
   267  // and if the offsets match, if they are available.
   268  func adjacent(m1, m2 *Mapping) bool {
   269  	if m1.File != "" && m2.File != "" {
   270  		if m1.File != m2.File {
   271  			return false
   272  		}
   273  	}
   274  	if m1.BuildID != "" && m2.BuildID != "" {
   275  		if m1.BuildID != m2.BuildID {
   276  			return false
   277  		}
   278  	}
   279  	if m1.Limit != m2.Start {
   280  		return false
   281  	}
   282  	if m1.Offset != 0 && m2.Offset != 0 {
   283  		offset := m1.Offset + (m1.Limit - m1.Start)
   284  		if offset != m2.Offset {
   285  			return false
   286  		}
   287  	}
   288  	return true
   289  }
   290  
   291  func (p *Profile) updateLocationMapping(from, to *Mapping) {
   292  	for _, l := range p.Location {
   293  		if l.Mapping == from {
   294  			l.Mapping = to
   295  		}
   296  	}
   297  }
   298  
   299  // Write writes the profile as a gzip-compressed marshaled protobuf.
   300  func (p *Profile) Write(w io.Writer) error {
   301  	p.preEncode()
   302  	b := marshal(p)
   303  	zw := gzip.NewWriter(w)
   304  	defer zw.Close()
   305  	_, err := zw.Write(b)
   306  	return err
   307  }
   308  
   309  // WriteUncompressed writes the profile as a marshaled protobuf.
   310  func (p *Profile) WriteUncompressed(w io.Writer) error {
   311  	p.preEncode()
   312  	b := marshal(p)
   313  	_, err := w.Write(b)
   314  	return err
   315  }
   316  
   317  // CheckValid tests whether the profile is valid. Checks include, but are
   318  // not limited to:
   319  //   - len(Profile.Sample[n].value) == len(Profile.value_unit)
   320  //   - Sample.id has a corresponding Profile.Location
   321  func (p *Profile) CheckValid() error {
   322  	// Check that sample values are consistent
   323  	sampleLen := len(p.SampleType)
   324  	if sampleLen == 0 && len(p.Sample) != 0 {
   325  		return fmt.Errorf("missing sample type information")
   326  	}
   327  	for _, s := range p.Sample {
   328  		if len(s.Value) != sampleLen {
   329  			return fmt.Errorf("mismatch: sample has: %d values vs. %d types", len(s.Value), len(p.SampleType))
   330  		}
   331  		for _, l := range s.Location {
   332  			if l == nil {
   333  				return fmt.Errorf("sample has nil location")
   334  			}
   335  		}
   336  	}
   337  
   338  	// Check that all mappings/locations/functions are in the tables
   339  	// Check that there are no duplicate ids
   340  	mappings := make(map[uint64]*Mapping, len(p.Mapping))
   341  	for _, m := range p.Mapping {
   342  		if m.ID == 0 {
   343  			return fmt.Errorf("found mapping with reserved ID=0")
   344  		}
   345  		if mappings[m.ID] != nil {
   346  			return fmt.Errorf("multiple mappings with same id: %d", m.ID)
   347  		}
   348  		mappings[m.ID] = m
   349  	}
   350  	functions := make(map[uint64]*Function, len(p.Function))
   351  	for _, f := range p.Function {
   352  		if f.ID == 0 {
   353  			return fmt.Errorf("found function with reserved ID=0")
   354  		}
   355  		if functions[f.ID] != nil {
   356  			return fmt.Errorf("multiple functions with same id: %d", f.ID)
   357  		}
   358  		functions[f.ID] = f
   359  	}
   360  	locations := make(map[uint64]*Location, len(p.Location))
   361  	for _, l := range p.Location {
   362  		if l.ID == 0 {
   363  			return fmt.Errorf("found location with reserved id=0")
   364  		}
   365  		if locations[l.ID] != nil {
   366  			return fmt.Errorf("multiple locations with same id: %d", l.ID)
   367  		}
   368  		locations[l.ID] = l
   369  		if m := l.Mapping; m != nil {
   370  			if m.ID == 0 || mappings[m.ID] != m {
   371  				return fmt.Errorf("inconsistent mapping %p: %d", m, m.ID)
   372  			}
   373  		}
   374  		for _, ln := range l.Line {
   375  			if f := ln.Function; f != nil {
   376  				if f.ID == 0 || functions[f.ID] != f {
   377  					return fmt.Errorf("inconsistent function %p: %d", f, f.ID)
   378  				}
   379  			}
   380  		}
   381  	}
   382  	return nil
   383  }
   384  
   385  // Aggregate merges the locations in the profile into equivalence
   386  // classes preserving the request attributes. It also updates the
   387  // samples to point to the merged locations.
   388  func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, address bool) error {
   389  	for _, m := range p.Mapping {
   390  		m.HasInlineFrames = m.HasInlineFrames && inlineFrame
   391  		m.HasFunctions = m.HasFunctions && function
   392  		m.HasFilenames = m.HasFilenames && filename
   393  		m.HasLineNumbers = m.HasLineNumbers && linenumber
   394  	}
   395  
   396  	// Aggregate functions
   397  	if !function || !filename {
   398  		for _, f := range p.Function {
   399  			if !function {
   400  				f.Name = ""
   401  				f.SystemName = ""
   402  			}
   403  			if !filename {
   404  				f.Filename = ""
   405  			}
   406  		}
   407  	}
   408  
   409  	// Aggregate locations
   410  	if !inlineFrame || !address || !linenumber {
   411  		for _, l := range p.Location {
   412  			if !inlineFrame && len(l.Line) > 1 {
   413  				l.Line = l.Line[len(l.Line)-1:]
   414  			}
   415  			if !linenumber {
   416  				for i := range l.Line {
   417  					l.Line[i].Line = 0
   418  				}
   419  			}
   420  			if !address {
   421  				l.Address = 0
   422  			}
   423  		}
   424  	}
   425  
   426  	return p.CheckValid()
   427  }
   428  
   429  // String dumps a text representation of a profile. Intended mainly
   430  // for debugging purposes.
   431  func (p *Profile) String() string {
   432  	ss := make([]string, 0, len(p.Comments)+len(p.Sample)+len(p.Mapping)+len(p.Location))
   433  	for _, c := range p.Comments {
   434  		ss = append(ss, "Comment: "+c)
   435  	}
   436  	if pt := p.PeriodType; pt != nil {
   437  		ss = append(ss, fmt.Sprintf("PeriodType: %s %s", pt.Type, pt.Unit))
   438  	}
   439  	ss = append(ss, fmt.Sprintf("Period: %d", p.Period))
   440  	if p.TimeNanos != 0 {
   441  		ss = append(ss, fmt.Sprintf("Time: %v", time.Unix(0, p.TimeNanos)))
   442  	}
   443  	if p.DurationNanos != 0 {
   444  		ss = append(ss, fmt.Sprintf("Duration: %.4v", time.Duration(p.DurationNanos)))
   445  	}
   446  
   447  	ss = append(ss, "Samples:")
   448  	var sh1 string
   449  	for _, s := range p.SampleType {
   450  		dflt := ""
   451  		if s.Type == p.DefaultSampleType {
   452  			dflt = "[dflt]"
   453  		}
   454  		sh1 = sh1 + fmt.Sprintf("%s/%s%s ", s.Type, s.Unit, dflt)
   455  	}
   456  	ss = append(ss, strings.TrimSpace(sh1))
   457  	for _, s := range p.Sample {
   458  		var sv string
   459  		for _, v := range s.Value {
   460  			sv = fmt.Sprintf("%s %10d", sv, v)
   461  		}
   462  		sv = sv + ": "
   463  		for _, l := range s.Location {
   464  			sv = sv + fmt.Sprintf("%d ", l.ID)
   465  		}
   466  		ss = append(ss, sv)
   467  		const labelHeader = "                "
   468  		if len(s.Label) > 0 {
   469  			ls := []string{}
   470  			for k, v := range s.Label {
   471  				ls = append(ls, fmt.Sprintf("%s:%v", k, v))
   472  			}
   473  			sort.Strings(ls)
   474  			ss = append(ss, labelHeader+strings.Join(ls, " "))
   475  		}
   476  		if len(s.NumLabel) > 0 {
   477  			ls := []string{}
   478  			for k, v := range s.NumLabel {
   479  				ls = append(ls, fmt.Sprintf("%s:%v", k, v))
   480  			}
   481  			sort.Strings(ls)
   482  			ss = append(ss, labelHeader+strings.Join(ls, " "))
   483  		}
   484  	}
   485  
   486  	ss = append(ss, "Locations")
   487  	for _, l := range p.Location {
   488  		locStr := fmt.Sprintf("%6d: %#x ", l.ID, l.Address)
   489  		if m := l.Mapping; m != nil {
   490  			locStr = locStr + fmt.Sprintf("M=%d ", m.ID)
   491  		}
   492  		if len(l.Line) == 0 {
   493  			ss = append(ss, locStr)
   494  		}
   495  		for li := range l.Line {
   496  			lnStr := "??"
   497  			if fn := l.Line[li].Function; fn != nil {
   498  				lnStr = fmt.Sprintf("%s %s:%d s=%d",
   499  					fn.Name,
   500  					fn.Filename,
   501  					l.Line[li].Line,
   502  					fn.StartLine)
   503  				if fn.Name != fn.SystemName {
   504  					lnStr = lnStr + "(" + fn.SystemName + ")"
   505  				}
   506  			}
   507  			ss = append(ss, locStr+lnStr)
   508  			// Do not print location details past the first line
   509  			locStr = "             "
   510  		}
   511  	}
   512  
   513  	ss = append(ss, "Mappings")
   514  	for _, m := range p.Mapping {
   515  		bits := ""
   516  		if m.HasFunctions {
   517  			bits = bits + "[FN]"
   518  		}
   519  		if m.HasFilenames {
   520  			bits = bits + "[FL]"
   521  		}
   522  		if m.HasLineNumbers {
   523  			bits = bits + "[LN]"
   524  		}
   525  		if m.HasInlineFrames {
   526  			bits = bits + "[IN]"
   527  		}
   528  		ss = append(ss, fmt.Sprintf("%d: %#x/%#x/%#x %s %s %s",
   529  			m.ID,
   530  			m.Start, m.Limit, m.Offset,
   531  			m.File,
   532  			m.BuildID,
   533  			bits))
   534  	}
   535  
   536  	return strings.Join(ss, "\n") + "\n"
   537  }
   538  
   539  // Scale multiplies all sample values in a profile by a constant.
   540  func (p *Profile) Scale(ratio float64) {
   541  	if ratio == 1 {
   542  		return
   543  	}
   544  	ratios := make([]float64, len(p.SampleType))
   545  	for i := range p.SampleType {
   546  		ratios[i] = ratio
   547  	}
   548  	p.ScaleN(ratios)
   549  }
   550  
   551  // ScaleN multiplies each sample values in a sample by a different amount.
   552  func (p *Profile) ScaleN(ratios []float64) error {
   553  	if len(p.SampleType) != len(ratios) {
   554  		return fmt.Errorf("mismatched scale ratios, got %d, want %d", len(ratios), len(p.SampleType))
   555  	}
   556  	allOnes := true
   557  	for _, r := range ratios {
   558  		if r != 1 {
   559  			allOnes = false
   560  			break
   561  		}
   562  	}
   563  	if allOnes {
   564  		return nil
   565  	}
   566  	for _, s := range p.Sample {
   567  		for i, v := range s.Value {
   568  			if ratios[i] != 1 {
   569  				s.Value[i] = int64(float64(v) * ratios[i])
   570  			}
   571  		}
   572  	}
   573  	return nil
   574  }
   575  
   576  // HasFunctions determines if all locations in this profile have
   577  // symbolized function information.
   578  func (p *Profile) HasFunctions() bool {
   579  	for _, l := range p.Location {
   580  		if l.Mapping != nil && !l.Mapping.HasFunctions {
   581  			return false
   582  		}
   583  	}
   584  	return true
   585  }
   586  
   587  // HasFileLines determines if all locations in this profile have
   588  // symbolized file and line number information.
   589  func (p *Profile) HasFileLines() bool {
   590  	for _, l := range p.Location {
   591  		if l.Mapping != nil && (!l.Mapping.HasFilenames || !l.Mapping.HasLineNumbers) {
   592  			return false
   593  		}
   594  	}
   595  	return true
   596  }
   597  
   598  // Unsymbolizable returns true if a mapping points to a binary for which
   599  // locations can't be symbolized in principle, at least now.
   600  func (m *Mapping) Unsymbolizable() bool {
   601  	name := filepath.Base(m.File)
   602  	return name == "[vdso]" || strings.HasPrefix(name, "linux-vdso") || name == "[heap]" || strings.HasPrefix(m.File, "/dev/dri/")
   603  }
   604  
   605  // Copy makes a fully independent copy of a profile.
   606  func (p *Profile) Copy() *Profile {
   607  	p.preEncode()
   608  	b := marshal(p)
   609  
   610  	pp := &Profile{}
   611  	if err := unmarshal(b, pp); err != nil {
   612  		panic(err)
   613  	}
   614  	if err := pp.postDecode(); err != nil {
   615  		panic(err)
   616  	}
   617  
   618  	return pp
   619  }