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