瀏覽代碼

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,21 +16,18 @@ package driver
16 16
 
17 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 22
 {{define "css"}}
27 23
 <style type="text/css">
28
-html, body {
24
+html {
29 25
   height: 100%;
30 26
   min-height: 100%;
31 27
   margin: 0px;
32 28
 }
33 29
 body {
30
+  margin: 0px;
34 31
   width: 100%;
35 32
   height: 100%;
36 33
   min-height: 100%;
@@ -129,11 +126,12 @@ button {
129 126
 #searchbox {
130 127
   margin-left: 10pt;
131 128
 }
132
-#topcontainer {
129
+#bodycontainer {
133 130
   width: 100%;
134 131
   height: 100%;
135 132
   max-height: 100%;
136 133
   overflow: scroll;
134
+  padding-top: 5px;
137 135
 }
138 136
 #toptable {
139 137
   border-spacing: 0px;
@@ -176,23 +174,13 @@ button {
176 174
 <div class="menu-header">
177 175
 View
178 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 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 181
 <button title="{{.Help.disasm}}" id="disasm">Disassemble</button>
182
+<hr>
183
+<button title="{{.Help.details}}" id="details">Details</button>
196 184
 </div>
197 185
 </div>
198 186
 
@@ -230,7 +218,7 @@ Refine
230 218
 {{template "header" .}}
231 219
 <div id="graphcontainer">
232 220
 <div id="graph">
233
-{{.Svg}}
221
+{{.HTMLBody}}
234 222
 </div>
235 223
 
236 224
 </div>
@@ -484,15 +472,15 @@ function viewer(baseUrl, nodes) {
484 472
   }
485 473
 
486 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 485
   function handleKey(e) {
498 486
     if (e.keyCode != 13) return
@@ -621,7 +609,7 @@ function viewer(baseUrl, nodes) {
621 609
 
622 610
   // Navigate to specified path with current selection reflected
623 611
   // in the named parameter.
624
-  function navigate(path, param, newWindow) {
612
+  function navigate(path, param) {
625 613
     // The selection can be in one of two modes: regexp-based or
626 614
     // list-based.  Construct regular expression depending on mode.
627 615
     let re = regexpActive ? search.value : ""
@@ -648,11 +636,7 @@ function viewer(baseUrl, nodes) {
648 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 642
   function handleTopClick(e) {
@@ -686,7 +670,7 @@ function viewer(baseUrl, nodes) {
686 670
     const enable = (search.value != "" || selected.size != 0)
687 671
     if (buttonsEnabled == enable) return
688 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 674
       const btn = document.getElementById(id)
691 675
       if (btn != null) {
692 676
         btn.disabled = !enable
@@ -730,6 +714,12 @@ function viewer(baseUrl, nodes) {
730 714
 
731 715
   search.addEventListener("input", handleSearch)
732 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 724
 </script>
735 725
 {{end}}
@@ -746,7 +736,7 @@ function viewer(baseUrl, nodes) {
746 736
 
747 737
 {{template "header" .}}
748 738
 
749
-<div id="topcontainer">
739
+<div id="bodycontainer">
750 740
 <table id="toptable">
751 741
 <tr><th>Flat<th>Flat%<th>Sum%<th>Cum<th>Cum%<th>Name<th>Inlined?</tr>
752 742
 {{range $i,$e := .Top}}
@@ -760,4 +750,53 @@ function viewer(baseUrl, nodes) {
760 750
 </body>
761 751
 </html>
762 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,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

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

@@ -32,11 +32,7 @@ import (
32 32
 
33 33
 func TestWebInterface(t *testing.T) {
34 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 37
 	// Start test server.
42 38
 	server := httptest.NewServer(http.HandlerFunc(
@@ -50,8 +46,8 @@ func TestWebInterface(t *testing.T) {
50 46
 				ui.disasm(w, r)
51 47
 			case "/peek":
52 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 53
 	defer server.Close()
@@ -69,7 +65,7 @@ func TestWebInterface(t *testing.T) {
69 65
 	testcases := []testCase{
70 66
 		{"/", []string{"F1", "F2", "F3", "testbin", "cpu"}, true},
71 67
 		{"/top", []string{"Flat", "200ms.*100%.*F2"}, false},
72
-		{"/weblist?f=" + url.QueryEscape("F[12]"),
68
+		{"/source?f=" + url.QueryEscape("F[12]"),
73 69
 			[]string{"F1", "F2", "300ms line1"}, false},
74 70
 		{"/peek?f=" + url.QueryEscape("F[12]"),
75 71
 			[]string{"300ms.*F1", "200ms.*300ms.*F2"}, false},

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

@@ -342,6 +342,11 @@ func (fm functionMap) FindOrAdd(ni graph.NodeInfo) *profile.Function {
342 342
 
343 343
 // printAssembly prints an annotated assembly listing.
344 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 350
 	o := rpt.options
346 351
 	prof := rpt.prof
347 352
 
@@ -357,12 +362,34 @@ func printAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
357 362
 	fmt.Fprintln(w, "Total:", rpt.formatValue(rpt.total))
358 363
 	symbols := symbolsFromBinaries(prof, g, o.Symbol, address, obj)
359 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 368
 	for s := range symNodes {
363 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 394
 	// Correlate the symbols from the binary with the profile samples.
368 395
 	for _, s := range syms {
@@ -492,23 +519,15 @@ type objSymbol struct {
492 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 532
 // nodesPerSymbol classifies nodes into a group of symbols.
514 533
 func nodesPerSymbol(ns graph.Nodes, symbols []*objSymbol) map[*objSymbol]graph.Nodes {

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

@@ -116,6 +116,16 @@ func printSource(w io.Writer, rpt *Report) error {
116 116
 // printWebSource prints an annotated source listing, include all
117 117
 // functions with samples that match the regexp rpt.options.symbol.
118 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 129
 	o := rpt.options
120 130
 	g := rpt.newGraph(nil)
121 131
 
@@ -176,10 +186,18 @@ func printWebSource(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
176 186
 		sNode.Flat, sNode.Cum = nodes.Sum()
177 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 200
 	// Print each file associated with this function.
182
-	printHeader(w, rpt)
183 201
 	for _, n := range sourceFiles {
184 202
 		ff := fileFunction{n.Info.File, n.Info.Name}
185 203
 		fns := fileNodes[ff]
@@ -198,7 +216,6 @@ func printWebSource(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
198 216
 		}
199 217
 		printFunctionClosing(w)
200 218
 	}
201
-	printPageClosing(w)
202 219
 	return nil
203 220
 }
204 221
 
@@ -272,7 +289,15 @@ func findMatchingSymbol(objSyms []*objSymbol, ns graph.Nodes) *objSymbol {
272 289
 
273 290
 // printHeader prints the page header for a weblist report.
274 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 302
 	var labels []string
278 303
 	for _, l := range ProfileLabels(rpt) {

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

@@ -14,13 +14,17 @@
14 14
 
15 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 28
 body {
25 29
 font-family: sans-serif;
26 30
 }
@@ -60,8 +64,9 @@ background-color: #eeeeee;
60 64
 color: #008800;
61 65
 display: none;
62 66
 }
63
-</style>
64
-<script type="text/javascript">
67
+</style>`
68
+
69
+const weblistPageScript = `<script type="text/javascript">
65 70
 function pprof_toggle_asm(e) {
66 71
   var target;
67 72
   if (!e) e = window.event;
@@ -77,10 +82,7 @@ function pprof_toggle_asm(e) {
77 82
     }
78 83
   }
79 84
 }
80
-</script>
81
-</head>
82
-<body>
83
-`
85
+</script>`
84 86
 
85 87
 const weblistPageClosing = `
86 88
 </body>