|
|
// 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 }
|