Commit 1b4493ff authored by Russ Cox's avatar Russ Cox

benchstat: use template to generate html

The generated html changes in a few minor ways,
either unavoidable or not worth working around:

- Trailing <td></td> at end of row no longer removed.
- <td>~</td> instead of <td>~    </td>
- Literal + escaped to &#43;

Change-Id: I73a0d1f74ff4c3597dd96b1a4dac2d6a3d5cc462
Reviewed-on: https://go-review.googlesource.com/35938Reviewed-by: default avatarQuentin Smith <quentin@golang.org>
parent a7a7b5b2
......@@ -44,9 +44,6 @@ type Metrics struct {
// FormatMean formats m.Mean using scaler.
func (m *Metrics) FormatMean(scaler Scaler) string {
if m == nil {
return ""
}
var s string
if scaler != nil {
s = scaler(m.Mean)
......@@ -59,7 +56,7 @@ func (m *Metrics) FormatMean(scaler Scaler) string {
// FormatDiff computes and formats the percent variation of max and min compared to mean.
// If b.Mean or b.Max is zero, FormatDiff returns an empty string.
func (m *Metrics) FormatDiff() string {
if m == nil || m.Mean == 0 || m.Max == 0 {
if m.Mean == 0 || m.Max == 0 {
return ""
}
diff := 1 - m.Min/m.Mean
......@@ -71,7 +68,7 @@ func (m *Metrics) FormatDiff() string {
// Format returns a textual formatting of "Mean ±Diff" using scaler.
func (m *Metrics) Format(scaler Scaler) string {
if m == nil {
if m.Unit == "" {
return ""
}
mean := m.FormatMean(scaler)
......
......@@ -6,34 +6,27 @@ package main
import (
"bytes"
"fmt"
"html"
"html/template"
)
var htmlTemplate = template.Must(template.New("").Parse(
`{{range $i, $table := .}}{{if gt $i 0}}
{{end}}<style>.benchstat tbody td:nth-child(1n+2) { text-align: right; padding: 0em 1em; }</style>
<table class='benchstat'>
{{if .OldNewDelta}}<tr><th>name</th><th>old {{.Metric}}</th><th>new {{.Metric}}</th><th>delta</th>
{{else if eq (len .Configs) 1}}<tr><th>name</th><th>{{.Metric}}</th>
{{else}}<tr><th>name \ {{.Metric}}</th>{{range .Configs}}<th>{{.}}</th>{{end}}
{{end}}{{range $row := $table.Rows}}<tr><td>{{.Benchmark}}</td>{{range $m := .Metrics}}<td>{{$m.Format $row.Scaler}}</td>{{end}}{{if $table.OldNewDelta}}<td>{{.Delta}}</td><td>{{.Note}}</td>{{end}}
{{end -}}
</table>
{{end}}`))
// FormatHTML appends an HTML formatting of the tables to buf.
func FormatHTML(buf *bytes.Buffer, tables []*Table) {
var textTables [][]*textRow
for _, t := range tables {
textTables = append(textTables, toText(t))
}
for i, table := range textTables {
if i > 0 {
fmt.Fprintf(buf, "\n")
}
fmt.Fprintf(buf, "<style>.benchstat tbody td:nth-child(1n+2) { text-align: right; padding: 0em 1em; }</style>\n")
fmt.Fprintf(buf, "<table class='benchstat'>\n")
printRow := func(row *textRow, tag string) {
fmt.Fprintf(buf, "<tr>")
for _, cell := range row.cols {
fmt.Fprintf(buf, "<%s>%s</%s>", tag, html.EscapeString(cell), tag)
}
fmt.Fprintf(buf, "\n")
}
printRow(table[0], "th")
for _, row := range table[1:] {
printRow(row, "td")
}
fmt.Fprintf(buf, "</table>\n")
err := htmlTemplate.Execute(buf, tables)
if err != nil {
// Only possible errors here are template not matching data structure.
// Don't make caller check - it's our fault.
panic(err)
}
}
......@@ -22,6 +22,7 @@ func TestGolden(t *testing.T) {
if t.Failed() {
t.Fatal("skipping other tests")
}
check(t, "exampleoldhtml", "-html", "exampleold.txt")
check(t, "examplehtml", "-html", "exampleold.txt", "examplenew.txt")
if t.Failed() {
t.Fatal("skipping other tests")
......
......@@ -12,16 +12,17 @@ import (
// A Table is a table for display in the benchstat output.
type Table struct {
Metric string
Configs []string
Rows []*Row
Metric string
OldNewDelta bool // is this an old-new-delta table?
Configs []string
Rows []*Row
}
// A Row is a table row for display in the benchstat output.
type Row struct {
Benchmark string // benchmark name
Scaler Scaler // formatter for stats means
Metrics []*Metrics // columns of statistics (nil slice entry means no data)
Metrics []*Metrics // columns of statistics
Delta string // formatted percent change
Note string // additional information
Same bool // likely no change
......@@ -40,31 +41,33 @@ func (c *Collection) Tables(deltaTest DeltaTest) []*Table {
table := new(Table)
table.Configs = c.Configs
table.Metric = metricOf(key.Unit)
table.OldNewDelta = len(c.Configs) == 2
for _, key.Benchmark = range c.Benchmarks {
row := &Row{Benchmark: key.Benchmark}
for _, key.Config = range c.Configs {
m := c.Metrics[key]
row.Metrics = append(row.Metrics, m)
if m == nil {
row.Metrics = append(row.Metrics, new(Metrics))
continue
}
row.Metrics = append(row.Metrics, m)
if row.Scaler == nil {
row.Scaler = NewScaler(m.Mean, m.Unit)
}
}
// If there are only two configs being compared, add stats.
// If one is missing, omit line entirely.
// TODO: Control this better.
if len(c.Configs) == 2 {
if table.OldNewDelta {
k0 := key
k0.Config = c.Configs[0]
k1 := key
k1.Config = c.Configs[1]
old := c.Metrics[k0]
new := c.Metrics[k1]
if old == nil || new == nil {
// If one is missing, omit row entirely.
// TODO: Control this better.
if old == new || new == nil {
continue
}
pval, testerr := deltaTest(old, new)
......@@ -90,7 +93,7 @@ func (c *Collection) Tables(deltaTest DeltaTest) []*Table {
if len(table.Rows) > 0 {
if *flagGeomean {
addGeomean(c, table, key.Unit, len(c.Configs) == 2)
addGeomean(c, table, key.Unit, table.OldNewDelta)
}
tables = append(tables, table)
}
......@@ -128,7 +131,7 @@ func addGeomean(c *Collection, t *Table, unit string, delta bool) {
}
}
if len(means) == 0 {
row.Metrics = append(row.Metrics, nil)
row.Metrics = append(row.Metrics, new(Metrics))
delta = false
} else {
geomean := stats.GeoMean(means)
......
<style>.benchstat tbody td:nth-child(1n+2) { text-align: right; padding: 0em 1em; }</style>
<table class='benchstat'>
<tr><th>name</th><th>old time/op</th><th>new time/op</th><th>delta</th>
<tr><td>GobEncode</td><td>13.6ms ± 1%</td><td>11.8ms ± 1%</td><td>-13.31%</td><td>(p=0.016 n=4+5)</td>
<tr><td>JSONEncode</td><td>32.1ms ± 1%</td><td>31.8ms ± 1%</td><td>~ </td><td>(p=0.286 n=4+5)</td>
<tr><td>GobEncode</td><td>13.6ms ± 1%</td><td>11.8ms ± 1%</td><td>-13.31%</td><td>(p=0.016 n=4&#43;5)</td>
<tr><td>JSONEncode</td><td>32.1ms ± 1%</td><td>31.8ms ± 1%</td><td>~</td><td>(p=0.286 n=4&#43;5)</td>
</table>
<style>.benchstat tbody td:nth-child(1n+2) { text-align: right; padding: 0em 1em; }</style>
<table class='benchstat'>
<tr><th>name</th><th>old speed</th><th>new speed</th><th>delta</th>
<tr><td>GobEncode</td><td>56.4MB/s ± 1%</td><td>65.1MB/s ± 1%</td><td>+15.36%</td><td>(p=0.016 n=4+5)</td>
<tr><td>JSONEncode</td><td>60.4MB/s ± 1%</td><td>61.1MB/s ± 2%</td><td>~ </td><td>(p=0.286 n=4+5)</td>
<tr><td>GobEncode</td><td>56.4MB/s ± 1%</td><td>65.1MB/s ± 1%</td><td>&#43;15.36%</td><td>(p=0.016 n=4&#43;5)</td>
<tr><td>JSONEncode</td><td>60.4MB/s ± 1%</td><td>61.1MB/s ± 2%</td><td>~</td><td>(p=0.286 n=4&#43;5)</td>
</table>
<style>.benchstat tbody td:nth-child(1n+2) { text-align: right; padding: 0em 1em; }</style>
<table class='benchstat'>
<tr><th>name</th><th>time/op</th>
<tr><td>GobEncode</td><td>13.6ms ± 1%</td>
<tr><td>JSONEncode</td><td>32.1ms ± 1%</td>
</table>
<style>.benchstat tbody td:nth-child(1n+2) { text-align: right; padding: 0em 1em; }</style>
<table class='benchstat'>
<tr><th>name</th><th>speed</th>
<tr><td>GobEncode</td><td>56.4MB/s ± 1%</td>
<tr><td>JSONEncode</td><td>60.4MB/s ± 1%</td>
</table>
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment