...

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

View as plain text