1
0
mirror of https://github.com/golang/go synced 2024-11-12 10:00:25 -07:00

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
This commit is contained in:
Russ Cox 2014-09-30 13:41:54 -04:00
parent 454d1b0e8b
commit 8b5221a57b
19 changed files with 7728 additions and 0 deletions

View File

@ -0,0 +1,197 @@
// 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
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,492 @@
// 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 driver
import (
"fmt"
"io"
"regexp"
"sort"
"strconv"
"strings"
"cmd/pprof/internal/commands"
"cmd/pprof/internal/plugin"
"cmd/pprof/internal/profile"
)
var profileFunctionNames = []string{}
// functionCompleter replaces provided substring with a function
// name retrieved from a profile if a single match exists. Otherwise,
// it returns unchanged substring. It defaults to no-op if the profile
// is not specified.
func functionCompleter(substring string) string {
found := ""
for _, fName := range profileFunctionNames {
if strings.Contains(fName, substring) {
if found != "" {
return substring
}
found = fName
}
}
if found != "" {
return found
}
return substring
}
// updateAutoComplete enhances autocompletion with information that can be
// retrieved from the profile
func updateAutoComplete(p *profile.Profile) {
profileFunctionNames = nil // remove function names retrieved previously
for _, fn := range p.Function {
profileFunctionNames = append(profileFunctionNames, fn.Name)
}
}
// splitCommand splits the command line input into tokens separated by
// spaces. Takes care to separate commands of the form 'top10' into
// two tokens: 'top' and '10'
func splitCommand(input string) []string {
fields := strings.Fields(input)
if num := strings.IndexAny(fields[0], "0123456789"); num != -1 {
inputNumber := fields[0][num:]
fields[0] = fields[0][:num]
fields = append([]string{fields[0], inputNumber}, fields[1:]...)
}
return fields
}
// interactive displays a prompt and reads commands for profile
// manipulation/visualization.
func interactive(p *profile.Profile, obj plugin.ObjTool, ui plugin.UI, f *flags) error {
updateAutoComplete(p)
// Enter command processing loop.
ui.Print("Entering interactive mode (type \"help\" for commands)")
ui.SetAutoComplete(commands.NewCompleter(f.commands))
for {
input, err := readCommand(p, ui, f)
if err != nil {
if err != io.EOF {
return err
}
if input == "" {
return nil
}
}
// Process simple commands.
switch input {
case "":
continue
case ":":
f.flagFocus = newString("")
f.flagIgnore = newString("")
f.flagTagFocus = newString("")
f.flagTagIgnore = newString("")
f.flagHide = newString("")
continue
}
fields := splitCommand(input)
// Process report generation commands.
if _, ok := f.commands[fields[0]]; ok {
if err := generateReport(p, fields, obj, ui, f); err != nil {
if err == io.EOF {
return nil
}
ui.PrintErr(err)
}
continue
}
switch cmd := fields[0]; cmd {
case "help":
commandHelp(fields, ui, f)
continue
case "exit", "quit":
return nil
}
// Process option settings.
if of, err := optFlags(p, input, f); err == nil {
f = of
} else {
ui.PrintErr("Error: ", err.Error())
}
}
}
func generateReport(p *profile.Profile, cmd []string, obj plugin.ObjTool, ui plugin.UI, f *flags) error {
prof := p.Copy()
cf, err := cmdFlags(prof, cmd, ui, f)
if err != nil {
return err
}
return generate(true, prof, obj, ui, cf)
}
// validateRegex checks if a string is a valid regular expression.
func validateRegex(v string) error {
_, err := regexp.Compile(v)
return err
}
// readCommand prompts for and reads the next command.
func readCommand(p *profile.Profile, ui plugin.UI, f *flags) (string, error) {
//ui.Print("Options:\n", f.String(p))
s, err := ui.ReadLine()
return strings.TrimSpace(s), err
}
func commandHelp(_ []string, ui plugin.UI, f *flags) error {
help := `
Commands:
cmd [n] [--cum] [focus_regex]* [-ignore_regex]*
Produce a text report with the top n entries.
Include samples matching focus_regex, and exclude ignore_regex.
Add --cum to sort using cumulative data.
Available commands:
`
var commands []string
for name, cmd := range f.commands {
commands = append(commands, fmt.Sprintf(" %-12s %s", name, cmd.Usage))
}
sort.Strings(commands)
help = help + strings.Join(commands, "\n") + `
peek func_regex
Display callers and callees of functions matching func_regex.
dot [n] [focus_regex]* [-ignore_regex]* [>file]
Produce an annotated callgraph with the top n entries.
Include samples matching focus_regex, and exclude ignore_regex.
For other outputs, replace dot with:
- Graphic formats: dot, svg, pdf, ps, gif, png (use > to name output file)
- Graph viewer: gv, web, evince, eog
callgrind [n] [focus_regex]* [-ignore_regex]* [>file]
Produce a file in callgrind-compatible format.
Include samples matching focus_regex, and exclude ignore_regex.
weblist func_regex [-ignore_regex]*
Show annotated source with interspersed assembly in a web browser.
list func_regex [-ignore_regex]*
Print source for routines matching func_regex, and exclude ignore_regex.
disasm func_regex [-ignore_regex]*
Disassemble routines matching func_regex, and exclude ignore_regex.
tags tag_regex [-ignore_regex]*
List tags with key:value matching tag_regex and exclude ignore_regex.
quit/exit/^D
Exit pprof.
option=value
The following options can be set individually:
cum/flat: Sort entries based on cumulative or flat data
call_tree: Build context-sensitive call trees
nodecount: Max number of entries to display
nodefraction: Min frequency ratio of nodes to display
edgefraction: Min frequency ratio of edges to display
focus/ignore: Regexp to include/exclude samples by name/file
tagfocus/tagignore: Regexp or value range to filter samples by tag
eg "1mb", "1mb:2mb", ":64kb"
functions: Level of aggregation for sample data
files:
lines:
addresses:
unit: Measurement unit to use on reports
Sample value selection by index:
sample_index: Index of sample value to display
mean: Average sample value over first value
Sample value selection by name:
alloc_space for heap profiles
alloc_objects
inuse_space
inuse_objects
total_delay for contention profiles
mean_delay
contentions
: Clear focus/ignore/hide/tagfocus/tagignore`
ui.Print(help)
return nil
}
// cmdFlags parses the options of an interactive command and returns
// an updated flags object.
func cmdFlags(prof *profile.Profile, input []string, ui plugin.UI, f *flags) (*flags, error) {
cf := *f
var focus, ignore string
output := *cf.flagOutput
nodeCount := *cf.flagNodeCount
cmd := input[0]
// Update output flags based on parameters.
tokens := input[1:]
for p := 0; p < len(tokens); p++ {
t := tokens[p]
if t == "" {
continue
}
if c, err := strconv.ParseInt(t, 10, 32); err == nil {
nodeCount = int(c)
continue
}
switch t[0] {
case '>':
if len(t) > 1 {
output = t[1:]
continue
}
// find next token
for p++; p < len(tokens); p++ {
if tokens[p] != "" {
output = tokens[p]
break
}
}
case '-':
if t == "--cum" || t == "-cum" {
cf.flagCum = newBool(true)
continue
}
ignore = catRegex(ignore, t[1:])
default:
focus = catRegex(focus, t)
}
}
pcmd, ok := f.commands[cmd]
if !ok {
return nil, fmt.Errorf("Unexpected parse failure: %v", input)
}
// Reset flags
cf.flagCommands = make(map[string]*bool)
cf.flagParamCommands = make(map[string]*string)
if !pcmd.HasParam {
cf.flagCommands[cmd] = newBool(true)
switch cmd {
case "tags":
cf.flagTagFocus = newString(focus)
cf.flagTagIgnore = newString(ignore)
default:
cf.flagFocus = newString(catRegex(*cf.flagFocus, focus))
cf.flagIgnore = newString(catRegex(*cf.flagIgnore, ignore))
}
} else {
if focus == "" {
focus = "."
}
cf.flagParamCommands[cmd] = newString(focus)
cf.flagIgnore = newString(catRegex(*cf.flagIgnore, ignore))
}
if nodeCount < 0 {
switch cmd {
case "text", "top":
// Default text/top to 10 nodes on interactive mode
nodeCount = 10
default:
nodeCount = 80
}
}
cf.flagNodeCount = newInt(nodeCount)
cf.flagOutput = newString(output)
// Do regular flags processing
if err := processFlags(prof, ui, &cf); err != nil {
cf.usage(ui)
return nil, err
}
return &cf, nil
}
func catRegex(a, b string) string {
if a == "" {
return b
}
if b == "" {
return a
}
return a + "|" + b
}
// optFlags parses an interactive option setting and returns
// an updated flags object.
func optFlags(p *profile.Profile, input string, f *flags) (*flags, error) {
inputs := strings.SplitN(input, "=", 2)
option := strings.ToLower(strings.TrimSpace(inputs[0]))
var value string
if len(inputs) == 2 {
value = strings.TrimSpace(inputs[1])
}
of := *f
var err error
var bv bool
var uv uint64
var fv float64
switch option {
case "cum":
if bv, err = parseBool(value); err != nil {
return nil, err
}
of.flagCum = newBool(bv)
case "flat":
if bv, err = parseBool(value); err != nil {
return nil, err
}
of.flagCum = newBool(!bv)
case "call_tree":
if bv, err = parseBool(value); err != nil {
return nil, err
}
of.flagCallTree = newBool(bv)
case "unit":
of.flagDisplayUnit = newString(value)
case "sample_index":
if uv, err = strconv.ParseUint(value, 10, 32); err != nil {
return nil, err
}
if ix := int(uv); ix < 0 || ix >= len(p.SampleType) {
return nil, fmt.Errorf("sample_index out of range [0..%d]", len(p.SampleType)-1)
}
of.flagSampleIndex = newInt(int(uv))
case "mean":
if bv, err = parseBool(value); err != nil {
return nil, err
}
of.flagMean = newBool(bv)
case "nodecount":
if uv, err = strconv.ParseUint(value, 10, 32); err != nil {
return nil, err
}
of.flagNodeCount = newInt(int(uv))
case "nodefraction":
if fv, err = strconv.ParseFloat(value, 64); err != nil {
return nil, err
}
of.flagNodeFraction = newFloat64(fv)
case "edgefraction":
if fv, err = strconv.ParseFloat(value, 64); err != nil {
return nil, err
}
of.flagEdgeFraction = newFloat64(fv)
case "focus":
if err = validateRegex(value); err != nil {
return nil, err
}
of.flagFocus = newString(value)
case "ignore":
if err = validateRegex(value); err != nil {
return nil, err
}
of.flagIgnore = newString(value)
case "tagfocus":
if err = validateRegex(value); err != nil {
return nil, err
}
of.flagTagFocus = newString(value)
case "tagignore":
if err = validateRegex(value); err != nil {
return nil, err
}
of.flagTagIgnore = newString(value)
case "hide":
if err = validateRegex(value); err != nil {
return nil, err
}
of.flagHide = newString(value)
case "addresses", "files", "lines", "functions":
if bv, err = parseBool(value); err != nil {
return nil, err
}
if !bv {
return nil, fmt.Errorf("select one of addresses/files/lines/functions")
}
setGranularityToggle(option, &of)
default:
if ix := findSampleIndex(p, "", option); ix >= 0 {
of.flagSampleIndex = newInt(ix)
} else if ix := findSampleIndex(p, "total_", option); ix >= 0 {
of.flagSampleIndex = newInt(ix)
of.flagMean = newBool(false)
} else if ix := findSampleIndex(p, "mean_", option); ix >= 1 {
of.flagSampleIndex = newInt(ix)
of.flagMean = newBool(true)
} else {
return nil, fmt.Errorf("unrecognized command: %s", input)
}
}
return &of, nil
}
// parseBool parses a string as a boolean value.
func parseBool(v string) (bool, error) {
switch strings.ToLower(v) {
case "true", "t", "yes", "y", "1", "":
return true, nil
case "false", "f", "no", "n", "0":
return false, nil
}
return false, fmt.Errorf(`illegal input "%s" for bool value`, v)
}
func findSampleIndex(p *profile.Profile, prefix, sampleType string) int {
if !strings.HasPrefix(sampleType, prefix) {
return -1
}
sampleType = strings.TrimPrefix(sampleType, prefix)
for i, r := range p.SampleType {
if r.Type == sampleType {
return i
}
}
return -1
}
// setGranularityToggle manages the set of granularity options. These
// operate as a toggle; turning one on turns the others off.
func setGranularityToggle(o string, fl *flags) {
t, f := newBool(true), newBool(false)
fl.flagFunctions = f
fl.flagFiles = f
fl.flagLines = f
fl.flagAddresses = f
switch o {
case "functions":
fl.flagFunctions = t
case "files":
fl.flagFiles = t
case "lines":
fl.flagLines = t
case "addresses":
fl.flagAddresses = t
default:
panic(fmt.Errorf("unexpected option %s", o))
}
}

