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:
parent
454d1b0e8b
commit
8b5221a57b
197
src/cmd/pprof/internal/commands/commands.go
Normal file
197
src/cmd/pprof/internal/commands/commands.go
Normal 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
|
||||
}
|
||||
}
|
1036
src/cmd/pprof/internal/driver/driver.go
Normal file
1036
src/cmd/pprof/internal/driver/driver.go
Normal file
File diff suppressed because it is too large
Load Diff
492
src/cmd/pprof/internal/driver/interactive.go
Normal file
492
src/cmd/pprof/internal/driver/interactive.go
Normal 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))
|
||||
}
|
||||
}
|
82
src/cmd/pprof/internal/fetch/fetch.go
Normal file
82
src/cmd/pprof/internal/fetch/fetch.go
Normal 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)
|
||||
}
|
213
src/cmd/pprof/internal/plugin/plugin.go
Normal file
213
src/cmd/pprof/internal/plugin/plugin.go
Normal 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
|
470
src/cmd/pprof/internal/profile/encode.go
Normal file
470
src/cmd/pprof/internal/profile/encode.go
Normal 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
|
||||
}
|
157
src/cmd/pprof/internal/profile/filter.go
Normal file
157
src/cmd/pprof/internal/profile/filter.go
Normal 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
|
||||
}
|
1250
src/cmd/pprof/internal/profile/legacy_profile.go
Normal file
1250
src/cmd/pprof/internal/profile/legacy_profile.go
Normal file
File diff suppressed because it is too large
Load Diff
567
src/cmd/pprof/internal/profile/profile.go
Normal file
567
src/cmd/pprof/internal/profile/profile.go
Normal 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
|
||||
}
|
298
src/cmd/pprof/internal/profile/proto.go
Normal file
298
src/cmd/pprof/internal/profile/proto.go
Normal 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
|
||||
}
|
97
src/cmd/pprof/internal/profile/prune.go
Normal file
97
src/cmd/pprof/internal/profile/prune.go
Normal 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
|
||||
}
|
1718
src/cmd/pprof/internal/report/report.go
Normal file
1718
src/cmd/pprof/internal/report/report.go
Normal file
File diff suppressed because it is too large
Load Diff
450
src/cmd/pprof/internal/report/source.go
Normal file
450
src/cmd/pprof/internal/report/source.go
Normal 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
|
||||
}
|
77
src/cmd/pprof/internal/report/source_html.go
Normal file
77
src/cmd/pprof/internal/report/source_html.go
Normal 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>
|
||||
`
|
75
src/cmd/pprof/internal/svg/svg.go
Normal file
75
src/cmd/pprof/internal/svg/svg.go
Normal 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, "&;", "&;", -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
|
||||
}
|
191
src/cmd/pprof/internal/symbolizer/symbolizer.go
Normal file
191
src/cmd/pprof/internal/symbolizer/symbolizer.go
Normal 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()
|
||||
}
|
||||
}
|
111
src/cmd/pprof/internal/symbolz/symbolz.go
Normal file
111
src/cmd/pprof/internal/symbolz/symbolz.go
Normal 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 ""
|
||||
}
|
45
src/cmd/pprof/internal/tempfile/tempfile.go
Normal file
45
src/cmd/pprof/internal/tempfile/tempfile.go
Normal 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
202
src/cmd/pprof/pprof.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user