summaryrefslogtreecommitdiffstats
path: root/src/cmd/pprof/pprof.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/pprof/pprof.go')
-rw-r--r--src/cmd/pprof/pprof.go379
1 files changed, 379 insertions, 0 deletions
diff --git a/src/cmd/pprof/pprof.go b/src/cmd/pprof/pprof.go
new file mode 100644
index 0000000..11f91cb
--- /dev/null
+++ b/src/cmd/pprof/pprof.go
@@ -0,0 +1,379 @@
+// 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"
+ "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),
+ UI: newUI(),
+ }
+ 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 := io.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, intelSyntax bool) ([]driver.Inst, error) {
+ if intelSyntax {
+ return nil, fmt.Errorf("printing assembly in Intel syntax is not supported")
+ }
+ d, err := t.cachedDisasm(file)
+ if err != nil {
+ return nil, err
+ }
+ var asm []driver.Inst
+ d.Decode(start, end, nil, false, 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
+}
+
+// newUI will be set in readlineui.go in some platforms
+// for interactive readline functionality.
+var newUI = func() driver.UI { return nil }