github.com/jgbaldwinbrown/perf@v0.1.1/benchseries/csv.go (about)

     1  // Copyright 2022 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 benchseries
     6  
     7  import (
     8  	"encoding/csv"
     9  	"fmt"
    10  	"io"
    11  	"math"
    12  	"sort"
    13  
    14  	"golang.org/x/perf/benchmath"
    15  )
    16  
    17  type CsvOptions int
    18  
    19  const (
    20  	CSV_DISTRIBUTION_BITS CsvOptions = 3
    21  	CSV_PLAIN             CsvOptions = 0
    22  	CSV_DELTA             CsvOptions = 1
    23  	CSV_LOHI              CsvOptions = 2
    24  
    25  	CSV_VALUES CsvOptions = 4
    26  
    27  	CSV_CHANGE_HEU CsvOptions = 8  // This is the interval-overlap heuristic
    28  	CSV_CHANGE_KS  CsvOptions = 16 // This is a Kolmogorov-Smirnov statistic
    29  )
    30  
    31  func (cs *ComparisonSeries) ToCsvBootstrapped(out io.Writer, options CsvOptions, threshold float64) {
    32  	tab, entries := cs.headerAndEntries(options)
    33  	summaries := cs.Summaries
    34  	for i, s := range cs.Series {
    35  		row := []string{s}
    36  		changesHeu := []float64{}
    37  		changesKs := []float64{}
    38  		for j, b := range cs.Benchmarks {
    39  			clear(entries)
    40  			// if _, ok := cs.SummaryAt(b, s); ok {
    41  			_ = b
    42  			if sum := summaries[i][j]; sum.Defined() {
    43  				center, low, high := sum.Center, sum.Low, sum.High
    44  
    45  				entries[0] = strof(center)
    46  				entries = entries[:1]
    47  
    48  				if i > 0 && summaries[i-1][j].Defined() {
    49  					p := summaries[i-1][j]
    50  					ch := p.HeurOverlap(sum, threshold)
    51  					changesHeu = append(changesHeu, ch)
    52  					cks := p.KSov(sum)
    53  					changesKs = append(changesKs, cks)
    54  
    55  					if options&CSV_CHANGE_HEU != 0 {
    56  						if !(math.IsInf(ch, 0) || math.IsNaN(ch)) {
    57  							entries = append(entries, strof(ch))
    58  						} else {
    59  							entries = append(entries, "")
    60  						}
    61  					}
    62  					if options&CSV_CHANGE_KS != 0 {
    63  						if !(math.IsInf(cks, 0) || math.IsNaN(cks)) {
    64  							entries = append(entries, strof(cks))
    65  						} else {
    66  							entries = append(entries, "")
    67  						}
    68  					}
    69  				} else {
    70  					if options&CSV_CHANGE_HEU != 0 {
    71  						entries = append(entries, "")
    72  					}
    73  					if options&CSV_CHANGE_KS != 0 {
    74  						entries = append(entries, "")
    75  					}
    76  				}
    77  
    78  				switch options & CSV_DISTRIBUTION_BITS {
    79  				case CSV_PLAIN:
    80  				case CSV_DELTA:
    81  					entries = append(entries, pctof((high-low)/(2*center)))
    82  				case CSV_LOHI:
    83  					entries = append(entries, strof(low), strof(high))
    84  				}
    85  			}
    86  			row = append(row, entries...)
    87  		}
    88  		changes := func(a []float64) []string {
    89  			sort.Float64s(a)
    90  			return []string{strof(percentile(a, .50)), strof(percentile(a, .75)), strof(percentile(a, .90)), strof(percentile(a, 1)), strof(norm(a, 1)), strof(norm(a, 2))}
    91  		}
    92  		if options&CSV_CHANGE_HEU != 0 {
    93  			row = append(row, changes(changesHeu)...)
    94  		}
    95  		if options&CSV_CHANGE_KS != 0 {
    96  			row = append(row, changes(changesKs)...)
    97  		}
    98  		tab = append(tab, row)
    99  	}
   100  	csvw := csv.NewWriter(out)
   101  	csvw.WriteAll(tab)
   102  	csvw.Flush()
   103  }
   104  
   105  func (cs *ComparisonSeries) headerAndEntries(options CsvOptions) (tab [][]string, entries []string) {
   106  	entriesLen := 1
   107  	switch options & CSV_DISTRIBUTION_BITS {
   108  	case CSV_PLAIN:
   109  	case CSV_DELTA:
   110  		entriesLen += 1
   111  	case CSV_LOHI:
   112  		entriesLen += 2
   113  	}
   114  	if options&CSV_CHANGE_HEU != 0 {
   115  		entriesLen += 6
   116  	}
   117  	if options&CSV_CHANGE_KS != 0 {
   118  		entriesLen += 6
   119  	}
   120  
   121  	entries = make([]string, entriesLen, entriesLen) // ratio,  change, +/- or (lo, hi)
   122  
   123  	hdr := []string{cs.Unit}
   124  	for _, b := range cs.Benchmarks {
   125  		hdr = append(hdr, b)
   126  		if options&CSV_CHANGE_HEU != 0 {
   127  			hdr = append(hdr, "change_heur")
   128  		}
   129  		if options&CSV_CHANGE_KS != 0 {
   130  			hdr = append(hdr, "change_ks")
   131  		}
   132  		switch options & CSV_DISTRIBUTION_BITS {
   133  		case CSV_PLAIN:
   134  		case CSV_DELTA:
   135  			hdr = append(hdr, "±")
   136  		case CSV_LOHI:
   137  			hdr = append(hdr, "lo", "hi")
   138  		}
   139  	}
   140  
   141  	if options&CSV_CHANGE_HEU != 0 {
   142  		hdr = append(hdr, "change_heur .5", "change_heur .75", "change_heur .9", "change_heur max", "change_heur avg", "change_heur rms")
   143  	}
   144  
   145  	if options&CSV_CHANGE_KS != 0 {
   146  		hdr = append(hdr, "change_ks .5", "change_ks .75", "change_ks .9", "change_ks max", "change_ks avg", "change_ks rms")
   147  	}
   148  
   149  	tab = [][]string{hdr}
   150  	return
   151  }
   152  
   153  func clear(entries []string) {
   154  	for i := range entries {
   155  		entries[i] = ""
   156  	}
   157  }
   158  
   159  func percentPlusOrMinus(sum benchmath.Summary) float64 {
   160  	return 100 * (sum.Hi - sum.Lo) / (2 * sum.Center)
   161  }
   162  
   163  func strof(x float64) string {
   164  	return fmt.Sprintf("%f", x)
   165  }
   166  
   167  func pctof(x float64) string {
   168  	return fmt.Sprintf("%f%%", x)
   169  }