github.com/jgbaldwinbrown/perf@v0.1.1/benchfmt/result.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 benchfmt provides a high-performance reader and writer for 6 // the Go benchmark format. 7 // 8 // This implements the format documented at 9 // https://golang.org/design/14313-benchmark-format. 10 // 11 // The reader and writer are structured as streaming operations to 12 // allow incremental processing and avoid dictating a data model. This 13 // allows consumers of these APIs to provide their own data model best 14 // suited to its needs. The reader also performs in-place updates to 15 // reduce allocation, enabling it to parse millions of benchmark 16 // results per second on a typical laptop. 17 // 18 // This package is designed to be used with the higher-level packages 19 // benchunit, benchmath, and benchproc. 20 package benchfmt 21 22 import "bytes" 23 24 // A Result is a single benchmark result and all of its measurements. 25 // 26 // Results are designed to be mutated in place and reused to reduce 27 // allocation. 28 type Result struct { 29 // Config is the set of key/value configuration pairs for this result, 30 // including file and internal configuration. This does not include 31 // sub-name configuration. 32 // 33 // This slice is mutable, as are the values in the slice. 34 // Result internally maintains an index of the keys of this slice, 35 // so callers must use SetConfig to add or delete keys, 36 // but may modify values in place. There is one exception to this: 37 // for convenience, new Results can be initialized directly, 38 // e.g., using a struct literal. 39 // 40 // SetConfig appends new keys to this slice and updates existing ones 41 // in place. To delete a key, it swaps the deleted key with 42 // the final slice element. This way, the order of these keys is 43 // deterministic. 44 Config []Config 45 46 // Name is the full name of this benchmark, including all 47 // sub-benchmark configuration. 48 Name Name 49 50 // Iters is the number of iterations this benchmark's results 51 // were averaged over. 52 Iters int 53 54 // Values is this benchmark's measurements and their units. 55 Values []Value 56 57 // configPos maps from Config.Key to index in Config. This 58 // may be nil, which indicates the index needs to be 59 // constructed. 60 configPos map[string]int 61 62 // fileName and line record where this Record was read from. 63 fileName string 64 line int 65 } 66 67 // A Config is a single key/value configuration pair. 68 // This can be a file configuration, which was read directly from 69 // a benchmark results file; or an "internal" configuration that was 70 // supplied by tooling. 71 type Config struct { 72 Key string 73 Value []byte 74 File bool // Set if this is a file configuration key, otherwise internal 75 } 76 77 // Note: I tried many approaches to Config. Using two strings is nice 78 // for the API, but forces a lot of allocation in extractors (since 79 // they either need to convert strings to []byte or vice-versa). Using 80 // a []byte for Value makes it slightly harder to use, but is good for 81 // reusing space efficiently (Value is likely to have more distinct 82 // values than Key) and lets all extractors work in terms of []byte 83 // views. Making Key a []byte is basically all downside. 84 85 // A Value is a single value/unit measurement from a benchmark result. 86 // 87 // Values should be tidied to use base units like "sec" and "B" when 88 // constructed. Reader ensures this. 89 type Value struct { 90 Value float64 91 Unit string 92 93 // OrigValue and OrigUnit, if non-zero, give the original, 94 // untidied value and unit, typically as read from the 95 // original input. OrigUnit may be "", indicating that the 96 // value wasn't transformed. 97 OrigValue float64 98 OrigUnit string 99 } 100 101 // Pos returns the file name and line number of a Result that was read 102 // by a Reader. For Results that were not read from a file, it returns 103 // "", 0. 104 func (r *Result) Pos() (fileName string, line int) { 105 return r.fileName, r.line 106 } 107 108 // Clone makes a copy of Result that shares no state with r. 109 func (r *Result) Clone() *Result { 110 r2 := &Result{ 111 Config: make([]Config, len(r.Config)), 112 Name: append([]byte(nil), r.Name...), 113 Iters: r.Iters, 114 Values: append([]Value(nil), r.Values...), 115 fileName: r.fileName, 116 line: r.line, 117 } 118 for i, cfg := range r.Config { 119 r2.Config[i].Key = cfg.Key 120 r2.Config[i].Value = append([]byte(nil), cfg.Value...) 121 r2.Config[i].File = cfg.File 122 } 123 return r2 124 } 125 126 // SetConfig sets configuration key to value, overriding or 127 // adding the configuration as necessary, and marks it internal. 128 // If value is "", SetConfig deletes key. 129 func (r *Result) SetConfig(key, value string) { 130 if value == "" { 131 r.deleteConfig(key) 132 } else { 133 cfg := r.ensureConfig(key, false) 134 cfg.Value = append(cfg.Value[:0], value...) 135 } 136 } 137 138 // ensureConfig returns the Config for key, creating it if necessary. 139 // 140 // This sets Key and File of the returned Config, but it's up to the caller to 141 // set Value. We take this approach because some callers have strings and others 142 // have []byte, so leaving this to the caller avoids allocation in one of these 143 // cases. 144 func (r *Result) ensureConfig(key string, file bool) *Config { 145 pos, ok := r.ConfigIndex(key) 146 if ok { 147 cfg := &r.Config[pos] 148 cfg.File = file 149 return cfg 150 } 151 // Add key. Reuse old space if possible. 152 r.configPos[key] = len(r.Config) 153 if len(r.Config) < cap(r.Config) { 154 r.Config = r.Config[:len(r.Config)+1] 155 cfg := &r.Config[len(r.Config)-1] 156 cfg.Key = key 157 cfg.File = file 158 return cfg 159 } 160 r.Config = append(r.Config, Config{key, nil, file}) 161 return &r.Config[len(r.Config)-1] 162 } 163 164 func (r *Result) deleteConfig(key string) { 165 pos, ok := r.ConfigIndex(key) 166 if !ok { 167 return 168 } 169 // Delete key. 170 cfg := &r.Config[pos] 171 cfg2 := &r.Config[len(r.Config)-1] 172 *cfg, *cfg2 = *cfg2, *cfg 173 r.configPos[cfg.Key] = pos 174 r.Config = r.Config[:len(r.Config)-1] 175 delete(r.configPos, key) 176 } 177 178 // GetConfig returns the value of a configuration key, 179 // or "" if not present. 180 func (r *Result) GetConfig(key string) string { 181 pos, ok := r.ConfigIndex(key) 182 if !ok { 183 return "" 184 } 185 return string(r.Config[pos].Value) 186 } 187 188 // ConfigIndex returns the index in r.Config of key. 189 func (r *Result) ConfigIndex(key string) (pos int, ok bool) { 190 if r.configPos == nil { 191 // This is a fresh Result. Construct the index. 192 r.configPos = make(map[string]int) 193 for i, cfg := range r.Config { 194 r.configPos[cfg.Key] = i 195 } 196 } 197 198 pos, ok = r.configPos[key] 199 return 200 } 201 202 // Value returns the measurement for the given unit. 203 func (r *Result) Value(unit string) (float64, bool) { 204 for _, v := range r.Values { 205 if v.Unit == unit { 206 return v.Value, true 207 } 208 } 209 return 0, false 210 } 211 212 // A Name is a full benchmark name, including all sub-benchmark 213 // configuration. 214 type Name []byte 215 216 // String returns the full benchmark name as a string. 217 func (n Name) String() string { 218 return string(n) 219 } 220 221 // Full returns the full benchmark name as a []byte. This is simply 222 // the value of n, but helps with code readability. 223 func (n Name) Full() []byte { 224 return n 225 } 226 227 // Base returns the base part of a full benchmark name, without any 228 // configuration keys or GOMAXPROCS. 229 func (n Name) Base() []byte { 230 slash := bytes.IndexByte(n.Full(), '/') 231 if slash >= 0 { 232 return n[:slash] 233 } 234 base, _ := n.splitGomaxprocs() 235 return base 236 } 237 238 // Parts splits a benchmark name into the base name and sub-benchmark 239 // configuration parts. Each sub-benchmark configuration part is one 240 // of three forms: 241 // 242 // 1. "/<key>=<value>" indicates a key/value configuration pair. 243 // 244 // 2. "/<string>" indicates a positional configuration pair. 245 // 246 // 3. "-<gomaxprocs>" indicates the GOMAXPROCS of this benchmark. This 247 // part can only appear last. 248 // 249 // Concatenating the base name and the configuration parts 250 // reconstructs the full name. 251 func (n Name) Parts() (baseName []byte, parts [][]byte) { 252 // First pull off any GOMAXPROCS. 253 buf, gomaxprocs := n.splitGomaxprocs() 254 // Split the remaining parts. 255 var nameParts [][]byte 256 prev := 0 257 for i, c := range buf { 258 if c == '/' { 259 nameParts = append(nameParts, buf[prev:i]) 260 prev = i 261 } 262 } 263 nameParts = append(nameParts, buf[prev:]) 264 if gomaxprocs != nil { 265 nameParts = append(nameParts, gomaxprocs) 266 } 267 return nameParts[0], nameParts[1:] 268 } 269 270 func (n Name) splitGomaxprocs() (prefix, gomaxprocs []byte) { 271 for i := len(n) - 1; i >= 0; i-- { 272 if n[i] == '-' && i < len(n)-1 { 273 return n[:i], n[i:] 274 } 275 if !('0' <= n[i] && n[i] <= '9') { 276 // Not a digit. 277 break 278 } 279 } 280 return n, nil 281 }