github.com/zach-klippenstein/go@v0.0.0-20150108044943-fcfbeb3adf58/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 }