fix(benchmarks): fix three parsing bugs in parse_results.go and bench_test.go
- parse_results.go: fix metric extraction order — Go outputs custom metrics (p50_µs, p95_µs, p99_µs, req/s) BEFORE B/op and allocs/op on the benchmark line. The old positional regex had B/op first, so p50/p95/p99 were always empty in latency_report.csv. Replaced with separate regexps for each field so order no longer matters. - parse_results.go: remove p95_latency_ms column from throughput_report.csv — parallel sweep files only emit ns/op and req/s, never p95 data. The column was structurally always empty. - bench_test.go: remove fmt.Printf from BenchmarkBAPCaller_RPS — the debug print raced with Go's own benchmark output line, garbling the result to 'BenchmarkRPS-N RPS: N over Ns' which the framework could not parse, causing req/s to never appear in the structured output. b.ReportMetric alone is sufficient.
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
package e2e_bench_test
|
package e2e_bench_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -162,9 +161,7 @@ func BenchmarkBAPCaller_RPS(b *testing.B) {
|
|||||||
|
|
||||||
elapsed := time.Since(start).Seconds()
|
elapsed := time.Since(start).Seconds()
|
||||||
if elapsed > 0 {
|
if elapsed > 0 {
|
||||||
rps := float64(count) / elapsed
|
b.ReportMetric(float64(count)/elapsed, "req/s")
|
||||||
b.ReportMetric(rps, "req/s")
|
|
||||||
fmt.Printf(" RPS: %.0f over %.1fs\n", rps, elapsed)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
// Output files:
|
// Output files:
|
||||||
//
|
//
|
||||||
// latency_report.csv — per-benchmark mean, p50, p95, p99 latency, allocs
|
// latency_report.csv — per-benchmark mean, p50, p95, p99 latency, allocs
|
||||||
// throughput_report.csv — RPS at each GOMAXPROCS level from the parallel sweep
|
// throughput_report.csv — RPS and mean latency at each GOMAXPROCS level from the parallel sweep
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -26,19 +26,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Matches standard go bench output:
|
// Matches the benchmark name and ns/op from a standard go test -bench output line.
|
||||||
// BenchmarkFoo-8 1000 1234567 ns/op 1234 B/op 56 allocs/op
|
// Go outputs custom metrics (p50_µs, req/s, …) BEFORE B/op and allocs/op, so we
|
||||||
benchLineRe = regexp.MustCompile(
|
// extract those fields with dedicated regexps rather than relying on positional groups.
|
||||||
`^(Benchmark\S+)\s+\d+\s+([\d.]+)\s+ns/op` +
|
//
|
||||||
`(?:\s+([\d.]+)\s+B/op)?` +
|
// Example lines:
|
||||||
`(?:\s+([\d.]+)\s+allocs/op)?` +
|
// BenchmarkBAPCaller_Discover-10 73542 164193 ns/op 82913 B/op 662 allocs/op
|
||||||
`(?:\s+([\d.]+)\s+p50_µs)?` +
|
// BenchmarkBAPCaller_Discover_Percentiles-10 72849 164518 ns/op 130.0 p50_µs 144.0 p95_µs 317.0 p99_µs 82528 B/op 660 allocs/op
|
||||||
`(?:\s+([\d.]+)\s+p95_µs)?` +
|
// BenchmarkBAPCaller_RPS-4 700465 73466 ns/op 14356.0 req/s 80375 B/op 660 allocs/op
|
||||||
`(?:\s+([\d.]+)\s+p99_µs)?` +
|
benchLineRe = regexp.MustCompile(`^(Benchmark\S+)\s+\d+\s+([\d.]+)\s+ns/op`)
|
||||||
`(?:\s+([\d.]+)\s+req/s)?`,
|
bytesRe = regexp.MustCompile(`([\d.]+)\s+B/op`)
|
||||||
)
|
allocsRe = regexp.MustCompile(`([\d.]+)\s+allocs/op`)
|
||||||
|
|
||||||
// Matches custom metric lines in percentile output.
|
// Extracts any custom metric value from a benchmark line.
|
||||||
metricRe = regexp.MustCompile(`([\d.]+)\s+(p50_µs|p95_µs|p99_µs|req/s)`)
|
metricRe = regexp.MustCompile(`([\d.]+)\s+(p50_µs|p95_µs|p99_µs|req/s)`)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -124,48 +124,43 @@ func parseRunFile(path string) ([]benchResult, error) {
|
|||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
var results []benchResult
|
var results []benchResult
|
||||||
currentBench := ""
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(f)
|
scanner := bufio.NewScanner(f)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := strings.TrimSpace(scanner.Text())
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
|
||||||
// Main benchmark line.
|
m := benchLineRe.FindStringSubmatch(line)
|
||||||
if m := benchLineRe.FindStringSubmatch(line); m != nil {
|
if m == nil {
|
||||||
r := benchResult{name: stripCPUSuffix(m[1])}
|
|
||||||
r.nsPerOp = parseFloat(m[2])
|
|
||||||
r.bytesOp = parseFloat(m[3])
|
|
||||||
r.allocsOp = parseFloat(m[4])
|
|
||||||
r.p50 = parseFloat(m[5])
|
|
||||||
r.p95 = parseFloat(m[6])
|
|
||||||
r.p99 = parseFloat(m[7])
|
|
||||||
r.rps = parseFloat(m[8])
|
|
||||||
results = append(results, r)
|
|
||||||
currentBench = r.name
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom metric lines (e.g., "123.4 p50_µs").
|
r := benchResult{name: stripCPUSuffix(m[1])}
|
||||||
if currentBench != "" {
|
r.nsPerOp = parseFloat(m[2])
|
||||||
for _, mm := range metricRe.FindAllStringSubmatch(line, -1) {
|
|
||||||
val := parseFloat(mm[1])
|
// B/op and allocs/op — extracted independently because Go places custom
|
||||||
metric := mm[2]
|
// metrics (p50_µs, req/s, …) between ns/op and B/op on the same line.
|
||||||
for i := range results {
|
if bm := bytesRe.FindStringSubmatch(line); bm != nil {
|
||||||
if results[i].name == currentBench {
|
r.bytesOp = parseFloat(bm[1])
|
||||||
switch metric {
|
}
|
||||||
case "p50_µs":
|
if am := allocsRe.FindStringSubmatch(line); am != nil {
|
||||||
results[i].p50 = val
|
r.allocsOp = parseFloat(am[1])
|
||||||
case "p95_µs":
|
}
|
||||||
results[i].p95 = val
|
|
||||||
case "p99_µs":
|
// Custom metrics — scan the whole line regardless of position.
|
||||||
results[i].p99 = val
|
for _, mm := range metricRe.FindAllStringSubmatch(line, -1) {
|
||||||
case "req/s":
|
switch mm[2] {
|
||||||
results[i].rps = val
|
case "p50_µs":
|
||||||
}
|
r.p50 = parseFloat(mm[1])
|
||||||
}
|
case "p95_µs":
|
||||||
}
|
r.p95 = parseFloat(mm[1])
|
||||||
|
case "p99_µs":
|
||||||
|
r.p99 = parseFloat(mm[1])
|
||||||
|
case "req/s":
|
||||||
|
r.rps = parseFloat(mm[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
results = append(results, r)
|
||||||
}
|
}
|
||||||
return results, scanner.Err()
|
return results, scanner.Err()
|
||||||
}
|
}
|
||||||
@@ -212,7 +207,11 @@ func writeThroughputCSV(path string, rows []cpuResult) error {
|
|||||||
w := csv.NewWriter(f)
|
w := csv.NewWriter(f)
|
||||||
defer w.Flush()
|
defer w.Flush()
|
||||||
|
|
||||||
header := []string{"gomaxprocs", "benchmark", "rps", "mean_latency_ms", "p95_latency_ms"}
|
// p95 latency is not available from the parallel sweep files — those benchmarks
|
||||||
|
// only emit ns/op and req/s. p95 data comes exclusively from
|
||||||
|
// BenchmarkBAPCaller_Discover_Percentiles, which runs at a single GOMAXPROCS
|
||||||
|
// setting and is not part of the concurrency sweep.
|
||||||
|
header := []string{"gomaxprocs", "benchmark", "rps", "mean_latency_ms"}
|
||||||
if err := w.Write(header); err != nil {
|
if err := w.Write(header); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -223,7 +222,6 @@ func writeThroughputCSV(path string, rows []cpuResult) error {
|
|||||||
row.res.name,
|
row.res.name,
|
||||||
fmtFloat(row.res.rps),
|
fmtFloat(row.res.rps),
|
||||||
fmtFloat(row.res.nsPerOp / 1e6),
|
fmtFloat(row.res.nsPerOp / 1e6),
|
||||||
fmtFloat(row.res.p95),
|
|
||||||
}
|
}
|
||||||
if err := w.Write(r); err != nil {
|
if err := w.Write(r); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
Reference in New Issue
Block a user