| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| |
|
| | package main |
| |
|
| | import ( |
| | "crypto/tls" |
| | "debug/dwarf" |
| | "flag" |
| | "fmt" |
| | "io" |
| | "net/http" |
| | "net/url" |
| | "os" |
| | "regexp" |
| | "strconv" |
| | "strings" |
| | "sync" |
| | "time" |
| |
|
| | "cmd/internal/disasm" |
| | "cmd/internal/objfile" |
| | "cmd/internal/telemetry/counter" |
| |
|
| | "github.com/google/pprof/driver" |
| | "github.com/google/pprof/profile" |
| | ) |
| |
|
| | func main() { |
| | counter.Open() |
| | counter.Inc("pprof/invocations") |
| | options := &driver.Options{ |
| | Fetch: new(fetcher), |
| | Obj: new(objTool), |
| | UI: newUI(), |
| | } |
| | err := driver.PProf(options) |
| | counter.CountFlags("pprof/flag:", *flag.CommandLine) |
| | if 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) { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | if _, openErr := os.Stat(src); openErr == nil { |
| | return nil, "", nil |
| | } |
| | sourceURL, timeout := adjustURL(src, duration, timeout) |
| | if sourceURL == "" { |
| | |
| | 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 |
| | } |
| | defer resp.Body.Close() |
| | if resp.StatusCode != http.StatusOK { |
| | 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") { |
| | |
| | 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) |
| | } |
| |
|
| | |
| | const cpuProfileHandler = "/debug/pprof/profile" |
| |
|
| | |
| | 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") { |
| | |
| | |
| | u, err = url.Parse("http://" + source) |
| | } |
| | if err != nil || u.Host == "" { |
| | return "", 0 |
| | } |
| |
|
| | if u.Path == "" || u.Path == "/" { |
| | u.Path = cpuProfileHandler |
| | } |
| |
|
| | |
| | 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 |
| | } |
| |
|
| | |
| | |
| | type objTool struct { |
| | mu sync.Mutex |
| | disasmCache map[string]*disasm.Disasm |
| | } |
| |
|
| | func (*objTool) Open(name string, start, limit, offset uint64, relocationSymbol string) (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) { |
| | |
| | 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) (*disasm.Disasm, error) { |
| | t.mu.Lock() |
| | defer t.mu.Unlock() |
| | if t.disasmCache == nil { |
| | t.disasmCache = make(map[string]*disasm.Disasm) |
| | } |
| | d := t.disasmCache[file] |
| | if d != nil { |
| | return d, nil |
| | } |
| | f, err := objfile.Open(file) |
| | if err != nil { |
| | return nil, err |
| | } |
| | d, err = disasm.DisasmForFile(f) |
| | f.Close() |
| | if err != nil { |
| | return nil, err |
| | } |
| | t.disasmCache[file] = d |
| | return d, nil |
| | } |
| |
|
| | func (*objTool) SetConfig(config string) { |
| | |
| | |
| | } |
| |
|
| | |
| | |
| | |
| | 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) ObjAddr(addr uint64) (uint64, error) { |
| | return addr - f.offset, nil |
| | } |
| |
|
| | func (f *file) BuildID() string { |
| | |
| | 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) |
| | } |
| |
|
| | |
| | |
| | |
| | func (f *file) dwarfSourceLine(addr uint64) []driver.Frame { |
| | if f.dwarf == nil && !f.triedDwarf { |
| | |
| | |
| | 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 |
| | } |
| |
|
| | |
| | |
| | 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 |
| | } |
| |
|
| | |
| | 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 |
| | |
| | name, ok = entry.Val(dwarf.AttrName).(string) |
| | if ok { |
| | break FindName |
| | } |
| | } |
| | } |
| | } |
| | } |
| |
|
| | |
| |
|
| | 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 { |
| | |
| | |
| | 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 |
| | } |
| |
|
| | |
| | |
| | var newUI = func() driver.UI { return nil } |
| |
|