github.com/mh-cbon/go@v0.0.0-20160603070303-9e112a3fe4c0/src/cmd/internal/pprof/commands/commands.go (about)

     1  // Copyright 2014 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package commands defines and manages the basic pprof commands
     6  package commands
     7  
     8  import (
     9  	"bytes"
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"os"
    14  	"os/exec"
    15  	"runtime"
    16  	"strings"
    17  	"time"
    18  
    19  	"cmd/internal/pprof/plugin"
    20  	"cmd/internal/pprof/report"
    21  	"cmd/internal/pprof/svg"
    22  	"cmd/internal/pprof/tempfile"
    23  )
    24  
    25  // Commands describes the commands accepted by pprof.
    26  type Commands map[string]*Command
    27  
    28  // Command describes the actions for a pprof command. Includes a
    29  // function for command-line completion, the report format to use
    30  // during report generation, any postprocessing functions, and whether
    31  // the command expects a regexp parameter (typically a function name).
    32  type Command struct {
    33  	Complete    Completer     // autocomplete for interactive mode
    34  	Format      int           // report format to generate
    35  	PostProcess PostProcessor // postprocessing to run on report
    36  	HasParam    bool          // Collect a parameter from the CLI
    37  	Usage       string        // Help text
    38  }
    39  
    40  // Completer is a function for command-line autocompletion
    41  type Completer func(prefix string) string
    42  
    43  // PostProcessor is a function that applies post-processing to the report output
    44  type PostProcessor func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error
    45  
    46  // PProf returns the basic pprof report-generation commands
    47  func PProf(c Completer, interactive **bool) Commands {
    48  	return Commands{
    49  		// Commands that require no post-processing.
    50  		"tags":   {nil, report.Tags, nil, false, "Outputs all tags in the profile"},
    51  		"raw":    {c, report.Raw, nil, false, "Outputs a text representation of the raw profile"},
    52  		"dot":    {c, report.Dot, nil, false, "Outputs a graph in DOT format"},
    53  		"top":    {c, report.Text, nil, false, "Outputs top entries in text form"},
    54  		"tree":   {c, report.Tree, nil, false, "Outputs a text rendering of call graph"},
    55  		"text":   {c, report.Text, nil, false, "Outputs top entries in text form"},
    56  		"disasm": {c, report.Dis, nil, true, "Output annotated assembly for functions matching regexp or address"},
    57  		"list":   {c, report.List, nil, true, "Output annotated source for functions matching regexp"},
    58  		"peek":   {c, report.Tree, nil, true, "Output callers/callees of functions matching regexp"},
    59  
    60  		// Save binary formats to a file
    61  		"callgrind": {c, report.Callgrind, awayFromTTY("callgraph.out"), false, "Outputs a graph in callgrind format"},
    62  		"proto":     {c, report.Proto, awayFromTTY("pb.gz"), false, "Outputs the profile in compressed protobuf format"},
    63  
    64  		// Generate report in DOT format and postprocess with dot
    65  		"gif": {c, report.Dot, invokeDot("gif"), false, "Outputs a graph image in GIF format"},
    66  		"pdf": {c, report.Dot, invokeDot("pdf"), false, "Outputs a graph in PDF format"},
    67  		"png": {c, report.Dot, invokeDot("png"), false, "Outputs a graph image in PNG format"},
    68  		"ps":  {c, report.Dot, invokeDot("ps"), false, "Outputs a graph in PS format"},
    69  
    70  		// Save SVG output into a file after including svgpan library
    71  		"svg": {c, report.Dot, saveSVGToFile(), false, "Outputs a graph in SVG format"},
    72  
    73  		// Visualize postprocessed dot output
    74  		"eog":    {c, report.Dot, invokeVisualizer(interactive, invokeDot("svg"), "svg", []string{"eog"}), false, "Visualize graph through eog"},
    75  		"evince": {c, report.Dot, invokeVisualizer(interactive, invokeDot("pdf"), "pdf", []string{"evince"}), false, "Visualize graph through evince"},
    76  		"gv":     {c, report.Dot, invokeVisualizer(interactive, invokeDot("ps"), "ps", []string{"gv --noantialias"}), false, "Visualize graph through gv"},
    77  		"web":    {c, report.Dot, invokeVisualizer(interactive, saveSVGToFile(), "svg", browsers()), false, "Visualize graph through web browser"},
    78  
    79  		// Visualize HTML directly generated by report.
    80  		"weblist": {c, report.WebList, invokeVisualizer(interactive, awayFromTTY("html"), "html", browsers()), true, "Output annotated source in HTML for functions matching regexp or address"},
    81  	}
    82  }
    83  
    84  // browsers returns a list of commands to attempt for web visualization
    85  // on the current platform
    86  func browsers() []string {
    87  	var cmds []string
    88  	if exe := os.Getenv("BROWSER"); exe != "" {
    89  		cmds = append(cmds, exe)
    90  	}
    91  	switch runtime.GOOS {
    92  	case "darwin":
    93  		cmds = append(cmds, "/usr/bin/open")
    94  	case "windows":
    95  		cmds = append(cmds, "cmd /c start")
    96  	default:
    97  		cmds = append(cmds, "xdg-open")
    98  	}
    99  	cmds = append(cmds, "chrome", "google-chrome", "firefox")
   100  	return cmds
   101  }
   102  
   103  // NewCompleter creates an autocompletion function for a set of commands.
   104  func NewCompleter(cs Commands) Completer {
   105  	return func(line string) string {
   106  		switch tokens := strings.Fields(line); len(tokens) {
   107  		case 0:
   108  			// Nothing to complete
   109  		case 1:
   110  			// Single token -- complete command name
   111  			found := ""
   112  			for c := range cs {
   113  				if strings.HasPrefix(c, tokens[0]) {
   114  					if found != "" {
   115  						return line
   116  					}
   117  					found = c
   118  				}
   119  			}
   120  			if found != "" {
   121  				return found
   122  			}
   123  		default:
   124  			// Multiple tokens -- complete using command completer
   125  			if c, ok := cs[tokens[0]]; ok {
   126  				if c.Complete != nil {
   127  					lastTokenIdx := len(tokens) - 1
   128  					lastToken := tokens[lastTokenIdx]
   129  					if strings.HasPrefix(lastToken, "-") {
   130  						lastToken = "-" + c.Complete(lastToken[1:])
   131  					} else {
   132  						lastToken = c.Complete(lastToken)
   133  					}
   134  					return strings.Join(append(tokens[:lastTokenIdx], lastToken), " ")
   135  				}
   136  			}
   137  		}
   138  		return line
   139  	}
   140  }
   141  
   142  // awayFromTTY saves the output in a file if it would otherwise go to
   143  // the terminal screen. This is used to avoid dumping binary data on
   144  // the screen.
   145  func awayFromTTY(format string) PostProcessor {
   146  	return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error {
   147  		if output == os.Stdout && ui.IsTerminal() {
   148  			tempFile, err := tempfile.New("", "profile", "."+format)
   149  			if err != nil {
   150  				return err
   151  			}
   152  			ui.PrintErr("Generating report in ", tempFile.Name())
   153  			_, err = fmt.Fprint(tempFile, input)
   154  			return err
   155  		}
   156  		_, err := fmt.Fprint(output, input)
   157  		return err
   158  	}
   159  }
   160  
   161  func invokeDot(format string) PostProcessor {
   162  	divert := awayFromTTY(format)
   163  	return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error {
   164  		if _, err := exec.LookPath("dot"); err != nil {
   165  			ui.PrintErr("Cannot find dot, have you installed Graphviz?")
   166  			return err
   167  		}
   168  		cmd := exec.Command("dot", "-T"+format)
   169  		var buf bytes.Buffer
   170  		cmd.Stdin, cmd.Stdout, cmd.Stderr = input, &buf, os.Stderr
   171  		if err := cmd.Run(); err != nil {
   172  			return err
   173  		}
   174  		return divert(&buf, output, ui)
   175  	}
   176  }
   177  
   178  func saveSVGToFile() PostProcessor {
   179  	generateSVG := invokeDot("svg")
   180  	divert := awayFromTTY("svg")
   181  	return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error {
   182  		baseSVG := &bytes.Buffer{}
   183  		generateSVG(input, baseSVG, ui)
   184  		massaged := &bytes.Buffer{}
   185  		fmt.Fprint(massaged, svg.Massage(*baseSVG))
   186  		return divert(massaged, output, ui)
   187  	}
   188  }
   189  
   190  var vizTmpDir string
   191  
   192  func makeVizTmpDir() error {
   193  	if vizTmpDir != "" {
   194  		return nil
   195  	}
   196  	name, err := ioutil.TempDir("", "pprof-")
   197  	if err != nil {
   198  		return err
   199  	}
   200  	tempfile.DeferDelete(name)
   201  	vizTmpDir = name
   202  	return nil
   203  }
   204  
   205  func invokeVisualizer(interactive **bool, format PostProcessor, suffix string, visualizers []string) PostProcessor {
   206  	return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error {
   207  		if err := makeVizTmpDir(); err != nil {
   208  			return err
   209  		}
   210  		tempFile, err := tempfile.New(vizTmpDir, "pprof", "."+suffix)
   211  		if err != nil {
   212  			return err
   213  		}
   214  		tempfile.DeferDelete(tempFile.Name())
   215  		if err = format(input, tempFile, ui); err != nil {
   216  			return err
   217  		}
   218  		tempFile.Close() // on windows, if the file is Open, start cannot access it.
   219  		// Try visualizers until one is successful
   220  		for _, v := range visualizers {
   221  			// Separate command and arguments for exec.Command.
   222  			args := strings.Split(v, " ")
   223  			if len(args) == 0 {
   224  				continue
   225  			}
   226  			viewer := exec.Command(args[0], append(args[1:], tempFile.Name())...)
   227  			viewer.Stderr = os.Stderr
   228  			if err = viewer.Start(); err == nil {
   229  				// The viewer might just send a message to another program
   230  				// to open the file. Give that program a little time to open the
   231  				// file before we remove it.
   232  				time.Sleep(1 * time.Second)
   233  
   234  				if !**interactive {
   235  					// In command-line mode, wait for the viewer to be closed
   236  					// before proceeding
   237  					return viewer.Wait()
   238  				}
   239  				return nil
   240  			}
   241  		}
   242  		return err
   243  	}
   244  }