...
Run Format

Source file src/runtime/pprof/internal/profile/profile.go

Documentation: runtime/pprof/internal/profile

     1  // Copyright 2014 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package profile provides a representation of profile.proto and
     6  // methods to encode/decode profiles in this format.
     7  //
     8  // This package is only for testing runtime/pprof.
     9  // It is not used by production Go programs.
    10  package profile
    11  
    12  import (
    13  	"bytes"
    14  	"compress/gzip"
    15  	"fmt"
    16  	"io"
    17  	"io/ioutil"
    18  	"regexp"
    19  	"strings"
    20  	"time"
    21  )
    22  
    23  // Profile is an in-memory representation of profile.proto.
    24  type Profile struct {
    25  	SampleType        []*ValueType
    26  	DefaultSampleType string
    27  	Sample            []*Sample
    28  	Mapping           []*Mapping
    29  	Location          []*Location
    30  	Function          []*Function
    31  	Comments          []string
    32  
    33  	DropFrames string
    34  	KeepFrames string
    35  
    36  	TimeNanos     int64
    37  	DurationNanos int64
    38  	PeriodType    *ValueType
    39  	Period        int64
    40  
    41  	commentX           []int64
    42  	dropFramesX        int64
    43  	keepFramesX        int64
    44  	stringTable        []string
    45  	defaultSampleTypeX int64
    46  }
    47  
    48  // ValueType corresponds to Profile.ValueType
    49  type ValueType struct {
    50  	Type string // cpu, wall, inuse_space, etc
    51  	Unit string // seconds, nanoseconds, bytes, etc
    52  
    53  	typeX int64
    54  	unitX int64
    55  }
    56  
    57  // Sample corresponds to Profile.Sample
    58  type Sample struct {
    59  	Location []*Location
    60  	Value    []int64
    61  	Label    map[string][]string
    62  	NumLabel map[string][]int64
    63  
    64  	locationIDX []uint64
    65  	labelX      []Label
    66  }
    67  
    68  // Label corresponds to Profile.Label
    69  type Label struct {
    70  	keyX int64
    71  	// Exactly one of the two following values must be set
    72  	strX int64
    73  	numX int64 // Integer value for this label
    74  }
    75  
    76  // Mapping corresponds to Profile.Mapping
    77  type Mapping struct {
    78  	ID              uint64
    79  	Start           uint64
    80  	Limit           uint64
    81  	Offset          uint64
    82  	File            string
    83  	BuildID         string
    84  	HasFunctions    bool
    85  	HasFilenames    bool
    86  	HasLineNumbers  bool
    87  	HasInlineFrames bool
    88  
    89  	fileX    int64
    90  	buildIDX int64
    91  }
    92  
    93  // Location corresponds to Profile.Location
    94  type Location struct {
    95  	ID      uint64
    96  	Mapping *Mapping
    97  	Address uint64
    98  	Line    []Line
    99  
   100  	mappingIDX uint64
   101  }
   102  
   103  // Line corresponds to Profile.Line
   104  type Line struct {
   105  	Function *Function
   106  	Line     int64
   107  
   108  	functionIDX uint64
   109  }
   110  
   111  // Function corresponds to Profile.Function
   112  type Function struct {
   113  	ID         uint64
   114  	Name       string
   115  	SystemName string
   116  	Filename   string
   117  	StartLine  int64
   118  
   119  	nameX       int64
   120  	systemNameX int64
   121  	filenameX   int64
   122  }
   123  
   124  // Parse parses a profile and checks for its validity. The input
   125  // may be a gzip-compressed encoded protobuf or one of many legacy
   126  // profile formats which may be unsupported in the future.
   127  func Parse(r io.Reader) (*Profile, error) {
   128  	orig, err := ioutil.ReadAll(r)
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	var p *Profile
   134  	if len(orig) >= 2 && orig[0] == 0x1f && orig[1] == 0x8b {
   135  		gz, err := gzip.NewReader(bytes.NewBuffer(orig))
   136  		if err != nil {
   137  			return nil, fmt.Errorf("decompressing profile: %v", err)
   138  		}
   139  		data, err := ioutil.ReadAll(gz)
   140  		if err != nil {
   141  			return nil, fmt.Errorf("decompressing profile: %v", err)
   142  		}
   143  		orig = data
   144  	}
   145  	if p, err = parseUncompressed(orig); err != nil {
   146  		if p, err = parseLegacy(orig); err != nil {
   147  			return nil, fmt.Errorf("parsing profile: %v", err)
   148  		}
   149  	}
   150  
   151  	if err := p.CheckValid(); err != nil {
   152  		return nil, fmt.Errorf("malformed profile: %v", err)
   153  	}
   154  	return p, nil
   155  }
   156  
   157  var errUnrecognized = fmt.Errorf("unrecognized profile format")
   158  var errMalformed = fmt.Errorf("malformed profile format")
   159  
   160  func parseLegacy(data []byte) (*Profile, error) {
   161  	parsers := []func([]byte) (*Profile, error){
   162  		parseCPU,
   163  		parseHeap,
   164  		parseGoCount, // goroutine, threadcreate
   165  		parseThread,
   166  		parseContention,
   167  	}
   168  
   169  	for _, parser := range parsers {
   170  		p, err := parser(data)
   171  		if err == nil {
   172  			p.setMain()
   173  			p.addLegacyFrameInfo()
   174  			return p, nil
   175  		}
   176  		if err != errUnrecognized {
   177  			return nil, err
   178  		}
   179  	}
   180  	return nil, errUnrecognized
   181  }
   182  
   183  func parseUncompressed(data []byte) (*Profile, error) {
   184  	p := &Profile{}
   185  	if err := unmarshal(data, p); err != nil {
   186  		return nil, err
   187  	}
   188  
   189  	if err := p.postDecode(); err != nil {
   190  		return nil, err
   191  	}
   192  
   193  	return p, nil
   194  }
   195  
   196  var libRx = regexp.MustCompile(`([.]so$|[.]so[._][0-9]+)`)
   197  
   198  // setMain scans Mapping entries and guesses which entry is main
   199  // because legacy profiles don't obey the convention of putting main
   200  // first.
   201  func (p *Profile) setMain() {
   202  	for i := 0; i < len(p.Mapping); i++ {
   203  		file := strings.TrimSpace(strings.Replace(p.Mapping[i].File, "(deleted)", "", -1))
   204  		if len(file) == 0 {
   205  			continue
   206  		}
   207  		if len(libRx.FindStringSubmatch(file)) > 0 {
   208  			continue
   209  		}
   210  		if strings.HasPrefix(file, "[") {
   211  			continue
   212  		}
   213  		// Swap what we guess is main to position 0.
   214  		tmp := p.Mapping[i]
   215  		p.Mapping[i] = p.Mapping[0]
   216  		p.Mapping[0] = tmp
   217  		break
   218  	}
   219  }
   220  
   221  // Write writes the profile as a gzip-compressed marshaled protobuf.
   222  func (p *Profile) Write(w io.Writer) error {
   223  	p.preEncode()
   224  	b := marshal(p)
   225  	zw := gzip.NewWriter(w)
   226  	defer zw.Close()
   227  	_, err := zw.Write(b)
   228  	return err
   229  }
   230  
   231  // CheckValid tests whether the profile is valid. Checks include, but are
   232  // not limited to:
   233  //   - len(Profile.Sample[n].value) == len(Profile.value_unit)
   234  //   - Sample.id has a corresponding Profile.Location
   235  func (p *Profile) CheckValid() error {
   236  	// Check that sample values are consistent
   237  	sampleLen := len(p.SampleType)
   238  	if sampleLen == 0 && len(p.Sample) != 0 {
   239  		return fmt.Errorf("missing sample type information")
   240  	}
   241  	for _, s := range p.Sample {
   242  		if len(s.Value) != sampleLen {
   243  			return fmt.Errorf("mismatch: sample has: %d values vs. %d types", len(s.Value), len(p.SampleType))
   244  		}
   245  	}
   246  
   247  	// Check that all mappings/locations/functions are in the tables
   248  	// Check that there are no duplicate ids
   249  	mappings := make(map[uint64]*Mapping, len(p.Mapping))
   250  	for _, m := range p.Mapping {
   251  		if m.ID == 0 {
   252  			return fmt.Errorf("found mapping with reserved ID=0")
   253  		}
   254  		if mappings[m.ID] != nil {
   255  			return fmt.Errorf("multiple mappings with same id: %d", m.ID)
   256  		}
   257  		mappings[m.ID] = m
   258  	}
   259  	functions := make(map[uint64]*Function, len(p.Function))
   260  	for _, f := range p.Function {
   261  		if f.ID == 0 {
   262  			return fmt.Errorf("found function with reserved ID=0")
   263  		}
   264  		if functions[f.ID] != nil {
   265  			return fmt.Errorf("multiple functions with same id: %d", f.ID)
   266  		}
   267  		functions[f.ID] = f
   268  	}
   269  	locations := make(map[uint64]*Location, len(p.Location))
   270  	for _, l := range p.Location {
   271  		if l.ID == 0 {
   272  			return fmt.Errorf("found location with reserved id=0")
   273  		}
   274  		if locations[l.ID] != nil {
   275  			return fmt.Errorf("multiple locations with same id: %d", l.ID)
   276  		}
   277  		locations[l.ID] = l
   278  		if m := l.Mapping; m != nil {
   279  			if m.ID == 0 || mappings[m.ID] != m {
   280  				return fmt.Errorf("inconsistent mapping %p: %d", m, m.ID)
   281  			}
   282  		}
   283  		for _, ln := range l.Line {
   284  			if f := ln.Function; f != nil {
   285  				if f.ID == 0 || functions[f.ID] != f {
   286  					return fmt.Errorf("inconsistent function %p: %d", f, f.ID)
   287  				}
   288  			}
   289  		}
   290  	}
   291  	return nil
   292  }
   293  
   294  // Aggregate merges the locations in the profile into equivalence
   295  // classes preserving the request attributes. It also updates the
   296  // samples to point to the merged locations.
   297  func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, address bool) error {
   298  	for _, m := range p.Mapping {
   299  		m.HasInlineFrames = m.HasInlineFrames && inlineFrame
   300  		m.HasFunctions = m.HasFunctions && function
   301  		m.HasFilenames = m.HasFilenames && filename
   302  		m.HasLineNumbers = m.HasLineNumbers && linenumber
   303  	}
   304  
   305  	// Aggregate functions
   306  	if !function || !filename {
   307  		for _, f := range p.Function {
   308  			if !function {
   309  				f.Name = ""
   310  				f.SystemName = ""
   311  			}
   312  			if !filename {
   313  				f.Filename = ""
   314  			}
   315  		}
   316  	}
   317  
   318  	// Aggregate locations
   319  	if !inlineFrame || !address || !linenumber {
   320  		for _, l := range p.Location {
   321  			if !inlineFrame && len(l.Line) > 1 {
   322  				l.Line = l.Line[len(l.Line)-1:]
   323  			}
   324  			if !linenumber {
   325  				for i := range l.Line {
   326  					l.Line[i].Line = 0
   327  				}
   328  			}
   329  			if !address {
   330  				l.Address = 0
   331  			}
   332  		}
   333  	}
   334  
   335  	return p.CheckValid()
   336  }
   337  
   338  // Print dumps a text representation of a profile. Intended mainly
   339  // for debugging purposes.
   340  func (p *Profile) String() string {
   341  
   342  	ss := make([]string, 0, len(p.Sample)+len(p.Mapping)+len(p.Location))
   343  	if pt := p.PeriodType; pt != nil {
   344  		ss = append(ss, fmt.Sprintf("PeriodType: %s %s", pt.Type, pt.Unit))
   345  	}
   346  	ss = append(ss, fmt.Sprintf("Period: %d", p.Period))
   347  	if p.TimeNanos != 0 {
   348  		ss = append(ss, fmt.Sprintf("Time: %v", time.Unix(0, p.TimeNanos)))
   349  	}
   350  	if p.DurationNanos != 0 {
   351  		ss = append(ss, fmt.Sprintf("Duration: %v", time.Duration(p.DurationNanos)))
   352  	}
   353  
   354  	ss = append(ss, "Samples:")
   355  	var sh1 string
   356  	for _, s := range p.SampleType {
   357  		sh1 = sh1 + fmt.Sprintf("%s/%s ", s.Type, s.Unit)
   358  	}
   359  	ss = append(ss, strings.TrimSpace(sh1))
   360  	for _, s := range p.Sample {
   361  		var sv string
   362  		for _, v := range s.Value {
   363  			sv = fmt.Sprintf("%s %10d", sv, v)
   364  		}
   365  		sv = sv + ": "
   366  		for _, l := range s.Location {
   367  			sv = sv + fmt.Sprintf("%d ", l.ID)
   368  		}
   369  		ss = append(ss, sv)
   370  		const labelHeader = "                "
   371  		if len(s.Label) > 0 {
   372  			ls := labelHeader
   373  			for k, v := range s.Label {
   374  				ls = ls + fmt.Sprintf("%s:%v ", k, v)
   375  			}
   376  			ss = append(ss, ls)
   377  		}
   378  		if len(s.NumLabel) > 0 {
   379  			ls := labelHeader
   380  			for k, v := range s.NumLabel {
   381  				ls = ls + fmt.Sprintf("%s:%v ", k, v)
   382  			}
   383  			ss = append(ss, ls)
   384  		}
   385  	}
   386  
   387  	ss = append(ss, "Locations")
   388  	for _, l := range p.Location {
   389  		locStr := fmt.Sprintf("%6d: %#x ", l.ID, l.Address)
   390  		if m := l.Mapping; m != nil {
   391  			locStr = locStr + fmt.Sprintf("M=%d ", m.ID)
   392  		}
   393  		if len(l.Line) == 0 {
   394  			ss = append(ss, locStr)
   395  		}
   396  		for li := range l.Line {
   397  			lnStr := "??"
   398  			if fn := l.Line[li].Function; fn != nil {
   399  				lnStr = fmt.Sprintf("%s %s:%d s=%d",
   400  					fn.Name,
   401  					fn.Filename,
   402  					l.Line[li].Line,
   403  					fn.StartLine)
   404  				if fn.Name != fn.SystemName {
   405  					lnStr = lnStr + "(" + fn.SystemName + ")"
   406  				}
   407  			}
   408  			ss = append(ss, locStr+lnStr)
   409  			// Do not print location details past the first line
   410  			locStr = "             "
   411  		}
   412  	}
   413  
   414  	ss = append(ss, "Mappings")
   415  	for _, m := range p.Mapping {
   416  		bits := ""
   417  		if m.HasFunctions {
   418  			bits = bits + "[FN]"
   419  		}
   420  		if m.HasFilenames {
   421  			bits = bits + "[FL]"
   422  		}
   423  		if m.HasLineNumbers {
   424  			bits = bits + "[LN]"
   425  		}
   426  		if m.HasInlineFrames {
   427  			bits = bits + "[IN]"
   428  		}
   429  		ss = append(ss, fmt.Sprintf("%d: %#x/%#x/%#x %s %s %s",
   430  			m.ID,
   431  			m.Start, m.Limit, m.Offset,
   432  			m.File,
   433  			m.BuildID,
   434  			bits))
   435  	}
   436  
   437  	return strings.Join(ss, "\n") + "\n"
   438  }
   439  
   440  // Merge adds profile p adjusted by ratio r into profile p. Profiles
   441  // must be compatible (same Type and SampleType).
   442  // TODO(rsilvera): consider normalizing the profiles based on the
   443  // total samples collected.
   444  func (p *Profile) Merge(pb *Profile, r float64) error {
   445  	if err := p.Compatible(pb); err != nil {
   446  		return err
   447  	}
   448  
   449  	pb = pb.Copy()
   450  
   451  	// Keep the largest of the two periods.
   452  	if pb.Period > p.Period {
   453  		p.Period = pb.Period
   454  	}
   455  
   456  	p.DurationNanos += pb.DurationNanos
   457  
   458  	p.Mapping = append(p.Mapping, pb.Mapping...)
   459  	for i, m := range p.Mapping {
   460  		m.ID = uint64(i + 1)
   461  	}
   462  	p.Location = append(p.Location, pb.Location...)
   463  	for i, l := range p.Location {
   464  		l.ID = uint64(i + 1)
   465  	}
   466  	p.Function = append(p.Function, pb.Function...)
   467  	for i, f := range p.Function {
   468  		f.ID = uint64(i + 1)
   469  	}
   470  
   471  	if r != 1.0 {
   472  		for _, s := range pb.Sample {
   473  			for i, v := range s.Value {
   474  				s.Value[i] = int64((float64(v) * r))
   475  			}
   476  		}
   477  	}
   478  	p.Sample = append(p.Sample, pb.Sample...)
   479  	return p.CheckValid()
   480  }
   481  
   482  // Compatible determines if two profiles can be compared/merged.
   483  // returns nil if the profiles are compatible; otherwise an error with
   484  // details on the incompatibility.
   485  func (p *Profile) Compatible(pb *Profile) error {
   486  	if !compatibleValueTypes(p.PeriodType, pb.PeriodType) {
   487  		return fmt.Errorf("incompatible period types %v and %v", p.PeriodType, pb.PeriodType)
   488  	}
   489  
   490  	if len(p.SampleType) != len(pb.SampleType) {
   491  		return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType)
   492  	}
   493  
   494  	for i := range p.SampleType {
   495  		if !compatibleValueTypes(p.SampleType[i], pb.SampleType[i]) {
   496  			return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType)
   497  		}
   498  	}
   499  
   500  	return nil
   501  }
   502  
   503  // HasFunctions determines if all locations in this profile have
   504  // symbolized function information.
   505  func (p *Profile) HasFunctions() bool {
   506  	for _, l := range p.Location {
   507  		if l.Mapping == nil || !l.Mapping.HasFunctions {
   508  			return false
   509  		}
   510  	}
   511  	return true
   512  }
   513  
   514  // HasFileLines determines if all locations in this profile have
   515  // symbolized file and line number information.
   516  func (p *Profile) HasFileLines() bool {
   517  	for _, l := range p.Location {
   518  		if l.Mapping == nil || (!l.Mapping.HasFilenames || !l.Mapping.HasLineNumbers) {
   519  			return false
   520  		}
   521  	}
   522  	return true
   523  }
   524  
   525  func compatibleValueTypes(v1, v2 *ValueType) bool {
   526  	if v1 == nil || v2 == nil {
   527  		return true // No grounds to disqualify.
   528  	}
   529  	return v1.Type == v2.Type && v1.Unit == v2.Unit
   530  }
   531  
   532  // Copy makes a fully independent copy of a profile.
   533  func (p *Profile) Copy() *Profile {
   534  	p.preEncode()
   535  	b := marshal(p)
   536  
   537  	pp := &Profile{}
   538  	if err := unmarshal(b, pp); err != nil {
   539  		panic(err)
   540  	}
   541  	if err := pp.postDecode(); err != nil {
   542  		panic(err)
   543  	}
   544  
   545  	return pp
   546  }
   547  
   548  // Demangler maps symbol names to a human-readable form. This may
   549  // include C++ demangling and additional simplification. Names that
   550  // are not demangled may be missing from the resulting map.
   551  type Demangler func(name []string) (map[string]string, error)
   552  
   553  // Demangle attempts to demangle and optionally simplify any function
   554  // names referenced in the profile. It works on a best-effort basis:
   555  // it will silently preserve the original names in case of any errors.
   556  func (p *Profile) Demangle(d Demangler) error {
   557  	// Collect names to demangle.
   558  	var names []string
   559  	for _, fn := range p.Function {
   560  		names = append(names, fn.SystemName)
   561  	}
   562  
   563  	// Update profile with demangled names.
   564  	demangled, err := d(names)
   565  	if err != nil {
   566  		return err
   567  	}
   568  	for _, fn := range p.Function {
   569  		if dd, ok := demangled[fn.SystemName]; ok {
   570  			fn.Name = dd
   571  		}
   572  	}
   573  	return nil
   574  }
   575  
   576  // Empty returns true if the profile contains no samples.
   577  func (p *Profile) Empty() bool {
   578  	return len(p.Sample) == 0
   579  }
   580  

View as plain text