...

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

     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.ReplaceAll(p.Mapping[i].File, "(deleted)", ""))
   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			p.Mapping[i], p.Mapping[0] = p.Mapping[0], p.Mapping[i]
   215			break
   216		}
   217	}
   218	
   219	// Write writes the profile as a gzip-compressed marshaled protobuf.
   220	func (p *Profile) Write(w io.Writer) error {
   221		p.preEncode()
   222		b := marshal(p)
   223		zw := gzip.NewWriter(w)
   224		defer zw.Close()
   225		_, err := zw.Write(b)
   226		return err
   227	}
   228	
   229	// CheckValid tests whether the profile is valid. Checks include, but are
   230	// not limited to:
   231	//   - len(Profile.Sample[n].value) == len(Profile.value_unit)
   232	//   - Sample.id has a corresponding Profile.Location
   233	func (p *Profile) CheckValid() error {
   234		// Check that sample values are consistent
   235		sampleLen := len(p.SampleType)
   236		if sampleLen == 0 && len(p.Sample) != 0 {
   237			return fmt.Errorf("missing sample type information")
   238		}
   239		for _, s := range p.Sample {
   240			if len(s.Value) != sampleLen {
   241				return fmt.Errorf("mismatch: sample has: %d values vs. %d types", len(s.Value), len(p.SampleType))
   242			}
   243		}
   244	
   245		// Check that all mappings/locations/functions are in the tables
   246		// Check that there are no duplicate ids
   247		mappings := make(map[uint64]*Mapping, len(p.Mapping))
   248		for _, m := range p.Mapping {
   249			if m.ID == 0 {
   250				return fmt.Errorf("found mapping with reserved ID=0")
   251			}
   252			if mappings[m.ID] != nil {
   253				return fmt.Errorf("multiple mappings with same id: %d", m.ID)
   254			}
   255			mappings[m.ID] = m
   256		}
   257		functions := make(map[uint64]*Function, len(p.Function))
   258		for _, f := range p.Function {
   259			if f.ID == 0 {
   260				return fmt.Errorf("found function with reserved ID=0")
   261			}
   262			if functions[f.ID] != nil {
   263				return fmt.Errorf("multiple functions with same id: %d", f.ID)
   264			}
   265			functions[f.ID] = f
   266		}
   267		locations := make(map[uint64]*Location, len(p.Location))
   268		for _, l := range p.Location {
   269			if l.ID == 0 {
   270				return fmt.Errorf("found location with reserved id=0")
   271			}
   272			if locations[l.ID] != nil {
   273				return fmt.Errorf("multiple locations with same id: %d", l.ID)
   274			}
   275			locations[l.ID] = l
   276			if m := l.Mapping; m != nil {
   277				if m.ID == 0 || mappings[m.ID] != m {
   278					return fmt.Errorf("inconsistent mapping %p: %d", m, m.ID)
   279				}
   280			}
   281			for _, ln := range l.Line {
   282				if f := ln.Function; f != nil {
   283					if f.ID == 0 || functions[f.ID] != f {
   284						return fmt.Errorf("inconsistent function %p: %d", f, f.ID)
   285					}
   286				}
   287			}
   288		}
   289		return nil
   290	}
   291	
   292	// Aggregate merges the locations in the profile into equivalence
   293	// classes preserving the request attributes. It also updates the
   294	// samples to point to the merged locations.
   295	func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, address bool) error {
   296		for _, m := range p.Mapping {
   297			m.HasInlineFrames = m.HasInlineFrames && inlineFrame
   298			m.HasFunctions = m.HasFunctions && function
   299			m.HasFilenames = m.HasFilenames && filename
   300			m.HasLineNumbers = m.HasLineNumbers && linenumber
   301		}
   302	
   303		// Aggregate functions
   304		if !function || !filename {
   305			for _, f := range p.Function {
   306				if !function {
   307					f.Name = ""
   308					f.SystemName = ""
   309				}
   310				if !filename {
   311					f.Filename = ""
   312				}
   313			}
   314		}
   315	
   316		// Aggregate locations
   317		if !inlineFrame || !address || !linenumber {
   318			for _, l := range p.Location {
   319				if !inlineFrame && len(l.Line) > 1 {
   320					l.Line = l.Line[len(l.Line)-1:]
   321				}
   322				if !linenumber {
   323					for i := range l.Line {
   324						l.Line[i].Line = 0
   325					}
   326				}
   327				if !address {
   328					l.Address = 0
   329				}
   330			}
   331		}
   332	
   333		return p.CheckValid()
   334	}
   335	
   336	// Print dumps a text representation of a profile. Intended mainly
   337	// for debugging purposes.
   338	func (p *Profile) String() string {
   339	
   340		ss := make([]string, 0, len(p.Sample)+len(p.Mapping)+len(p.Location))
   341		if pt := p.PeriodType; pt != nil {
   342			ss = append(ss, fmt.Sprintf("PeriodType: %s %s", pt.Type, pt.Unit))
   343		}
   344		ss = append(ss, fmt.Sprintf("Period: %d", p.Period))
   345		if p.TimeNanos != 0 {
   346			ss = append(ss, fmt.Sprintf("Time: %v", time.Unix(0, p.TimeNanos)))
   347		}
   348		if p.DurationNanos != 0 {
   349			ss = append(ss, fmt.Sprintf("Duration: %v", time.Duration(p.DurationNanos)))
   350		}
   351	
   352		ss = append(ss, "Samples:")
   353		var sh1 string
   354		for _, s := range p.SampleType {
   355			sh1 = sh1 + fmt.Sprintf("%s/%s ", s.Type, s.Unit)
   356		}
   357		ss = append(ss, strings.TrimSpace(sh1))
   358		for _, s := range p.Sample {
   359			var sv string
   360			for _, v := range s.Value {
   361				sv = fmt.Sprintf("%s %10d", sv, v)
   362			}
   363			sv = sv + ": "
   364			for _, l := range s.Location {
   365				sv = sv + fmt.Sprintf("%d ", l.ID)
   366			}
   367			ss = append(ss, sv)
   368			const labelHeader = "                "
   369			if len(s.Label) > 0 {
   370				ls := labelHeader
   371				for k, v := range s.Label {
   372					ls = ls + fmt.Sprintf("%s:%v ", k, v)
   373				}
   374				ss = append(ss, ls)
   375			}
   376			if len(s.NumLabel) > 0 {
   377				ls := labelHeader
   378				for k, v := range s.NumLabel {
   379					ls = ls + fmt.Sprintf("%s:%v ", k, v)
   380				}
   381				ss = append(ss, ls)
   382			}
   383		}
   384	
   385		ss = append(ss, "Locations")
   386		for _, l := range p.Location {
   387			locStr := fmt.Sprintf("%6d: %#x ", l.ID, l.Address)
   388			if m := l.Mapping; m != nil {
   389				locStr = locStr + fmt.Sprintf("M=%d ", m.ID)
   390			}
   391			if len(l.Line) == 0 {
   392				ss = append(ss, locStr)
   393			}
   394			for li := range l.Line {
   395				lnStr := "??"
   396				if fn := l.Line[li].Function; fn != nil {
   397					lnStr = fmt.Sprintf("%s %s:%d s=%d",
   398						fn.Name,
   399						fn.Filename,
   400						l.Line[li].Line,
   401						fn.StartLine)
   402					if fn.Name != fn.SystemName {
   403						lnStr = lnStr + "(" + fn.SystemName + ")"
   404					}
   405				}
   406				ss = append(ss, locStr+lnStr)
   407				// Do not print location details past the first line
   408				locStr = "             "
   409			}
   410		}
   411	
   412		ss = append(ss, "Mappings")
   413		for _, m := range p.Mapping {
   414			bits := ""
   415			if m.HasFunctions {
   416				bits += "[FN]"
   417			}
   418			if m.HasFilenames {
   419				bits += "[FL]"
   420			}
   421			if m.HasLineNumbers {
   422				bits += "[LN]"
   423			}
   424			if m.HasInlineFrames {
   425				bits += "[IN]"
   426			}
   427			ss = append(ss, fmt.Sprintf("%d: %#x/%#x/%#x %s %s %s",
   428				m.ID,
   429				m.Start, m.Limit, m.Offset,
   430				m.File,
   431				m.BuildID,
   432				bits))
   433		}
   434	
   435		return strings.Join(ss, "\n") + "\n"
   436	}
   437	
   438	// Merge adds profile p adjusted by ratio r into profile p. Profiles
   439	// must be compatible (same Type and SampleType).
   440	// TODO(rsilvera): consider normalizing the profiles based on the
   441	// total samples collected.
   442	func (p *Profile) Merge(pb *Profile, r float64) error {
   443		if err := p.Compatible(pb); err != nil {
   444			return err
   445		}
   446	
   447		pb = pb.Copy()
   448	
   449		// Keep the largest of the two periods.
   450		if pb.Period > p.Period {
   451			p.Period = pb.Period
   452		}
   453	
   454		p.DurationNanos += pb.DurationNanos
   455	
   456		p.Mapping = append(p.Mapping, pb.Mapping...)
   457		for i, m := range p.Mapping {
   458			m.ID = uint64(i + 1)
   459		}
   460		p.Location = append(p.Location, pb.Location...)
   461		for i, l := range p.Location {
   462			l.ID = uint64(i + 1)
   463		}
   464		p.Function = append(p.Function, pb.Function...)
   465		for i, f := range p.Function {
   466			f.ID = uint64(i + 1)
   467		}
   468	
   469		if r != 1.0 {
   470			for _, s := range pb.Sample {
   471				for i, v := range s.Value {
   472					s.Value[i] = int64((float64(v) * r))
   473				}
   474			}
   475		}
   476		p.Sample = append(p.Sample, pb.Sample...)
   477		return p.CheckValid()
   478	}
   479	
   480	// Compatible determines if two profiles can be compared/merged.
   481	// returns nil if the profiles are compatible; otherwise an error with
   482	// details on the incompatibility.
   483	func (p *Profile) Compatible(pb *Profile) error {
   484		if !compatibleValueTypes(p.PeriodType, pb.PeriodType) {
   485			return fmt.Errorf("incompatible period types %v and %v", p.PeriodType, pb.PeriodType)
   486		}
   487	
   488		if len(p.SampleType) != len(pb.SampleType) {
   489			return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType)
   490		}
   491	
   492		for i := range p.SampleType {
   493			if !compatibleValueTypes(p.SampleType[i], pb.SampleType[i]) {
   494				return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType)
   495			}
   496		}
   497	
   498		return nil
   499	}
   500	
   501	// HasFunctions determines if all locations in this profile have
   502	// symbolized function information.
   503	func (p *Profile) HasFunctions() bool {
   504		for _, l := range p.Location {
   505			if l.Mapping == nil || !l.Mapping.HasFunctions {
   506				return false
   507			}
   508		}
   509		return true
   510	}
   511	
   512	// HasFileLines determines if all locations in this profile have
   513	// symbolized file and line number information.
   514	func (p *Profile) HasFileLines() bool {
   515		for _, l := range p.Location {
   516			if l.Mapping == nil || (!l.Mapping.HasFilenames || !l.Mapping.HasLineNumbers) {
   517				return false
   518			}
   519		}
   520		return true
   521	}
   522	
   523	func compatibleValueTypes(v1, v2 *ValueType) bool {
   524		if v1 == nil || v2 == nil {
   525			return true // No grounds to disqualify.
   526		}
   527		return v1.Type == v2.Type && v1.Unit == v2.Unit
   528	}
   529	
   530	// Copy makes a fully independent copy of a profile.
   531	func (p *Profile) Copy() *Profile {
   532		p.preEncode()
   533		b := marshal(p)
   534	
   535		pp := &Profile{}
   536		if err := unmarshal(b, pp); err != nil {
   537			panic(err)
   538		}
   539		if err := pp.postDecode(); err != nil {
   540			panic(err)
   541		}
   542	
   543		return pp
   544	}
   545	
   546	// Demangler maps symbol names to a human-readable form. This may
   547	// include C++ demangling and additional simplification. Names that
   548	// are not demangled may be missing from the resulting map.
   549	type Demangler func(name []string) (map[string]string, error)
   550	
   551	// Demangle attempts to demangle and optionally simplify any function
   552	// names referenced in the profile. It works on a best-effort basis:
   553	// it will silently preserve the original names in case of any errors.
   554	func (p *Profile) Demangle(d Demangler) error {
   555		// Collect names to demangle.
   556		var names []string
   557		for _, fn := range p.Function {
   558			names = append(names, fn.SystemName)
   559		}
   560	
   561		// Update profile with demangled names.
   562		demangled, err := d(names)
   563		if err != nil {
   564			return err
   565		}
   566		for _, fn := range p.Function {
   567			if dd, ok := demangled[fn.SystemName]; ok {
   568				fn.Name = dd
   569			}
   570		}
   571		return nil
   572	}
   573	
   574	// Empty reports whether the profile contains no samples.
   575	func (p *Profile) Empty() bool {
   576		return len(p.Sample) == 0
   577	}
   578	

View as plain text