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  }