|
@@ -18,7 +18,6 @@ import (
|
18
|
18
|
"bytes"
|
19
|
19
|
"fmt"
|
20
|
20
|
"html/template"
|
21
|
|
- "io"
|
22
|
21
|
"net"
|
23
|
22
|
"net/http"
|
24
|
23
|
gourl "net/url"
|
|
@@ -35,11 +34,27 @@ import (
|
35
|
34
|
|
36
|
35
|
// webInterface holds the state needed for serving a browser based interface.
|
37
|
36
|
type webInterface struct {
|
38
|
|
- prof *profile.Profile
|
39
|
|
- options *plugin.Options
|
40
|
|
- help map[string]string
|
|
37
|
+ prof *profile.Profile
|
|
38
|
+ options *plugin.Options
|
|
39
|
+ help map[string]string
|
|
40
|
+ templates *template.Template
|
41
|
41
|
}
|
42
|
42
|
|
|
43
|
+func makeWebInterface(p *profile.Profile, opt *plugin.Options) *webInterface {
|
|
44
|
+ templates := template.New("templategroup")
|
|
45
|
+ addTemplates(templates)
|
|
46
|
+ report.AddSourceTemplates(templates)
|
|
47
|
+ return &webInterface{
|
|
48
|
+ prof: p,
|
|
49
|
+ options: opt,
|
|
50
|
+ help: make(map[string]string),
|
|
51
|
+ templates: templates,
|
|
52
|
+ }
|
|
53
|
+}
|
|
54
|
+
|
|
55
|
+// maxEntries is the maximum number of entries to print for text interfaces.
|
|
56
|
+const maxEntries = 50
|
|
57
|
+
|
43
|
58
|
// errorCatcher is a UI that captures errors for reporting to the browser.
|
44
|
59
|
type errorCatcher struct {
|
45
|
60
|
plugin.UI
|
|
@@ -53,24 +68,20 @@ func (ec *errorCatcher) PrintErr(args ...interface{}) {
|
53
|
68
|
|
54
|
69
|
// webArgs contains arguments passed to templates in webhtml.go.
|
55
|
70
|
type webArgs struct {
|
56
|
|
- BaseURL string
|
57
|
|
- Type string
|
58
|
|
- Title string
|
59
|
|
- Errors []string
|
60
|
|
- Legend []string
|
61
|
|
- Help map[string]string
|
62
|
|
- Nodes []string
|
63
|
|
- Svg template.HTML
|
64
|
|
- Top []report.TextItem
|
|
71
|
+ BaseURL string
|
|
72
|
+ Title string
|
|
73
|
+ Errors []string
|
|
74
|
+ Legend []string
|
|
75
|
+ Help map[string]string
|
|
76
|
+ Nodes []string
|
|
77
|
+ HTMLBody template.HTML
|
|
78
|
+ TextBody string
|
|
79
|
+ Top []report.TextItem
|
65
|
80
|
}
|
66
|
81
|
|
67
|
82
|
func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options) error {
|
68
|
83
|
interactiveMode = true
|
69
|
|
- ui := &webInterface{
|
70
|
|
- prof: p,
|
71
|
|
- options: o,
|
72
|
|
- help: make(map[string]string),
|
73
|
|
- }
|
|
84
|
+ ui := makeWebInterface(p, o)
|
74
|
85
|
for n, c := range pprofCommands {
|
75
|
86
|
ui.help[n] = c.description
|
76
|
87
|
}
|
|
@@ -101,7 +112,7 @@ func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options) e
|
101
|
112
|
mux.Handle("/", wrap(http.HandlerFunc(ui.dot)))
|
102
|
113
|
mux.Handle("/top", wrap(http.HandlerFunc(ui.top)))
|
103
|
114
|
mux.Handle("/disasm", wrap(http.HandlerFunc(ui.disasm)))
|
104
|
|
- mux.Handle("/weblist", wrap(http.HandlerFunc(ui.weblist)))
|
|
115
|
+ mux.Handle("/source", wrap(http.HandlerFunc(ui.source)))
|
105
|
116
|
mux.Handle("/peek", wrap(http.HandlerFunc(ui.peek)))
|
106
|
117
|
|
107
|
118
|
s := &http.Server{Handler: mux}
|
|
@@ -187,27 +198,59 @@ func varsFromURL(u *gourl.URL) variables {
|
187
|
198
|
return vars
|
188
|
199
|
}
|
189
|
200
|
|
190
|
|
-// dot generates a web page containing an svg diagram.
|
191
|
|
-func (ui *webInterface) dot(w http.ResponseWriter, req *http.Request) {
|
192
|
|
- if req.URL.Path != "/" {
|
193
|
|
- http.NotFound(w, req)
|
194
|
|
- return
|
|
201
|
+// makeReport generates a report for the specified command.
|
|
202
|
+func (ui *webInterface) makeReport(w http.ResponseWriter, req *http.Request,
|
|
203
|
+ cmd []string, vars ...string) (*report.Report, []string) {
|
|
204
|
+ v := varsFromURL(req.URL)
|
|
205
|
+ for i := 0; i+1 < len(vars); i += 2 {
|
|
206
|
+ v[vars[i]].value = vars[i+1]
|
195
|
207
|
}
|
196
|
|
-
|
197
|
|
- // Capture any error messages generated while generating a report.
|
198
|
208
|
catcher := &errorCatcher{UI: ui.options.UI}
|
199
|
209
|
options := *ui.options
|
200
|
210
|
options.UI = catcher
|
201
|
|
-
|
202
|
|
- // Generate dot graph.
|
203
|
|
- args := []string{"svg"}
|
204
|
|
- vars := varsFromURL(req.URL)
|
205
|
|
- _, rpt, err := generateRawReport(ui.prof, args, vars, &options)
|
|
211
|
+ _, rpt, err := generateRawReport(ui.prof, cmd, v, &options)
|
206
|
212
|
if err != nil {
|
207
|
213
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
208
|
214
|
ui.options.UI.PrintErr(err)
|
|
215
|
+ return nil, nil
|
|
216
|
+ }
|
|
217
|
+ return rpt, catcher.errors
|
|
218
|
+}
|
|
219
|
+
|
|
220
|
+// render generates html using the named template based on the contents of data.
|
|
221
|
+func (ui *webInterface) render(w http.ResponseWriter, baseURL, tmpl string,
|
|
222
|
+ errList, legend []string, data webArgs) {
|
|
223
|
+ file := getFromLegend(legend, "File: ", "unknown")
|
|
224
|
+ profile := getFromLegend(legend, "Type: ", "unknown")
|
|
225
|
+ data.BaseURL = baseURL
|
|
226
|
+ data.Title = file + " " + profile
|
|
227
|
+ data.Errors = errList
|
|
228
|
+ data.Legend = legend
|
|
229
|
+ data.Help = ui.help
|
|
230
|
+ html := &bytes.Buffer{}
|
|
231
|
+ if err := ui.templates.ExecuteTemplate(html, tmpl, data); err != nil {
|
|
232
|
+ http.Error(w, "internal template error", http.StatusInternalServerError)
|
|
233
|
+ ui.options.UI.PrintErr(err)
|
|
234
|
+ return
|
|
235
|
+ }
|
|
236
|
+ w.Header().Set("Content-Type", "text/html")
|
|
237
|
+ w.Write(html.Bytes())
|
|
238
|
+}
|
|
239
|
+
|
|
240
|
+// dot generates a web page containing an svg diagram.
|
|
241
|
+func (ui *webInterface) dot(w http.ResponseWriter, req *http.Request) {
|
|
242
|
+ // Disable prefix matching behavior of net/http.
|
|
243
|
+ if req.URL.Path != "/" {
|
|
244
|
+ http.NotFound(w, req)
|
209
|
245
|
return
|
210
|
246
|
}
|
|
247
|
+
|
|
248
|
+ rpt, errList := ui.makeReport(w, req, []string{"svg"})
|
|
249
|
+ if rpt == nil {
|
|
250
|
+ return // error already reported
|
|
251
|
+ }
|
|
252
|
+
|
|
253
|
+ // Generate dot graph.
|
211
|
254
|
g, config := report.GetDOT(rpt)
|
212
|
255
|
legend := config.Labels
|
213
|
256
|
config.Labels = nil
|
|
@@ -229,27 +272,10 @@ func (ui *webInterface) dot(w http.ResponseWriter, req *http.Request) {
|
229
|
272
|
nodes = append(nodes, n.Info.Name)
|
230
|
273
|
}
|
231
|
274
|
|
232
|
|
- // Embed in html.
|
233
|
|
- file := getFromLegend(legend, "File: ", "unknown")
|
234
|
|
- profile := getFromLegend(legend, "Type: ", "unknown")
|
235
|
|
- data := webArgs{
|
236
|
|
- BaseURL: "/",
|
237
|
|
- Type: "dot",
|
238
|
|
- Title: file + " " + profile,
|
239
|
|
- Errors: catcher.errors,
|
240
|
|
- Svg: template.HTML(string(svg)),
|
241
|
|
- Legend: legend,
|
242
|
|
- Nodes: nodes,
|
243
|
|
- Help: ui.help,
|
244
|
|
- }
|
245
|
|
- html := &bytes.Buffer{}
|
246
|
|
- if err := webTemplate.ExecuteTemplate(html, "graph", data); err != nil {
|
247
|
|
- http.Error(w, "internal template error", http.StatusInternalServerError)
|
248
|
|
- ui.options.UI.PrintErr(err)
|
249
|
|
- return
|
250
|
|
- }
|
251
|
|
- w.Header().Set("Content-Type", "text/html")
|
252
|
|
- w.Write(html.Bytes())
|
|
275
|
+ ui.render(w, "/", "graph", errList, legend, webArgs{
|
|
276
|
+ HTMLBody: template.HTML(string(svg)),
|
|
277
|
+ Nodes: nodes,
|
|
278
|
+ })
|
253
|
279
|
}
|
254
|
280
|
|
255
|
281
|
func dotToSvg(dot []byte) ([]byte, error) {
|
|
@@ -271,91 +297,75 @@ func dotToSvg(dot []byte) ([]byte, error) {
|
271
|
297
|
}
|
272
|
298
|
|
273
|
299
|
func (ui *webInterface) top(w http.ResponseWriter, req *http.Request) {
|
274
|
|
- // Capture any error messages generated while generating a report.
|
275
|
|
- catcher := &errorCatcher{UI: ui.options.UI}
|
276
|
|
- options := *ui.options
|
277
|
|
- options.UI = catcher
|
278
|
|
-
|
279
|
|
- // Generate top report
|
280
|
|
- args := []string{"top"}
|
281
|
|
- vars := varsFromURL(req.URL)
|
282
|
|
- vars["nodecount"].value = "500"
|
283
|
|
- _, rpt, err := generateRawReport(ui.prof, args, vars, &options)
|
284
|
|
- if err != nil {
|
285
|
|
- http.Error(w, err.Error(), http.StatusBadRequest)
|
286
|
|
- ui.options.UI.PrintErr(err)
|
287
|
|
- return
|
|
300
|
+ rpt, errList := ui.makeReport(w, req, []string{"top"}, "nodecount", "500")
|
|
301
|
+ if rpt == nil {
|
|
302
|
+ return // error already reported
|
288
|
303
|
}
|
289
|
|
-
|
290
|
304
|
top, legend := report.TextItems(rpt)
|
291
|
|
-
|
292
|
|
- // Get all node names into an array.
|
293
|
305
|
var nodes []string
|
294
|
306
|
for _, item := range top {
|
295
|
307
|
nodes = append(nodes, item.Name)
|
296
|
308
|
}
|
297
|
309
|
|
298
|
|
- // Embed in html.
|
299
|
|
- file := getFromLegend(legend, "File: ", "unknown")
|
300
|
|
- profile := getFromLegend(legend, "Type: ", "unknown")
|
301
|
|
- data := webArgs{
|
302
|
|
- BaseURL: "/top",
|
303
|
|
- Type: "top",
|
304
|
|
- Title: file + " " + profile,
|
305
|
|
- Errors: catcher.errors,
|
306
|
|
- Legend: legend,
|
307
|
|
- Help: ui.help,
|
308
|
|
- Top: top,
|
309
|
|
- Nodes: nodes,
|
310
|
|
- }
|
311
|
|
- html := &bytes.Buffer{}
|
312
|
|
- if err := webTemplate.ExecuteTemplate(html, "top", data); err != nil {
|
313
|
|
- http.Error(w, "internal template error", http.StatusInternalServerError)
|
314
|
|
- ui.options.UI.PrintErr(err)
|
315
|
|
- return
|
316
|
|
- }
|
317
|
|
- w.Header().Set("Content-Type", "text/html")
|
318
|
|
- w.Write(html.Bytes())
|
|
310
|
+ ui.render(w, "/top", "top", errList, legend, webArgs{
|
|
311
|
+ Top: top,
|
|
312
|
+ Nodes: nodes,
|
|
313
|
+ })
|
319
|
314
|
}
|
320
|
315
|
|
321
|
316
|
// disasm generates a web page containing disassembly.
|
322
|
317
|
func (ui *webInterface) disasm(w http.ResponseWriter, req *http.Request) {
|
323
|
|
- ui.output(w, req, "disasm", "text/plain", pprofVariables.makeCopy())
|
324
|
|
-}
|
|
318
|
+ args := []string{"disasm", req.URL.Query().Get("f")}
|
|
319
|
+ rpt, errList := ui.makeReport(w, req, args)
|
|
320
|
+ if rpt == nil {
|
|
321
|
+ return // error already reported
|
|
322
|
+ }
|
325
|
323
|
|
326
|
|
-// weblist generates a web page containing disassembly.
|
327
|
|
-func (ui *webInterface) weblist(w http.ResponseWriter, req *http.Request) {
|
328
|
|
- ui.output(w, req, "weblist", "text/html", pprofVariables.makeCopy())
|
329
|
|
-}
|
|
324
|
+ out := &bytes.Buffer{}
|
|
325
|
+ if err := report.PrintAssembly(out, rpt, ui.options.Obj, maxEntries); err != nil {
|
|
326
|
+ http.Error(w, err.Error(), http.StatusBadRequest)
|
|
327
|
+ ui.options.UI.PrintErr(err)
|
|
328
|
+ return
|
|
329
|
+ }
|
|
330
|
+
|
|
331
|
+ legend := report.ProfileLabels(rpt)
|
|
332
|
+ ui.render(w, "/disasm", "plaintext", errList, legend, webArgs{
|
|
333
|
+ TextBody: out.String(),
|
|
334
|
+ })
|
330
|
335
|
|
331
|
|
-// peek generates a web page listing callers/callers.
|
332
|
|
-func (ui *webInterface) peek(w http.ResponseWriter, req *http.Request) {
|
333
|
|
- vars := pprofVariables.makeCopy()
|
334
|
|
- vars.set("lines", "t") // Switch to line granularity
|
335
|
|
- ui.output(w, req, "peek", "text/plain", vars)
|
336
|
336
|
}
|
337
|
337
|
|
338
|
|
-// output generates a webpage that contains the output of the specified pprof cmd.
|
339
|
|
-func (ui *webInterface) output(w http.ResponseWriter, req *http.Request, cmd, ctype string, vars variables) {
|
340
|
|
- focus := req.URL.Query().Get("f")
|
341
|
|
- if focus == "" {
|
342
|
|
- fmt.Fprintln(w, "no argument supplied for "+cmd)
|
343
|
|
- return
|
|
338
|
+// source generates a web page containing source code annotated with profile
|
|
339
|
+// data.
|
|
340
|
+func (ui *webInterface) source(w http.ResponseWriter, req *http.Request) {
|
|
341
|
+ args := []string{"weblist", req.URL.Query().Get("f")}
|
|
342
|
+ rpt, errList := ui.makeReport(w, req, args)
|
|
343
|
+ if rpt == nil {
|
|
344
|
+ return // error already reported
|
344
|
345
|
}
|
345
|
346
|
|
346
|
|
- // Capture any error messages generated while generating a report.
|
347
|
|
- catcher := &errorCatcher{UI: ui.options.UI}
|
348
|
|
- options := *ui.options
|
349
|
|
- options.UI = catcher
|
350
|
|
-
|
351
|
|
- args := []string{cmd, focus}
|
352
|
|
- _, rpt, err := generateRawReport(ui.prof, args, vars, &options)
|
353
|
|
- if err != nil {
|
|
347
|
+ // Generate source listing.
|
|
348
|
+ var body bytes.Buffer
|
|
349
|
+ if err := report.PrintWebList(&body, rpt, ui.options.Obj, maxEntries); err != nil {
|
354
|
350
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
355
|
351
|
ui.options.UI.PrintErr(err)
|
356
|
352
|
return
|
357
|
353
|
}
|
358
|
354
|
|
|
355
|
+ legend := report.ProfileLabels(rpt)
|
|
356
|
+ ui.render(w, "/source", "sourcelisting", errList, legend, webArgs{
|
|
357
|
+ HTMLBody: template.HTML(body.String()),
|
|
358
|
+ })
|
|
359
|
+}
|
|
360
|
+
|
|
361
|
+// peek generates a web page listing callers/callers.
|
|
362
|
+func (ui *webInterface) peek(w http.ResponseWriter, req *http.Request) {
|
|
363
|
+ args := []string{"peek", req.URL.Query().Get("f")}
|
|
364
|
+ rpt, errList := ui.makeReport(w, req, args, "lines", "t")
|
|
365
|
+ if rpt == nil {
|
|
366
|
+ return // error already reported
|
|
367
|
+ }
|
|
368
|
+
|
359
|
369
|
out := &bytes.Buffer{}
|
360
|
370
|
if err := report.Generate(out, rpt, ui.options.Obj); err != nil {
|
361
|
371
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
@@ -363,16 +373,10 @@ func (ui *webInterface) output(w http.ResponseWriter, req *http.Request, cmd, ct
|
363
|
373
|
return
|
364
|
374
|
}
|
365
|
375
|
|
366
|
|
- if len(catcher.errors) > 0 {
|
367
|
|
- w.Header().Set("Content-Type", "text/plain")
|
368
|
|
- for _, msg := range catcher.errors {
|
369
|
|
- fmt.Println(w, msg)
|
370
|
|
- }
|
371
|
|
- return
|
372
|
|
- }
|
373
|
|
-
|
374
|
|
- w.Header().Set("Content-Type", ctype)
|
375
|
|
- io.Copy(w, out)
|
|
376
|
+ legend := report.ProfileLabels(rpt)
|
|
377
|
+ ui.render(w, "/peek", "plaintext", errList, legend, webArgs{
|
|
378
|
+ TextBody: out.String(),
|
|
379
|
+ })
|
376
|
380
|
}
|
377
|
381
|
|
378
|
382
|
// getFromLegend returns the suffix of an entry in legend that starts
|