github.com/karrick/go@v0.0.0-20170817181416-d5b0ec858b37/src/runtime/pprof/internal/profile/profile.go (about)

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