123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- package main
- import (
- "bufio"
- "bytes"
- "errors"
- "flag"
- "fmt"
- "os"
- "regexp"
- "strconv"
- "strings"
- "time"
- bench "golang.org/x/tools/benchmark/parse"
- )
- var noPassthrough = flag.Bool("no-passthrough", false, "Don't print non-benchmark lines")
- type BenchOutputGroup struct {
- Lines []*bench.Benchmark
- // Columns which are in use
- Measured int
- }
- type Table struct {
- MaxLengths []int
- Cells [][]string
- }
- func (g *BenchOutputGroup) String() string {
- if len(g.Lines) == 0 {
- return ""
- }
- columnNames := []string{"benchmark", "iter", "time/iter"}
- if (g.Measured & bench.MBPerS) > 0 {
- columnNames = append(columnNames, "throughput")
- }
- if (g.Measured & bench.AllocedBytesPerOp) > 0 {
- columnNames = append(columnNames, "bytes alloc")
- }
- if (g.Measured & bench.AllocsPerOp) > 0 {
- columnNames = append(columnNames, "allocs")
- }
- table := &Table{Cells: [][]string{columnNames}}
- var underlines []string
- for _, name := range columnNames {
- underlines = append(underlines, strings.Repeat("-", len(name)))
- }
- table.Cells = append(table.Cells, underlines)
- timeFormatFunc := g.TimeFormatFunc()
- for _, line := range g.Lines {
- row := []string{line.Name, FormatIterations(line.N), timeFormatFunc(line.NsPerOp)}
- if (g.Measured & bench.MBPerS) > 0 {
- row = append(row, FormatMegaBytesPerSecond(line))
- }
- if (g.Measured & bench.AllocedBytesPerOp) > 0 {
- row = append(row, FormatBytesAllocPerOp(line))
- }
- if (g.Measured & bench.AllocsPerOp) > 0 {
- row = append(row, FormatAllocsPerOp(line))
- }
- table.Cells = append(table.Cells, row)
- }
- for i := range columnNames {
- maxLength := 0
- for _, row := range table.Cells {
- if len(row[i]) > maxLength {
- maxLength = len(row[i])
- }
- }
- table.MaxLengths = append(table.MaxLengths, maxLength)
- }
- var buf bytes.Buffer
- for _, row := range table.Cells {
- for i, cell := range row {
- var format string
- switch i {
- case 0:
- format = "%%-%ds "
- case len(row) - 1:
- format = "%%%ds"
- default:
- format = "%%%ds "
- }
- fmt.Fprintf(&buf, fmt.Sprintf(format, table.MaxLengths[i]), cell)
- }
- fmt.Fprint(&buf, "\n")
- }
- return buf.String()
- }
- func FormatIterations(iter int) string {
- return strconv.FormatInt(int64(iter), 10)
- }
- func (g *BenchOutputGroup) TimeFormatFunc() func(float64) string {
- // Find the smallest time
- smallest := g.Lines[0].NsPerOp
- for _, line := range g.Lines[1:] {
- if line.NsPerOp < smallest {
- smallest = line.NsPerOp
- }
- }
- switch {
- case smallest < float64(10000*time.Nanosecond):
- return func(ns float64) string {
- return fmt.Sprintf("%.2f ns/op", ns)
- }
- case smallest < float64(time.Millisecond):
- return func(ns float64) string {
- return fmt.Sprintf("%.2f μs/op", ns/1000)
- }
- case smallest < float64(10*time.Second):
- return func(ns float64) string {
- return fmt.Sprintf("%.2f ms/op", (ns / 1e6))
- }
- default:
- return func(ns float64) string {
- return fmt.Sprintf("%.2f s/op", ns/1e9)
- }
- }
- }
- func FormatMegaBytesPerSecond(l *bench.Benchmark) string {
- if (l.Measured & bench.MBPerS) == 0 {
- return ""
- }
- return fmt.Sprintf("%.2f MB/s", l.MBPerS)
- }
- func FormatBytesAllocPerOp(l *bench.Benchmark) string {
- if (l.Measured & bench.AllocedBytesPerOp) == 0 {
- return ""
- }
- return fmt.Sprintf("%d B/op", l.AllocedBytesPerOp)
- }
- func FormatAllocsPerOp(l *bench.Benchmark) string {
- if (l.Measured & bench.AllocsPerOp) == 0 {
- return ""
- }
- return fmt.Sprintf("%d allocs/op", l.AllocsPerOp)
- }
- func (g *BenchOutputGroup) AddLine(line *bench.Benchmark) {
- g.Lines = append(g.Lines, line)
- g.Measured |= line.Measured
- }
- var (
- benchLineMatcher = regexp.MustCompile(`^Benchmark.*\t.*\d+`)
- okLineMatcher = regexp.MustCompile(`^ok\s`)
- notBenchLineErr = errors.New("Not a bench line")
- )
- func ParseLine(line string) (*bench.Benchmark, error) {
- if !benchLineMatcher.MatchString(line) {
- return nil, notBenchLineErr
- }
- fields := strings.Split(line, "\t")
- if len(fields) < 3 {
- return nil, notBenchLineErr
- }
- return bench.ParseLine(line)
- }
- func main() {
- flag.Parse()
- currentBenchmark := &BenchOutputGroup{}
- scanner := bufio.NewScanner(os.Stdin)
- for scanner.Scan() {
- text := scanner.Text()
- line, err := ParseLine(text)
- switch err {
- case notBenchLineErr:
- if okLineMatcher.MatchString(text) {
- fmt.Print(currentBenchmark)
- currentBenchmark = &BenchOutputGroup{}
- }
- if !*noPassthrough {
- fmt.Println(text)
- }
- case nil:
- currentBenchmark.AddLine(line)
- default:
- fmt.Fprintln(os.Stderr, "prettybench unrecognized line:")
- fmt.Println(text)
- }
- }
- if err := scanner.Err(); err != nil {
- fmt.Println(err)
- os.Exit(1)
- }
- }
|