瀏覽代碼

Use common header for remaining web views (source code, disassembly, peek) (#214)

Use common header for remaining web views (source code, disassembly, peek).

Details:
* Made it easier to reuse css, javascript, etc. across views.
* Added two new templates: sourcelisting, plaintext.
* Use new templates for showing source code, disassembly, peek.
* Merged Functions menu into View menu.
* View change menu entries are now always enabled.
* Removed now unused "newWindow" argument to navigate.
* Removed redundancy in web handlers.
* Renamed /weblist URL to /source.
* Assembly and source code listings are now limited to 500 functions
  sorted by decreasing flat value (to prevent huge delays when
  selecting asm/source view across the entire profile).
Sanjay Ghemawat 7 年之前
父節點
當前提交
94bf658b7d
共有 6 個檔案被更改,包括 295 行新增210 行删除
  1. 81
    42
      internal/driver/webhtml.go
  2. 129
    125
      internal/driver/webui.go
  3. 4
    8
      internal/driver/webui_test.go
  4. 37
    18
      internal/report/report.go
  5. 29
    4
      internal/report/source.go
  6. 15
    13
      internal/report/source_html.go

+ 81
- 42
internal/driver/webhtml.go 查看文件

16
 
16
 
17
 import "html/template"
17
 import "html/template"
18
 
18
 
19
-// webTemplate defines a collection of related templates:
20
-//    css
21
-//    header
22
-//    script
23
-//    graph
24
-//    top
25
-var webTemplate = template.Must(template.New("web").Parse(`
19
+// addTemplates adds a set of template definitions to templates.
20
+func addTemplates(templates *template.Template) {
21
+	template.Must(templates.Parse(`
26
 {{define "css"}}
22
 {{define "css"}}
27
 <style type="text/css">
23
 <style type="text/css">
28
-html, body {
24
+html {
29
   height: 100%;
25
   height: 100%;
30
   min-height: 100%;
26
   min-height: 100%;
31
   margin: 0px;
27
   margin: 0px;
32
 }
28
 }
33
 body {
29
 body {
30
+  margin: 0px;
34
   width: 100%;
31
   width: 100%;
35
   height: 100%;
32
   height: 100%;
36
   min-height: 100%;
33
   min-height: 100%;
129
 #searchbox {
126
 #searchbox {
130
   margin-left: 10pt;
127
   margin-left: 10pt;
131
 }
128
 }
132
-#topcontainer {
129
+#bodycontainer {
133
   width: 100%;
130
   width: 100%;
134
   height: 100%;
131
   height: 100%;
135
   max-height: 100%;
132
   max-height: 100%;
136
   overflow: scroll;
133
   overflow: scroll;
134
+  padding-top: 5px;
137
 }
135
 }
138
 #toptable {
136
 #toptable {
139
   border-spacing: 0px;
137
   border-spacing: 0px;
176
 <div class="menu-header">
174
 <div class="menu-header">
177
 View
175
 View
178
 <div class="menu">
176
 <div class="menu">
179
-{{if (ne .Type "top")}}
180
-  <button title="{{.Help.top}}" id="topbtn">Top</button>
181
-{{end}}
182
-{{if (ne .Type "dot")}}
183
-  <button title="{{.Help.graph}}" id="graphbtn">Graph</button>
184
-{{end}}
185
-<hr>
186
-<button title="{{.Help.details}}" id="details">Details</button>
187
-</div>
188
-</div>
189
-
190
-<div class="menu-header">
191
-Functions
192
-<div class="menu">
177
+<button title="{{.Help.top}}" id="topbtn">Top</button>
178
+<button title="{{.Help.graph}}" id="graphbtn">Graph</button>
193
 <button title="{{.Help.peek}}" id="peek">Peek</button>
179
 <button title="{{.Help.peek}}" id="peek">Peek</button>
194
-<button title="{{.Help.list}}" id="list">List</button>
180
+<button title="{{.Help.list}}" id="list">Source</button>
195
 <button title="{{.Help.disasm}}" id="disasm">Disassemble</button>
181
 <button title="{{.Help.disasm}}" id="disasm">Disassemble</button>
182
+<hr>
183
+<button title="{{.Help.details}}" id="details">Details</button>
196
 </div>
184
 </div>
197
 </div>
185
 </div>
198
 
186
 
230
 {{template "header" .}}
218
 {{template "header" .}}
231
 <div id="graphcontainer">
219
 <div id="graphcontainer">
232
 <div id="graph">
220
 <div id="graph">
233
-{{.Svg}}
221
+{{.HTMLBody}}
234
 </div>
222
 </div>
235
 
223
 
236
 </div>
224
 </div>
484
   }
472
   }
485
 
473
 
486
   function handleReset() { window.location.href = baseUrl }
474
   function handleReset() { window.location.href = baseUrl }
487
-  function handleTop() { navigate("/top", "f", false) }
488
-  function handleGraph() { navigate("/", "f", false) }
489
-  function handleList() { navigate("/weblist", "f", true) }
490
-  function handleDisasm() { navigate("/disasm", "f", true) }
491
-  function handlePeek() { navigate("/peek", "f", true) }
492
-  function handleFocus() { navigate(baseUrl, "f", false) }
493
-  function handleShow() { navigate(baseUrl, "s", false) }
494
-  function handleIgnore() { navigate(baseUrl, "i", false) }
495
-  function handleHide() { navigate(baseUrl, "h", false) }
475
+  function handleTop() { navigate("/top", "f") }
476
+  function handleGraph() { navigate("/", "f") }
477
+  function handleList() { navigate("/source", "f") }
478
+  function handleDisasm() { navigate("/disasm", "f") }
479
+  function handlePeek() { navigate("/peek", "f") }
480
+  function handleFocus() { navigate(baseUrl, "f") }
481
+  function handleShow() { navigate(baseUrl, "s") }
482
+  function handleIgnore() { navigate(baseUrl, "i") }
483
+  function handleHide() { navigate(baseUrl, "h") }
496
 
484
 
497
   function handleKey(e) {
485
   function handleKey(e) {
498
     if (e.keyCode != 13) return
486
     if (e.keyCode != 13) return
621
 
609
 
622
   // Navigate to specified path with current selection reflected
610
   // Navigate to specified path with current selection reflected
623
   // in the named parameter.
611
   // in the named parameter.
624
-  function navigate(path, param, newWindow) {
612
+  function navigate(path, param) {
625
     // The selection can be in one of two modes: regexp-based or
613
     // The selection can be in one of two modes: regexp-based or
626
     // list-based.  Construct regular expression depending on mode.
614
     // list-based.  Construct regular expression depending on mode.
627
     let re = regexpActive ? search.value : ""
615
     let re = regexpActive ? search.value : ""
648
       params.set(param, re)
636
       params.set(param, re)
649
     }
637
     }
650
 
638
 
651
-    if (newWindow) {
652
-      window.open(url.toString(), "_blank")
653
-    } else {
654
-      window.location.href = url.toString()
655
-    }
639
+    window.location.href = url.toString()
656
   }
640
   }
657
 
641
 