View File

@ -0,0 +1,82 @@
// 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)
}

View File

@ -0,0 +1,213 @@
// 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

View File

@ -0,0 +1,470 @@
// 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 profile
import (
"errors"
"fmt"
"sort"
)
func (p *Profile) decoder() []decoder {
return profileDecoder
}
// preEncode populates the unexported fields to be used by encode
// (with suffix X) from the corresponding exported fields. The
// exported fields are cleared up to facilitate testing.
func (p *Profile) preEncode() {
strings := make(map[string]int)
addString(strings, "")
for _, st := range p.SampleType {
st.typeX = addString(strings, st.Type)
st.unitX = addString(strings, st.Unit)
}
for _, s := range p.Sample {
s.labelX = nil
var keys []string
for k := range s.Label {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
vs := s.Label[k]
for _, v := range vs {
s.labelX = append(s.labelX,
Label{
keyX: addString(strings, k),
strX: addString(strings, v),
},
)
}
}
var numKeys []string
for k := range s.NumLabel {
numKeys = append(numKeys, k)
}
sort.Strings(numKeys)
for _, k := range numKeys {
vs := s.NumLabel[k]
for _, v := range vs {
s.labelX = append(s.labelX,
Label{
keyX: addString(strings, k),
numX: v,
},
)
}
}
s.locationIDX = nil
for _, l := range s.Location {
s.locationIDX = append(s.locationIDX, l.ID)
}
}
for _, m := range p.Mapping {
m.fileX = addString(strings, m.File)
m.buildIDX = addString(strings, m.BuildID)
}
for _, l := range p.Location {
for i, ln := range l.Line {
if ln.Function != nil {
l.Line[i].functionIDX = ln.Function.ID
} else {
l.Line[i].functionIDX = 0
}
}
if l.Mapping != nil {
l.mappingIDX = l.Mapping.ID
} else {
l.mappingIDX = 0
}
}
for _, f := range p.Function {
f.nameX = addString(strings, f.Name)
f.systemNameX = addString(strings, f.SystemName)
f.filenameX = addString(strings, f.Filename)
}
p.dropFramesX = addString(strings, p.DropFrames)
p.keepFramesX = addString(strings, p.KeepFrames)
if pt := p.PeriodType; pt != nil {
pt.typeX = addString(strings, pt.Type)
pt.unitX = addString(strings, pt.Unit)
}
p.stringTable = make([]string, len(strings))
for s, i := range strings {
p.stringTable[i] = s
}
}
func (p *Profile) encode(b *buffer) {
for _, x := range p.SampleType {
encodeMessage(b, 1, x)
}
for _, x := range p.Sample {
encodeMessage(b, 2, x)
}
for _, x := range p.Mapping {
encodeMessage(b, 3, x)
}
for _, x := range p.Location {
encodeMessage(b, 4, x)
}
for _, x := range p.Function {
encodeMessage(b, 5, x)
}
encodeStrings(b, 6, p.stringTable)
encodeInt64Opt(b, 7, p.dropFramesX)
encodeInt64Opt(b, 8, p.keepFramesX)
encodeInt64Opt(b, 9, p.TimeNanos)
encodeInt64Opt(b, 10, p.DurationNanos)
if pt := p.PeriodType; pt != nil && (pt.typeX != 0 || pt.unitX != 0) {
encodeMessage(b, 11, p.PeriodType)
}
encodeInt64Opt(b, 12, p.Period)
}
var profileDecoder = []decoder{
nil, // 0
// repeated ValueType sample_type = 1
func(b *buffer, m message) error {
x := new(ValueType)
pp := m.(*Profile)
pp.SampleType = append(pp.SampleType, x)
return decodeMessage(b, x)
},
// repeated Sample sample = 2
func(b *buffer, m message) error {
x := new(Sample)
pp := m.(*Profile)
pp.Sample = append(pp.Sample, x)
return decodeMessage(b, x)
},
// repeated Mapping mapping = 3
func(b *buffer, m message) error {
x := new(Mapping)
pp := m.(*Profile)
pp.Mapping = append(pp.Mapping, x)
return decodeMessage(b, x)
},
// repeated Location location = 4
func(b *buffer, m message) error {
x := new(Location)
pp := m.(*Profile)
pp.Location = append(pp.Location, x)
return decodeMessage(b, x)
},
// repeasted Function function = 5
func(b *buffer, m message) error {
x := new(Function)
pp := m.(*Profile)
pp.Function = append(pp.Function, x)
return decodeMessage(b, x)
},
// repeated string string_table = 6
func(b *buffer, m message) error {
err := decodeStrings(b, &m.(*Profile).stringTable)
if err != nil {
return err
}
if *&m.(*Profile).stringTable[0] != "" {
return errors.New("string_table[0] must be ''")
}
return nil
},
// repeated int64 drop_frames = 7
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).dropFramesX) },
// repeated int64 keep_frames = 8
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).keepFramesX) },
// repeated int64 time_nanos = 9
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).TimeNanos) },
// repeated int64 duration_nanos = 10
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).DurationNanos) },
// optional string period_type = 11
func(b *buffer, m message) error {
x := new(ValueType)
pp := m.(*Profile)
pp.PeriodType = x
return decodeMessage(b, x)
},
// repeated int64 period = 12
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).Period) },
}
// postDecode takes the unexported fields populated by decode (with
// suffix X) and populates the corresponding exported fields.
// The unexported fields are cleared up to facilitate testing.
func (p *Profile) postDecode() error {
var err error
mappings := make(map[uint64]*Mapping)
for _, m := range p.Mapping {
m.File, err = getString(p.stringTable, &m.fileX, err)
m.BuildID, err = getString(p.stringTable, &m.buildIDX, err)
mappings[m.ID] = m
}
functions := make(map[uint64]*Function)
for _, f := range p.Function {
f.Name, err = getString(p.stringTable, &f.nameX, err)
f.SystemName, err = getString(p.stringTable, &f.systemNameX, err)
f.Filename, err = getString(p.stringTable, &f.filenameX, err)
functions[f.ID] = f
}
locations := make(map[uint64]*Location)
for _, l := range p.Location {
l.Mapping = mappings[l.mappingIDX]
l.mappingIDX = 0
for i, ln := range l.Line {
if id := ln.functionIDX; id != 0 {
l.Line[i].Function = functions[id]
if l.Line[i].Function == nil {
return fmt.Errorf("Function ID %d not found", id)
}
l.Line[i].functionIDX = 0
}
}
locations[l.ID] = l
}
for _, st := range p.SampleType {
st.Type, err = getString(p.stringTable, &st.typeX, err)
st.Unit, err = getString(p.stringTable, &st.unitX, err)
}
for _, s := range p.Sample {
labels := make(map[string][]string)
numLabels := make(map[string][]int64)
for _, l := range s.labelX {
var key, value string
key, err = getString(p.stringTable, &l.keyX, err)
if l.strX != 0 {
value, err = getString(p.stringTable, &l.strX, err)
labels[key] = append(labels[key], value)
} else {
numLabels[key] = append(numLabels[key], l.numX)
}
}
if len(labels) > 0 {
s.Label = labels
}
if len(numLabels) > 0 {
s.NumLabel = numLabels
}
s.Location = nil
for _, lid := range s.locationIDX {
s.Location = append(s.Location, locations[lid])
}
s.locationIDX = nil
}
p.DropFrames, err = getString(p.stringTable, &p.dropFramesX, err)
p.KeepFrames, err = getString(p.stringTable, &p.keepFramesX, err)
if pt := p.PeriodType; pt == nil {
p.PeriodType = &ValueType{}
}
if pt := p.PeriodType; pt != nil {
pt.Type, err = getString(p.stringTable, &pt.typeX, err)
pt.Unit, err = getString(p.stringTable, &pt.unitX, err)
}
p.stringTable = nil
return nil
}
func (p *ValueType) decoder() []decoder {
return valueTypeDecoder
}
func (p *ValueType) encode(b *buffer) {
encodeInt64Opt(b, 1, p.typeX)
encodeInt64Opt(b, 2, p.unitX)
}
var valueTypeDecoder = []decoder{
nil, // 0
// optional int64 type = 1
func(b *buffer, m message) error { return decodeInt64(b, &m.(*ValueType).typeX) },
// optional int64 unit = 2
func(b *buffer, m message) error { return decodeInt64(b, &m.(*ValueType).unitX) },
}
func (p *Sample) decoder() []decoder {
return sampleDecoder
}
func (p *Sample) encode(b *buffer) {
encodeUint64s(b, 1, p.locationIDX)
for _, x := range p.Value {
encodeInt64(b, 2, x)
}
for _, x := range p.labelX {
encodeMessage(b, 3, x)
}
}
var sampleDecoder = []decoder{
nil, // 0
// repeated uint64 location = 1
func(b *buffer, m message) error { return decodeUint64s(b, &m.(*Sample).locationIDX) },
// repeated int64 value = 2
func(b *buffer, m message) error { return decodeInt64s(b, &m.(*Sample).Value) },
// repeated Label label = 3
func(b *buffer, m message) error {
s := m.(*Sample)
n := len(s.labelX)
s.labelX = append(s.labelX, Label{})
return decodeMessage(b, &s.labelX[n])
},
}
func (p Label) decoder() []decoder {
return labelDecoder
}
func (p Label) encode(b *buffer) {
encodeInt64Opt(b, 1, p.keyX)
encodeInt64Opt(b, 2, p.strX)
encodeInt64Opt(b, 3, p.numX)
}
var labelDecoder = []decoder{
nil, // 0
// optional int64 key = 1
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Label).keyX) },
// optional int64 str = 2
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Label).strX) },
// optional int64 num = 3
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Label).numX) },
}
func (p *Mapping) decoder() []decoder {
return mappingDecoder
}
func (p *Mapping) encode(b *buffer) {
encodeUint64Opt(b, 1, p.ID)
encodeUint64Opt(b, 2, p.Start)
encodeUint64Opt(b, 3, p.Limit)
encodeUint64Opt(b, 4, p.Offset)
encodeInt64Opt(b, 5, p.fileX)
encodeInt64Opt(b, 6, p.buildIDX)
encodeBoolOpt(b, 7, p.HasFunctions)
encodeBoolOpt(b, 8, p.HasFilenames)
encodeBoolOpt(b, 9, p.HasLineNumbers)
encodeBoolOpt(b, 10, p.HasInlineFrames)
}
var mappingDecoder = []decoder{
nil, // 0
func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).ID) }, // optional uint64 id = 1
func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Start) }, // optional uint64 memory_offset = 2
func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Limit) }, // optional uint64 memory_limit = 3
func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Offset) }, // optional uint64 file_offset = 4
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Mapping).fileX) }, // optional int64 filename = 5
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Mapping).buildIDX) }, // optional int64 build_id = 6
func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasFunctions) }, // optional bool has_functions = 7
func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasFilenames) }, // optional bool has_filenames = 8
func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasLineNumbers) }, // optional bool has_line_numbers = 9
func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasInlineFrames) }, // optional bool has_inline_frames = 10
}
func (p *Location) decoder() []decoder {
return locationDecoder
}
func (p *Location) encode(b *buffer) {
encodeUint64Opt(b, 1, p.ID)
encodeUint64Opt(b, 2, p.mappingIDX)
encodeUint64Opt(b, 3, p.Address)
for i := range p.Line {
encodeMessage(b, 4, &p.Line[i])
}
}
var locationDecoder = []decoder{
nil, // 0
func(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).ID) }, // optional uint64 id = 1;
func(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).mappingIDX) }, // optional uint64 mapping_id = 2;
func(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).Address) }, // optional uint64 address = 3;
func(b *buffer, m message) error { // repeated Line line = 4
pp := m.(*Location)
n := len(pp.Line)
pp.Line = append(pp.Line, Line{})
return decodeMessage(b, &pp.Line[n])
},
}
func (p *Line) decoder() []decoder {
return lineDecoder
}
func (p *Line) encode(b *buffer) {
encodeUint64Opt(b, 1, p.functionIDX)
encodeInt64Opt(b, 2, p.Line)
}
var lineDecoder = []decoder{
nil, // 0
// optional uint64 function_id = 1
func(b *buffer, m message) error { return decodeUint64(b, &m.(*Line).functionIDX) },
// optional int64 line = 2
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Line).Line) },
}
func (p *Function) decoder() []decoder {
return functionDecoder
}
func (p *Function) encode(b *buffer) {
encodeUint64Opt(b, 1, p.ID)
encodeInt64Opt(b, 2, p.nameX)
encodeInt64Opt(b, 3, p.systemNameX)
encodeInt64Opt(b, 4, p.filenameX)
encodeInt64Opt(b, 5, p.StartLine)
}
var functionDecoder = []decoder{
nil, // 0
// optional uint64 id = 1
func(b *buffer, m message) error { return decodeUint64(b, &m.(*Function).ID) },
// optional int64 function_name = 2
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).nameX) },
// optional int64 function_system_name = 3
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).systemNameX) },
// repeated int64 filename = 4
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).filenameX) },
// optional int64 start_line = 5
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).StartLine) },
}
func addString(strings map[string]int, s string) int64 {
i, ok := strings[s]
if !ok {
i = len(strings)
strings[s] = i
}
return int64(i)
}
func getString(strings []string, strng *int64, err error) (string, error) {
if err != nil {
return "", err
}
s := int(*strng)
if s < 0 || s >= len(strings) {
return "", errMalformed
}
*strng = 0
return strings[s], nil
}

