github.com/freddyisaac/sicortex-golang@v0.0.0-20231019035217-e03519e66f60/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  	"io/ioutil"
    13  	"os"
    14  	"os/exec"
    15  	"strings"
    16  	"time"
    17  
    18  	"cmd/internal/browser"
    19  	"cmd/pprof/internal/plugin"
    20  	"cmd/pprof/internal/report"
    21  	"cmd/pprof/internal/svg"
    22  	"cmd/pprof/internal/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(interactive, "callgraph.out"), false, "Outputs a graph in callgrind format"},
    62  		"proto":     {c, report.Proto, awayFromTTY(interactive, "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(interactive, "gif"), false, "Outputs a graph image in GIF format"},
    66  		"pdf": {c, report.Dot, invokeDot(interactive, "pdf"), false, "Outputs a graph in PDF format"},
    67  		"png": {c, report.Dot, invokeDot(interactive, "png"), false, "Outputs a graph image in PNG format"},
    68  		"ps":  {c, report.Dot, invokeDot(interactive, "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(interactive), false, "Outputs a graph in SVG format"},
    72  
    73  		// Visualize postprocessed dot output
    74  		"eog":    {c, report.Dot, invokeVisualizer(interactive, invokeDot(nil, "svg"), "svg", []string{"eog"}), false, "Visualize graph through eog"},
    75  		"evince": {c, report.Dot, invokeVisualizer(interactive, invokeDot(nil, "pdf"), "pdf", []string{"evince"}), false, "Visualize graph through evince"},
    76  		"gv":     {c, report.Dot, invokeVisualizer(interactive, invokeDot(nil, "ps"), "ps", []string{"gv --noantialias"}), false, "Visualize graph through gv"},
    77  		"web":    {c, report.Dot, invokeVisualizer(interactive, saveSVGToFile(nil), "svg", browsers()), false, "Visualize graph through web browser"},
    78  
    79  		// Visualize HTML directly generated by report.
    80  		"weblist": {c, report.WebList, invokeVisualizer(interactive, awayFromTTY(nil, "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  	for _, cmd := range browser.Commands() {
    89  		cmds = append(cmds, strings.Join(cmd, " "))
    90  	}
    91  	return cmds
    92  }
    93  
    94  // NewCompleter creates an autocompletion function for a set of commands.
    95  func NewCompleter(cs Commands) Completer {
    96  	return func(line string) string {
    97  		switch tokens := strings.Fields(line); len(tokens) {
    98  		case 0:
    99  			// Nothing to complete
   100  		case 1:
   101  			// Single token -- complete command name
   102  			found := ""
   103  			for c := range cs {
   104  				if strings.HasPrefix(c, tokens[0]) {
   105  					if found != "" {
   106  						return line
   107  					}
   108  					found = c
   109  				}
   110  			}
   111  			if found != "" {
   112  				return found
   113  			}
   114  		default:
   115  			// Multiple tokens -- complete using command completer
   116  			if c, ok := cs[tokens[0]]; ok {
   117  				if c.Complete != nil {
   118  					lastTokenIdx := len(tokens) - 1
   119  					lastToken := tokens[lastTokenIdx]
   120  					if strings.HasPrefix(lastToken, "-") {
   121  						lastToken = "-" + c.Complete(lastToken[1:])
   122  					} else {
   123  						lastToken = c.Complete(lastToken)
   124  					}
   125  					return strings.Join(append(tokens[:lastTokenIdx], lastToken), " ")
   126  				}
   127  			}
   128  		}
   129  		return line
   130  	}
   131  }
   132  
   133  // awayFromTTY saves the output in a file if it would otherwise go to
   134  // the terminal screen. This is used to avoid dumping binary data on
   135  // the screen.
   136  func awayFromTTY(interactive **bool, format string) PostProcessor {
   137  	return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error {
   138  		if output == os.Stdout && (ui.IsTerminal() || interactive != nil && **interactive) {
   139  			tempFile, err := tempfile.New("", "profile", "."+format)
   140  			if err != nil {
   141  				return err
   142  			}
   143  			ui.PrintErr("Generating report in ", tempFile.Name())
   144  			_, err = fmt.Fprint(tempFile, input)
   145  			return err
   146  		}
   147  		_, err := fmt.Fprint(output, input)
   148  		return err
   149  	}
   150  }
   151  
   152  func invokeDot(interactive **bool, format string) PostProcessor {
   153  	divert := awayFromTTY(interactive, format)
   154  	return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error {
   155  		if _, err := exec.LookPath("dot"); err != nil {
   156  			ui.PrintErr("Cannot find dot, have you installed Graphviz?")
   157  			return err
   158  		}
   159  		cmd := exec.Command("dot", "-T"+format)
   160  		var buf bytes.Buffer
   161  		cmd.Stdin, cmd.Stdout, cmd.Stderr = input, &buf, os.Stderr
   162  		if err := cmd.Run(); err != nil {
   163  			return err
   164  		}
   165  		return divert(&buf, output, ui)
   166  	}
   167  }
   168  
   169  func saveSVGToFile(interactive **bool) PostProcessor {
   170  	generateSVG := invokeDot(nil, "svg")
   171  	divert := awayFromTTY(interactive, "svg")
   172  	return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error {
   173  		baseSVG := &bytes.Buffer{}
   174  		generateSVG(input, baseSVG, ui)
   175  		massaged := &bytes.Buffer{}
   176  		fmt.Fprint(massaged, svg.Massage(*baseSVG))
   177  		return divert(massaged, output, ui)
   178  	}
   179  }
   180  
   181  var vizTmpDir string
   182  
   183  func makeVizTmpDir() error {
   184  	if vizTmpDir != "" {
   185  		return nil
   186  	}
   187  	name, err := ioutil.TempDir("", "pprof-")
   188  	if err != nil {
   189  		return err
   190  	}
   191  	tempfile.DeferDelete(name)
   192  	vizTmpDir = name
   193  	return nil
   194  }
   195  
   196  func invokeVisualizer(interactive **bool, format PostProcessor, suffix string, visualizers []string) PostProcessor {
   197  	return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error {
   198  		if err := makeVizTmpDir(); err != nil {
   199  			return err
   200  		}
   201  		tempFile, err := tempfile.New(vizTmpDir, "pprof", "."+suffix)
   202  		if err != nil {
   203  			return err
   204  		}
   205  		tempfile.DeferDelete(tempFile.Name())
   206  		if err = format(input, tempFile, ui); err != nil {
   207  			return err
   208  		}
   209  		tempFile.Close() // on windows, if the file is Open, start cannot access it.
   210  		// Try visualizers until one is successful
   211  		for _, v := range visualizers {
   212  			// Separate command and arguments for exec.Command.
   213  			args := strings.Split(v, " ")
   214  			if len(args) == 0 {
   215  				continue
   216  			}
   217  			viewer := exec.Command(args[0], append(args[1:], tempFile.Name())...)
   218  			viewer.Stderr = os.Stderr
   219  			if err = viewer.Start(); err == nil {
   220  				// The viewer might just send a message to another program
   221  				// to open the file. Give that program a little time to open the
   222  				// file before we remove it.
   223  				time.Sleep(1 * time.Second)
   224  
   225  				if !**interactive {
   226  					// In command-line mode, wait for the viewer to be closed
   227  					// before proceeding
   228  					return viewer.Wait()
   229  				}
   230  				return nil
   231  			}
   232  		}
   233  		return err
   234  	}
   235  }