github.com/jgbaldwinbrown/perf@v0.1.1/benchproc/extract.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 benchproc 6 7 import ( 8 "bytes" 9 "fmt" 10 "strings" 11 12 "golang.org/x/perf/benchfmt" 13 ) 14 15 // An extractor returns some field of a benchmark result. The 16 // result may be a view into a mutable []byte in *benchfmt.Result, so 17 // it may change if the Result is modified. 18 type extractor func(*benchfmt.Result) []byte 19 20 // newExtractor returns a function that extracts some field of a 21 // benchmark result. 22 // 23 // The key must be one of the following: 24 // 25 // - ".name" for the benchmark name (excluding per-benchmark 26 // configuration). 27 // 28 // - ".fullname" for the full benchmark name (including per-benchmark 29 // configuration). 30 // 31 // - "/{key}" for a benchmark sub-name key. This may be "/gomaxprocs" 32 // and the extractor will normalize the name as needed. 33 // 34 // - Any other string is a file configuration key. 35 func newExtractor(key string) (extractor, error) { 36 if len(key) == 0 { 37 return nil, fmt.Errorf("key must not be empty") 38 } 39 40 switch { 41 case key == ".config", key == ".unit": 42 // The caller should already have handled this more gracefully. 43 panic(key + " is not an extractor") 44 45 case key == ".name": 46 return extractName, nil 47 48 case key == ".fullname": 49 return extractFull, nil 50 51 case strings.HasPrefix(key, "/"): 52 // Construct the byte prefix to search for. 53 prefix := make([]byte, len(key)+1) 54 copy(prefix, key) 55 prefix[len(prefix)-1] = '=' 56 isGomaxprocs := key == "/gomaxprocs" 57 return func(res *benchfmt.Result) []byte { 58 return extractNamePart(res, prefix, isGomaxprocs) 59 }, nil 60 } 61 62 return func(res *benchfmt.Result) []byte { 63 return extractConfig(res, key) 64 }, nil 65 } 66 67 // newExtractorFullName returns an extractor for the full name of a 68 // benchmark, but optionally with the base name or sub-name 69 // configuration keys excluded. Any excluded sub-name keys will be 70 // deleted from the name. If ".name" is excluded, the name will be 71 // normalized to "*". This will ignore anything in the exclude list that 72 // isn't in the form of a /-prefixed sub-name key or ".name". 73 func newExtractorFullName(exclude []string) extractor { 74 // Extract the sub-name keys, turn them into substrings and 75 // construct their normalized replacement. 76 // 77 // It's important that we fully delete excluded sub-name keys rather 78 // than, say, normalizing them to "*". Simply normalizing them will 79 // leak whether or not they are present into the result, resulting 80 // in different strings. This is most noticeable when excluding 81 // /gomaxprocs: since this is already omitted if it's 1, benchmarks 82 // running at /gomaxprocs of 1 won't merge with benchmarks at higher 83 // /gomaxprocs. 84 var delete [][]byte 85 excName := false 86 excGomaxprocs := false 87 for _, k := range exclude { 88 if k == ".name" { 89 excName = true 90 } 91 if !strings.HasPrefix(k, "/") { 92 continue 93 } 94 delete = append(delete, append([]byte(k), '=')) 95 if k == "/gomaxprocs" { 96 excGomaxprocs = true 97 } 98 } 99 if len(delete) == 0 && !excName && !excGomaxprocs { 100 return extractFull 101 } 102 return func(res *benchfmt.Result) []byte { 103 return extractFullExcluded(res, delete, excName, excGomaxprocs) 104 } 105 } 106 107 func extractName(res *benchfmt.Result) []byte { 108 return res.Name.Base() 109 } 110 111 func extractFull(res *benchfmt.Result) []byte { 112 return res.Name.Full() 113 } 114 115 func extractFullExcluded(res *benchfmt.Result, delete [][]byte, excName, excGomaxprocs bool) []byte { 116 name := res.Name.Full() 117 found := false 118 if excName { 119 found = true 120 } 121 if !found { 122 for _, k := range delete { 123 if bytes.Contains(name, k) { 124 found = true 125 break 126 } 127 } 128 } 129 if !found && excGomaxprocs && bytes.IndexByte(name, '-') >= 0 { 130 found = true 131 } 132 if !found { 133 // No need to transform name. 134 return name 135 } 136 137 // Delete excluded keys from the name. 138 base, parts := res.Name.Parts() 139 var newName []byte 140 if excName { 141 newName = append(newName, '*') 142 } else { 143 newName = append(newName, base...) 144 } 145 outer: 146 for _, part := range parts { 147 for _, k := range delete { 148 if bytes.HasPrefix(part, k) { 149 // Skip this part. 150 continue outer 151 } 152 } 153 if excGomaxprocs && part[0] == '-' { 154 // Skip gomaxprocs. 155 continue outer 156 } 157 newName = append(newName, part...) 158 } 159 return newName 160 } 161 162 func extractNamePart(res *benchfmt.Result, prefix []byte, isGomaxprocs bool) []byte { 163 _, parts := res.Name.Parts() 164 if isGomaxprocs && len(parts) > 0 { 165 last := parts[len(parts)-1] 166 if last[0] == '-' { 167 // GOMAXPROCS specified as "-N" suffix. 168 return last[1:] 169 } 170 } 171 // Search for the prefix. 172 for _, part := range parts { 173 if bytes.HasPrefix(part, prefix) { 174 return part[len(prefix):] 175 } 176 } 177 // Not found. 178 return nil 179 } 180 181 func extractConfig(res *benchfmt.Result, key string) []byte { 182 pos, ok := res.ConfigIndex(key) 183 if !ok { 184 return nil 185 } 186 return res.Config[pos].Value 187 }