View File

@ -0,0 +1,157 @@
// 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
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,567 @@
// 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 profile provides a representation of profile.proto and
// methods to encode/decode profiles in this format.
package profile
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"regexp"
"strings"
"time"
)
// Profile is an in-memory representation of profile.proto.
type Profile struct {
SampleType []*ValueType
Sample []*Sample
Mapping []*Mapping
Location []*Location
Function []*Function
DropFrames string
KeepFrames string
TimeNanos int64
DurationNanos int64
PeriodType *ValueType
Period int64
dropFramesX int64
keepFramesX int64
stringTable []string
}
// ValueType corresponds to Profile.ValueType
type ValueType struct {
Type string // cpu, wall, inuse_space, etc
Unit string // seconds, nanoseconds, bytes, etc
typeX int64
unitX int64
}
// Sample corresponds to Profile.Sample
type Sample struct {
Location []*Location
Value []int64
Label map[string][]string
NumLabel map[string][]int64
locationIDX []uint64
labelX []Label
}
// Label corresponds to Profile.Label
type Label struct {
keyX int64
// Exactly one of the two following values must be set
strX int64
numX int64 // Integer value for this label
}
// Mapping corresponds to Profile.Mapping
type Mapping struct {
ID uint64
Start uint64
Limit uint64
Offset uint64
File string
BuildID string
HasFunctions bool
HasFilenames bool
HasLineNumbers bool
HasInlineFrames bool
fileX int64
buildIDX int64
}
// Location corresponds to Profile.Location
type Location struct {
ID uint64
Mapping *Mapping
Address uint64
Line []Line
mappingIDX uint64
}
// Line corresponds to Profile.Line
type Line struct {
Function *Function
Line int64
functionIDX uint64
}
// Function corresponds to Profile.Function
type Function struct {
ID uint64
Name string
SystemName string
Filename string
StartLine int64
nameX int64
systemNameX int64
filenameX int64
}
// Parse parses a profile and checks for its validity. The input
// may be a gzip-compressed encoded protobuf or one of many legacy
// profile formats which may be unsupported in the future.
func Parse(r io.Reader) (*Profile, error) {
orig, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
var p *Profile
if len(orig) >= 2 && orig[0] == 0x1f && orig[1] == 0x8b {
var data []byte
if gz, err := gzip.NewReader(bytes.NewBuffer(orig)); err == nil {
data, err = ioutil.ReadAll(gz)
}
if err != nil {
return nil, fmt.Errorf("decompressing profile: %v", err)
}
orig = data
}
if p, err = parseUncompressed(orig); err != nil {
if p, err = parseLegacy(orig); err != nil {
return nil, fmt.Errorf("parsing profile: %v", err)
}
}
if err := p.CheckValid(); err != nil {
return nil, fmt.Errorf("malformed profile: %v", err)
}
return p, nil
}
var errUnrecognized = fmt.Errorf("unrecognized profile format")
var errMalformed = fmt.Errorf("malformed profile format")
func parseLegacy(data []byte) (*Profile, error) {
parsers := []func([]byte) (*Profile, error){
parseCPU,
parseHeap,
parseGoCount, // goroutine, threadcreate
parseThread,
parseContention,
}
for _, parser := range parsers {
p, err := parser(data)
if err == nil {
p.setMain()
p.addLegacyFrameInfo()
return p, nil
}
if err != errUnrecognized {
return nil, err
}
}
return nil, errUnrecognized
}
func parseUncompressed(data []byte) (*Profile, error) {
p := &Profile{}
if err := unmarshal(data, p); err != nil {
return nil, err
}
if err := p.postDecode(); err != nil {
return nil, err
}
return p, nil
}
var libRx = regexp.MustCompile(`([.]so$|[.]so[._][0-9]+)`)
// setMain scans Mapping entries and guesses which entry is main
// because legacy profiles don't obey the convention of putting main
// first.
func (p *Profile) setMain() {
for i := 0; i < len(p.Mapping); i++ {
file := strings.TrimSpace(strings.Replace(p.Mapping[i].File, "(deleted)", "", -1))
if len(file) == 0 {
continue
}
if len(libRx.FindStringSubmatch(file)) > 0 {
continue
}
if strings.HasPrefix(file, "[") {
continue
}
// Swap what we guess is main to position 0.
tmp := p.Mapping[i]
p.Mapping[i] = p.Mapping[0]
p.Mapping[0] = tmp
break
}
}
// Write writes the profile as a gzip-compressed marshaled protobuf.
func (p *Profile) Write(w io.Writer) error {
p.preEncode()
b := marshal(p)
zw := gzip.NewWriter(w)
defer zw.Close()
_, err := zw.Write(b)
return err
}
// CheckValid tests whether the profile is valid. Checks include, but are
// not limited to:
// - len(Profile.Sample[n].value) == len(Profile.value_unit)
// - Sample.id has a corresponding Profile.Location
func (p *Profile) CheckValid() error {
// Check that sample values are consistent
sampleLen := len(p.SampleType)
if sampleLen == 0 && len(p.Sample) != 0 {
return fmt.Errorf("missing sample type information")
}
for _, s := range p.Sample {
if len(s.Value) != sampleLen {
return fmt.Errorf("mismatch: sample has: %d values vs. %d types", len(s.Value), len(p.SampleType))
}
}
// Check that all mappings/locations/functions are in the tables
// Check that there are no duplicate ids
mappings := make(map[uint64]*Mapping, len(p.Mapping))
for _, m := range p.Mapping {
if m.ID == 0 {
return fmt.Errorf("found mapping with reserved ID=0")
}
if mappings[m.ID] != nil {
return fmt.Errorf("multiple mappings with same id: %d", m.ID)
}
mappings[m.ID] = m
}
functions := make(map[uint64]*Function, len(p.Function))
for _, f := range p.Function {
if f.ID == 0 {
return fmt.Errorf("found function with reserved ID=0")
}
if functions[f.ID] != nil {
return fmt.Errorf("multiple functions with same id: %d", f.ID)
}
functions[f.ID] = f
}
locations := make(map[uint64]*Location, len(p.Location))
for _, l := range p.Location {
if l.ID == 0 {
return fmt.Errorf("found location with reserved id=0")
}
if locations[l.ID] != nil {
return fmt.Errorf("multiple locations with same id: %d", l.ID)
}
locations[l.ID] = l
if m := l.Mapping; m != nil {
if m.ID == 0 || mappings[m.ID] != m {
return fmt.Errorf("inconsistent mapping %p: %d", m, m.ID)
}
}
for _, ln := range l.Line {
if f := ln.Function; f != nil {
if f.ID == 0 || functions[f.ID] != f {
return fmt.Errorf("inconsistent function %p: %d", f, f.ID)
}
}
}
}
return nil
}
// Aggregate merges the locations in the profile into equivalence
// classes preserving the request attributes. It also updates the
// samples to point to the merged locations.
func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, address bool) error {
for _, m := range p.Mapping {
m.HasInlineFrames = m.HasInlineFrames && inlineFrame
m.HasFunctions = m.HasFunctions && function
m.HasFilenames = m.HasFilenames && filename
m.HasLineNumbers = m.HasLineNumbers && linenumber
}
// Aggregate functions
if !function || !filename {
for _, f := range p.Function {
if !function {
f.Name = ""
f.SystemName = ""
}
if !filename {
f.Filename = ""
}
}
}
// Aggregate locations
if !inlineFrame || !address || !linenumber {
for _, l := range p.Location {
if !inlineFrame && len(l.Line) > 1 {
l.Line = l.Line[len(l.Line)-1:]
}
if !linenumber {
for i := range l.Line {
l.Line[i].Line = 0
}
}
if !address {
l.Address = 0
}
}
}
return p.CheckValid()
}
// Print dumps a text representation of a profile. Intended mainly
// for debugging purposes.
func (p *Profile) String() string {
ss := make([]string, 0, len(p.Sample)+len(p.Mapping)+len(p.Location))
if pt := p.PeriodType; pt != nil {
ss = append(ss, fmt.Sprintf("PeriodType: %s %s", pt.Type, pt.Unit))
}
ss = append(ss, fmt.Sprintf("Period: %d", p.Period))
if p.TimeNanos != 0 {
ss = append(ss, fmt.Sprintf("Time: %v", time.Unix(0, p.TimeNanos)))
}
if p.DurationNanos != 0 {
ss = append(ss, fmt.Sprintf("Duration: %v", time.Duration(p.DurationNanos)))
}
ss = append(ss, "Samples:")
var sh1 string
for _, s := range p.SampleType {
sh1 = sh1 + fmt.Sprintf("%s/%s ", s.Type, s.Unit)
}
ss = append(ss, strings.TrimSpace(sh1))
for _, s := range p.Sample {
var sv string
for _, v := range s.Value {
sv = fmt.Sprintf("%s %10d", sv, v)
}
sv = sv + ": "
for _, l := range s.Location {
sv = sv + fmt.Sprintf("%d ", l.ID)
}
ss = append(ss, sv)
const labelHeader = " "
if len(s.Label) > 0 {
ls := labelHeader
for k, v := range s.Label {
ls = ls + fmt.Sprintf("%s:%v ", k, v)
}
ss = append(ss, ls)
}
if len(s.NumLabel) > 0 {
ls := labelHeader
for k, v := range s.NumLabel {
ls = ls + fmt.Sprintf("%s:%v ", k, v)
}
ss = append(ss, ls)
}
}
ss = append(ss, "Locations")
for _, l := range p.Location {
locStr := fmt.Sprintf("%6d: %#x ", l.ID, l.Address)
if m := l.Mapping; m != nil {
locStr = locStr + fmt.Sprintf("M=%d ", m.ID)
}
if len(l.Line) == 0 {
ss = append(ss, locStr)
}
for li := range l.Line {
lnStr := "??"
if fn := l.Line[li].Function; fn != nil {
lnStr = fmt.Sprintf("%s %s:%d s=%d",
fn.Name,
fn.Filename,
l.Line[li].Line,
fn.StartLine)
if fn.Name != fn.SystemName {
lnStr = lnStr + "(" + fn.SystemName + ")"
}
}
ss = append(ss, locStr+lnStr)
// Do not print location details past the first line
locStr = " "
}
}
ss = append(ss, "Mappings")
for _, m := range p.Mapping {
bits := ""
if m.HasFunctions {
bits = bits + "[FN]"
}
if m.HasFilenames {
bits = bits + "[FL]"
}
if m.HasLineNumbers {
bits = bits + "[LN]"
}
if m.HasInlineFrames {
bits = bits + "[IN]"
}
ss = append(ss, fmt.Sprintf("%d: %#x/%#x/%#x %s %s %s",
m.ID,
m.Start, m.Limit, m.Offset,
m.File,
m.BuildID,
bits))
}
return strings.Join(ss, "\n") + "\n"
}
// Merge adds profile p adjusted by ratio r into profile p. Profiles
// must be compatible (same Type and SampleType).
// TODO(rsilvera): consider normalizing the profiles based on the
// total samples collected.
func (p *Profile) Merge(pb *Profile, r float64) error {
if err := p.Compatible(pb); err != nil {
return err
}
pb = pb.Copy()
// Keep the largest of the two periods.
if pb.Period > p.Period {
p.Period = pb.Period
}
p.DurationNanos += pb.DurationNanos
p.Mapping = append(p.Mapping, pb.Mapping...)
for i, m := range p.Mapping {
m.ID = uint64(i + 1)
}
p.Location = append(p.Location, pb.Location...)
for i, l := range p.Location {
l.ID = uint64(i + 1)
}
p.Function = append(p.Function, pb.Function...)
for i, f := range p.Function {
f.ID = uint64(i + 1)
}
if r != 1.0 {
for _, s := range pb.Sample {
for i, v := range s.Value {
s.Value[i] = int64((float64(v) * r))
}
}
}
p.Sample = append(p.Sample, pb.Sample...)
return p.CheckValid()
}
// Compatible determines if two profiles can be compared/merged.
// returns nil if the profiles are compatible; otherwise an error with
// details on the incompatibility.
func (p *Profile) Compatible(pb *Profile) error {
if !compatibleValueTypes(p.PeriodType, pb.PeriodType) {
return fmt.Errorf("incompatible period types %v and %v", p.PeriodType, pb.PeriodType)
}
if len(p.SampleType) != len(pb.SampleType) {
return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType)
}
for i := range p.SampleType {
if !compatibleValueTypes(p.SampleType[i], pb.SampleType[i]) {
return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType)
}
}
return nil
}
// HasFunctions determines if all locations in this profile have
// symbolized function information.
func (p *Profile) HasFunctions() bool {
for _, l := range p.Location {
if l.Mapping == nil || !l.Mapping.HasFunctions {
return false
}
}
return true
}
// HasFileLines determines if all locations in this profile have
// symbolized file and line number information.
func (p *Profile) HasFileLines() bool {
for _, l := range p.Location {
if l.Mapping == nil || (!l.Mapping.HasFilenames || !l.Mapping.HasLineNumbers) {
return false
}
}
return true
}
func compatibleValueTypes(v1, v2 *ValueType) bool {
if v1 == nil || v2 == nil {
return true // No grounds to disqualify.
}
return v1.Type == v2.Type && v1.Unit == v2.Unit
}
// Copy makes a fully independent copy of a profile.
func (p *Profile) Copy() *Profile {
p.preEncode()
b := marshal(p)
pp := &Profile{}
if err := unmarshal(b, pp); err != nil {
panic(err)
}
if err := pp.postDecode(); err != nil {
panic(err)
}
return pp
}
// Demangler maps symbol names to a human-readable form. This may
// include C++ demangling and additional simplification. Names that
// are not demangled may be missing from the resulting map.
type Demangler func(name []string) (map[string]string, error)
// Demangle attempts to demangle and optionally simplify any function
// names referenced in the profile. It works on a best-effort basis:
// it will silently preserve the original names in case of any errors.
func (p *Profile) Demangle(d Demangler) error {
// Collect names to demangle.
var names []string
for _, fn := range p.Function {
names = append(names, fn.SystemName)
}
// Update profile with demangled names.
demangled, err := d(names)
if err != nil {
return err
}
for _, fn := range p.Function {
if dd, ok := demangled[fn.SystemName]; ok {
fn.Name = dd
}
}
return nil
}

