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