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