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 }