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