...
Run Format

Source file src/cmd/pprof/pprof.go

Documentation: cmd/pprof

  // Copyright 2014 The Go Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style
  // license that can be found in the LICENSE file.
  
  // pprof is a tool for visualization of profile.data. It is based on
  // the upstream version at github.com/google/pprof, with minor
  // modifications specific to the Go distribution. Please consider
  // upstreaming any modifications to these packages.
  
  package main
  
  import (
  	"crypto/tls"
  	"debug/dwarf"
  	"fmt"
  	"io/ioutil"
  	"net/http"
  	"net/url"
  	"os"
  	"regexp"
  	"strconv"
  	"strings"
  	"sync"
  	"time"
  
  	"cmd/internal/objfile"
  
  	"github.com/google/pprof/driver"
  	"github.com/google/pprof/profile"
  )
  
  func main() {
  	options := &driver.Options{
  		Fetch: new(fetcher),
  		Obj:   new(objTool),
  	}
  	if err := driver.PProf(options); err != nil {
  		fmt.Fprintf(os.Stderr, "%v\n", err)
  		os.Exit(2)
  	}
  }
  
  type fetcher struct {
  }
  
  func (f *fetcher) Fetch(src string, duration, timeout time.Duration) (*profile.Profile, string, error) {
  	sourceURL, timeout := adjustURL(src, duration, timeout)
  	if sourceURL == "" {
  		// Could not recognize URL, let regular pprof attempt to fetch the profile (eg. from a file)
  		return nil, "", nil
  	}
  	fmt.Fprintln(os.Stderr, "Fetching profile over HTTP from", sourceURL)
  	if duration > 0 {
  		fmt.Fprintf(os.Stderr, "Please wait... (%v)\n", duration)
  	}
  	p, err := getProfile(sourceURL, timeout)
  	return p, sourceURL, err
  }
  
  func getProfile(source string, timeout time.Duration) (*profile.Profile, error) {
  	url, err := url.Parse(source)
  	if err != nil {
  		return nil, err
  	}
  
  	var tlsConfig *tls.Config
  	if url.Scheme == "https+insecure" {
  		tlsConfig = &tls.Config{
  			InsecureSkipVerify: true,
  		}
  		url.Scheme = "https"
  		source = url.String()
  	}
  
  	client := &http.Client{
  		Transport: &http.Transport{
  			ResponseHeaderTimeout: timeout + 5*time.Second,
  			Proxy:           http.ProxyFromEnvironment,
  			TLSClientConfig: tlsConfig,
  		},
  	}
  	resp, err := client.Get(source)
  	if err != nil {
  		return nil, err
  	}
  	if resp.StatusCode != http.StatusOK {
  		defer resp.Body.Close()
  		return nil, statusCodeError(resp)
  	}
  	return profile.Parse(resp.Body)
  }
  
  func statusCodeError(resp *http.Response) error {
  	if resp.Header.Get("X-Go-Pprof") != "" && strings.Contains(resp.Header.Get("Content-Type"), "text/plain") {
  		// error is from pprof endpoint
  		if body, err := ioutil.ReadAll(resp.Body); err == nil {
  			return fmt.Errorf("server response: %s - %s", resp.Status, body)
  		}
  	}
  	return fmt.Errorf("server response: %s", resp.Status)
  }
  
  // cpuProfileHandler is the Go pprof CPU profile handler URL.
  const cpuProfileHandler = "/debug/pprof/profile"
  
  // adjustURL applies the duration/timeout values and Go specific defaults
  func adjustURL(source string, duration, timeout time.Duration) (string, time.Duration) {
  	u, err := url.Parse(source)
  	if err != nil || (u.Host == "" && u.Scheme != "" && u.Scheme != "file") {
  		// Try adding http:// to catch sources of the form hostname:port/path.
  		// url.Parse treats "hostname" as the scheme.
  		u, err = url.Parse("http://" + source)
  	}
  	if err != nil || u.Host == "" {
  		return "", 0
  	}
  
  	if u.Path == "" || u.Path == "/" {
  		u.Path = cpuProfileHandler
  	}
  
  	// Apply duration/timeout overrides to URL.
  	values := u.Query()
  	if duration > 0 {
  		values.Set("seconds", fmt.Sprint(int(duration.Seconds())))
  	} else {
  		if urlSeconds := values.Get("seconds"); urlSeconds != "" {
  			if us, err := strconv.ParseInt(urlSeconds, 10, 32); err == nil {
  				duration = time.Duration(us) * time.Second
  			}
  		}
  	}
  	if timeout <= 0 {
  		if duration > 0 {
  			timeout = duration + duration/2
  		} else {
  			timeout = 60 * time.Second
  		}
  	}
  	u.RawQuery = values.Encode()
  	return u.String(), timeout
  }
  
  // objTool implements driver.ObjTool using Go libraries
  // (instead of invoking GNU binutils).
  type objTool struct {
  	mu          sync.Mutex
  	disasmCache map[string]*objfile.Disasm
  }
  
  func (*objTool) Open(name string, start, limit, offset uint64) (driver.ObjFile, error) {
  	of, err := objfile.Open(name)
  	if err != nil {
  		return nil, err
  	}
  	f := &file{
  		name: name,
  		file: of,
  	}
  	if start != 0 {
  		if load, err := of.LoadAddress(); err == nil {
  			f.offset = start - load
  		}
  	}
  	return f, nil
  }
  
  func (*objTool) Demangle(names []string) (map[string]string, error) {
  	// No C++, nothing to demangle.
  	return make(map[string]string), nil
  }
  
  func (t *objTool) Disasm(file string, start, end uint64) ([]driver.Inst, error) {
  	d, err := t.cachedDisasm(file)
  	if err != nil {
  		return nil, err
  	}
  	var asm []driver.Inst
  	d.Decode(start, end, nil, func(pc, size uint64, file string, line int, text string) {
  		asm = append(asm, driver.Inst{Addr: pc, File: file, Line: line, Text: text})
  	})
  	return asm, nil
  }
  
  func (t *objTool) cachedDisasm(file string) (*objfile.Disasm, error) {
  	t.mu.Lock()
  	defer t.mu.Unlock()
  	if t.disasmCache == nil {
  		t.disasmCache = make(map[string]*objfile.Disasm)
  	}
  	d := t.disasmCache[file]
  	if d != nil {
  		return d, nil
  	}
  	f, err := objfile.Open(file)
  	if err != nil {
  		return nil, err
  	}
  	d, err = f.Disasm()
  	f.Close()
  	if err != nil {
  		return nil, err
  	}
  	t.disasmCache[file] = d
  	return d, nil
  }
  
  func (*objTool) SetConfig(config string) {
  	// config is usually used to say what binaries to invoke.
  	// Ignore entirely.
  }
  
  // file implements driver.ObjFile using Go libraries
  // (instead of invoking GNU binutils).
  // A file represents a single executable being analyzed.
  type file struct {
  	name   string
  	offset uint64
  	sym    []objfile.Sym
  	file   *objfile.File
  	pcln   objfile.Liner
  
  	triedDwarf bool
  	dwarf      *dwarf.Data
  }
  
  func (f *file) Name() string {
  	return f.name
  }
  
  func (f *file) Base() uint64 {
  	// No support for shared libraries.
  	return 0
  }
  
  func (f *file) BuildID() string {
  	// No support for build ID.
  	return ""
  }
  
  func (f *file) SourceLine(addr uint64) ([]driver.Frame, error) {
  	if f.pcln == nil {
  		pcln, err := f.file.PCLineTable()
  		if err != nil {
  			return nil, err
  		}
  		f.pcln = pcln
  	}
  	addr -= f.offset
  	file, line, fn := f.pcln.PCToLine(addr)
  	if fn != nil {
  		frame := []driver.Frame{
  			{
  				Func: fn.Name,
  				File: file,
  				Line: line,
  			},
  		}
  		return frame, nil
  	}
  
  	frames := f.dwarfSourceLine(addr)
  	if frames != nil {
  		return frames, nil
  	}
  
  	return nil, fmt.Errorf("no line information for PC=%#x", addr)
  }
  
  // dwarfSourceLine tries to get file/line information using DWARF.
  // This is for C functions that appear in the profile.
  // Returns nil if there is no information available.
  func (f *file) dwarfSourceLine(addr uint64) []driver.Frame {
  	if f.dwarf == nil && !f.triedDwarf {
  		// Ignore any error--we don't care exactly why there
  		// is no DWARF info.
  		f.dwarf, _ = f.file.DWARF()
  		f.triedDwarf = true
  	}
  
  	if f.dwarf != nil {
  		r := f.dwarf.Reader()
  		unit, err := r.SeekPC(addr)
  		if err == nil {
  			if frames := f.dwarfSourceLineEntry(r, unit, addr); frames != nil {
  				return frames
  			}
  		}
  	}
  
  	return nil
  }
  
  // dwarfSourceLineEntry tries to get file/line information from a
  // DWARF compilation unit. Returns nil if it doesn't find anything.
  func (f *file) dwarfSourceLineEntry(r *dwarf.Reader, entry *dwarf.Entry, addr uint64) []driver.Frame {
  	lines, err := f.dwarf.LineReader(entry)
  	if err != nil {
  		return nil
  	}
  	var lentry dwarf.LineEntry
  	if err := lines.SeekPC(addr, &lentry); err != nil {
  		return nil
  	}
  
  	// Try to find the function name.
  	name := ""
  FindName:
  	for entry, err := r.Next(); entry != nil && err == nil; entry, err = r.Next() {
  		if entry.Tag == dwarf.TagSubprogram {
  			ranges, err := f.dwarf.Ranges(entry)
  			if err != nil {
  				return nil
  			}
  			for _, pcs := range ranges {
  				if pcs[0] <= addr && addr < pcs[1] {
  					var ok bool
  					// TODO: AT_linkage_name, AT_MIPS_linkage_name.
  					name, ok = entry.Val(dwarf.AttrName).(string)
  					if ok {
  						break FindName
  					}
  				}
  			}
  		}
  	}
  
  	// TODO: Report inlined functions.
  
  	frames := []driver.Frame{
  		{
  			Func: name,
  			File: lentry.File.Name,
  			Line: lentry.Line,
  		},
  	}
  
  	return frames
  }
  
  func (f *file) Symbols(r *regexp.Regexp, addr uint64) ([]*driver.Sym, error) {
  	if f.sym == nil {
  		sym, err := f.file.Symbols()
  		if err != nil {
  			return nil, err
  		}
  		f.sym = sym
  	}
  	var out []*driver.Sym
  	for _, s := range f.sym {
  		// Ignore a symbol with address 0 and size 0.
  		// An ELF STT_FILE symbol will look like that.
  		if s.Addr == 0 && s.Size == 0 {
  			continue
  		}
  		if (r == nil || r.MatchString(s.Name)) && (addr == 0 || s.Addr <= addr && addr < s.Addr+uint64(s.Size)) {
  			out = append(out, &driver.Sym{
  				Name:  []string{s.Name},
  				File:  f.name,
  				Start: s.Addr,
  				End:   s.Addr + uint64(s.Size) - 1,
  			})
  		}
  	}
  	return out, nil
  }
  
  func (f *file) Close() error {
  	f.file.Close()
  	return nil
  }
  

View as plain text