Parcourir la source

Make top table sortable by column (#220)

* Make top table sortable.

* Recompute Sum% column contents after sorting.

* Cleanups identified during review.

* Simplify top table sorting by generating it in Javascript.
Sanjay Ghemawat il y a 7 ans
Parent
révision
15340c8ac9
4 fichiers modifiés avec 137 ajouts et 30 suppressions
  1. 108
    9
      internal/driver/webhtml.go
  2. 8
    6
      internal/driver/webui.go
  3. 5
    2
      internal/driver/webui_test.go
  4. 16
    13
      internal/report/report.go

+ 108
- 9
internal/driver/webhtml.go Voir le fichier

@@ -142,14 +142,15 @@ button {
142 142
 #toptable {
143 143
   border-spacing: 0px;
144 144
   width: 100%;
145
+  padding-bottom: 1em;
145 146
 }
146 147
 #toptable tr th {
147 148
   border-bottom: 1px solid black;
148 149
   text-align: right;
149 150
   padding-left: 1em;
151
+  padding-top: 0.2em;
152
+  padding-bottom: 0.2em;
150 153
 }
151
-#toptable tr th:nth-child(6) { text-align: left; }
152
-#toptable tr th:nth-child(7) { text-align: left; }
153 154
 #toptable tr td {
154 155
   padding-left: 1em;
155 156
   font: monospace;
@@ -157,12 +158,19 @@ button {
157 158
   white-space: nowrap;
158 159
   cursor: default;
159 160
 }
160
-#toptable tr td:nth-child(6) {
161
+#toptable tr th:nth-child(6),
162
+#toptable tr th:nth-child(7),
163
+#toptable tr td:nth-child(6),
164
+#toptable tr td:nth-child(7) {
161 165
   text-align: left;
166
+}
167
+#toptable tr td:nth-child(6) {
162 168
   max-width: 30em;  // Truncate very long names
163 169
   overflow: hidden;
164 170
 }
165
-#toptable tr td:nth-child(7) { text-align: left; }
171
+#flathdr1, #flathdr2, #cumhdr1, #cumhdr2, #namehdr {
172
+  cursor: ns-resize;
173
+}
166 174
 .hilite {
167 175
   background-color: #ccf;
168 176
 }
