|
@@ -41,6 +41,7 @@ type commands map[string]*command
|
41
|
41
|
type command struct {
|
42
|
42
|
format int // report format to generate
|
43
|
43
|
postProcess PostProcessor // postprocessing to run on report
|
|
44
|
+ visualizer PostProcessor // display output using some callback
|
44
|
45
|
hasParam bool // collect a parameter from the CLI
|
45
|
46
|
description string // single-line description text saying what the command does
|
46
|
47
|
usage string // multi-line help text saying how the command is used
|
|
@@ -64,7 +65,7 @@ func (c *command) help(name string) string {
|
64
|
65
|
// specialized visualization formats. If the command specified already
|
65
|
66
|
// exists, it is overwritten.
|
66
|
67
|
func AddCommand(cmd string, format int, post PostProcessor, desc, usage string) {
|
67
|
|
- pprofCommands[cmd] = &command{format, post, false, desc, usage}
|
|
68
|
+ pprofCommands[cmd] = &command{format, post, nil, false, desc, usage}
|
68
|
69
|
}
|
69
|
70
|
|
70
|
71
|
// SetVariableDefault sets the default value for a pprof
|
|
@@ -76,7 +77,7 @@ func SetVariableDefault(variable, value string) {
|
76
|
77
|
}
|
77
|
78
|
|
78
|
79
|
// PostProcessor is a function that applies post-processing to the report output
|
79
|
|
-type PostProcessor func(input []byte, output io.Writer, ui plugin.UI) error
|
|
80
|
+type PostProcessor func(input io.Reader, output io.Writer, ui plugin.UI) error
|
80
|
81
|
|
81
|
82
|
// interactiveMode is true if pprof is running on interactive mode, reading
|
82
|
83
|
// commands from its shell.
|
|
@@ -85,43 +86,43 @@ var interactiveMode = false
|
85
|
86
|
// pprofCommands are the report generation commands recognized by pprof.
|
86
|
87
|
var pprofCommands = commands{
|
87
|
88
|
// Commands that require no post-processing.
|
88
|
|
- "comments": {report.Comments, nil, false, "Output all profile comments", ""},
|
89
|
|
- "disasm": {report.Dis, nil, true, "Output assembly listings annotated with samples", listHelp("disasm", true)},
|
90
|
|
- "dot": {report.Dot, nil, false, "Outputs a graph in DOT format", reportHelp("dot", false, true)},
|
91
|
|
- "list": {report.List, nil, true, "Output annotated source for functions matching regexp", listHelp("list", false)},
|
92
|
|
- "peek": {report.Tree, nil, true, "Output callers/callees of functions matching regexp", "peek func_regex\nDisplay callers and callees of functions matching func_regex."},
|
93
|
|
- "raw": {report.Raw, nil, false, "Outputs a text representation of the raw profile", ""},
|
94
|
|
- "tags": {report.Tags, nil, false, "Outputs all tags in the profile", "tags [tag_regex]* [-ignore_regex]* [>file]\nList tags with key:value matching tag_regex and exclude ignore_regex."},
|
95
|
|
- "text": {report.Text, nil, false, "Outputs top entries in text form", reportHelp("text", true, true)},
|
96
|
|
- "top": {report.Text, nil, false, "Outputs top entries in text form", reportHelp("top", true, true)},
|
97
|
|
- "topproto": {report.TopProto, awayFromTTY("pb.gz"), false, "Outputs top entries in compressed protobuf format", ""},
|
98
|
|
- "traces": {report.Traces, nil, false, "Outputs all profile samples in text form", ""},
|
99
|
|
- "tree": {report.Tree, nil, false, "Outputs a text rendering of call graph", reportHelp("tree", true, true)},
|
|
89
|
+ "comments": {report.Comments, nil, nil, false, "Output all profile comments", ""},
|
|
90
|
+ "disasm": {report.Dis, nil, nil, true, "Output assembly listings annotated with samples", listHelp("disasm", true)},
|
|
91
|
+ "dot": {report.Dot, nil, nil, false, "Outputs a graph in DOT format", reportHelp("dot", false, true)},
|
|
92
|
+ "list": {report.List, nil, nil, true, "Output annotated source for functions matching regexp", listHelp("list", false)},
|
|
93
|
+ "peek": {report.Tree, nil, nil, true, "Output callers/callees of functions matching regexp", "peek func_regex\nDisplay callers and callees of functions matching func_regex."},
|
|
94
|
+ "raw": {report.Raw, nil, nil, false, "Outputs a text representation of the raw profile", ""},
|
|
95
|
+ "tags": {report.Tags, nil, nil, false, "Outputs all tags in the profile", "tags [tag_regex]* [-ignore_regex]* [>file]\nList tags with key:value matching tag_regex and exclude ignore_regex."},
|
|
96
|
+ "text": {report.Text, nil, nil, false, "Outputs top entries in text form", reportHelp("text", true, true)},
|
|
97
|
+ "top": {report.Text, nil, nil, false, "Outputs top entries in text form", reportHelp("top", true, true)},
|
|
98
|
+ "traces": {report.Traces, nil, nil, false, "Outputs all profile samples in text form", ""},
|
|
99
|
+ "tree": {report.Tree, nil, nil, false, "Outputs a text rendering of call graph", reportHelp("tree", true, true)},
|
100
|
100
|
|
101
|
101
|
// Save binary formats to a file
|
102
|
|
- "callgrind": {report.Callgrind, awayFromTTY("callgraph.out"), false, "Outputs a graph in callgrind format", reportHelp("callgrind", false, true)},
|
103
|
|
- "proto": {report.Proto, awayFromTTY("pb.gz"), false, "Outputs the profile in compressed protobuf format", ""},
|
|
102
|
+ "callgrind": {report.Callgrind, nil, awayFromTTY("callgraph.out"), false, "Outputs a graph in callgrind format", reportHelp("callgrind", false, true)},
|
|
103
|
+ "proto": {report.Proto, nil, awayFromTTY("pb.gz"), false, "Outputs the profile in compressed protobuf format", ""},
|
|
104
|
+ "topproto": {report.TopProto, nil, awayFromTTY("pb.gz"), false, "Outputs top entries in compressed protobuf format", ""},
|
104
|
105
|
|
105
|
106
|
// Generate report in DOT format and postprocess with dot
|
106
|
|
- "gif": {report.Dot, invokeDot("gif"), false, "Outputs a graph image in GIF format", reportHelp("gif", false, true)},
|
107
|
|
- "pdf": {report.Dot, invokeDot("pdf"), false, "Outputs a graph in PDF format", reportHelp("pdf", false, true)},
|
108
|
|
- "png": {report.Dot, invokeDot("png"), false, "Outputs a graph image in PNG format", reportHelp("png", false, true)},
|
109
|
|
- "ps": {report.Dot, invokeDot("ps"), false, "Outputs a graph in PS format", reportHelp("ps", false, true)},
|
|
107
|
+ "gif": {report.Dot, invokeDot("gif"), awayFromTTY("gif"), false, "Outputs a graph image in GIF format", reportHelp("gif", false, true)},
|
|
108
|
+ "pdf": {report.Dot, invokeDot("pdf"), awayFromTTY("pdf"), false, "Outputs a graph in PDF format", reportHelp("pdf", false, true)},
|
|
109
|
+ "png": {report.Dot, invokeDot("png"), awayFromTTY("png"), false, "Outputs a graph image in PNG format", reportHelp("png", false, true)},
|
|
110
|
+ "ps": {report.Dot, invokeDot("ps"), awayFromTTY("ps"), false, "Outputs a graph in PS format", reportHelp("ps", false, true)},
|
110
|
111
|
|
111
|
112
|
// Save SVG output into a file
|
112
|
|
- "svg": {report.Dot, saveSVGToFile(), false, "Outputs a graph in SVG format", reportHelp("svg", false, true)},
|
|
113
|
+ "svg": {report.Dot, massageDotSVG(), awayFromTTY("svg"), false, "Outputs a graph in SVG format", reportHelp("svg", false, true)},
|
113
|
114
|
|
114
|
115
|
// Visualize postprocessed dot output
|
115
|
|
- "eog": {report.Dot, invokeVisualizer(invokeDot("svg"), "svg", []string{"eog"}), false, "Visualize graph through eog", reportHelp("eog", false, false)},
|
116
|
|
- "evince": {report.Dot, invokeVisualizer(invokeDot("pdf"), "pdf", []string{"evince"}), false, "Visualize graph through evince", reportHelp("evince", false, false)},
|
117
|
|
- "gv": {report.Dot, invokeVisualizer(invokeDot("ps"), "ps", []string{"gv --noantialias"}), false, "Visualize graph through gv", reportHelp("gv", false, false)},
|
118
|
|
- "web": {report.Dot, invokeVisualizer(saveSVGToFile(), "svg", browsers()), false, "Visualize graph through web browser", reportHelp("web", false, false)},
|
|
116
|
+ "eog": {report.Dot, invokeDot("svg"), invokeVisualizer("svg", []string{"eog"}), false, "Visualize graph through eog", reportHelp("eog", false, false)},
|
|
117
|
+ "evince": {report.Dot, invokeDot("pdf"), invokeVisualizer("pdf", []string{"evince"}), false, "Visualize graph through evince", reportHelp("evince", false, false)},
|
|
118
|
+ "gv": {report.Dot, invokeDot("ps"), invokeVisualizer("ps", []string{"gv --noantialias"}), false, "Visualize graph through gv", reportHelp("gv", false, false)},
|
|
119
|
+ "web": {report.Dot, massageDotSVG(), invokeVisualizer("svg", browsers()), false, "Visualize graph through web browser", reportHelp("web", false, false)},
|
119
|
120
|
|
120
|
121
|
// Visualize callgrind output
|
121
|
|
- "kcachegrind": {report.Callgrind, invokeVisualizer(nil, "grind", kcachegrind), false, "Visualize report in KCachegrind", reportHelp("kcachegrind", false, false)},
|
|
122
|
+ "kcachegrind": {report.Callgrind, nil, invokeVisualizer("grind", kcachegrind), false, "Visualize report in KCachegrind", reportHelp("kcachegrind", false, false)},
|
122
|
123
|
|
123
|
124
|
// Visualize HTML directly generated by report.
|
124
|
|
- "weblist": {report.WebList, invokeVisualizer(awayFromTTY("html"), "html", browsers()), true, "Display annotated source in a web browser", listHelp("weblist", false)},
|
|
125
|
+ "weblist": {report.WebList, nil, invokeVisualizer("html", browsers()), true, "Display annotated source in a web browser", listHelp("weblist", false)},
|
125
|
126
|
}
|
126
|
127
|
|
127
|
128
|
// pprofVariables are the configuration parameters that affect the
|
|
@@ -360,70 +361,54 @@ var kcachegrind = []string{"kcachegrind"}
|
360
|
361
|
// the terminal screen. This is used to avoid dumping binary data on
|
361
|
362
|
// the screen.
|
362
|
363
|
func awayFromTTY(format string) PostProcessor {
|
363
|
|
- return func(input []byte, output io.Writer, ui plugin.UI) error {
|
|
364
|
+ return func(input io.Reader, output io.Writer, ui plugin.UI) error {
|
364
|
365
|
if output == os.Stdout && (ui.IsTerminal() || interactiveMode) {
|
365
|
366
|
tempFile, err := newTempFile("", "profile", "."+format)
|
366
|
367
|
if err != nil {
|
367
|
368
|
return err
|
368
|
369
|
}
|
369
|
370
|
ui.PrintErr("Generating report in ", tempFile.Name())
|
370
|
|
- _, err = fmt.Fprint(tempFile, string(input))
|
371
|
|
- return err
|
|
371
|
+ output = tempFile
|
372
|
372
|
}
|
373
|
|
- _, err := fmt.Fprint(output, string(input))
|
|
373
|
+ _, err := io.Copy(output, input)
|
374
|
374
|
return err
|
375
|
375
|
}
|
376
|
376
|
}
|
377
|
377
|
|
378
|
378
|
func invokeDot(format string) PostProcessor {
|
379
|
|
- divert := awayFromTTY(format)
|
380
|
|
- return func(input []byte, output io.Writer, ui plugin.UI) error {
|
|
379
|
+ return func(input io.Reader, output io.Writer, ui plugin.UI) error {
|
381
|
380
|
cmd := exec.Command("dot", "-T"+format)
|
382
|
|
- var buf bytes.Buffer
|
383
|
|
- cmd.Stdin, cmd.Stdout, cmd.Stderr = bytes.NewBuffer(input), &buf, os.Stderr
|
|
381
|
+ cmd.Stdin, cmd.Stdout, cmd.Stderr = input, output, os.Stderr
|
384
|
382
|
if err := cmd.Run(); err != nil {
|
385
|
383
|
return fmt.Errorf("Failed to execute dot. Is Graphviz installed? Error: %v", err)
|
386
|
384
|
}
|
387
|
|
- return divert(buf.Bytes(), output, ui)
|
|
385
|
+ return nil
|
388
|
386
|
}
|
389
|
387
|
}
|
390
|
388
|
|
391
|
|
-func saveSVGToFile() PostProcessor {
|
|
389
|
+// massageDotSVG invokes the dot tool to generate an SVG image and alters
|
|
390
|
+// the image to have panning capabilities when viewed in a browser.
|
|
391
|
+func massageDotSVG() PostProcessor {
|
392
|
392
|
generateSVG := invokeDot("svg")
|
393
|
|
- divert := awayFromTTY("svg")
|
394
|
|
- return func(input []byte, output io.Writer, ui plugin.UI) error {
|
395
|
|
- baseSVG := &bytes.Buffer{}
|
|
393
|
+ return func(input io.Reader, output io.Writer, ui plugin.UI) error {
|
|
394
|
+ baseSVG := new(bytes.Buffer)
|
396
|
395
|
if err := generateSVG(input, baseSVG, ui); err != nil {
|
397
|
396
|
return err
|
398
|
397
|
}
|
399
|
|
-
|
400
|
|
- return divert([]byte(svg.Massage(*baseSVG)), output, ui)
|
|
398
|
+ _, err := output.Write([]byte(svg.Massage(baseSVG.String())))
|
|
399
|
+ return err
|
401
|
400
|
}
|
402
|
401
|
}
|
403
|
402
|
|
404
|
|
-func invokeVisualizer(format PostProcessor, suffix string, visualizers []string) PostProcessor {
|
405
|
|
- return func(input []byte, output io.Writer, ui plugin.UI) error {
|
406
|
|
- if output != os.Stdout {
|
407
|
|
- if format != nil {
|
408
|
|
- return format(input, output, ui)
|
409
|
|
- }
|
410
|
|
- _, err := fmt.Fprint(output, string(input))
|
411
|
|
- return err
|
412
|
|
- }
|
413
|
|
-
|
|
403
|
+func invokeVisualizer(suffix string, visualizers []string) PostProcessor {
|
|
404
|
+ return func(input io.Reader, output io.Writer, ui plugin.UI) error {
|
414
|
405
|
tempFile, err := newTempFile(os.TempDir(), "pprof", "."+suffix)
|
415
|
406
|
if err != nil {
|
416
|
407
|
return err
|
417
|
408
|
}
|
418
|
409
|
deferDeleteTempFile(tempFile.Name())
|
419
|
|
- if format != nil {
|
420
|
|
- if err := format(input, tempFile, ui); err != nil {
|
421
|
|
- return err
|
422
|
|
- }
|
423
|
|
- } else {
|
424
|
|
- if _, err := fmt.Fprint(tempFile, string(input)); err != nil {
|
425
|
|
- return err
|
426
|
|
- }
|
|
410
|
+ if _, err := io.Copy(tempFile, input); err != nil {
|
|
411
|
+ return err
|
427
|
412
|
}
|
428
|
413
|
tempFile.Close()
|
429
|
414
|
// Try visualizers until one is successful
|