mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
Fixes #11729. Change-Id: I6e5e23169ac1368afcbd016ed544a710aa045326 Reviewed-on: https://go-review.googlesource.com/12553 Reviewed-by: Rob Pike <r@golang.org>
243 lines
8.2 KiB
Go
243 lines
8.2 KiB
Go
// 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"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
"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) 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(), 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(), "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"},
|
|
}
|
|
}
|
|
|
|
// browsers returns a list of commands to attempt for web visualization
|
|
// on the current platform
|
|
func browsers() []string {
|
|
var cmds []string
|
|
if exe := os.Getenv("BROWSER"); exe != "" {
|
|
cmds = append(cmds, exe)
|
|
}
|
|
switch runtime.GOOS {
|
|
case "darwin":
|
|
cmds = append(cmds, "/usr/bin/open")
|
|
case "windows":
|
|
cmds = append(cmds, "cmd /c start")
|
|
default:
|
|
cmds = append(cmds, "xdg-open")
|
|
}
|
|
cmds = append(cmds, "chrome", "google-chrome", "firefox")
|
|
return cmds
|
|
}
|
|
|
|
// 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 {
|
|
if _, err := exec.LookPath("dot"); err != nil {
|
|
ui.PrintErr("Cannot find dot, have you installed Graphviz?")
|
|
return err
|
|
}
|
|
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() 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))
|
|
return divert(massaged, output, ui)
|
|
}
|
|
}
|
|
|
|
var vizTmpDir string
|
|
|
|
func makeVizTmpDir() error {
|
|
if vizTmpDir != "" {
|
|
return nil
|
|
}
|
|
name, err := ioutil.TempDir("", "pprof-")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
vizTmpDir = name
|
|
return nil
|
|
}
|
|
|
|
func invokeVisualizer(interactive **bool, format PostProcessor, suffix string, visualizers []string) PostProcessor {
|
|
return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error {
|
|
if err := makeVizTmpDir(); err != nil {
|
|
return err
|
|
}
|
|
tempFile, err := tempfile.New(vizTmpDir, "pprof", "."+suffix)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tempfile.DeferDelete(tempFile.Name())
|
|
if err = format(input, tempFile, ui); err != nil {
|
|
return err
|
|
}
|
|
tempFile.Close() // on windows, if the file is Open, start cannot access it.
|
|
// 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 {
|
|
// The viewer might just send a message to another program
|
|
// to open the file. Give that program a little time to open the
|
|
// file before we remove it.
|
|
time.Sleep(1 * time.Second)
|
|
|
|
if !**interactive {
|
|
// In command-line mode, wait for the viewer to be closed
|
|
// before proceeding
|
|
return viewer.Wait()
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
}
|