@@ -741,6 +749,8 @@ function viewer(baseUrl, nodes) {
741 749
 <meta charset="utf-8">
742 750
 <title>{{.Title}}</title>
743 751
 {{template "css" .}}
752
+<style type="text/css">
753
+</style>
744 754
 </head>
745 755
 <body>
746 756
 
@@ -748,15 +758,104 @@ function viewer(baseUrl, nodes) {
748 758
 
749 759
 <div id="bodycontainer">
750 760
 <table id="toptable">
751
-<tr><th>Flat<th>Flat%<th>Sum%<th>Cum<th>Cum%<th>Name<th>Inlined?</tr>
752
-{{range $i,$e := .Top}}
753
-  <tr id="node{{$i}}"><td>{{$e.Flat}}<td>{{$e.FlatPercent}}<td>{{$e.SumPercent}}<td>{{$e.Cum}}<td>{{$e.CumPercent}}<td>{{$e.Name}}<td>{{$e.InlineLabel}}</tr>
754
-{{end}}
761
+<tr>
762
+<th id="flathdr1">Flat
763
+<th id="flathdr2">Flat%
764
+<th>Sum%
765
+<th id="cumhdr1">Cum
766
+<th id="cumhdr2">Cum%
767
+<th id="namehdr">Name
768
+<th>Inlined?</tr>
769
+<tbody id="rows">
770
+</tbody>
755 771
 </table>
756 772
 </div>
757 773
 
758 774
 {{template "script" .}}
759
-<script>viewer({{.BaseURL}}, {{.Nodes}})</script>
775
+<script>
776
+function makeTopTable(total, entries) {
777
+  const rows = document.getElementById("rows")
778
+  if (rows == null) return
779
+
780
+  // Store initial index in each entry so we have stable node ids for selection.
781
+  for (let i = 0; i < entries.length; i++) {
782
+    entries[i].Id = "node" + i
783
+  }
784
+
785
+  // Which column are we currently sorted by and in what order?
786
+  let currentColumn = ""
787
+  let descending = false
788
+  sortBy("Flat")
789
+
790
+  function sortBy(column) {
791
+    // Update sort criteria
792
+    if (column == currentColumn) {
793
+      descending = !descending  // Reverse order
794
+    } else {
795
+      currentColumn = column
796
+      descending = (column != "Name")
797
+    }
798
+
799
+    // Sort according to current criteria.
800
+    function cmp(a, b) {
801
+      const av = a[currentColumn]
802
+      const bv = b[currentColumn]
803
+      if (av < bv) return -1
804
+      if (av > bv) return +1
805
+      return 0
806
+    }
807
+    entries.sort(cmp)
808
+    if (descending) entries.reverse()
809
+
810
+    function addCell(tr, val) {
811
+      const td = document.createElement('td')
812
+      td.textContent = val
813
+      tr.appendChild(td)
814
+    }
815
+
816
+    function percent(v) {
817
+      return (v * 100.0 / total).toFixed(2) + "%"
818
+    }
819
+
820
+    // Generate rows
821
+    const fragment = document.createDocumentFragment()
822
+    let sum = 0
823
+    for (const row of entries) {
824
+      const tr = document.createElement('tr')
825
+      tr.id = row.Id
826
+      sum += row.Flat
827
+      addCell(tr, row.FlatFormat)
828
+      addCell(tr, percent(row.Flat))
829
+      addCell(tr, percent(sum))
830
+      addCell(tr, row.CumFormat)
831
+      addCell(tr, percent(row.Cum))
832
+      addCell(tr, row.Name)
833
+      addCell(tr, row.InlineLabel)
834
+      fragment.appendChild(tr)
835
+    }
836
+
837
+    rows.textContent = ''  // Remove old rows
838
+    rows.appendChild(fragment)
839
+  }
840
+
841
+  // Make different column headers trigger sorting.
842
+  function bindSort(id, column) {
843
+    const hdr = document.getElementById(id)
844
+    if (hdr == null) return
845
+    const fn = function() { sortBy(column) }
846
+    hdr.addEventListener("click", fn)
847
+    hdr.addEventListener("touch", fn)
848
+  }
849
+  bindSort("flathdr1", "Flat")
850
+  bindSort("flathdr2", "Flat")
851
+  bindSort("cumhdr1", "Cum")
852
+  bindSort("cumhdr2", "Cum")
853
+  bindSort("namehdr", "Name")
854
+}
855
+
856
+viewer({{.BaseURL}}, {{.Nodes}})
857
+makeTopTable({{.Total}}, {{.Top}})
858
+</script>
760 859
 </body>
761 860
 </html>
762 861
 {{end}}

+ 8
- 6
internal/driver/webui.go Voir le fichier

@@ -71,6 +71,7 @@ type webArgs struct {
71 71
 	BaseURL  string
72 72
 	Title    string
73 73
 	Errors   []string
74
+	Total    int64
74 75
 	Legend   []string
75 76
 	Help     map[string]string
76 77
 	Nodes    []string
@@ -219,12 +220,13 @@ func (ui *webInterface) makeReport(w http.ResponseWriter, req *http.Request,
219 220
 
220 221
 // render generates html using the named template based on the contents of data.
221 222
 func (ui *webInterface) render(w http.ResponseWriter, baseURL, tmpl string,
222
-	errList, legend []string, data webArgs) {
223
+	rpt *report.Report, errList, legend []string, data webArgs) {
223 224
 	file := getFromLegend(legend, "File: ", "unknown")
224 225
 	profile := getFromLegend(legend, "Type: ", "unknown")
225 226
 	data.BaseURL = baseURL
226 227
 	data.Title = file + " " + profile
227 228
 	data.Errors = errList
229
+	data.Total = rpt.Total()
228 230
 	data.Legend = legend
229 231
 	data.Help = ui.help
230 232
 	html := &bytes.Buffer{}
@@ -272,7 +274,7 @@ func (ui *webInterface) dot(w http.ResponseWriter, req *http.Request) {
272 274
 		nodes = append(nodes, n.Info.Name)
273 275
 	}
274 276
 
275
-	ui.render(w, "/", "graph", errList, legend, webArgs{
277
+	ui.render(w, "/", "graph", rpt, errList, legend, webArgs{
276 278
 		HTMLBody: template.HTML(string(svg)),
277 279
 		Nodes:    nodes,
278 280
 	})
@@ -307,7 +309,7 @@ func (ui *webInterface) top(w http.ResponseWriter, req *http.Request) {
307 309
 		nodes = append(nodes, item.Name)
308 310
 	}
309 311
 
310
-	ui.render(w, "/top", "top", errList, legend, webArgs{
312
+	ui.render(w, "/top", "top", rpt, errList, legend, webArgs{
311 313
 		Top:   top,
312 314
 		Nodes: nodes,
313 315
 	})
@@ -329,7 +331,7 @@ func (ui *webInterface) disasm(w http.ResponseWriter, req *http.Request) {
329 331
 	}
330 332
 
331 333
 	legend := report.ProfileLabels(rpt)
332
-	ui.render(w, "/disasm", "plaintext", errList, legend, webArgs{
334
+	ui.render(w, "/disasm", "plaintext", rpt, errList, legend, webArgs{
333 335
 		TextBody: out.String(),
334 336
 	})
335 337
 
@@ -353,7 +355,7 @@ func (ui *webInterface) source(w http.ResponseWriter, req *http.Request) {
353 355
 	}
354 356
 
355 357
 	legend := report.ProfileLabels(rpt)
356
-	ui.render(w, "/source", "sourcelisting", errList, legend, webArgs{
358
+	ui.render(w, "/source", "sourcelisting", rpt, errList, legend, webArgs{
357 359
 		HTMLBody: template.HTML(body.String()),
358 360
 	})
359 361
 }
@@ -374,7 +376,7 @@ func (ui *webInterface) peek(w http.ResponseWriter, req *http.Request) {
374 376
 	}
375 377
 
376 378
 	legend := report.ProfileLabels(rpt)
377
-	ui.render(w, "/peek", "plaintext", errList, legend, webArgs{
379
+	ui.render(w, "/peek", "plaintext", rpt, errList, legend, webArgs{
378 380
 		TextBody: out.String(),
379 381
 	})
380 382
 }

+ 5
- 2
internal/driver/webui_test.go Voir le fichier

@@ -32,7 +32,10 @@ import (
32 32
 
33 33
 func TestWebInterface(t *testing.T) {
34 34
 	prof := makeFakeProfile()
35
-	ui := makeWebInterface(prof, &plugin.Options{Obj: fakeObjTool{}})
35
+	ui := makeWebInterface(prof, &plugin.Options{
36
+		Obj: fakeObjTool{},
37
+		UI:  &stdUI{},
38
+	})
36 39
 
37 40
 	// Start test server.
38 41
 	server := httptest.NewServer(http.HandlerFunc(
@@ -64,7 +67,7 @@ func TestWebInterface(t *testing.T) {
64 67
 	}
65 68
 	testcases := []testCase{
66 69
 		{"/", []string{"F1", "F2", "F3", "testbin", "cpu"}, true},
67
-		{"/top", []string{"Flat", "200ms.*100%.*F2"}, false},
70
+		{"/top", []string{`"Name":"F2","InlineLabel":"","Flat":200,"Cum":300,"FlatFormat":"200ms","CumFormat":"300ms"}`}, false},
68 71
 		{"/source?f=" + url.QueryEscape("F[12]"),
69 72
 			[]string{"F1", "F2", "300ms line1"}, false},
70 73
 		{"/peek?f=" + url.QueryEscape("F[12]"),

+ 16
- 13
internal/report/report.go Voir le fichier

@@ -704,11 +704,10 @@ func printComments(w io.Writer, rpt *Report) error {
704 704
 
705 705
 // TextItem holds a single text report entry.
706 706
 type TextItem struct {
707
-	Name              string
708
-	InlineLabel       string // Not empty if inlined
709
-	Flat, FlatPercent string
710
-	SumPercent        string
711
-	Cum, CumPercent   string
707
+	Name                  string
708
+	InlineLabel           string // Not empty if inlined
709
+	Flat, Cum             int64  // Raw values
710
+	FlatFormat, CumFormat string // Formatted values
712 711
 }
713 712
 
714 713
 // TextItems returns a list of text items from the report and a list
@@ -745,11 +744,10 @@ func TextItems(rpt *Report) ([]TextItem, []string) {
745 744
 		items = append(items, TextItem{
746 745
 			Name:        name,
747 746
 			InlineLabel: inl,
748
-			Flat:        rpt.formatValue(flat),
749
-			FlatPercent: percentage(flat, rpt.total),
750
-			SumPercent:  percentage(flatSum, rpt.total),
751
-			Cum:         rpt.formatValue(cum),
752
-			CumPercent:  percentage(cum, rpt.total),
747
+			Flat:        flat,
748
+			Cum:         cum,
749
+			FlatFormat:  rpt.formatValue(flat),
750
+			CumFormat:   rpt.formatValue(cum),
753 751
 		})
754 752
 	}
755 753
 	return items, labels
@@ -761,15 +759,17 @@ func printText(w io.Writer, rpt *Report) error {
761 759
 	fmt.Fprintln(w, strings.Join(labels, "\n"))
762 760
 	fmt.Fprintf(w, "%10s %5s%% %5s%% %10s %5s%%\n",
763 761
 		"flat", "flat", "sum", "cum", "cum")
762
+	var flatSum int64
764 763
 	for _, item := range items {
765 764
 		inl := item.InlineLabel
766 765
 		if inl != "" {
767 766
 			inl = " " + inl
768 767
 		}
768
+		flatSum += item.Flat
769 769
 		fmt.Fprintf(w, "%10s %s %s %10s %s  %s%s\n",
770
-			item.Flat, item.FlatPercent,
771
-			item.SumPercent,
772
-			item.Cum, item.CumPercent,
770
+			item.FlatFormat, percentage(item.Flat, rpt.total),
771
+			percentage(flatSum, rpt.total),
772
+			item.CumFormat, percentage(item.Cum, rpt.total),
773 773
 			item.Name, inl)
774 774
 	}
775 775
 	return nil
@@ -1239,6 +1239,9 @@ type Report struct {
1239 1239
 	formatValue func(int64) string
1240 1240
 }
1241 1241
 
1242
+// Total returns the total number of samples in a report.
1243
+func (rpt *Report) Total() int64 { return rpt.total }
1244
+
1242 1245
 func abs64(i int64) int64 {
1243 1246
 	if i < 0 {
1244 1247
 		return -i