-
Notifications
You must be signed in to change notification settings - Fork 17.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
Showing
19 changed files
with
7,728 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} |
Oops, something went wrong.