View File

@ -0,0 +1,298 @@
// 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
}

View File

@ -0,0 +1,97 @@
// 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
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,450 @@
// 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
// This file contains routines related to the generation of annotated
// source listings.
import (
"bufio"
"fmt"
"html/template"
"io"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"cmd/pprof/internal/plugin"
)
// printSource prints an annotated source listing, include all
// functions with samples that match the regexp rpt.options.symbol.
// The sources are sorted by function name and then by filename to
// eliminate potential nondeterminism.
func printSource(w io.Writer, rpt *Report) error {
o := rpt.options
g, err := newGraph(rpt)
if err != nil {
return err
}
// Identify all the functions that match the regexp provided.
// Group nodes for each matching function.
var functions nodes
functionNodes := make(map[string]nodes)
for _, n := range g.ns {
if !o.Symbol.MatchString(n.info.name) {
continue
}
if functionNodes[n.info.name] == nil {
functions = append(functions, n)
}
functionNodes[n.info.name] = append(functionNodes[n.info.name], n)
}
functions.sort(nameOrder)
fmt.Fprintf(w, "Total: %s\n", rpt.formatValue(rpt.total))
for _, fn := range functions {
name := fn.info.name
// Identify all the source files associated to this function.
// Group nodes for each source file.
var sourceFiles nodes
fileNodes := make(map[string]nodes)
for _, n := range functionNodes[name] {
if n.info.file == "" {
continue
}
if fileNodes[n.info.file] == nil {
sourceFiles = append(sourceFiles, n)
}
fileNodes[n.info.file] = append(fileNodes[n.info.file], n)
}
if len(sourceFiles) == 0 {
fmt.Printf("No source information for %s\n", name)
continue
}
sourceFiles.sort(fileOrder)
// Print each file associated with this function.
for _, fl := range sourceFiles {
filename := fl.info.file
fns := fileNodes[filename]
flatSum, cumSum := sumNodes(fns)
fnodes, path, err := getFunctionSource(name, filename, fns, 0, 0)
fmt.Fprintf(w, "ROUTINE ======================== %s in %s\n", name, path)
fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n",
rpt.formatValue(flatSum), rpt.formatValue(cumSum),
percentage(cumSum, rpt.total))
if err != nil {
fmt.Fprintf(w, " Error: %v\n", err)
continue
}
for _, fn := range fnodes {
fmt.Fprintf(w, "%10s %10s %6d:%s\n", valueOrDot(fn.flat, rpt), valueOrDot(fn.cum, rpt), fn.info.lineno, fn.info.name)
}
}
}
return nil
}
// printWebSource prints an annotated source listing, include all
// functions with samples that match the regexp rpt.options.symbol.
func printWebSource(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
o := rpt.options
g, err := newGraph(rpt)
if err != nil {
return err
}
// If the regexp source can be parsed as an address, also match
// functions that land on that address.
var address *uint64
if hex, err := strconv.ParseUint(o.Symbol.String(), 0, 64); err == nil {
address = &hex
}
// Extract interesting symbols from binary files in the profile and
// classify samples per symbol.
symbols := symbolsFromBinaries(rpt.prof, g, o.Symbol, address, obj)
symNodes := nodesPerSymbol(g.ns, symbols)
// Sort symbols for printing.
var syms objSymbols
for s := range symNodes {
syms = append(syms, s)
}
sort.Sort(syms)
if len(syms) == 0 {
return fmt.Errorf("no samples found on routines matching: %s", o.Symbol.String())
}
printHeader(w, rpt)
for _, s := range syms {
name := s.sym.Name[0]
// Identify sources associated to a symbol by examining
// symbol samples. Classify samples per source file.
var sourceFiles nodes
fileNodes := make(map[string]nodes)
for _, n := range symNodes[s] {
if n.info.file == "" {
continue
}
if fileNodes[n.info.file] == nil {
sourceFiles = append(sourceFiles, n)
}
fileNodes[n.info.file] = append(fileNodes[n.info.file], n)
}
if len(sourceFiles) == 0 {
fmt.Printf("No source information for %s\n", name)
continue
}
sourceFiles.sort(fileOrder)
// Print each file associated with this function.
for _, fl := range sourceFiles {
filename := fl.info.file
fns := fileNodes[filename]
asm := assemblyPerSourceLine(symbols, fns, filename, obj)
start, end := sourceCoordinates(asm)
fnodes, path, err := getFunctionSource(name, filename, fns, start, end)
if err != nil {
fnodes, path = getMissingFunctionSource(filename, asm, start, end)
}
flatSum, cumSum := sumNodes(fnodes)
printFunctionHeader(w, name, path, flatSum, cumSum, rpt)
for _, fn := range fnodes {
printFunctionSourceLine(w, fn, asm[fn.info.lineno], rpt)
}
printFunctionClosing(w)
}
}
printPageClosing(w)
return nil
}
// sourceCoordinates returns the lowest and highest line numbers from
// a set of assembly statements.
func sourceCoordinates(asm map[int]nodes) (start, end int) {
for l := range asm {
if start == 0 || l < start {
start = l
}
if end == 0 || l > end {
end = l
}
}
return start, end
}
// assemblyPerSourceLine disassembles the binary containing a symbol
// and classifies the assembly instructions according to its
// corresponding source line, annotating them with a set of samples.
func assemblyPerSourceLine(objSyms []*objSymbol, rs nodes, src string, obj plugin.ObjTool) map[int]nodes {
assembly := make(map[int]nodes)
// Identify symbol to use for this collection of samples.
o := findMatchingSymbol(objSyms, rs)
if o == nil {
return assembly
}
// Extract assembly for matched symbol
insns, err := obj.Disasm(o.sym.File, o.sym.Start, o.sym.End)
if err != nil {
return assembly
}
srcBase := filepath.Base(src)
anodes := annotateAssembly(insns, rs, o.base)
var lineno = 0
for _, an := range anodes {
if filepath.Base(an.info.file) == srcBase {
lineno = an.info.lineno
}
if lineno != 0 {
assembly[lineno] = append(assembly[lineno], an)
}
}
return assembly
}
// findMatchingSymbol looks for the symbol that corresponds to a set
// of samples, by comparing their addresses.
func findMatchingSymbol(objSyms []*objSymbol, ns nodes) *objSymbol {
for _, n := range ns {
for _, o := range objSyms {
if filepath.Base(o.sym.File) == n.info.objfile &&
o.sym.Start <= n.info.address-o.base &&
n.info.address-o.base <= o.sym.End {
return o
}
}
}
return nil
}
// printHeader prints the page header for a weblist report.
func printHeader(w io.Writer, rpt *Report) {
fmt.Fprintln(w, weblistPageHeader)
var labels []string
for _, l := range legendLabels(rpt) {
labels = append(labels, template.HTMLEscapeString(l))
}
fmt.Fprintf(w, `<div class="legend">%s<br>Total: %s</div>`,
strings.Join(labels, "<br>\n"),
rpt.formatValue(rpt.total),
)
}
// printFunctionHeader prints a function header for a weblist report.
func printFunctionHeader(w io.Writer, name, path string, flatSum, cumSum int64, rpt *Report) {
fmt.Fprintf(w, `<h1>%s</h1>%s
<pre onClick="pprof_toggle_asm()">
Total: %10s %10s (flat, cum) %s
`,
template.HTMLEscapeString(name), template.HTMLEscapeString(path),
rpt.formatValue(flatSum), rpt.formatValue(cumSum),
percentage(cumSum, rpt.total))
}
// printFunctionSourceLine prints a source line and the corresponding assembly.
func printFunctionSourceLine(w io.Writer, fn *node, assembly nodes, rpt *Report) {
if len(assembly) == 0 {
fmt.Fprintf(w,
"<span class=line> %6d</span> <span class=nop> %10s %10s %s </span>\n",
fn.info.lineno,
valueOrDot(fn.flat, rpt), valueOrDot(fn.cum, rpt),
template.HTMLEscapeString(fn.info.name))
return
}
fmt.Fprintf(w,
"<span class=line> %6d</span> <span class=deadsrc> %10s %10s %s </span>",
fn.info.lineno,
valueOrDot(fn.flat, rpt), valueOrDot(fn.cum, rpt),
template.HTMLEscapeString(fn.info.name))
fmt.Fprint(w, "<span class=asm>")
for _, an := range assembly {
var fileline string
class := "disasmloc"
if an.info.file != "" {
fileline = fmt.Sprintf("%s:%d", template.HTMLEscapeString(an.info.file), an.info.lineno)
if an.info.lineno != fn.info.lineno {
class = "unimportant"
}
}
fmt.Fprintf(w, " %8s %10s %10s %8x: %-48s <span class=%s>%s</span>\n", "",
valueOrDot(an.flat, rpt), valueOrDot(an.cum, rpt),
an.info.address,
template.HTMLEscapeString(an.info.name),
class,
template.HTMLEscapeString(fileline))
}
fmt.Fprintln(w, "</span>")
}
// printFunctionClosing prints the end of a function in a weblist report.
func printFunctionClosing(w io.Writer) {
fmt.Fprintln(w, "</pre>")
}
// printPageClosing prints the end of the page in a weblist report.
func printPageClosing(w io.Writer) {
fmt.Fprintln(w, weblistPageClosing)
}
// getFunctionSource collects the sources of a function from a source
// file and annotates it with the samples in fns. Returns the sources
// as nodes, using the info.name field to hold the source code.
func getFunctionSource(fun, file string, fns nodes, start, end int) (nodes, string, error) {
f, file, err := adjustSourcePath(file)
if err != nil {
return nil, file, err
}
lineNodes := make(map[int]nodes)
// Collect source coordinates from profile.
const margin = 5 // Lines before first/after last sample.
if start == 0 {
if fns[0].info.startLine != 0 {
start = fns[0].info.startLine
} else {
start = fns[0].info.lineno - margin
}
} else {
start -= margin
}
if end == 0 {
end = fns[0].info.lineno
}
end += margin
for _, n := range fns {
lineno := n.info.lineno
nodeStart := n.info.startLine
if nodeStart == 0 {
nodeStart = lineno - margin
}
nodeEnd := lineno + margin
if nodeStart < start {
start = nodeStart
} else if nodeEnd > end {
end = nodeEnd
}
lineNodes[lineno] = append(lineNodes[lineno], n)
}
var src nodes
buf := bufio.NewReader(f)
lineno := 1
for {
line, err := buf.ReadString('\n')
if err != nil {
if line == "" || err != io.EOF {
return nil, file, err
}
}
if lineno >= start {
flat, cum := sumNodes(lineNodes[lineno])
src = append(src, &node{
info: nodeInfo{
name: strings.TrimRight(line, "\n"),
lineno: lineno,
},
flat: flat,
cum: cum,
})
}
lineno++
if lineno > end {
break
}
}
return src, file, nil
}
// getMissingFunctionSource creates a dummy function body to point to
// the source file and annotates it with the samples in asm.
func getMissingFunctionSource(filename string, asm map[int]nodes, start, end int) (nodes, string) {
var fnodes nodes
for i := start; i <= end; i++ {
lrs := asm[i]
if len(lrs) == 0 {
continue
}
flat, cum := sumNodes(lrs)
fnodes = append(fnodes, &node{
info: nodeInfo{
name: "???",
lineno: i,
},
flat: flat,
cum: cum,
})
}
return fnodes, filename
}
// adjustSourcePath adjusts the pathe for a source file by trimmming
// known prefixes and searching for the file on all parents of the
// current working dir.
func adjustSourcePath(path string) (*os.File, string, error) {
path = trimPath(path)
f, err := os.Open(path)
if err == nil {
return f, path, nil
}
if dir, wderr := os.Getwd(); wderr == nil {
for {
parent := filepath.Dir(dir)
if parent == dir {
break
}
if f, err := os.Open(filepath.Join(parent, path)); err == nil {
return f, filepath.Join(parent, path), nil
}
dir = parent
}
}
return nil, path, err
}
// trimPath cleans up a path by removing prefixes that are commonly
// found on profiles.
func trimPath(path string) string {
basePaths := []string{
"/proc/self/cwd/./",
"/proc/self/cwd/",
}
sPath := filepath.ToSlash(path)
for _, base := range basePaths {
if strings.HasPrefix(sPath, base) {
return filepath.FromSlash(sPath[len(base):])
}
}
return path
}

