github.com/aclements/go-misc@v0.0.0-20240129233631-2f6ede80790c/benchmany/readlog.go (about)

     1  // Copyright 2015 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 main
     6  
     7  import (
     8  	"io/ioutil"
     9  	"log"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/aclements/go-moremath/stats"
    14  )
    15  
    16  // ComputeStats updates the derived statistics in s from the raw
    17  // samples in s.Values.
    18  func (stat *Benchstat) ComputeStats() {
    19  	stat.Mean = stats.Mean(stat.Values)
    20  }
    21  
    22  // A Benchstat is the metrics along one axis (e.g., ns/op or MB/s)
    23  // for all runs of a specific benchmark.
    24  type Benchstat struct {
    25  	Unit   string
    26  	Values []float64 // metrics
    27  	Mean   float64   // mean of Values
    28  }
    29  
    30  // A BenchKey identifies one metric (e.g., "ns/op", "B/op") from one
    31  // benchmark (function name sans "Benchmark" prefix) in one
    32  // configuration (input file name).
    33  type BenchKey struct {
    34  	Config, Benchmark, Unit string
    35  }
    36  
    37  type Collection struct {
    38  	Stats map[BenchKey]*Benchstat
    39  
    40  	// Keys gives all keys of Stats in the order added.
    41  	Keys []BenchKey
    42  
    43  	// Configs, Benchmarks, and Units give the set of configs,
    44  	// benchmarks, and units from the keys in Stats in an order
    45  	// meant to match the order the benchmarks were read in.
    46  	Configs, Benchmarks, Units []string
    47  
    48  	// ConfigSet, BenchmarkSet, and UnitSet are set
    49  	// representations of Configs, Benchmarks, and Units.
    50  	ConfigSet, BenchmarkSet, UnitSet map[string]bool
    51  }
    52  
    53  func (c *Collection) AddStat(key BenchKey) *Benchstat {
    54  	if stat, ok := c.Stats[key]; ok {
    55  		return stat
    56  	}
    57  
    58  	c.addKey(key)
    59  	stat := &Benchstat{Unit: key.Unit}
    60  	c.Stats[key] = stat
    61  	return stat
    62  }
    63  
    64  func (c *Collection) addKey(key BenchKey) {
    65  	addString := func(strings *[]string, set map[string]bool, add string) {
    66  		if set[add] {
    67  			return
    68  		}
    69  		*strings = append(*strings, add)
    70  		set[add] = true
    71  	}
    72  	c.Keys = append(c.Keys, key)
    73  	addString(&c.Configs, c.ConfigSet, key.Config)
    74  	addString(&c.Benchmarks, c.BenchmarkSet, key.Benchmark)
    75  	addString(&c.Units, c.UnitSet, key.Unit)
    76  }
    77  
    78  func (c *Collection) Filter(key BenchKey) *Collection {
    79  	c2 := NewCollection()
    80  	for _, k := range c.Keys {
    81  		if (key.Config == "" || key.Config == k.Config) &&
    82  			(key.Benchmark == "" || key.Benchmark == k.Benchmark) &&
    83  			(key.Unit == "" || key.Unit == k.Unit) {
    84  			c2.addKey(k)
    85  			c2.Stats[k] = c.Stats[k]
    86  		}
    87  	}
    88  	return c2
    89  }
    90  
    91  func NewCollection() *Collection {
    92  	return &Collection{
    93  		Stats:        make(map[BenchKey]*Benchstat),
    94  		ConfigSet:    make(map[string]bool),
    95  		BenchmarkSet: make(map[string]bool),
    96  		UnitSet:      make(map[string]bool),
    97  	}
    98  }
    99  
   100  // readFiles reads a set of benchmark files as a Collection.
   101  func readFiles(files ...string) *Collection {
   102  	c := NewCollection()
   103  	for _, file := range files {
   104  		readFile(file, c)
   105  	}
   106  	return c
   107  }
   108  
   109  var unitOfXMetric = map[string]string{
   110  	"time":           "ns/op",
   111  	"allocated":      "allocated bytes/op",      // ΔMemStats.TotalAlloc / N
   112  	"allocs":         "allocs/op",               // ΔMemStats.Mallocs / N
   113  	"sys-total":      "bytes from system",       // MemStats.Sys
   114  	"sys-heap":       "heap bytes from system",  // MemStats.HeapSys
   115  	"sys-stack":      "stack bytes from system", // MemStats.StackSys
   116  	"sys-gc":         "GC bytes from system",    // MemStats.GCSys
   117  	"sys-other":      "other bytes from system", // MemStats.OtherSys+MSpanSys+MCacheSys+BuckHashSys
   118  	"gc-pause-total": "STW ns/op",               // ΔMemStats.PauseTotalNs / N
   119  	"gc-pause-one":   "STW ns/GC",               // ΔMemStats.PauseTotalNs / ΔNumGC
   120  	"rss":            "max RSS bytes",           // Rusage.Maxrss * 1<<10
   121  	"cputime":        "user+sys ns/op",          // Rusage.Utime+Stime
   122  	"virtual-mem":    "peak VM bytes",           // /proc/self/status VmPeak
   123  }
   124  
   125  // readFile reads a set of benchmarks from a file in to a Collection.
   126  func readFile(file string, c *Collection) {
   127  	c.Configs = append(c.Configs, file)
   128  	key := BenchKey{Config: file}
   129  
   130  	text, err := ioutil.ReadFile(file)
   131  	if err != nil {
   132  		log.Fatal(err)
   133  	}
   134  	for _, line := range strings.Split(string(text), "\n") {
   135  		if strings.HasPrefix(line, "GOPERF-METRIC:") {
   136  			// x/benchmarks-style output.
   137  			line := line[14:]
   138  			f := strings.Split(line, "=")
   139  			val, err := strconv.ParseFloat(f[1], 64)
   140  			if err != nil {
   141  				continue
   142  			}
   143  			key.Benchmark = f[0]
   144  			key.Unit = unitOfXMetric[f[0]]
   145  			if key.Unit == "" {
   146  				continue
   147  			}
   148  			stat := c.AddStat(key)
   149  			stat.Values = append(stat.Values, val)
   150  			continue
   151  		}
   152  
   153  		f := strings.Fields(line)
   154  		if len(f) < 4 {
   155  			continue
   156  		}
   157  		name := f[0]
   158  		if !strings.HasPrefix(name, "Benchmark") {
   159  			continue
   160  		}
   161  		name = strings.TrimPrefix(name, "Benchmark")
   162  		n, _ := strconv.Atoi(f[1])
   163  		if n == 0 {
   164  			continue
   165  		}
   166  
   167  		key.Benchmark = name
   168  		for i := 2; i+2 <= len(f); i += 2 {
   169  			val, err := strconv.ParseFloat(f[i], 64)
   170  			if err != nil {
   171  				continue
   172  			}
   173  			key.Unit = f[i+1]
   174  			stat := c.AddStat(key)
   175  			stat.Values = append(stat.Values, val)
   176  		}
   177  	}
   178  }