Skip to content

Commit

Permalink
cmd/pprof: add Go implementation
Browse files Browse the repository at this point in the history
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
rsc committed Sep 30, 2014
1 parent 454d1b0 commit 8b5221a
Show file tree
Hide file tree
Showing 19 changed files with 7,728 additions and 0 deletions.
197 changes: 197 additions & 0 deletions src/cmd/pprof/internal/commands/commands.go
@@ -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
}
}

0 comments on commit 8b5221a

Please sign in to comment.