View File

@ -0,0 +1,77 @@
// 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>
`

View File

@ -0,0 +1,75 @@
// 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 svg provides tools related to handling of SVG files
package svg
import (
"bytes"
"regexp"
"strings"
)
var (
viewBox = regexp.MustCompile(`<svg\s*width="[^"]+"\s*height="[^"]+"\s*viewBox="[^"]+"`)
graphId = regexp.MustCompile(`<g id="graph\d"`)
svgClose = regexp.MustCompile(`</svg>`)
)
// Massage enhances the SVG output from DOT to provide bettern
// panning inside a web browser. It uses the SVGPan library, which is
// accessed through the svgPan URL.
func Massage(in bytes.Buffer, svgPan string) string {
svg := string(in.Bytes())
// Work around for dot bug which misses quoting some ampersands,
// resulting on unparsable SVG.
svg = strings.Replace(svg, "&;", "&amp;;", -1)
if svgPan == "" {
return svg
}
//Dot's SVG output is
//
// <svg width="___" height="___"
// viewBox="___" xmlns=...>
// <g id="graph0" transform="...">
// ...
// </g>
// </svg>
//
// Change it to
//
// <svg width="100%" height="100%"
// xmlns=...>
// <script xlink:href=" ...$svgpan.. "/>
// <g id="viewport" transform="translate(0,0)">
// <g id="graph0" transform="...">
// ...
// </g>
// </g>
// </svg>
if loc := viewBox.FindStringIndex(svg); loc != nil {
svg = svg[:loc[0]] +
`<svg width="100%" height="100%"` +
svg[loc[1]:]
}
if loc := graphId.FindStringIndex(svg); loc != nil {
svg = svg[:loc[0]] +
`<script xlink:href="` + svgPan + `"/>` +
`<g id="viewport" transform="scale(0.5,0.5) translate(0,0)">` +
svg[loc[0]:]
}
if loc := svgClose.FindStringIndex(svg); loc != nil {
svg = svg[:loc[0]] +
`</g>` +
svg[loc[0]:]
}
return svg
}

