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