github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/benchmark/parse/parse.go (about) 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 "github.com/powerman/golang-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 }