View File

@ -0,0 +1,191 @@
// 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 symbolizer provides a routine to populate a profile with
// symbol, file and line number information. It relies on the
// addr2liner and demangler packages to do the actual work.
package symbolizer
import (
"fmt"
"os"
"path/filepath"
"strings"
"cmd/pprof/internal/plugin"
"cmd/pprof/internal/profile"
)
// Symbolize adds symbol and line number information to all locations
// in a profile. mode enables some options to control
// symbolization. Currently only recognizes "force", which causes it
// to overwrite any existing data.
func Symbolize(mode string, prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI) error {
force := false
// Disable some mechanisms based on mode string.
for _, o := range strings.Split(strings.ToLower(mode), ":") {
switch o {
case "force":
force = true
default:
}
}
mt, err := newMapping(prof, obj, ui, force)
if err != nil {
return err
}
defer mt.close()
functions := make(map[profile.Function]*profile.Function)
for _, l := range mt.prof.Location {
m := l.Mapping
segment := mt.segments[m]
if segment == nil {
// Nothing to do
continue
}
stack, err := segment.SourceLine(l.Address)
if err != nil || len(stack) == 0 {
// No answers from addr2line
continue
}
l.Line = make([]profile.Line, len(stack))
for i, frame := range stack {
if frame.Func != "" {
m.HasFunctions = true
}
if frame.File != "" {
m.HasFilenames = true
}
if frame.Line != 0 {
m.HasLineNumbers = true
}
f := &profile.Function{
Name: frame.Func,
SystemName: frame.Func,
Filename: frame.File,
}
if fp := functions[*f]; fp != nil {
f = fp
} else {
functions[*f] = f
f.ID = uint64(len(mt.prof.Function)) + 1
mt.prof.Function = append(mt.prof.Function, f)
}
l.Line[i] = profile.Line{
Function: f,
Line: int64(frame.Line),
}
}
if len(stack) > 0 {
m.HasInlineFrames = true
}
}
return nil
}
// newMapping creates a mappingTable for a profile.
func newMapping(prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI, force bool) (*mappingTable, error) {
mt := &mappingTable{
prof: prof,
segments: make(map[*profile.Mapping]plugin.ObjFile),
}
// Identify used mappings
mappings := make(map[*profile.Mapping]bool)
for _, l := range prof.Location {
mappings[l.Mapping] = true
}
for _, m := range prof.Mapping {
if !mappings[m] {
continue
}
// Do not attempt to re-symbolize a mapping that has already been symbolized.
if !force && (m.HasFunctions || m.HasFilenames || m.HasLineNumbers) {
continue
}
f, err := locateFile(obj, m.File, m.BuildID, m.Start)
if err != nil {
ui.PrintErr("Local symbolization failed for ", filepath.Base(m.File), ": ", err)
// Move on to other mappings
continue
}
if fid := f.BuildID(); m.BuildID != "" && fid != "" && fid != m.BuildID {
// Build ID mismatch - ignore.
f.Close()
continue
}
mt.segments[m] = f
}
return mt, nil
}
// locateFile opens a local file for symbolization on the search path
// at $PPROF_BINARY_PATH. Looks inside these directories for files
// named $BUILDID/$BASENAME and $BASENAME (if build id is available).
func locateFile(obj plugin.ObjTool, file, buildID string, start uint64) (plugin.ObjFile, error) {
// Construct search path to examine
searchPath := os.Getenv("PPROF_BINARY_PATH")
if searchPath == "" {
// Use $HOME/pprof/binaries as default directory for local symbolization binaries
searchPath = filepath.Join(os.Getenv("HOME"), "pprof", "binaries")
}
// Collect names to search: {buildid/basename, basename}
var fileNames []string
if baseName := filepath.Base(file); buildID != "" {
fileNames = []string{filepath.Join(buildID, baseName), baseName}
} else {
fileNames = []string{baseName}
}
for _, path := range filepath.SplitList(searchPath) {
for nameIndex, name := range fileNames {
file := filepath.Join(path, name)
if f, err := obj.Open(file, start); err == nil {
fileBuildID := f.BuildID()
if buildID == "" || buildID == fileBuildID {
return f, nil
}
f.Close()
if nameIndex == 0 {
// If this is the first name, the path includes the build id. Report inconsistency.
return nil, fmt.Errorf("found file %s with inconsistent build id %s", file, fileBuildID)
}
}
}
}
// Try original file name
f, err := obj.Open(file, start)
if err == nil && buildID != "" {
if fileBuildID := f.BuildID(); fileBuildID != "" && fileBuildID != buildID {
// Mismatched build IDs, ignore
f.Close()
return nil, fmt.Errorf("mismatched build ids %s != %s", fileBuildID, buildID)
}
}
return f, err
}
// mappingTable contains the mechanisms for symbolization of a
// profile.
type mappingTable struct {
prof *profile.Profile
segments map[*profile.Mapping]plugin.ObjFile
}
// Close releases any external processes being used for the mapping.
func (mt *mappingTable) close() {
for _, segment := range mt.segments {
segment.Close()
}
}

