github.com/muesli/go@v0.0.0-20170208044820-e410d2a81ef2/src/runtime/pprof/internal/protopprof/protopprof.go (about)

     1  // Copyright 2016 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 protopprof converts the runtime's raw profile logs
     6  // to Profile structs containing a representation of the pprof
     7  // protocol buffer profile format.
     8  package protopprof
     9  
    10  import (
    11  	"fmt"
    12  	"os"
    13  	"runtime"
    14  	"strings"
    15  	"time"
    16  	"unsafe"
    17  
    18  	"internal/pprof/profile"
    19  )
    20  
    21  // TranslateCPUProfile parses binary CPU profiling stack trace data
    22  // generated by runtime.CPUProfile() into a profile struct.
    23  func TranslateCPUProfile(b []byte, startTime time.Time) (*profile.Profile, error) {
    24  	const wordSize = unsafe.Sizeof(uintptr(0))
    25  	const minRawProfile = 5 * wordSize // Need a minimum of 5 words.
    26  	if uintptr(len(b)) < minRawProfile {
    27  		return nil, fmt.Errorf("truncated profile")
    28  	}
    29  	n := int(uintptr(len(b)) / wordSize)
    30  	data := ((*[1 << 28]uintptr)(unsafe.Pointer(&b[0])))[:n:n]
    31  	period := data[3]
    32  	data = data[5:] // skip header
    33  
    34  	// profile initialization taken from pprof tool
    35  	p := &profile.Profile{
    36  		Period:     int64(period) * 1000,
    37  		PeriodType: &profile.ValueType{Type: "cpu", Unit: "nanoseconds"},
    38  		SampleType: []*profile.ValueType{
    39  			{Type: "samples", Unit: "count"},
    40  			{Type: "cpu", Unit: "nanoseconds"},
    41  		},
    42  		TimeNanos:     int64(startTime.UnixNano()),
    43  		DurationNanos: time.Since(startTime).Nanoseconds(),
    44  	}
    45  	// Parse CPU samples from the profile.
    46  	locs := make(map[uint64]*profile.Location)
    47  	for len(b) > 0 {
    48  		if len(data) < 2 || uintptr(len(data)) < 2+data[1] {
    49  			return nil, fmt.Errorf("truncated profile")
    50  		}
    51  		count := data[0]
    52  		nstk := data[1]
    53  		if uintptr(len(data)) < 2+nstk {
    54  			return nil, fmt.Errorf("truncated profile")
    55  		}
    56  		stk := data[2 : 2+nstk]
    57  		data = data[2+nstk:]
    58  
    59  		if count == 0 && nstk == 1 && stk[0] == 0 {
    60  			// end of data marker
    61  			break
    62  		}
    63  
    64  		sloc := make([]*profile.Location, len(stk))
    65  		for i, addr := range stk {
    66  			addr := uint64(addr)
    67  			// Addresses from stack traces point to the next instruction after
    68  			// each call.  Adjust by -1 to land somewhere on the actual call
    69  			// (except for the leaf, which is not a call).
    70  			if i > 0 {
    71  				addr--
    72  			}
    73  			loc := locs[addr]
    74  			if loc == nil {
    75  				loc = &profile.Location{
    76  					ID:      uint64(len(p.Location) + 1),
    77  					Address: addr,
    78  				}
    79  				locs[addr] = loc
    80  				p.Location = append(p.Location, loc)
    81  			}
    82  			sloc[i] = loc
    83  		}
    84  		p.Sample = append(p.Sample, &profile.Sample{
    85  			Value:    []int64{int64(count), int64(count) * int64(p.Period)},
    86  			Location: sloc,
    87  		})
    88  	}
    89  
    90  	if runtime.GOOS == "linux" {
    91  		if err := addMappings(p); err != nil {
    92  			return nil, err
    93  		}
    94  	}
    95  	symbolize(p)
    96  	return p, nil
    97  }
    98  
    99  func addMappings(p *profile.Profile) error {
   100  	// Parse memory map from /proc/self/maps
   101  	f, err := os.Open("/proc/self/maps")
   102  	if err != nil {
   103  		return err
   104  	}
   105  	defer f.Close()
   106  	return p.ParseMemoryMap(f)
   107  }
   108  
   109  type function interface {
   110  	Name() string
   111  	FileLine(pc uintptr) (string, int)
   112  }
   113  
   114  // funcForPC is a wrapper for runtime.FuncForPC. Defined as var for testing.
   115  var funcForPC = func(pc uintptr) function {
   116  	if f := runtime.FuncForPC(pc); f != nil {
   117  		return f
   118  	}
   119  	return nil
   120  }
   121  
   122  func symbolize(p *profile.Profile) {
   123  	fns := profileFunctionMap{}
   124  	for _, l := range p.Location {
   125  		pc := uintptr(l.Address)
   126  		f := funcForPC(pc)
   127  		if f == nil {
   128  			continue
   129  		}
   130  		file, lineno := f.FileLine(pc)
   131  		l.Line = []profile.Line{
   132  			{
   133  				Function: fns.findOrAddFunction(f.Name(), file, p),
   134  				Line:     int64(lineno),
   135  			},
   136  		}
   137  	}
   138  	// Trim runtime functions. Always hide runtime.goexit. Other runtime
   139  	// functions are only hidden for heapz when they appear at the beginning.
   140  	isHeapz := p.PeriodType != nil && p.PeriodType.Type == "space"
   141  	for _, s := range p.Sample {
   142  		show := !isHeapz
   143  		var i int
   144  		for _, l := range s.Location {
   145  			if len(l.Line) > 0 && l.Line[0].Function != nil {
   146  				name := l.Line[0].Function.Name
   147  				if name == "runtime.goexit" || !show && strings.HasPrefix(name, "runtime.") {
   148  					continue
   149  				}
   150  			}
   151  			show = true
   152  			s.Location[i] = l
   153  			i++
   154  		}
   155  		s.Location = s.Location[:i]
   156  	}
   157  }
   158  
   159  type profileFunctionMap map[profile.Function]*profile.Function
   160  
   161  func (fns profileFunctionMap) findOrAddFunction(name, filename string, p *profile.Profile) *profile.Function {
   162  	f := profile.Function{
   163  		Name:       name,
   164  		SystemName: name,
   165  		Filename:   filename,
   166  	}
   167  	if fp := fns[f]; fp != nil {
   168  		return fp
   169  	}
   170  	fp := new(profile.Function)
   171  	fns[f] = fp
   172  
   173  	*fp = f
   174  	fp.ID = uint64(len(p.Function) + 1)
   175  	p.Function = append(p.Function, fp)
   176  	return fp
   177  }