github.com/slayercat/go@v0.0.0-20170428012452-c51559813f61/src/cmd/pprof/pprof.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  // pprof is a tool for visualization of profile.data. It is based on
     6  // the upstream version at github.com/google/pprof, with minor
     7  // modifications specific to the Go distribution. Please consider
     8  // upstreaming any modifications to these packages.
     9  
    10  package main
    11  
    12  import (
    13  	"crypto/tls"
    14  	"debug/dwarf"
    15  	"fmt"
    16  	"net/http"
    17  	"net/url"
    18  	"os"
    19  	"regexp"
    20  	"strconv"
    21  	"sync"
    22  	"time"
    23  
    24  	"cmd/internal/objfile"
    25  
    26  	"github.com/google/pprof/driver"
    27  	"github.com/google/pprof/profile"
    28  )
    29  
    30  func main() {
    31  	options := &driver.Options{
    32  		Fetch: new(fetcher),
    33  		Obj:   new(objTool),
    34  	}
    35  	if err := driver.PProf(options); err != nil {
    36  		fmt.Fprintf(os.Stderr, "%v\n", err)
    37  		os.Exit(2)
    38  	}
    39  }
    40  
    41  type fetcher struct {
    42  }
    43  
    44  func (f *fetcher) Fetch(src string, duration, timeout time.Duration) (*profile.Profile, string, error) {
    45  	sourceURL, timeout := adjustURL(src, duration, timeout)
    46  	if sourceURL == "" {
    47  		// Could not recognize URL, let regular pprof attempt to fetch the profile (eg. from a file)
    48  		return nil, "", nil
    49  	}
    50  	fmt.Fprintln(os.Stderr, "Fetching profile over HTTP from", sourceURL)
    51  	if duration > 0 {
    52  		fmt.Fprintf(os.Stderr, "Please wait... (%v)\n", duration)
    53  	}
    54  	p, err := getProfile(sourceURL, timeout)
    55  	return p, sourceURL, err
    56  }
    57  
    58  func getProfile(source string, timeout time.Duration) (*profile.Profile, error) {
    59  	url, err := url.Parse(source)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  
    64  	var tlsConfig *tls.Config
    65  	if url.Scheme == "https+insecure" {
    66  		tlsConfig = &tls.Config{
    67  			InsecureSkipVerify: true,
    68  		}
    69  		url.Scheme = "https"
    70  		source = url.String()
    71  	}
    72  
    73  	client := &http.Client{
    74  		Transport: &http.Transport{
    75  			ResponseHeaderTimeout: timeout + 5*time.Second,
    76  			Proxy:           http.ProxyFromEnvironment,
    77  			TLSClientConfig: tlsConfig,
    78  		},
    79  	}
    80  	resp, err := client.Get(source)
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  	if resp.StatusCode != http.StatusOK {
    85  		return nil, fmt.Errorf("server response: %s", resp.Status)
    86  	}
    87  	return profile.Parse(resp.Body)
    88  }
    89  
    90  // cpuProfileHandler is the Go pprof CPU profile handler URL.
    91  const cpuProfileHandler = "/debug/pprof/profile"
    92  
    93  // adjustURL applies the duration/timeout values and Go specific defaults
    94  func adjustURL(source string, duration, timeout time.Duration) (string, time.Duration) {
    95  	u, err := url.Parse(source)
    96  	if err != nil || (u.Host == "" && u.Scheme != "" && u.Scheme != "file") {
    97  		// Try adding http:// to catch sources of the form hostname:port/path.
    98  		// url.Parse treats "hostname" as the scheme.
    99  		u, err = url.Parse("http://" + source)
   100  	}
   101  	if err != nil || u.Host == "" {
   102  		return "", 0
   103  	}
   104  
   105  	if u.Path == "" || u.Path == "/" {
   106  		u.Path = cpuProfileHandler
   107  	}
   108  
   109  	// Apply duration/timeout overrides to URL.
   110  	values := u.Query()
   111  	if duration > 0 {
   112  		values.Set("seconds", fmt.Sprint(int(duration.Seconds())))
   113  	} else {
   114  		if urlSeconds := values.Get("seconds"); urlSeconds != "" {
   115  			if us, err := strconv.ParseInt(urlSeconds, 10, 32); err == nil {
   116  				duration = time.Duration(us) * time.Second
   117  			}
   118  		}
   119  	}
   120  	if timeout <= 0 {
   121  		if duration > 0 {
   122  			timeout = duration + duration/2
   123  		} else {
   124  			timeout = 60 * time.Second
   125  		}
   126  	}
   127  	u.RawQuery = values.Encode()
   128  	return u.String(), timeout
   129  }
   130  
   131  // objTool implements driver.ObjTool using Go libraries
   132  // (instead of invoking GNU binutils).
   133  type objTool struct {
   134  	mu          sync.Mutex
   135  	disasmCache map[string]*objfile.Disasm
   136  }
   137  
   138  func (*objTool) Open(name string, start, limit, offset uint64) (driver.ObjFile, error) {
   139  	of, err := objfile.Open(name)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  	f := &file{
   144  		name: name,
   145  		file: of,
   146  	}
   147  	if start != 0 {
   148  		if load, err := of.LoadAddress(); err == nil {
   149  			f.offset = start - load
   150  		}
   151  	}
   152  	return f, nil
   153  }
   154  
   155  func (*objTool) Demangle(names []string) (map[string]string, error) {
   156  	// No C++, nothing to demangle.
   157  	return make(map[string]string), nil
   158  }
   159  
   160  func (t *objTool) Disasm(file string, start, end uint64) ([]driver.Inst, error) {
   161  	d, err := t.cachedDisasm(file)
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  	var asm []driver.Inst
   166  	d.Decode(start, end, nil, func(pc, size uint64, file string, line int, text string) {
   167  		asm = append(asm, driver.Inst{Addr: pc, File: file, Line: line, Text: text})
   168  	})
   169  	return asm, nil
   170  }
   171  
   172  func (t *objTool) cachedDisasm(file string) (*objfile.Disasm, error) {
   173  	t.mu.Lock()
   174  	defer t.mu.Unlock()
   175  	if t.disasmCache == nil {
   176  		t.disasmCache = make(map[string]*objfile.Disasm)
   177  	}
   178  	d := t.disasmCache[file]
   179  	if d != nil {
   180  		return d, nil
   181  	}
   182  	f, err := objfile.Open(file)
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  	d, err = f.Disasm()
   187  	f.Close()
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  	t.disasmCache[file] = d
   192  	return d, nil
   193  }
   194  
   195  func (*objTool) SetConfig(config string) {
   196  	// config is usually used to say what binaries to invoke.
   197  	// Ignore entirely.
   198  }
   199  
   200  // file implements driver.ObjFile using Go libraries
   201  // (instead of invoking GNU binutils).
   202  // A file represents a single executable being analyzed.
   203  type file struct {
   204  	name   string
   205  	offset uint64
   206  	sym    []objfile.Sym
   207  	file   *objfile.File
   208  	pcln   objfile.Liner
   209  
   210  	triedDwarf bool
   211  	dwarf      *dwarf.Data
   212  }
   213  
   214  func (f *file) Name() string {
   215  	return f.name
   216  }
   217  
   218  func (f *file) Base() uint64 {
   219  	// No support for shared libraries.
   220  	return 0
   221  }
   222  
   223  func (f *file) BuildID() string {
   224  	// No support for build ID.
   225  	return ""
   226  }
   227  
   228  func (f *file) SourceLine(addr uint64) ([]driver.Frame, error) {
   229  	if f.pcln == nil {
   230  		pcln, err := f.file.PCLineTable()
   231  		if err != nil {
   232  			return nil, err
   233  		}
   234  		f.pcln = pcln
   235  	}
   236  	addr -= f.offset
   237  	file, line, fn := f.pcln.PCToLine(addr)
   238  	if fn != nil {
   239  		frame := []driver.Frame{
   240  			{
   241  				Func: fn.Name,
   242  				File: file,
   243  				Line: line,
   244  			},
   245  		}
   246  		return frame, nil
   247  	}
   248  
   249  	frames := f.dwarfSourceLine(addr)
   250  	if frames != nil {
   251  		return frames, nil
   252  	}
   253  
   254  	return nil, fmt.Errorf("no line information for PC=%#x", addr)
   255  }
   256  
   257  // dwarfSourceLine tries to get file/line information using DWARF.
   258  // This is for C functions that appear in the profile.
   259  // Returns nil if there is no information available.
   260  func (f *file) dwarfSourceLine(addr uint64) []driver.Frame {
   261  	if f.dwarf == nil && !f.triedDwarf {
   262  		// Ignore any error--we don't care exactly why there
   263  		// is no DWARF info.
   264  		f.dwarf, _ = f.file.DWARF()
   265  		f.triedDwarf = true
   266  	}
   267  
   268  	if f.dwarf != nil {
   269  		r := f.dwarf.Reader()
   270  		unit, err := r.SeekPC(addr)
   271  		if err == nil {
   272  			if frames := f.dwarfSourceLineEntry(r, unit, addr); frames != nil {
   273  				return frames
   274  			}
   275  		}
   276  	}
   277  
   278  	return nil
   279  }
   280  
   281  // dwarfSourceLineEntry tries to get file/line information from a
   282  // DWARF compilation unit. Returns nil if it doesn't find anything.
   283  func (f *file) dwarfSourceLineEntry(r *dwarf.Reader, entry *dwarf.Entry, addr uint64) []driver.Frame {
   284  	lines, err := f.dwarf.LineReader(entry)
   285  	if err != nil {
   286  		return nil
   287  	}
   288  	var lentry dwarf.LineEntry
   289  	if err := lines.SeekPC(addr, &lentry); err != nil {
   290  		return nil
   291  	}
   292  
   293  	// Try to find the function name.
   294  	name := ""
   295  FindName:
   296  	for entry, err := r.Next(); entry != nil && err == nil; entry, err = r.Next() {
   297  		if entry.Tag == dwarf.TagSubprogram {
   298  			ranges, err := f.dwarf.Ranges(entry)
   299  			if err != nil {
   300  				return nil
   301  			}
   302  			for _, pcs := range ranges {
   303  				if pcs[0] <= addr && addr < pcs[1] {
   304  					var ok bool
   305  					// TODO: AT_linkage_name, AT_MIPS_linkage_name.
   306  					name, ok = entry.Val(dwarf.AttrName).(string)
   307  					if ok {
   308  						break FindName
   309  					}
   310  				}
   311  			}
   312  		}
   313  	}
   314  
   315  	// TODO: Report inlined functions.
   316  
   317  	frames := []driver.Frame{
   318  		{
   319  			Func: name,
   320  			File: lentry.File.Name,
   321  			Line: lentry.Line,
   322  		},
   323  	}
   324  
   325  	return frames
   326  }
   327  
   328  func (f *file) Symbols(r *regexp.Regexp, addr uint64) ([]*driver.Sym, error) {
   329  	if f.sym == nil {
   330  		sym, err := f.file.Symbols()
   331  		if err != nil {
   332  			return nil, err
   333  		}
   334  		f.sym = sym
   335  	}
   336  	var out []*driver.Sym
   337  	for _, s := range f.sym {
   338  		// Ignore a symbol with address 0 and size 0.
   339  		// An ELF STT_FILE symbol will look like that.
   340  		if s.Addr == 0 && s.Size == 0 {
   341  			continue
   342  		}
   343  		if (r == nil || r.MatchString(s.Name)) && (addr == 0 || s.Addr <= addr && addr < s.Addr+uint64(s.Size)) {
   344  			out = append(out, &driver.Sym{
   345  				Name:  []string{s.Name},
   346  				File:  f.name,
   347  				Start: s.Addr,
   348  				End:   s.Addr + uint64(s.Size) - 1,
   349  			})
   350  		}
   351  	}
   352  	return out, nil
   353  }
   354  
   355  func (f *file) Close() error {
   356  	f.file.Close()
   357  	return nil
   358  }