658
   function handleTopClick(e) {
642
   function handleTopClick(e) {
686
     const enable = (search.value != "" || selected.size != 0)
670
     const enable = (search.value != "" || selected.size != 0)
687
     if (buttonsEnabled == enable) return
671
     if (buttonsEnabled == enable) return
688
     buttonsEnabled = enable
672
     buttonsEnabled = enable
689
-    for (const id of ["peek", "list", "disasm", "focus", "ignore", "hide", "show"]) {
673
+    for (const id of ["focus", "ignore", "hide", "show"]) {
690
       const btn = document.getElementById(id)
674
       const btn = document.getElementById(id)
691
       if (btn != null) {
675
       if (btn != null) {
692
         btn.disabled = !enable
676
         btn.disabled = !enable
730
 
714
 
731
   search.addEventListener("input", handleSearch)
715
   search.addEventListener("input", handleSearch)
732
   search.addEventListener("keydown", handleKey)
716
   search.addEventListener("keydown", handleKey)
717
+
718
+  // Give initial focus to main container so it can be scrolled using keys.
719
+  const main = document.getElementById("bodycontainer")
720
+  if (main) {
721
+    main.focus()
722
+  }
733
 }
723
 }
734
 </script>
724
 </script>
735
 {{end}}
725
 {{end}}
746
 
736
 
747
 {{template "header" .}}
737
 {{template "header" .}}
748
 
738
 
749
-<div id="topcontainer">
739
+<div id="bodycontainer">
750
 <table id="toptable">
740
 <table id="toptable">
751
 <tr><th>Flat<th>Flat%<th>Sum%<th>Cum<th>Cum%<th>Name<th>Inlined?</tr>
741
 <tr><th>Flat<th>Flat%<th>Sum%<th>Cum<th>Cum%<th>Name<th>Inlined?</tr>
752
 {{range $i,$e := .Top}}
742
 {{range $i,$e := .Top}}
760
 </body>
750
 </body>
761
 </html>
751
 </html>
762
 {{end}}
752
 {{end}}
753
+
754
+{{define "sourcelisting" -}}
755
+<!DOCTYPE html>
756
+<html>
757
+<head>
758
+<meta charset="utf-8">
759
+<title>{{.Title}}</title>
760
+{{template "css" .}}
761
+{{template "weblistcss" .}}
762
+{{template "weblistjs" .}}
763
+</head>
764
+<body>
765
+
766
+{{template "header" .}}
767
+
768
+<div id="bodycontainer">
769
+{{.HTMLBody}}
770
+</div>
771
+
772
+{{template "script" .}}
773
+<script>viewer({{.BaseURL}}, null)</script>
774
+</body>
775
+</html>
776
+{{end}}
777
+
778
+{{define "plaintext" -}}
779
+<!DOCTYPE html>
780
+<html>
781
+<head>
782
+<meta charset="utf-8">
783
+<title>{{.Title}}</title>
784
+{{template "css" .}}
785
+</head>
786
+<body>
787
+
788
+{{template "header" .}}
789
+
790
+<div id="bodycontainer">
791
+<pre>
792
+{{.TextBody}}
793
+</pre>
794
+</div>
795
+
796
+{{template "script" .}}
797
+<script>viewer({{.BaseURL}}, null)</script>
798
+</body>
799
+</html>
800
+{{end}}
763
 `))
801
 `))
802
+}

+ 129
- 125
internal/driver/webui.go 查看文件

18
 	"bytes"
18
 	"bytes"
19
 	"fmt"
19
 	"fmt"
20
 	"html/template"
20
 	"html/template"
21
-	"io"
22
 	"net"
21
 	"net"
23
 	"net/http"
22
 	"net/http"
24
 	gourl "net/url"
23
 	gourl "net/url"
35
 
34
 
36
 // webInterface holds the state needed for serving a browser based interface.
35
 // webInterface holds the state needed for serving a browser based interface.
37
 type webInterface struct {
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
 // errorCatcher is a UI that captures errors for reporting to the browser.
58
 // errorCatcher is a UI that captures errors for reporting to the browser.
44
 type errorCatcher struct {
59
 type errorCatcher struct {
45
 	plugin.UI
60
 	plugin.UI
53
 
68
 
54
 // webArgs contains arguments passed to templates in webhtml.go.
69
 // webArgs contains arguments passed to templates in webhtml.go.
55
 type webArgs struct {
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
 func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options) error {
82
 func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options) error {
68
 	interactiveMode = true
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
 	for n, c := range pprofCommands {
85
 	for n, c := range pprofCommands {
75
 		ui.help[n] = c.description
86
 		ui.help[n] = c.description
76
 	}
87
 	}
101
 	mux.Handle("/", wrap(http.HandlerFunc(ui.dot)))
112
 	mux.Handle("/", wrap(http.HandlerFunc(ui.dot)))
102
 	mux.Handle("/top", wrap(http.HandlerFunc(ui.top)))
113
 	mux.Handle("/top", wrap(http.HandlerFunc(ui.top)))
103
 	mux.Handle("/disasm", wrap(http.HandlerFunc(ui.disasm)))
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
 	mux.Handle("/peek", wrap(http.HandlerFunc(ui.peek)))
116
 	mux.Handle("/peek", wrap(http.HandlerFunc(ui.peek)))
106
 
117
 
107
 	s := &http.Server{Handler: mux}
118
 	s := &http.Server{Handler: mux}
187
 	return vars
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
 	catcher := &errorCatcher{UI: ui.options.UI}
208
 	catcher := &errorCatcher{UI: ui.options.UI}
199
 	options := *ui.options
209
 	options := *ui.options
200
 	options.UI = catcher
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
 	if err != nil {
212
 	if err != nil {
207
 		http.Error(w, err.Error(), http.StatusBadRequest)
213
 		http.Error(w, err.Error(), http.StatusBadRequest)
208
 		ui.options.UI.PrintErr(err)
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
 		return
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
 	g, config := report.GetDOT(rpt)
254
 	g, config := report.GetDOT(rpt)
212
 	legend := config.Labels
255
 	legend := config.Labels
213
 	config.Labels = nil
256
 	config.Labels = nil
229
 		nodes = append(nodes, n.Info.Name)
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
 func dotToSvg(dot []byte) ([]byte, error) {
281
 func dotToSvg(dot []byte) ([]byte, error) {
271
 }
297
 }
272
 
298
 
273
 func (ui *webInterface) top(w http.ResponseWriter, req *http.Request) {
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
 	top, legend := report.TextItems(rpt)
304
 	top, legend := report.TextItems(rpt)
291
-
292
-	// Get all node names into an array.
293
 	var nodes []string
305
 	var nodes []string
294
 	for _, item := range top {
306
 	for _, item := range top {
295
 		nodes = append(nodes, item.Name)
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
 // disasm generates a web page containing disassembly.
316
 // disasm generates a web page containing disassembly.
322
 func (ui *webInterface) disasm(w http.ResponseWriter, req *http.Request) {
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
 		http.Error(w, err.Error(), http.StatusBadRequest)
350
 		http.Error(w, err.Error(), http.StatusBadRequest)
355
 		ui.options.UI.PrintErr(err)
351
 		ui.options.UI.PrintErr(err)
356
 		return
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
 	out := &bytes.Buffer{}
369
 	out := &bytes.Buffer{}
360
 	if err := report.Generate(out, rpt, ui.options.Obj); err != nil {
370
 	if err := report.Generate(out, rpt, ui.options.Obj); err != nil {
361
 		http.Error(w, err.Error(), http.StatusBadRequest)
371
 		http.Error(w, err.Error(), http.StatusBadRequest)
363
 		return
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
 // getFromLegend returns the suffix of an entry in legend that starts
382
 // getFromLegend returns the suffix of an entry in legend that starts

+ 4
- 8
internal/driver/webui_test.go 查看文件

32
 
32
 
33
 func TestWebInterface(t *testing.T) {
33
 func TestWebInterface(t *testing.T) {
34
 	prof := makeFakeProfile()
34
 	prof := makeFakeProfile()
35
-	ui := &webInterface{
36
-		prof:    prof,
37
-		options: &plugin.Options{Obj: fakeObjTool{}},
38
-		help:    make(map[string]string),
39
-	}
35
+	ui := makeWebInterface(prof, &plugin.Options{Obj: fakeObjTool{}})
40
 
36
 
41
 	// Start test server.
37
 	// Start test server.
42
 	server := httptest.NewServer(http.HandlerFunc(
38
 	server := httptest.NewServer(http.HandlerFunc(
50
 				ui.disasm(w, r)
46
 				ui.disasm(w, r)
51
 			case "/peek":
47
 			case "/peek":
52
 				ui.peek(w, r)
48
 				ui.peek(w, r)
53
-			case "/weblist":
54
-				ui.weblist(w, r)
49
+			case "/source":
50
+				ui.source(w, r)
55
 			}
51
 			}
56
 		}))
52
 		}))
57
 	defer server.Close()
53
 	defer server.Close()
69
 	testcases := []testCase{
65
 	testcases := []testCase{
70
 		{"/", []string{"F1", "F2", "F3", "testbin", "cpu"}, true},
66
 		{"/", []string{"F1", "F2", "F3", "testbin", "cpu"}, true},
71
 		{"/top", []string{"Flat", "200ms.*100%.*F2"}, false},
67
 		{"/top", []string{"Flat", "200ms.*100%.*F2"}, false},
72
-		{"/weblist?f=" + url.QueryEscape("F[12]"),
68
+		{"/source?f=" + url.QueryEscape("F[12]"),
73
 			[]string{"F1", "F2", "300ms line1"}, false},
69
 			[]string{"F1", "F2", "300ms line1"}, false},
74
 		{"/peek?f=" + url.QueryEscape("F[12]"),
70
 		{"/peek?f=" + url.QueryEscape("F[12]"),
75
 			[]string{"300ms.*F1", "200ms.*300ms.*F2"}, false},
71
 			[]string{"300ms.*F1", "200ms.*300ms.*F2"}, false},

+ 37
- 18
internal/report/report.go 查看文件

342
 
342
 
343
 // printAssembly prints an annotated assembly listing.
343
 // printAssembly prints an annotated assembly listing.
344
 func printAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
344
 func printAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
345
+	return PrintAssembly(w, rpt, obj, -1)
346
+}
347
+
348
+// PrintAssembly prints annotated disasssembly of rpt to w.
349
+func PrintAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFuncs int) error {
345
 	o := rpt.options
350
 	o := rpt.options
346
 	prof := rpt.prof
351
 	prof := rpt.prof
347
 
352
 
357
 	fmt.Fprintln(w, "Total:", rpt.formatValue(rpt.total))
362
 	fmt.Fprintln(w, "Total:", rpt.formatValue(rpt.total))
358
 	symbols := symbolsFromBinaries(prof, g, o.Symbol, address, obj)
363
 	symbols := symbolsFromBinaries(prof, g, o.Symbol, address, obj)
359
 	symNodes := nodesPerSymbol(g.Nodes, symbols)
364
 	symNodes := nodesPerSymbol(g.Nodes, symbols)
360
-	// Sort function names for printing.
361
-	var syms objSymbols
365
+
366
+	// Sort for printing.
367
+	var syms []*objSymbol
362
 	for s := range symNodes {
368
 	for s := range symNodes {
363
 		syms = append(syms, s)
369
 		syms = append(syms, s)
364
 	}
370
 	}
365
-	sort.Sort(syms)
371
+	byName := func(a, b *objSymbol) bool {
372
+		if na, nb := a.sym.Name[0], b.sym.Name[0]; na != nb {
373
+			return na < nb
374
+		}
375
+		return a.sym.Start < b.sym.Start
376
+	}
377
+	if maxFuncs < 0 {
378
+		sort.Sort(orderSyms{syms, byName})
379
+	} else {
380
+		byFlatSum := func(a, b *objSymbol) bool {
381
+			suma, _ := symNodes[a].Sum()
382
+			sumb, _ := symNodes[b].Sum()
383
+			if suma != sumb {
384
+				return suma > sumb
385
+			}
386
+			return byName(a, b)
387
+		}
388
+		sort.Sort(orderSyms{syms, byFlatSum})
389
+		if len(syms) > maxFuncs {
390
+			syms = syms[:maxFuncs]
391
+		}
392
+	}
366
 
393
 
367
 	// Correlate the symbols from the binary with the profile samples.
394
 	// Correlate the symbols from the binary with the profile samples.
368
 	for _, s := range syms {
395
 	for _, s := range syms {
492
 	base uint64
519
 	base uint64
493
 }
520
 }
494
 
521
 
495
-// objSymbols is a wrapper type to enable sorting of []*objSymbol.
496
-type objSymbols []*objSymbol
497
-
498
-func (o objSymbols) Len() int {
499
-	return len(o)
522
+// orderSyms is a wrapper type to sort []*objSymbol by a supplied comparator.
523
+type orderSyms struct {
524
+	v    []*objSymbol
525
+	less func(a, b *objSymbol) bool
500
 }
526
 }
501
 
527
 
502
-func (o objSymbols) Less(i, j int) bool {
503
-	if namei, namej := o[i].sym.Name[0], o[j].sym.Name[0]; namei != namej {
504
-		return namei < namej
505
-	}
506
-	return o[i].sym.Start < o[j].sym.Start
507
-}
508
-
509
-func (o objSymbols) Swap(i, j int) {
510
-	o[i], o[j] = o[j], o[i]
511
-}
528
+func (o orderSyms) Len() int           { return len(o.v) }
529
+func (o orderSyms) Less(i, j int) bool { return o.less(o.v[i], o.v[j]) }
530
+func (o orderSyms) Swap(i, j int)      { o.v[i], o.v[j] = o.v[j], o.v[i] }
512
 
531
 
513
 // nodesPerSymbol classifies nodes into a group of symbols.
532
 // nodesPerSymbol classifies nodes into a group of symbols.
514
 func nodesPerSymbol(ns graph.Nodes, symbols []*objSymbol) map[*objSymbol]graph.Nodes {
533
 func nodesPerSymbol(ns graph.Nodes, symbols []*objSymbol) map[*objSymbol]graph.Nodes {

+ 29
- 4
internal/report/source.go 查看文件

116
 // printWebSource prints an annotated source listing, include all
116
 // printWebSource prints an annotated source listing, include all
117
 // functions with samples that match the regexp rpt.options.symbol.
117
 // functions with samples that match the regexp rpt.options.symbol.
118
 func printWebSource(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
118
 func printWebSource(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
119
+	printHeader(w, rpt)
120
+	if err := PrintWebList(w, rpt, obj, -1); err != nil {
121
+		return err
122
+	}
123
+	printPageClosing(w)
124
+	return nil
125
+}
126
+
127
+// PrintWebList prints annotated source listing of rpt to w.
128
+func PrintWebList(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFiles int) error {
119
 	o := rpt.options
129
 	o := rpt.options
120
 	g := rpt.newGraph(nil)
130
 	g := rpt.newGraph(nil)
121
 
131
 
176
 		sNode.Flat, sNode.Cum = nodes.Sum()
186
 		sNode.Flat, sNode.Cum = nodes.Sum()
177
 		sourceFiles = append(sourceFiles, &sNode)
187
 		sourceFiles = append(sourceFiles, &sNode)
178
 	}
188
 	}
179
-	sourceFiles.Sort(graph.FileOrder)
189
+
190
+	// Limit number of files printed?
191
+	if maxFiles < 0 {
192
+		sourceFiles.Sort(graph.FileOrder)
193
+	} else {
194
+		sourceFiles.Sort(graph.FlatNameOrder)
195
+		if maxFiles < len(sourceFiles) {
196
+			sourceFiles = sourceFiles[:maxFiles]
197
+		}
198
+	}
180
 
199
 
181
 	// Print each file associated with this function.
200
 	// Print each file associated with this function.
182
-	printHeader(w, rpt)
183
 	for _, n := range sourceFiles {
201
 	for _, n := range sourceFiles {
184
 		ff := fileFunction{n.Info.File, n.Info.Name}
202
 		ff := fileFunction{n.Info.File, n.Info.Name}
185
 		fns := fileNodes[ff]
203
 		fns := fileNodes[ff]
198
 		}
216
 		}
199
 		printFunctionClosing(w)
217
 		printFunctionClosing(w)
200
 	}
218
 	}
201
-	printPageClosing(w)
202
 	return nil
219
 	return nil
203
 }
220
 }
204
 
221
 
272
 
289
 
273
 // printHeader prints the page header for a weblist report.
290
 // printHeader prints the page header for a weblist report.
274
 func printHeader(w io.Writer, rpt *Report) {
291
 func printHeader(w io.Writer, rpt *Report) {
275
-	fmt.Fprintln(w, weblistPageHeader)
292
+	fmt.Fprintln(w, `
293
+<!DOCTYPE html>
294
+<html>
295
+<head>
296
+<meta charset="UTF-8">
297
+<title>Pprof listing</title>`)
298
+	fmt.Fprintln(w, weblistPageCSS)
299
+	fmt.Fprintln(w, weblistPageScript)
300
+	fmt.Fprint(w, "</head>\n<body>\n\n")
276
 
301
 
277
 	var labels []string
302
 	var labels []string
278
 	for _, l := range ProfileLabels(rpt) {
303
 	for _, l := range ProfileLabels(rpt) {

+ 15
- 13
internal/report/source_html.go 查看文件

14
 
14
 
15
 package report
15
 package report
16
 
16
 
17
-const weblistPageHeader = `
18
-<!DOCTYPE html>
19
-<html>
20
-<head>
21
-<meta charset="UTF-8">
22
-<title>Pprof listing</title>
23
-<style type="text/css">
17
+import (
18
+	"html/template"
19
+)
20
+
21
+// AddSourceTemplates adds templates used by PrintWebList to t.
22
+func AddSourceTemplates(t *template.Template) {
23
+	template.Must(t.Parse(`{{define "weblistcss"}}` + weblistPageCSS + `{{end}}`))
24
+	template.Must(t.Parse(`{{define "weblistjs"}}` + weblistPageScript + `{{end}}`))
25
+}
26
+
27
+const weblistPageCSS = `<style type="text/css">
24
 body {
28
 body {
25
 font-family: sans-serif;
29
 font-family: sans-serif;
26
 }
30
 }
60
 color: #008800;
64
 color: #008800;
61
 display: none;
65
 display: none;
62
 }
66
 }
63
-</style>
64
-<script type="text/javascript">
67
+</style>`
68
+
69
+const weblistPageScript = `<script type="text/javascript">
65
 function pprof_toggle_asm(e) {
70
 function pprof_toggle_asm(e) {
66
   var target;
71
   var target;
67
   if (!e) e = window.event;
72
   if (!e) e = window.event;
77
     }
82
     }
78
   }
83
   }
79
 }
84
 }
80
-</script>
81
-</head>
82
-<body>
83
-`
85
+</script>`
84
 
86
 
85
 const weblistPageClosing = `
87
 const weblistPageClosing = `
86
 </body>
88
 </body>