View File

@ -0,0 +1,111 @@
// 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 symbolz symbolizes a profile using the output from the symbolz
// service.
package symbolz
import (
"bytes"
"fmt"
"io"
"net/url"
"regexp"
"strconv"
"strings"
"cmd/pprof/internal/profile"
)
var (
symbolzRE = regexp.MustCompile(`(0x[[:xdigit:]]+)\s+(.*)`)
)
// Symbolize symbolizes profile p by parsing data returned by a
// symbolz handler. syms receives the symbolz query (hex addresses
// separated by '+') and returns the symbolz output in a string. It
// symbolizes all locations based on their addresses, regardless of
// mapping.
func Symbolize(source string, syms func(string, string) ([]byte, error), p *profile.Profile) error {
if source = symbolz(source, p); source == "" {
// If the source is not a recognizable URL, do nothing.
return nil
}
// Construct query of addresses to symbolize.
var a []string
for _, l := range p.Location {
if l.Address != 0 && len(l.Line) == 0 {
a = append(a, fmt.Sprintf("%#x", l.Address))
}
}
if len(a) == 0 {
// No addresses to symbolize.
return nil
}
lines := make(map[uint64]profile.Line)
functions := make(map[string]*profile.Function)
if b, err := syms(source, strings.Join(a, "+")); err == nil {
buf := bytes.NewBuffer(b)
for {
l, err := buf.ReadString('\n')
if err != nil {
if err == io.EOF {
break
}
return err
}
if symbol := symbolzRE.FindStringSubmatch(l); len(symbol) == 3 {
addr, err := strconv.ParseUint(symbol[1], 0, 64)
if err != nil {
return fmt.Errorf("unexpected parse failure %s: %v", symbol[1], err)
}
name := symbol[2]
fn := functions[name]
if fn == nil {
fn = &profile.Function{
ID: uint64(len(p.Function) + 1),
Name: name,
SystemName: name,
}
functions[name] = fn
p.Function = append(p.Function, fn)
}
lines[addr] = profile.Line{Function: fn}
}
}
}
for _, l := range p.Location {
if line, ok := lines[l.Address]; ok {
l.Line = []profile.Line{line}
if l.Mapping != nil {
l.Mapping.HasFunctions = true
}
}
}
return nil
}
// symbolz returns the corresponding symbolz source for a profile URL.
func symbolz(source string, p *profile.Profile) string {
if url, err := url.Parse(source); err == nil && url.Host != "" {
if last := strings.LastIndex(url.Path, "/"); last != -1 {
if strings.HasSuffix(url.Path[:last], "pprof") {
url.Path = url.Path[:last] + "/symbol"
} else {
url.Path = url.Path[:last] + "/symbolz"
}
return url.String()
}
}
return ""
}

