1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
|
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package parse provides support for parsing benchmark results as
// generated by 'go test -bench'.
package parse // import "golang.org/x/tools/benchmark/parse"
import (
"bufio"
"bytes"
"fmt"
"io"
"strconv"
"strings"
)
// Flags used by Benchmark.Measured to indicate
// which measurements a Benchmark contains.
const (
NsPerOp = 1 << iota
MBPerS
AllocedBytesPerOp
AllocsPerOp
)
// Benchmark is one run of a single benchmark.
type Benchmark struct {
Name string // benchmark name
N int // number of iterations
NsPerOp float64 // nanoseconds per iteration
AllocedBytesPerOp uint64 // bytes allocated per iteration
AllocsPerOp uint64 // allocs per iteration
MBPerS float64 // MB processed per second
Measured int // which measurements were recorded
Ord int // ordinal position within a benchmark run
}
// ParseLine extracts a Benchmark from a single line of testing.B
// output.
func ParseLine(line string) (*Benchmark, error) {
fields := strings.Fields(line)
// Two required, positional fields: Name and iterations.
if len(fields) < 2 {
return nil, fmt.Errorf("two fields required, have %d", len(fields))
}
if !strings.HasPrefix(fields[0], "Benchmark") {
return nil, fmt.Errorf(`first field does not start with "Benchmark"`)
}
n, err := strconv.Atoi(fields[1])
if err != nil {
return nil, err
}
b := &Benchmark{Name: fields[0], N: n}
// Parse any remaining pairs of fields; we've parsed one pair already.
for i := 1; i < len(fields)/2; i++ {
b.parseMeasurement(fields[i*2], fields[i*2+1])
}
return b, nil
}
func (b *Benchmark) parseMeasurement(quant string, unit string) {
switch unit {
case "ns/op":
if f, err := strconv.ParseFloat(quant, 64); err == nil {
b.NsPerOp = f
b.Measured |= NsPerOp
}
case "MB/s":
if f, err := strconv.ParseFloat(quant, 64); err == nil {
b.MBPerS = f
b.Measured |= MBPerS
}
case "B/op":
if i, err := strconv.ParseUint(quant, 10, 64); err == nil {
b.AllocedBytesPerOp = i
b.Measured |= AllocedBytesPerOp
}
case "allocs/op":
if i, err := strconv.ParseUint(quant, 10, 64); err == nil {
b.AllocsPerOp = i
b.Measured |= AllocsPerOp
}
}
}
func (b *Benchmark) String() string {
buf := new(bytes.Buffer)
fmt.Fprintf(buf, "%s %d", b.Name, b.N)
if (b.Measured & NsPerOp) != 0 {
fmt.Fprintf(buf, " %.2f ns/op", b.NsPerOp)
}
if (b.Measured & MBPerS) != 0 {
fmt.Fprintf(buf, " %.2f MB/s", b.MBPerS)
}
if (b.Measured & AllocedBytesPerOp) != 0 {
fmt.Fprintf(buf, " %d B/op", b.AllocedBytesPerOp)
}
if (b.Measured & AllocsPerOp) != 0 {
fmt.Fprintf(buf, " %d allocs/op", b.AllocsPerOp)
}
return buf.String()
}
// Set is a collection of benchmarks from one
// testing.B run, keyed by name to facilitate comparison.
type Set map[string][]*Benchmark
// ParseSet extracts a Set from testing.B output.
// ParseSet preserves the order of benchmarks that have identical
// names.
func ParseSet(r io.Reader) (Set, error) {
bb := make(Set)
scan := bufio.NewScanner(r)
ord := 0
for scan.Scan() {
if b, err := ParseLine(scan.Text()); err == nil {
b.Ord = ord
ord++
bb[b.Name] = append(bb[b.Name], b)
}
}
if err := scan.Err(); err != nil {
return nil, err
}
return bb, nil
}
|