src/cmd/pprof/pprof.go GO 396 lines View on github.com → Search inside
1// Copyright 2014 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45// pprof is a tool for visualization of profile.data. It is based on6// the upstream version at github.com/google/pprof, with minor7// modifications specific to the Go distribution. Please consider8// upstreaming any modifications to these packages.910package main1112import (13	"crypto/tls"14	"debug/dwarf"15	"flag"16	"fmt"17	"io"18	"net/http"19	"net/url"20	"os"21	"regexp"22	"strconv"23	"strings"24	"sync"25	"time"2627	"cmd/internal/disasm"28	"cmd/internal/objfile"29	"cmd/internal/telemetry/counter"3031	"github.com/google/pprof/driver"32	"github.com/google/pprof/profile"33)3435func main() {36	counter.Open()37	counter.Inc("pprof/invocations")38	options := &driver.Options{39		Fetch: new(fetcher),40		Obj:   new(objTool),41		UI:    newUI(),42	}43	err := driver.PProf(options)44	counter.CountFlags("pprof/flag:", *flag.CommandLine) // pprof will use the flag package as its default45	if err != nil {46		fmt.Fprintf(os.Stderr, "%v\n", err)47		os.Exit(2)48	}49}5051type fetcher struct {52}5354func (f *fetcher) Fetch(src string, duration, timeout time.Duration) (*profile.Profile, string, error) {55	// Firstly, determine if the src is an existing file on the disk.56	// If it is a file, let regular pprof open it.57	// If it is not a file, when the src contains `:`58	// (e.g. mem_2023-11-02_03:55:24 or abc:123/mem_2023-11-02_03:55:24),59	// url.Parse will recognize it as a link and ultimately report an error,60	// similar to `abc:123/mem_2023-11-02_03:55:24:61	// Get "http://abc:123/mem_2023-11-02_03:55:24": dial tcp: lookup abc: no such host`62	if _, openErr := os.Stat(src); openErr == nil {63		return nil, "", nil64	}65	sourceURL, timeout := adjustURL(src, duration, timeout)66	if sourceURL == "" {67		// Could not recognize URL, let regular pprof attempt to fetch the profile (eg. from a file)68		return nil, "", nil69	}70	fmt.Fprintln(os.Stderr, "Fetching profile over HTTP from", sourceURL)71	if duration > 0 {72		fmt.Fprintf(os.Stderr, "Please wait... (%v)\n", duration)73	}74	p, err := getProfile(sourceURL, timeout)75	return p, sourceURL, err76}7778func getProfile(source string, timeout time.Duration) (*profile.Profile, error) {79	url, err := url.Parse(source)80	if err != nil {81		return nil, err82	}8384	var tlsConfig *tls.Config85	if url.Scheme == "https+insecure" {86		tlsConfig = &tls.Config{87			InsecureSkipVerify: true,88		}89		url.Scheme = "https"90		source = url.String()91	}9293	client := &http.Client{94		Transport: &http.Transport{95			ResponseHeaderTimeout: timeout + 5*time.Second,96			Proxy:                 http.ProxyFromEnvironment,97			TLSClientConfig:       tlsConfig,98		},99	}100	resp, err := client.Get(source)101	if err != nil {102		return nil, err103	}104	defer resp.Body.Close()105	if resp.StatusCode != http.StatusOK {106		return nil, statusCodeError(resp)107	}108	return profile.Parse(resp.Body)109}110111func statusCodeError(resp *http.Response) error {112	if resp.Header.Get("X-Go-Pprof") != "" && strings.Contains(resp.Header.Get("Content-Type"), "text/plain") {113		// error is from pprof endpoint114		if body, err := io.ReadAll(resp.Body); err == nil {115			return fmt.Errorf("server response: %s - %s", resp.Status, body)116		}117	}118	return fmt.Errorf("server response: %s", resp.Status)119}120121// cpuProfileHandler is the Go pprof CPU profile handler URL.122const cpuProfileHandler = "/debug/pprof/profile"123124// adjustURL applies the duration/timeout values and Go specific defaults.125func adjustURL(source string, duration, timeout time.Duration) (string, time.Duration) {126	u, err := url.Parse(source)127	if err != nil || (u.Host == "" && u.Scheme != "" && u.Scheme != "file") {128		// Try adding http:// to catch sources of the form hostname:port/path.129		// url.Parse treats "hostname" as the scheme.130		u, err = url.Parse("http://" + source)131	}132	if err != nil || u.Host == "" {133		return "", 0134	}135136	if u.Path == "" || u.Path == "/" {137		u.Path = cpuProfileHandler138	}139140	// Apply duration/timeout overrides to URL.141	values := u.Query()142	if duration > 0 {143		values.Set("seconds", fmt.Sprint(int(duration.Seconds())))144	} else {145		if urlSeconds := values.Get("seconds"); urlSeconds != "" {146			if us, err := strconv.ParseInt(urlSeconds, 10, 32); err == nil {147				duration = time.Duration(us) * time.Second148			}149		}150	}151	if timeout <= 0 {152		if duration > 0 {153			timeout = duration + duration/2154		} else {155			timeout = 60 * time.Second156		}157	}158	u.RawQuery = values.Encode()159	return u.String(), timeout160}161162// objTool implements driver.ObjTool using Go libraries163// (instead of invoking GNU binutils).164type objTool struct {165	mu          sync.Mutex166	disasmCache map[string]*disasm.Disasm167}168169func (*objTool) Open(name string, start, limit, offset uint64, relocationSymbol string) (driver.ObjFile, error) {170	of, err := objfile.Open(name)171	if err != nil {172		return nil, err173	}174	f := &file{175		name: name,176		file: of,177	}178	if start != 0 {179		if load, err := of.LoadAddress(); err == nil {180			f.offset = start - load181		}182	}183	return f, nil184}185186func (*objTool) Demangle(names []string) (map[string]string, error) {187	// No C++, nothing to demangle.188	return make(map[string]string), nil189}190191func (t *objTool) Disasm(file string, start, end uint64, intelSyntax bool) ([]driver.Inst, error) {192	if intelSyntax {193		return nil, fmt.Errorf("printing assembly in Intel syntax is not supported")194	}195	d, err := t.cachedDisasm(file)196	if err != nil {197		return nil, err198	}199	var asm []driver.Inst200	d.Decode(start, end, nil, false, func(pc, size uint64, file string, line int, text string) {201		asm = append(asm, driver.Inst{Addr: pc, File: file, Line: line, Text: text})202	})203	return asm, nil204}205206func (t *objTool) cachedDisasm(file string) (*disasm.Disasm, error) {207	t.mu.Lock()208	defer t.mu.Unlock()209	if t.disasmCache == nil {210		t.disasmCache = make(map[string]*disasm.Disasm)211	}212	d := t.disasmCache[file]213	if d != nil {214		return d, nil215	}216	f, err := objfile.Open(file)217	if err != nil {218		return nil, err219	}220	d, err = disasm.DisasmForFile(f)221	f.Close()222	if err != nil {223		return nil, err224	}225	t.disasmCache[file] = d226	return d, nil227}228229func (*objTool) SetConfig(config string) {230	// config is usually used to say what binaries to invoke.231	// Ignore entirely.232}233234// file implements driver.ObjFile using Go libraries235// (instead of invoking GNU binutils).236// A file represents a single executable being analyzed.237type file struct {238	name   string239	offset uint64240	sym    []objfile.Sym241	file   *objfile.File242	pcln   objfile.Liner243244	triedDwarf bool245	dwarf      *dwarf.Data246}247248func (f *file) Name() string {249	return f.name250}251252func (f *file) ObjAddr(addr uint64) (uint64, error) {253	return addr - f.offset, nil254}255256func (f *file) BuildID() string {257	// No support for build ID.258	return ""259}260261func (f *file) SourceLine(addr uint64) ([]driver.Frame, error) {262	if f.pcln == nil {263		pcln, err := f.file.PCLineTable()264		if err != nil {265			return nil, err266		}267		f.pcln = pcln268	}269	addr -= f.offset270	file, line, fn := f.pcln.PCToLine(addr)271	if fn != nil {272		frame := []driver.Frame{273			{274				Func: fn.Name,275				File: file,276				Line: line,277			},278		}279		return frame, nil280	}281282	frames := f.dwarfSourceLine(addr)283	if frames != nil {284		return frames, nil285	}286287	return nil, fmt.Errorf("no line information for PC=%#x", addr)288}289290// dwarfSourceLine tries to get file/line information using DWARF.291// This is for C functions that appear in the profile.292// Returns nil if there is no information available.293func (f *file) dwarfSourceLine(addr uint64) []driver.Frame {294	if f.dwarf == nil && !f.triedDwarf {295		// Ignore any error--we don't care exactly why there296		// is no DWARF info.297		f.dwarf, _ = f.file.DWARF()298		f.triedDwarf = true299	}300301	if f.dwarf != nil {302		r := f.dwarf.Reader()303		unit, err := r.SeekPC(addr)304		if err == nil {305			if frames := f.dwarfSourceLineEntry(r, unit, addr); frames != nil {306				return frames307			}308		}309	}310311	return nil312}313314// dwarfSourceLineEntry tries to get file/line information from a315// DWARF compilation unit. Returns nil if it doesn't find anything.316func (f *file) dwarfSourceLineEntry(r *dwarf.Reader, entry *dwarf.Entry, addr uint64) []driver.Frame {317	lines, err := f.dwarf.LineReader(entry)318	if err != nil {319		return nil320	}321	var lentry dwarf.LineEntry322	if err := lines.SeekPC(addr, &lentry); err != nil {323		return nil324	}325326	// Try to find the function name.327	name := ""328FindName:329	for entry, err := r.Next(); entry != nil && err == nil; entry, err = r.Next() {330		if entry.Tag == dwarf.TagSubprogram {331			ranges, err := f.dwarf.Ranges(entry)332			if err != nil {333				return nil334			}335			for _, pcs := range ranges {336				if pcs[0] <= addr && addr < pcs[1] {337					var ok bool338					// TODO: AT_linkage_name, AT_MIPS_linkage_name.339					name, ok = entry.Val(dwarf.AttrName).(string)340					if ok {341						break FindName342					}343				}344			}345		}346	}347348	// TODO: Report inlined functions.349350	frames := []driver.Frame{351		{352			Func: name,353			File: lentry.File.Name,354			Line: lentry.Line,355		},356	}357358	return frames359}360361func (f *file) Symbols(r *regexp.Regexp, addr uint64) ([]*driver.Sym, error) {362	if f.sym == nil {363		sym, err := f.file.Symbols()364		if err != nil {365			return nil, err366		}367		f.sym = sym368	}369	var out []*driver.Sym370	for _, s := range f.sym {371		// Ignore a symbol with address 0 and size 0.372		// An ELF STT_FILE symbol will look like that.373		if s.Addr == 0 && s.Size == 0 {374			continue375		}376		if (r == nil || r.MatchString(s.Name)) && (addr == 0 || s.Addr <= addr && addr < s.Addr+uint64(s.Size)) {377			out = append(out, &driver.Sym{378				Name:  []string{s.Name},379				File:  f.name,380				Start: s.Addr,381				End:   s.Addr + uint64(s.Size) - 1,382			})383		}384	}385	return out, nil386}387388func (f *file) Close() error {389	f.file.Close()390	return nil391}392393// newUI will be set in readlineui.go in some platforms394// for interactive readline functionality.395var newUI = func() driver.UI { return nil }

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.