Commit 8b5221a5 authored by Russ Cox's avatar Russ Cox

cmd/pprof: add Go implementation

Update #8798

This is a new implementation of pprof,
written in Go instead of in Perl.
It was written primarily by Raul Silvera and
is in use for profiling programs of all languages
inside Google.

The internal structure is a bit package-heavy,
but it matches the copy used inside Google, and
since it is in an internal directory, we can make
changes to it later if we need to.

The only "new" file here is src/cmd/pprof/pprof.go,
which stitches together the Google pprof and the
Go command libraries for object file access.

I am explicitly NOT interested in style or review
comments on the rest of the files
(that is, src/cmd/pprof/internal/...).
Those are intended to stay as close to the Google
copies as possible, like we did with the pprof Perl script.

Still to do:

- Basic tests.
- Real command documentation.
- Hook up disassemblers.

LGTM=r
R=r, bradfitz, alex.brainman, dave
CC=golang-codereviews
https://golang.org/cl/153750043
parent 454d1b0e
// 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.
// Package commands defines and manages the basic pprof commands
package commands
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"strings"
"cmd/pprof/internal/plugin"
"cmd/pprof/internal/report"
"cmd/pprof/internal/svg"
"cmd/pprof/internal/tempfile"
)
// Commands describes the commands accepted by pprof.
type Commands map[string]*Command
// Command describes the actions for a pprof command. Includes a
// function for command-line completion, the report format to use
// during report generation, any postprocessing functions, and whether
// the command expects a regexp parameter (typically a function name).
type Command struct {
Complete Completer // autocomplete for interactive mode
Format int // report format to generate
PostProcess PostProcessor // postprocessing to run on report
HasParam bool // Collect a parameter from the CLI
Usage string // Help text
}
// Completer is a function for command-line autocompletion
type Completer func(prefix string) string
// PostProcessor is a function that applies post-processing to the report output
type PostProcessor func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error
// PProf returns the basic pprof report-generation commands
func PProf(c Completer, interactive **bool, svgpan **string) Commands {
return Commands{
// Commands that require no post-processing.
"tags": {nil, report.Tags, nil, false, "Outputs all tags in the profile"},
"raw": {c, report.Raw, nil, false, "Outputs a text representation of the raw profile"},
"dot": {c, report.Dot, nil, false, "Outputs a graph in DOT format"},
"top": {c, report.Text, nil, false, "Outputs top entries in text form"},
"tree": {c, report.Tree, nil, false, "Outputs a text rendering of call graph"},
"text": {c, report.Text, nil, false, "Outputs top entries in text form"},
"disasm": {c, report.Dis, nil, true, "Output annotated assembly for functions matching regexp or address"},
"list": {c, report.List, nil, true, "Output annotated source for functions matching regexp"},
"peek": {c, report.Tree, nil, true, "Output callers/callees of functions matching regexp"},
// Save binary formats to a file
"callgrind": {c, report.Callgrind, awayFromTTY("callgraph.out"), false, "Outputs a graph in callgrind format"},
"proto": {c, report.Proto, awayFromTTY("pb.gz"), false, "Outputs the profile in compressed protobuf format"},
// Generate report in DOT format and postprocess with dot
"gif": {c, report.Dot, invokeDot("gif"), false, "Outputs a graph image in GIF format"},
"pdf": {c, report.Dot, invokeDot("pdf"), false, "Outputs a graph in PDF format"},
"png": {c, report.Dot, invokeDot("png"), false, "Outputs a graph image in PNG format"},
"ps": {c, report.Dot, invokeDot("ps"), false, "Outputs a graph in PS format"},
// Save SVG output into a file after including svgpan library
"svg": {c, report.Dot, saveSVGToFile(svgpan), false, "Outputs a graph in SVG format"},
// Visualize postprocessed dot output
"eog": {c, report.Dot, invokeVisualizer(interactive, invokeDot("svg"), "svg", []string{"eog"}), false, "Visualize graph through eog"},
"evince": {c, report.Dot, invokeVisualizer(interactive, invokeDot("pdf"), "pdf", []string{"evince"}), false, "Visualize graph through evince"},
"gv": {c, report.Dot, invokeVisualizer(interactive, invokeDot("ps"), "ps", []string{"gv --noantialias"}), false, "Visualize graph through gv"},
"web": {c, report.Dot, invokeVisualizer(interactive, saveSVGToFile(svgpan), "svg", browsers), false, "Visualize graph through web browser"},
// Visualize HTML directly generated by report.
"weblist": {c, report.WebList, invokeVisualizer(interactive, awayFromTTY("html"), "html", browsers), true, "Output annotated source in HTML for functions matching regexp or address"},
}
}
// List of web browsers to attempt for web visualization
var browsers = []string{"chrome", "google-chrome", "firefox", "/usr/bin/open"}
// NewCompleter creates an autocompletion function for a set of commands.
func NewCompleter(cs Commands) Completer {
return func(line string) string {
switch tokens := strings.Fields(line); len(tokens) {
case 0:
// Nothing to complete
case 1:
// Single token -- complete command name
found := ""
for c := range cs {
if strings.HasPrefix(c, tokens[0]) {
if found != "" {
return line
}
found = c
}
}
if found != "" {
return found
}
default:
// Multiple tokens -- complete using command completer
if c, ok := cs[tokens[0]]; ok {
if c.Complete != nil {
lastTokenIdx := len(tokens) - 1
lastToken := tokens[lastTokenIdx]
if strings.HasPrefix(lastToken, "-") {
lastToken = "-" + c.Complete(lastToken[1:])
} else {
lastToken = c.Complete(lastToken)
}
return strings.Join(append(tokens[:lastTokenIdx], lastToken), " ")
}
}
}
return line
}
}
// awayFromTTY saves the output in a file if it would otherwise go to
// the terminal screen. This is used to avoid dumping binary data on
// the screen.
func awayFromTTY(format string) PostProcessor {
return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error {
if output == os.Stdout && ui.IsTerminal() {
tempFile, err := tempfile.New("", "profile", "."+format)
if err != nil {
return err
}
ui.PrintErr("Generating report in ", tempFile.Name())
_, err = fmt.Fprint(tempFile, input)
return err
}
_, err := fmt.Fprint(output, input)
return err
}
}
func invokeDot(format string) PostProcessor {
divert := awayFromTTY(format)
return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error {
cmd := exec.Command("dot", "-T"+format)
var buf bytes.Buffer
cmd.Stdin, cmd.Stdout, cmd.Stderr = input, &buf, os.Stderr
if err := cmd.Run(); err != nil {
return err
}
return divert(&buf, output, ui)
}
}
func saveSVGToFile(svgpan **string) PostProcessor {
generateSVG := invokeDot("svg")
divert := awayFromTTY("svg")
return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error {
baseSVG := &bytes.Buffer{}
generateSVG(input, baseSVG, ui)
massaged := &bytes.Buffer{}
fmt.Fprint(massaged, svg.Massage(*baseSVG, **svgpan))
return divert(massaged, output, ui)
}
}
func invokeVisualizer(interactive **bool, format PostProcessor, suffix string, visualizers []string) PostProcessor {
return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error {
tempFile, err := tempfile.New(os.Getenv("PPROF_TMPDIR"), "pprof", "."+suffix)
if err != nil {
return err
}
tempfile.DeferDelete(tempFile.Name())
if err = format(input, tempFile, ui); err != nil {
return err
}
// Try visualizers until one is successful
for _, v := range visualizers {
// Separate command and arguments for exec.Command.
args := strings.Split(v, " ")
if len(args) == 0 {
continue
}
viewer := exec.Command(args[0], append(args[1:], tempFile.Name())...)
viewer.Stderr = os.Stderr
if err = viewer.Start(); err == nil {
if !**interactive {
// In command-line mode, wait for the viewer to be closed
// before proceeding
return viewer.Wait()
}
return nil
}
}
return err
}
}
This diff is collapsed.
This diff is collapsed.
// 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.
// Package fetch provides an extensible mechanism to fetch a profile
// from a data source.
package fetch
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"strings"
"time"
"cmd/pprof/internal/plugin"
"cmd/pprof/internal/profile"
)
// FetchProfile reads from a data source (network, file) and generates a
// profile.
func FetchProfile(source string, timeout time.Duration) (*profile.Profile, error) {
return Fetcher(source, timeout, plugin.StandardUI())
}
// Fetcher is the plugin.Fetcher version of FetchProfile.
func Fetcher(source string, timeout time.Duration, ui plugin.UI) (*profile.Profile, error) {
var f io.ReadCloser
var err error
url, err := url.Parse(source)
if err == nil && url.Host != "" {
f, err = FetchURL(source, timeout)
} else {
f, err = os.Open(source)
}
if err != nil {
return nil, err
}
defer f.Close()
return profile.Parse(f)
}
// FetchURL fetches a profile from a URL using HTTP.
func FetchURL(source string, timeout time.Duration) (io.ReadCloser, error) {
resp, err := httpGet(source, timeout)
if err != nil {
return nil, fmt.Errorf("http fetch %s: %v", source, err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("server response: %s", resp.Status)
}
return resp.Body, nil
}
// PostURL issues a POST to a URL over HTTP.
func PostURL(source, post string) ([]byte, error) {
resp, err := http.Post(source, "application/octet-stream", strings.NewReader(post))
if err != nil {
return nil, fmt.Errorf("http post %s: %v", source, err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("server response: %s", resp.Status)
}
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
}
// httpGet is a wrapper around http.Get; it is defined as a variable
// so it can be redefined during for testing.
var httpGet = func(url string, timeout time.Duration) (*http.Response, error) {
client := &http.Client{
Transport: &http.Transport{
ResponseHeaderTimeout: timeout + 5*time.Second,
},
}
return client.Get(url)
}
// 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.
// Package plugin defines the plugin implementations that the main pprof driver requires.
package plugin
import (
"bufio"
"fmt"
"os"
"regexp"
"strings"
"time"
"cmd/pprof/internal/profile"
)
// A FlagSet creates and parses command-line flags.
// It is similar to the standard flag.FlagSet.
type FlagSet interface {
// Bool, Int, Float64, and String define new flags,
// like the functions of the same name in package flag.
Bool(name string, def bool, usage string) *bool
Int(name string, def int, usage string) *int
Float64(name string, def float64, usage string) *float64
String(name string, def string, usage string) *string
// ExtraUsage returns any additional text that should be
// printed after the standard usage message.
// The typical use of ExtraUsage is to show any custom flags
// defined by the specific pprof plugins being used.
ExtraUsage() string
// Parse initializes the flags with their values for this run
// and returns the non-flag command line arguments.
// If an unknown flag is encountered or there are no arguments,
// Parse should call usage and return nil.
Parse(usage func()) []string
}
// An ObjTool inspects shared libraries and executable files.
type ObjTool interface {
// Open opens the named object file.
// If the object is a shared library, start is the address where
// it is mapped into memory in the address space being inspected.
Open(file string, start uint64) (ObjFile, error)
// Demangle translates a batch of symbol names from mangled
// form to human-readable form.
Demangle(names []string) (map[string]string, error)
// Disasm disassembles the named object file, starting at
// the start address and stopping at (before) the end address.
Disasm(file string, start, end uint64) ([]Inst, error)
// SetConfig configures the tool.
// The implementation defines the meaning of the string
// and can ignore it entirely.
SetConfig(config string)
}
// NoObjTool returns a trivial implementation of the ObjTool interface.
// Open returns an error indicating that the requested file does not exist.
// Demangle returns an empty map and a nil error.
// Disasm returns an error.
// SetConfig is a no-op.
func NoObjTool() ObjTool {
return noObjTool{}
}
type noObjTool struct{}
func (noObjTool) Open(file string, start uint64) (ObjFile, error) {
return nil, &os.PathError{Op: "open", Path: file, Err: os.ErrNotExist}
}
func (noObjTool) Demangle(name []string) (map[string]string, error) {
return make(map[string]string), nil
}
func (noObjTool) Disasm(file string, start, end uint64) ([]Inst, error) {
return nil, fmt.Errorf("disassembly not supported")
}
func (noObjTool) SetConfig(config string) {
}
// An ObjFile is a single object file: a shared library or executable.
type ObjFile interface {
// Name returns the underlyinf file name, if available
Name() string
// Base returns the base address to use when looking up symbols in the file.
Base() uint64
// BuildID returns the GNU build ID of the file, or an empty string.
BuildID() string
// SourceLine reports the source line information for a given
// address in the file. Due to inlining, the source line information
// is in general a list of positions representing a call stack,
// with the leaf function first.
SourceLine(addr uint64) ([]Frame, error)
// Symbols returns a list of symbols in the object file.
// If r is not nil, Symbols restricts the list to symbols
// with names matching the regular expression.
// If addr is not zero, Symbols restricts the list to symbols
// containing that address.
Symbols(r *regexp.Regexp, addr uint64) ([]*Sym, error)
// Close closes the file, releasing associated resources.
Close() error
}
// A Frame describes a single line in a source file.
type Frame struct {
Func string // name of function
File string // source file name
Line int // line in file
}
// A Sym describes a single symbol in an object file.
type Sym struct {
Name []string // names of symbol (many if symbol was dedup'ed)
File string // object file containing symbol
Start uint64 // start virtual address
End uint64 // virtual address of last byte in sym (Start+size-1)
}
// An Inst is a single instruction in an assembly listing.
type Inst struct {
Addr uint64 // virtual address of instruction
Text string // instruction text
File string // source file
Line int // source line
}
// A UI manages user interactions.
type UI interface {
// Read returns a line of text (a command) read from the user.
ReadLine() (string, error)
// Print shows a message to the user.
// It formats the text as fmt.Print would and adds a final \n if not already present.
// For line-based UI, Print writes to standard error.
// (Standard output is reserved for report data.)
Print(...interface{})
// PrintErr shows an error message to the user.
// It formats the text as fmt.Print would and adds a final \n if not already present.
// For line-based UI, PrintErr writes to standard error.
PrintErr(...interface{})
// IsTerminal returns whether the UI is known to be tied to an
// interactive terminal (as opposed to being redirected to a file).
IsTerminal() bool
// SetAutoComplete instructs the UI to call complete(cmd) to obtain
// the auto-completion of cmd, if the UI supports auto-completion at all.
SetAutoComplete(complete func(string) string)
}
// StandardUI returns a UI that reads from standard input,
// prints messages to standard output,
// prints errors to standard error, and doesn't use auto-completion.
func StandardUI() UI {
return &stdUI{r: bufio.NewReader(os.Stdin)}
}
type stdUI struct {
r *bufio.Reader
}
func (ui *stdUI) ReadLine() (string, error) {
os.Stdout.WriteString("(pprof) ")
return ui.r.ReadString('\n')
}
func (ui *stdUI) Print(args ...interface{}) {
ui.fprint(os.Stderr, args)
}
func (ui *stdUI) PrintErr(args ...interface{}) {
ui.fprint(os.Stderr, args)
}
func (ui *stdUI) IsTerminal() bool {
return false
}
func (ui *stdUI) SetAutoComplete(func(string) string) {
}
func (ui *stdUI) fprint(f *os.File, args []interface{}) {
text := fmt.Sprint(args...)
if !strings.HasSuffix(text, "\n") {
text += "\n"
}
f.WriteString(text)
}
// A Fetcher reads and returns the profile named by src.
// It gives up after the given timeout, unless src contains a timeout override
// (as defined by the implementation).
// It can print messages to ui.
type Fetcher func(src string, timeout time.Duration, ui UI) (*profile.Profile, error)
// A Symbolizer annotates a profile with symbol information.
// The profile was fetch from src.
// The meaning of mode is defined by the implementation.
type Symbolizer func(mode, src string, prof *profile.Profile, obj ObjTool, ui UI) error
This diff is collapsed.
// 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.
// Implements methods to filter samples from profiles.
package profile
import "regexp"
// FilterSamplesByName filters the samples in a profile and only keeps
// samples where at least one frame matches focus but none match ignore.
// Returns true is the corresponding regexp matched at least one sample.
func (p *Profile) FilterSamplesByName(focus, ignore, hide *regexp.Regexp) (fm, im, hm bool) {
focusOrIgnore := make(map[uint64]bool)
hidden := make(map[uint64]bool)
for _, l := range p.Location {
if ignore != nil && l.matchesName(ignore) {
im = true
focusOrIgnore[l.ID] = false
} else if focus == nil || l.matchesName(focus) {
fm = true
focusOrIgnore[l.ID] = true
}
if hide != nil && l.matchesName(hide) {
hm = true
l.Line = l.unmatchedLines(hide)
if len(l.Line) == 0 {
hidden[l.ID] = true
}
}
}
s := make([]*Sample, 0, len(p.Sample))
for _, sample := range p.Sample {
if focusedAndNotIgnored(sample.Location, focusOrIgnore) {
if len(hidden) > 0 {
var locs []*Location
for _, loc := range sample.Location {
if !hidden[loc.ID] {
locs = append(locs, loc)
}
}
if len(locs) == 0 {
// Remove sample with no locations (by not adding it to s).
continue
}
sample.Location = locs
}
s = append(s, sample)
}
}
p.Sample = s
return
}
// matchesName returns whether the function name or file in the
// location matches the regular expression.
func (loc *Location) matchesName(re *regexp.Regexp) bool {
for _, ln := range loc.Line {
if fn := ln.Function; fn != nil {
if re.MatchString(fn.Name) {
return true
}
if re.MatchString(fn.Filename) {
return true
}
}
}
return false
}
// unmatchedLines returns the lines in the location that do not match
// the regular expression.
func (loc *Location) unmatchedLines(re *regexp.Regexp) []Line {
var lines []Line
for _, ln := range loc.Line {
if fn := ln.Function; fn != nil {
if re.MatchString(fn.Name) {
continue
}
if re.MatchString(fn.Filename) {
continue
}
}
lines = append(lines, ln)
}
return lines
}
// focusedAndNotIgnored looks up a slice of ids against a map of
// focused/ignored locations. The map only contains locations that are
// explicitly focused or ignored. Returns whether there is at least
// one focused location but no ignored locations.
func focusedAndNotIgnored(locs []*Location, m map[uint64]bool) bool {
var f bool
for _, loc := range locs {
if focus, focusOrIgnore := m[loc.ID]; focusOrIgnore {
if focus {
// Found focused location. Must keep searching in case there
// is an ignored one as well.
f = true
} else {
// Found ignored location. Can return false right away.
return false
}
}
}
return f
}
// TagMatch selects tags for filtering
type TagMatch func(key, val string, nval int64) bool
// FilterSamplesByTag removes all samples from the profile, except
// those that match focus and do not match the ignore regular
// expression.
func (p *Profile) FilterSamplesByTag(focus, ignore TagMatch) (fm, im bool) {
samples := make([]*Sample, 0, len(p.Sample))
for _, s := range p.Sample {
focused, ignored := focusedSample(s, focus, ignore)
fm = fm || focused
im = im || ignored
if focused && !ignored {
samples = append(samples, s)
}
}
p.Sample = samples
return
}
// focusedTag checks a sample against focus and ignore regexps.
// Returns whether the focus/ignore regexps match any tags
func focusedSample(s *Sample, focus, ignore TagMatch) (fm, im bool) {
fm = focus == nil
for key, vals := range s.Label {
for _, val := range vals {
if ignore != nil && ignore(key, val, 0) {
im = true
}
if !fm && focus(key, val, 0) {
fm = true
}
}
}
for key, vals := range s.NumLabel {
for _, val := range vals {
if ignore != nil && ignore(key, "", val) {
im = true
}
if !fm && focus(key, "", val) {
fm = true
}
}
}
return fm, im
}
This diff is collapsed.
This diff is collapsed.
// 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.
// This file is a simple protocol buffer encoder and decoder.
//
// A protocol message must implement the message interface:
// decoder() []decoder
// encode(*buffer)
//
// The decode method returns a slice indexed by field number that gives the
// function to decode that field.
// The encode method encodes its receiver into the given buffer.
//
// The two methods are simple enough to be implemented by hand rather than
// by using a protocol compiler.
//
// See profile.go for examples of messages implementing this interface.
//
// There is no support for groups, message sets, or "has" bits.
package profile
import "errors"
type buffer struct {
field int
typ int
u64 uint64
data []byte
tmp [16]byte
}
type decoder func(*buffer, message) error
type message interface {
decoder() []decoder
encode(*buffer)
}
func marshal(m message) []byte {
var b buffer
m.encode(&b)
return b.data
}
func encodeVarint(b *buffer, x uint64) {
for x >= 128 {
b.data = append(b.data, byte(x)|0x80)
x >>= 7
}
b.data = append(b.data, byte(x))
}
func encodeLength(b *buffer, tag int, len int) {
encodeVarint(b, uint64(tag)<<3|2)
encodeVarint(b, uint64(len))
}
func encodeUint64(b *buffer, tag int, x uint64) {
// append varint to b.data
encodeVarint(b, uint64(tag)<<3|0)
encodeVarint(b, x)
}
func encodeUint64s(b *buffer, tag int, x []uint64) {
for _, u := range x {
encodeUint64(b, tag, u)
}
}
func encodeUint64Opt(b *buffer, tag int, x uint64) {
if x == 0 {
return
}
encodeUint64(b, tag, x)
}
func encodeInt64(b *buffer, tag int, x int64) {
u := uint64(x)
encodeUint64(b, tag, u)
}
func encodeInt64Opt(b *buffer, tag int, x int64) {
if x == 0 {
return
}
encodeInt64(b, tag, x)
}
func encodeString(b *buffer, tag int, x string) {
encodeLength(b, tag, len(x))
b.data = append(b.data, x...)
}
func encodeStrings(b *buffer, tag int, x []string) {
for _, s := range x {
encodeString(b, tag, s)
}
}
func encodeStringOpt(b *buffer, tag int, x string) {
if x == "" {
return
}
encodeString(b, tag, x)
}
func encodeBool(b *buffer, tag int, x bool) {
if x {
encodeUint64(b, tag, 1)
} else {
encodeUint64(b, tag, 0)
}
}
func encodeBoolOpt(b *buffer, tag int, x bool) {
if x == false {
return
}
encodeBool(b, tag, x)
}
func encodeMessage(b *buffer, tag int, m message) {
n1 := len(b.data)
m.encode(b)
n2 := len(b.data)
encodeLength(b, tag, n2-n1)
n3 := len(b.data)
copy(b.tmp[:], b.data[n2:n3])
copy(b.data[n1+(n3-n2):], b.data[n1:n2])
copy(b.data[n1:], b.tmp[:n3-n2])
}
func unmarshal(data []byte, m message) (err error) {
b := buffer{data: data, typ: 2}
return decodeMessage(&b, m)
}
func le64(p []byte) uint64 {
return uint64(p[0]) | uint64(p[1])<<8 | uint64(p[2])<<16 | uint64(p[3])<<24 | uint64(p[4])<<32 | uint64(p[5])<<40 | uint64(p[6])<<48 | uint64(p[7])<<56
}
func le32(p []byte) uint32 {
return uint32(p[0]) | uint32(p[1])<<8 | uint32(p[2])<<16 | uint32(p[3])<<24
}
func decodeVarint(data []byte) (uint64, []byte, error) {
var i int
var u uint64
for i = 0; ; i++ {
if i >= 10 || i >= len(data) {
return 0, nil, errors.New("bad varint")
}
u |= uint64(data[i]&0x7F) << uint(7*i)
if data[i]&0x80 == 0 {
return u, data[i+1:], nil
}
}
}
func decodeField(b *buffer, data []byte) ([]byte, error) {
x, data, err := decodeVarint(data)
if err != nil {
return nil, err
}
b.field = int(x >> 3)
b.typ = int(x & 7)
b.data = nil
b.u64 = 0
switch b.typ {
case 0:
b.u64, data, err = decodeVarint(data)
if err != nil {
return nil, err
}
case 1:
if len(data) < 8 {
return nil, errors.New("not enough data")
}
b.u64 = le64(data[:8])
data = data[8:]
case 2:
var n uint64
n, data, err = decodeVarint(data)
if err != nil {
return nil, err
}
if n > uint64(len(data)) {
return nil, errors.New("too much data")
}
b.data = data[:n]
data = data[n:]
case 5:
if len(data) < 4 {
return nil, errors.New("not enough data")
}
b.u64 = uint64(le32(data[:4]))
data = data[4:]
default:
return nil, errors.New("unknown type: " + string(b.typ))
}
return data, nil
}
func checkType(b *buffer, typ int) error {
if b.typ != typ {
return errors.New("type mismatch")
}
return nil
}
func decodeMessage(b *buffer, m message) error {
if err := checkType(b, 2); err != nil {
return err
}
dec := m.decoder()
data := b.data
for len(data) > 0 {
// pull varint field# + type
var err error
data, err = decodeField(b, data)
if err != nil {
return err
}
if b.field >= len(dec) || dec[b.field] == nil {
continue
}
if err := dec[b.field](b, m); err != nil {
return err
}
}
return nil
}
func decodeInt64(b *buffer, x *int64) error {
if err := checkType(b, 0); err != nil {
return err
}
*x = int64(b.u64)
return nil
}
func decodeInt64s(b *buffer, x *[]int64) error {
var i int64
if err := decodeInt64(b, &i); err != nil {
return err
}
*x = append(*x, i)
return nil
}
func decodeUint64(b *buffer, x *uint64) error {
if err := checkType(b, 0); err != nil {
return err
}
*x = b.u64
return nil
}
func decodeUint64s(b *buffer, x *[]uint64) error {
var u uint64
if err := decodeUint64(b, &u); err != nil {
return err
}
*x = append(*x, u)
return nil
}
func decodeString(b *buffer, x *string) error {
if err := checkType(b, 2); err != nil {
return err
}
*x = string(b.data)
return nil
}
func decodeStrings(b *buffer, x *[]string) error {
var s string
if err := decodeString(b, &s); err != nil {
return err
}
*x = append(*x, s)
return nil
}
func decodeBool(b *buffer, x *bool) error {
if err := checkType(b, 0); err != nil {
return err
}
if int64(b.u64) == 0 {
*x = false
} else {
*x = true
}
return nil
}
// 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.
// Implements methods to remove frames from profiles.
package profile
import (
"fmt"
"regexp"
)
// Prune removes all nodes beneath a node matching dropRx, and not
// matching keepRx. If the root node of a Sample matches, the sample
// will have an empty stack.
func (p *Profile) Prune(dropRx, keepRx *regexp.Regexp) {
prune := make(map[uint64]bool)
pruneBeneath := make(map[uint64]bool)
for _, loc := range p.Location {
var i int
for i = len(loc.Line) - 1; i >= 0; i-- {
if fn := loc.Line[i].Function; fn != nil && fn.Name != "" {
funcName := fn.Name
// Account for leading '.' on the PPC ELF v1 ABI.
if funcName[0] == '.' {
funcName = funcName[1:]
}
if dropRx.MatchString(funcName) {
if keepRx == nil || !keepRx.MatchString(funcName) {
break
}
}
}
}
if i >= 0 {
// Found matching entry to prune.
pruneBeneath[loc.ID] = true
// Remove the matching location.
if i == len(loc.Line)-1 {
// Matched the top entry: prune the whole location.
prune[loc.ID] = true
} else {
loc.Line = loc.Line[i+1:]
}
}
}
// Prune locs from each Sample
for _, sample := range p.Sample {
// Scan from the root to the leaves to find the prune location.
// Do not prune frames before the first user frame, to avoid
// pruning everything.
foundUser := false
for i := len(sample.Location) - 1; i >= 0; i-- {
id := sample.Location[i].ID
if !prune[id] && !pruneBeneath[id] {
foundUser = true
continue
}
if !foundUser {
continue
}
if prune[id] {
sample.Location = sample.Location[i+1:]
break
}
if pruneBeneath[id] {
sample.Location = sample.Location[i:]
break
}
}
}
}
// RemoveUninteresting prunes and elides profiles using built-in
// tables of uninteresting function names.
func (p *Profile) RemoveUninteresting() error {
var keep, drop *regexp.Regexp
var err error
if p.DropFrames != "" {
if drop, err = regexp.Compile("^(" + p.DropFrames + ")$"); err != nil {
return fmt.Errorf("failed to compile regexp %s: %v", p.DropFrames, err)
}
if p.KeepFrames != "" {
if keep, err = regexp.Compile("^(" + p.KeepFrames + ")$"); err != nil {
return fmt.Errorf("failed to compile regexp %s: %v", p.KeepFrames, err)
}
}
p.Prune(drop, keep)
}
return nil
}
This diff is collapsed.
This diff is collapsed.
// 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.
package report
const weblistPageHeader = `
<!DOCTYPE html>
<html>
<head>
<title>Pprof listing</title>
<style type="text/css">
body {
font-family: sans-serif;
}
h1 {
font-size: 1.5em;
margin-bottom: 4px;
}
.legend {
font-size: 1.25em;
}
.line {
color: #aaaaaa;
}
.nop {
color: #aaaaaa;
}
.unimportant {
color: #cccccc;
}
.disasmloc {
color: #000000;
}
.deadsrc {
cursor: pointer;
}
.deadsrc:hover {
background-color: #eeeeee;
}
.livesrc {
color: #0000ff;
cursor: pointer;
}
.livesrc:hover {
background-color: #eeeeee;
}
.asm {
color: #008800;
display: none;
}
</style>
<script type="text/javascript">
function pprof_toggle_asm(e) {
var target;
if (!e) e = window.event;
if (e.target) target = e.target;
else if (e.srcElement) target = e.srcElement;
if (target) {
var asm = target.nextSibling;
if (asm && asm.className == "asm") {
asm.style.display = (asm.style.display == "block" ? "" : "block");
e.preventDefault();
return false;
}
}
}
</script>
</head>
<body>
`
const weblistPageClosing = `
</body>
</html>
`
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
// 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.
// Package tempfile provides tools to create and delete temporary files
package tempfile
import (
"fmt"
"os"
"path/filepath"
"sync"
)
// New returns an unused filename for output files.
func New(dir, prefix, suffix string) (*os.File, error) {
for index := 1; index < 10000; index++ {
path := filepath.Join(dir, fmt.Sprintf("%s%03d%s", prefix, index, suffix))
if _, err := os.Stat(path); err != nil {
return os.Create(path)
}
}
// Give up
return nil, fmt.Errorf("could not create file of the form %s%03d%s", prefix, 1, suffix)
}
var tempFiles []string
var tempFilesMu = sync.Mutex{}
// DeferDelete marks a file to be deleted by next call to Cleanup()
func DeferDelete(path string) {
tempFilesMu.Lock()
tempFiles = append(tempFiles, path)
tempFilesMu.Unlock()
}
// Cleanup removes any temporary files selected for deferred cleaning.
func Cleanup() {
tempFilesMu.Lock()
for _, f := range tempFiles {
os.Remove(f)
}
tempFiles = nil
tempFilesMu.Unlock()
}
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment