mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
198 lines
7.3 KiB
Go
198 lines
7.3 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"
|
||
|
|
"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
|
||
|
|
}
|
||
|
|
}
|