Skip to content

Commit 027eb14

Browse files
EtiennePerotgvisor-bot
authored andcommitted
BigQuery metrics: Add ability to output benchstat-formatted benchmark data.
PiperOrigin-RevId: 421914403
1 parent 196baa6 commit 027eb14

File tree

1 file changed

+90
-0
lines changed

1 file changed

+90
-0
lines changed

tools/bigquery/bigquery.go

+90
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ package bigquery
2121
import (
2222
"context"
2323
"fmt"
24+
"regexp"
25+
"sort"
2426
"strconv"
2527
"strings"
2628
"time"
@@ -81,6 +83,31 @@ func (s *Suite) debugString(sb *strings.Builder, prefix string) {
8183
sb.WriteString(fmt.Sprintf("End of data for benchmark suite %s.", s.Name))
8284
}
8385

86+
// Benchstat returns a benchstat-formatted output string.
87+
// See https://pkg.go.dev/golang.org/x/perf/cmd/benchstat
88+
// `includeConditions` contains names of `Condition`s that should be included
89+
// as part of the benchmark name.
90+
func (s *Suite) Benchstat(includeConditions []string) string {
91+
var sb strings.Builder
92+
benchmarkNames := make([]string, 0, len(s.Benchmarks))
93+
benchmarks := make(map[string]*Benchmark, len(s.Benchmarks))
94+
for _, bm := range s.Benchmarks {
95+
if _, found := benchmarks[bm.Name]; !found {
96+
benchmarkNames = append(benchmarkNames, bm.Name)
97+
benchmarks[bm.Name] = bm
98+
}
99+
}
100+
sort.Strings(benchmarkNames)
101+
includeConditionsMap := make(map[string]bool, len(includeConditions))
102+
for _, condName := range includeConditions {
103+
includeConditionsMap[condName] = true
104+
}
105+
for _, bmName := range benchmarkNames {
106+
benchmarks[bmName].benchstat(&sb, s.Name, includeConditionsMap, s.Conditions)
107+
}
108+
return sb.String()
109+
}
110+
84111
// Benchmark represents an individual benchmark in a suite.
85112
type Benchmark struct {
86113
Name string `bq:"name"`
@@ -117,6 +144,69 @@ func (bm *Benchmark) debugString(sb *strings.Builder, prefix string) {
117144
}
118145
}
119146

147+
// noSpaceRe is used to remove whitespace characters in `noSpace`.
148+
var noSpaceRe = regexp.MustCompile("\\s+")
149+
150+
// noSpace replaces whitespace characters from `s` with "_".
151+
func noSpace(s string) string {
152+
return noSpaceRe.ReplaceAllString(s, "_")
153+
}
154+
155+
// benchstat produces benchmark-formatted output for this Benchmark.
156+
func (bm *Benchmark) benchstat(sb *strings.Builder, suiteName string, includeConditions map[string]bool, suiteConditions []*Condition) {
157+
var conditionsStr string
158+
conditionNames := make([]string, 0, len(suiteConditions)+len(bm.Condition))
159+
conditionMap := make(map[string]string, len(suiteConditions)+len(bm.Condition))
160+
for _, c := range suiteConditions {
161+
cName := noSpace(c.Name)
162+
if _, found := conditionMap[cName]; !found && includeConditions[cName] {
163+
conditionNames = append(conditionNames, cName)
164+
conditionMap[cName] = noSpace(c.Value)
165+
}
166+
}
167+
for _, c := range bm.Condition {
168+
cName := noSpace(c.Name)
169+
if _, found := conditionMap[cName]; !found && includeConditions[cName] {
170+
conditionNames = append(conditionNames, cName)
171+
conditionMap[cName] = noSpace(c.Value)
172+
}
173+
}
174+
sort.Strings(conditionNames)
175+
var conditionsBuilder strings.Builder
176+
if len(conditionNames) > 0 {
177+
conditionsBuilder.WriteByte('{')
178+
for i, condName := range conditionNames {
179+
if i != 0 {
180+
conditionsBuilder.WriteByte(',')
181+
}
182+
conditionsBuilder.WriteString(condName)
183+
conditionsBuilder.WriteByte('=')
184+
conditionsBuilder.WriteString(conditionMap[condName])
185+
}
186+
conditionsBuilder.WriteByte('}')
187+
}
188+
conditionsStr = conditionsBuilder.String()
189+
for _, m := range bm.Metric {
190+
if !strings.HasPrefix(suiteName, "Benchmark") {
191+
// benchstat format requires all benchmark names to start with "Benchmark".
192+
sb.WriteString("Benchmark")
193+
}
194+
sb.WriteString(noSpace(suiteName))
195+
if suiteName != bm.Name {
196+
sb.WriteByte('/')
197+
sb.WriteString(noSpace(bm.Name))
198+
}
199+
sb.WriteString(conditionsStr)
200+
sb.WriteByte('/')
201+
sb.WriteString(noSpace(m.Name))
202+
sb.WriteString(" 1 ") // 1 sample
203+
sb.WriteString(fmt.Sprintf("%f", m.Sample))
204+
sb.WriteByte(' ')
205+
sb.WriteString(noSpace(m.Unit))
206+
sb.WriteByte('\n')
207+
}
208+
}
209+
120210
// AddMetric adds a metric to an existing Benchmark.
121211
func (bm *Benchmark) AddMetric(metricName, unit string, sample float64) {
122212
m := &Metric{

0 commit comments

Comments
 (0)