github.com/euank/go@v0.0.0-20160829210321-495514729181/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 }