View File

@ -0,0 +1,45 @@
// 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()
}

202
src/cmd/pprof/pprof.go Normal file
View File

@ -0,0 +1,202 @@
// 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 main
import (
"debug/gosym"
"flag"
"fmt"
"os"
"regexp"
"strings"
"cmd/internal/objfile"
"cmd/pprof/internal/commands"
"cmd/pprof/internal/driver"
"cmd/pprof/internal/fetch"
"cmd/pprof/internal/plugin"
"cmd/pprof/internal/profile"
"cmd/pprof/internal/symbolizer"
"cmd/pprof/internal/symbolz"
)
func main() {
var extraCommands map[string]*commands.Command // no added Go-specific commands
if err := driver.PProf(flags{}, fetch.Fetcher, symbolize, new(objTool), plugin.StandardUI(), extraCommands); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
}
}
// symbolize attempts to symbolize profile p.
// If the source is a local binary, it tries using symbolizer and obj.
// If the source is a URL, it fetches symbol information using symbolz.
func symbolize(mode, source string, p *profile.Profile, obj plugin.ObjTool, ui plugin.UI) error {
remote, local := true, true
for _, o := range strings.Split(strings.ToLower(mode), ":") {
switch o {
case "none", "no":
return nil
case "local":
remote, local = false, true
case "remote":
remote, local = true, false
default:
ui.PrintErr("ignoring unrecognized symbolization option: " + mode)
ui.PrintErr("expecting -symbolize=[local|remote|none][:force]")
fallthrough
case "", "force":
// Ignore these options, -force is recognized by symbolizer.Symbolize
}
}
var err error
if local {
// Symbolize using binutils.
if err = symbolizer.Symbolize(mode, p, obj, ui); err == nil {
return nil
}
}
if remote {
err = symbolz.Symbolize(source, fetch.PostURL, p)
}
return err
}
// flags implements the driver.FlagPackage interface using the builtin flag package.
type flags struct {
}
func (flags) Bool(o string, d bool, c string) *bool {
return flag.Bool(o, d, c)
}
func (flags) Int(o string, d int, c string) *int {
return flag.Int(o, d, c)
}
func (flags) Float64(o string, d float64, c string) *float64 {
return flag.Float64(o, d, c)
}
func (flags) String(o, d, c string) *string {
return flag.String(o, d, c)
}
func (flags) Parse(usage func()) []string {
flag.Usage = usage
flag.Parse()
args := flag.Args()
if len(args) == 0 {
usage()
}
return args
}
func (flags) ExtraUsage() string {
return ""
}
// objTool implements plugin.ObjTool using Go libraries
// (instead of invoking GNU binutils).
type objTool struct{}
func (*objTool) Open(name string, start uint64) (plugin.ObjFile, error) {
of, err := objfile.Open(name)
if err != nil {
return nil, err
}
f := &file{
name: name,
file: of,
}
return f, nil
}
func (*objTool) Demangle(names []string) (map[string]string, error) {
// No C++, nothing to demangle.
return make(map[string]string), nil
}
func (*objTool) Disasm(file string, start, end uint64) ([]plugin.Inst, error) {
return nil, fmt.Errorf("disassembly not supported")
}
func (*objTool) SetConfig(config string) {
// config is usually used to say what binaries to invoke.
// Ignore entirely.
}
// file implements plugin.ObjFile using Go libraries
// (instead of invoking GNU binutils).
// A file represents a single executable being analyzed.
type file struct {
name string
sym []objfile.Sym
file *objfile.File
pcln *gosym.Table
}
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) ([]plugin.Frame, error) {
if f.pcln == nil {
pcln, err := f.file.PCLineTable()
if err != nil {
return nil, err
}
f.pcln = pcln
}
file, line, fn := f.pcln.PCToLine(addr)
if fn == nil {
return nil, fmt.Errorf("no line information for PC=%#x", addr)
}
frame := []plugin.Frame{
{
Func: fn.Name,
File: file,
Line: line,
},
}
return frame, nil
}
func (f *file) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) {
if f.sym == nil {
sym, err := f.file.Symbols()
if err != nil {
return nil, err
}
f.sym = sym
}
var out []*plugin.Sym
for _, s := range f.sym {
if (r == nil || r.MatchString(s.Name)) && (addr == 0 || s.Addr <= addr && addr < s.Addr+uint64(s.Size)) {
out = append(out, &plugin.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
}