Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

roachprod-microbench: improvements to reporting #102033

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/cmd/roachprod-microbench/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ go_library(
"//pkg/cmd/roachprod-microbench/cluster",
"//pkg/cmd/roachprod-microbench/google",
"//pkg/roachprod",
"//pkg/roachprod/config",
"//pkg/roachprod/logger",
"//pkg/roachprod/ssh",
"//pkg/roachprod/vm",
Expand Down
97 changes: 71 additions & 26 deletions pkg/cmd/roachprod-microbench/benchmark.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ type benchmarkIndexed struct {
index int
}

type benchmarkExtractionResult struct {
results [][]string
errors bool
skipped bool
}

// prepareCluster prepares the cluster for executing microbenchmarks. It copies
// the binaries to the remote cluster and extracts it. It also copies the lib
// directory to the remote cluster. The number of nodes in the cluster is
Expand Down Expand Up @@ -130,7 +136,9 @@ func listBenchmarks(
errorCount := 0
benchmarkCounts := make(map[benchmark]int)
cluster.ExecuteRemoteCommands(log, *flagCluster, commands, numNodes, true, func(response cluster.RemoteResponse) {
fmt.Print(".")
if !*flagQuiet {
fmt.Print(".")
}
if response.Err == nil {
pkg := response.Metadata.(string)
outer:
Expand All @@ -140,7 +148,9 @@ func listBenchmarks(
continue
}
if !isValidBenchmarkName(benchmarkName) {
fmt.Println()
if !*flagQuiet {
fmt.Println()
}
l.Printf("Ignoring invalid benchmark name: %s", benchmarkName)
continue
}
Expand All @@ -154,13 +164,17 @@ func listBenchmarks(
benchmarkCounts[benchmarkEntry]++
}
} else {
fmt.Println()
if !*flagQuiet {
fmt.Println()
}
l.Errorf("Remote command = {%s}, error = {%v}, stderr output = {%s}",
strings.Join(response.Args, " "), response.Err, response.Stderr)
errorCount++
}
})
fmt.Println()
if !*flagQuiet {
fmt.Println()
}

if errorCount > 0 {
return nil, nil, errors.New("Failed to list benchmarks")
Expand Down Expand Up @@ -241,7 +255,7 @@ func executeBenchmarks(binaries, packages []string) error {
// Generate commands for running benchmarks.
commands := make([][]cluster.RemoteCommand, 0)
for _, bench := range benchmarks {
runCommand := fmt.Sprintf("./run.sh %s -test.benchmem -test.bench=^%s$ -test.run=^$",
runCommand := fmt.Sprintf("./run.sh %s -test.benchmem -test.bench=^%s$ -test.run=^$ -test.v",
strings.Join(testArgs, " "), bench.name)
if *flagTimeout != "" {
runCommand = fmt.Sprintf("timeout %s %s", *flagTimeout, runCommand)
Expand Down Expand Up @@ -277,22 +291,32 @@ func executeBenchmarks(binaries, packages []string) error {
errorCount := 0
logIndex := 0
missingBenchmarks := make(map[benchmark]int, 0)
failedBenchmarks := make(map[benchmark]int, 0)
skippedBenchmarks := make(map[benchmark]int, 0)
l.Printf("Found %d benchmarks, distributing and running benchmarks for %d iteration(s) across cluster %s",
len(benchmarks), *flagIterations, *flagCluster)
cluster.ExecuteRemoteCommands(muteLogger, *flagCluster, commands, numNodes, !*flagLenient, func(response cluster.RemoteResponse) {
fmt.Print(".")
benchmarkResults, containsErrors := extractBenchmarkResults(response.Stdout)
if !*flagQuiet {
fmt.Print(".")
}
extractResults := extractBenchmarkResults(response.Stdout)
benchmarkResponse := response.Metadata.(benchmarkIndexed)
report := reporters[benchmarkResponse.index]
for _, benchmarkResult := range benchmarkResults {
for _, benchmarkResult := range extractResults.results {
if _, writeErr := report.benchmarkOutput[benchmarkResponse.pkg].WriteString(
fmt.Sprintf("%s\n", strings.Join(benchmarkResult, " "))); writeErr != nil {
l.Errorf("Failed to write benchmark result to file - %v", writeErr)
}
}
if containsErrors || response.Err != nil {
fmt.Println()
err = report.writeBenchmarkErrorLogs(response, logIndex)
if extractResults.errors || response.Err != nil {
if !*flagQuiet {
fmt.Println()
}
tag := fmt.Sprintf("%d", logIndex)
if response.ExitStatus == 124 {
tag = fmt.Sprintf("%d-timeout", logIndex)
}
err = report.writeBenchmarkErrorLogs(response, tag)
if err != nil {
l.Errorf("Failed to write error logs - %v", err)
}
Expand All @@ -304,17 +328,34 @@ func executeBenchmarks(binaries, packages []string) error {
response.Duration.Milliseconds())); writeErr != nil {
l.Errorf("Failed to write analytics to file - %v", writeErr)
}
if len(benchmarkResults) == 0 {
missingBenchmarks[benchmarkResponse.benchmark]++

// If we didn't find any results, increment the appropriate counter.
if len(extractResults.results) == 0 {
switch {
case extractResults.errors:
failedBenchmarks[benchmarkResponse.benchmark]++
case extractResults.skipped:
skippedBenchmarks[benchmarkResponse.benchmark]++
default:
missingBenchmarks[benchmarkResponse.benchmark]++
}
}
})

fmt.Println()
l.Printf("Completed benchmarks, results located at %s for time %s", workingDir, timestamp.Format(timeFormat))
if len(missingBenchmarks) > 0 {
l.Errorf("Failed to find results for %d benchmarks", len(missingBenchmarks))
l.Errorf("Missing benchmarks %v", missingBenchmarks)
if !*flagQuiet {
fmt.Println()
}
for res, count := range failedBenchmarks {
l.Errorf("Failed benchmark: %s/%s in %d iterations", res.pkg, res.name, count)
}
for res, count := range skippedBenchmarks {
l.Errorf("Skipped benchmark: %s/%s in %d iterations", res.pkg, res.name, count)
}
for res, count := range missingBenchmarks {
l.Errorf("Missing benchmark: %s/%s in %d iterations", res.pkg, res.name, count)
}

l.Printf("Completed benchmarks, results located at %s for time %s", workingDir, timestamp.Format(timeFormat))
if errorCount != 0 {
if *flagLenient {
l.Printf("Ignoring errors in benchmark results (lenient flag was set)")
Expand Down Expand Up @@ -350,12 +391,12 @@ func getRegexExclusionPairs() [][]*regexp.Regexp {
}

// extractBenchmarkResults extracts the microbenchmark results generated by a
// test binary and reports if any failures were found in the output. This method
// makes specific assumptions regarding the format of the output, and attempts
// to ignore any spurious output that the test binary may have logged. The
// returned list of string arrays each represent a row of metrics as outputted
// by the test binary.
func extractBenchmarkResults(benchmarkOutput string) ([][]string, bool) {
// test binary and reports if any failures or skips were found in the output.
// This method makes specific assumptions regarding the format of the output,
// and attempts to ignore any spurious output that the test binary may have
// logged. The returned list of string arrays each represent a row of metrics as
// outputted by the test binary.
func extractBenchmarkResults(benchmarkOutput string) benchmarkExtractionResult {
keywords := map[string]struct{}{
"ns/op": {},
"b/op": {},
Expand All @@ -364,12 +405,16 @@ func extractBenchmarkResults(benchmarkOutput string) ([][]string, bool) {
results := make([][]string, 0)
buf := make([]string, 0)
containsErrors := false
skipped := false
var benchName string
for _, line := range strings.Split(benchmarkOutput, "\n") {
elems := strings.Fields(line)
for _, s := range elems {
if !containsErrors {
containsErrors = strings.Contains(s, "FAIL") || strings.Contains(s, "panic")
containsErrors = strings.Contains(s, "FAIL") || strings.Contains(s, "panic:")
}
if !skipped {
skipped = strings.Contains(s, "SKIP")
}
if strings.HasPrefix(s, "Benchmark") && len(s) > 9 {
benchName = s
Expand All @@ -390,5 +435,5 @@ func extractBenchmarkResults(benchmarkOutput string) ([][]string, bool) {
}
}
}
return results, containsErrors
return benchmarkExtractionResult{results, containsErrors, skipped}
}
4 changes: 2 additions & 2 deletions pkg/cmd/roachprod-microbench/benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ func TestExtractBenchmarkResultsDataDriven(t *testing.T) {
if d.Cmd != "benchmark" {
d.Fatalf(t, "unknown command %s", d.Cmd)
}
result, fail := extractBenchmarkResults(d.Input)
output := fmt.Sprintf("%v %v", fail, result)
result := extractBenchmarkResults(d.Input)
output := fmt.Sprintf("%v", result)
return output
})
}
15 changes: 9 additions & 6 deletions pkg/cmd/roachprod-microbench/cluster/execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ type RemoteCommand struct {
// A Duration of -1 indicates that the command was cancelled.
type RemoteResponse struct {
RemoteCommand
Stdout string
Stderr string
Err error
Duration time.Duration
Stdout string
Stderr string
Err error
ExitStatus int
Duration time.Duration
}

func remoteWorker(
Expand All @@ -54,7 +55,7 @@ func remoteWorker(
for index, command := range commands {
if errors.Is(ctx.Err(), context.Canceled) {
for _, cancelCommand := range commands[index:] {
response <- RemoteResponse{cancelCommand, "", "", nil, -1}
response <- RemoteResponse{cancelCommand, "", "", nil, -1, -1}
}
break
}
Expand All @@ -63,12 +64,14 @@ func remoteWorker(
"", "", false, command.Args)
duration := timeutil.Since(start)
var stdout, stderr string
var exitStatus int
if len(runResult) > 0 {
stdout = runResult[0].Stdout
stderr = runResult[0].Stderr
exitStatus = runResult[0].RemoteExitStatus
err = errors.CombineErrors(err, runResult[0].Err)
}
response <- RemoteResponse{command, stdout, stderr, err, duration}
response <- RemoteResponse{command, stdout, stderr, err, exitStatus, duration}
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion pkg/cmd/roachprod-microbench/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"time"

"github.com/cockroachdb/cockroach/pkg/cmd/roachprod-microbench/google"
"github.com/cockroachdb/cockroach/pkg/roachprod/config"
"github.com/cockroachdb/cockroach/pkg/roachprod/logger"
"github.com/cockroachdb/cockroach/pkg/roachprod/ssh"
"github.com/cockroachdb/cockroach/pkg/util/timeutil"
Expand All @@ -46,6 +47,7 @@ var (
flagCopy = flags.Bool("copy", true, "copy and extract test binaries and libraries to the target cluster")
flagLenient = flags.Bool("lenient", true, "tolerate errors in the benchmark results")
flagAffinity = flags.Bool("affinity", true, "run benchmarks with iterations and binaries having affinity to the same node")
flagQuiet = flags.Bool("quiet", false, "suppress progress output")
flagIterations = flags.Int("iterations", 1, "number of iterations to run each benchmark")
workingDir string
testArgs []string
Expand Down Expand Up @@ -118,6 +120,7 @@ func setupVars() error {
}
}

config.Quiet = *flagQuiet
timestamp = timeutil.Now()
initLogger(filepath.Join(workingDir, fmt.Sprintf("roachprod-microbench-%s.log", timestamp.Format(timeFormat))))
l.Printf("roachprod-microbench %s", strings.Join(os.Args, " "))
Expand All @@ -130,7 +133,6 @@ func run() error {
if err != nil {
return err
}

var packages []string
if *flagCluster != "" {
binaries := []string{*flagBinaries}
Expand Down
6 changes: 3 additions & 3 deletions pkg/cmd/roachprod-microbench/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,10 @@ func (report *Report) closeReports() {
}
}

func (report *Report) writeBenchmarkErrorLogs(response cluster.RemoteResponse, index int) error {
func (report *Report) writeBenchmarkErrorLogs(response cluster.RemoteResponse, tag string) error {
benchmarkResponse := response.Metadata.(benchmarkIndexed)
stdoutLogName := fmt.Sprintf("%s-%d-stdout.log", benchmarkResponse.name, index)
stderrLogName := fmt.Sprintf("%s-%d-stderr.log", benchmarkResponse.name, index)
stdoutLogName := fmt.Sprintf("%s-%s-stdout.log", benchmarkResponse.name, tag)
stderrLogName := fmt.Sprintf("%s-%s-stderr.log", benchmarkResponse.name, tag)
l.Printf("Writing error logs for benchmark at %s, %s\n", stdoutLogName, stderrLogName)

if err := os.WriteFile(filepath.Join(report.path, stdoutLogName), []byte(response.Stdout), 0644); err != nil {
Expand Down
22 changes: 17 additions & 5 deletions pkg/cmd/roachprod-microbench/testdata/benchmark
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ BenchmarkFastIntMap/4x4-4/map-sized-24 1 977.0 ns/op 0 B/
BenchmarkFastIntMap/4x4-4/slice-24 1 1406 ns/op 64 B/op 3 allocs/op
PASS
----
false [[BenchmarkFastIntMap/4x4-4/fastintmap-24 1 603.0 ns/op 0 B/op 0 allocs/op] [BenchmarkFastIntMap/4x4-4/map-24 1 1039 ns/op 0 B/op 0 allocs/op] [BenchmarkFastIntMap/4x4-4/map-sized-24 1 977.0 ns/op 0 B/op 0 allocs/op] [BenchmarkFastIntMap/4x4-4/slice-24 1 1406 ns/op 64 B/op 3 allocs/op]]
{[[BenchmarkFastIntMap/4x4-4/fastintmap-24 1 603.0 ns/op 0 B/op 0 allocs/op] [BenchmarkFastIntMap/4x4-4/map-24 1 1039 ns/op 0 B/op 0 allocs/op] [BenchmarkFastIntMap/4x4-4/map-sized-24 1 977.0 ns/op 0 B/op 0 allocs/op] [BenchmarkFastIntMap/4x4-4/slice-24 1 1406 ns/op 64 B/op 3 allocs/op]] false false}

# Scattered benchmark output
benchmark
Expand All @@ -28,7 +28,7 @@ extra log noise
1 1406 ns/op 64 B/op 3 allocs/op
PASS
----
false [[BenchmarkFastIntMap/4x4-4/fastintmap-24 1 603.0 ns/op 0 B/op 0 allocs/op] [BenchmarkFastIntMap/4x4-4/map-24 1 1039 ns/op 0 B/op 0 allocs/op] [BenchmarkFastIntMap/4x4-4/map-sized-24 1 977.0 ns/op 0 B/op 0 allocs/op] [BenchmarkFastIntMap/4x4-4/slice-24 1 1406 ns/op 64 B/op 3 allocs/op]]
{[[BenchmarkFastIntMap/4x4-4/fastintmap-24 1 603.0 ns/op 0 B/op 0 allocs/op] [BenchmarkFastIntMap/4x4-4/map-24 1 1039 ns/op 0 B/op 0 allocs/op] [BenchmarkFastIntMap/4x4-4/map-sized-24 1 977.0 ns/op 0 B/op 0 allocs/op] [BenchmarkFastIntMap/4x4-4/slice-24 1 1406 ns/op 64 B/op 3 allocs/op]] false false}

# Missing benchmark name, and missing benchmark metrics (incorrect column count)
benchmark
Expand All @@ -40,7 +40,7 @@ BenchmarkFastIntMap/4x4-4/map-sized-24 1 977.0 ns/op
BenchmarkFastIntMap/4x4-4/slice-24 1 1406 ns/op 64 B/op 3 allocs/op
PASS
----
false [[BenchmarkFastIntMap/4x4-4/map-24 1 1039 ns/op 0 B/op 0 allocs/op] [BenchmarkFastIntMap/4x4-4/map-sized-24 1 977.0 ns/op] [BenchmarkFastIntMap/4x4-4/slice-24 1 1406 ns/op 64 B/op 3 allocs/op]]
{[[BenchmarkFastIntMap/4x4-4/map-24 1 1039 ns/op 0 B/op 0 allocs/op] [BenchmarkFastIntMap/4x4-4/map-sized-24 1 977.0 ns/op] [BenchmarkFastIntMap/4x4-4/slice-24 1 1406 ns/op 64 B/op 3 allocs/op]] false false}

# Failed benchmark
benchmark
Expand All @@ -49,7 +49,7 @@ cpu: Intel(R) Xeon(R) CPU @ 2.80GHz
BenchmarkFastIntMap/4x4-4/slice-24 1 1406 ns/op 64 B/op 3 allocs/op
FAIL
----
true [[BenchmarkFastIntMap/4x4-4/slice-24 1 1406 ns/op 64 B/op 3 allocs/op]]
{[[BenchmarkFastIntMap/4x4-4/slice-24 1 1406 ns/op 64 B/op 3 allocs/op]] true false}

# Benchmark with panic
benchmark
Expand All @@ -58,4 +58,16 @@ cpu: Intel(R) Xeon(R) CPU @ 2.80GHz
BenchmarkFastIntMap/4x4-4/slice-24 1 1406 ns/op 64 B/op 3 allocs/op
panic: something went wrong
----
true [[BenchmarkFastIntMap/4x4-4/slice-24 1 1406 ns/op 64 B/op 3 allocs/op]]
{[[BenchmarkFastIntMap/4x4-4/slice-24 1 1406 ns/op 64 B/op 3 allocs/op]] true false}

# Benchmark with skipped benchmarks
benchmark
goos: linux
goarch: amd64
cpu: Intel(R) Xeon(R) CPU @ 2.80GHz
BenchmarkIntersectsLargePolygonsAndPoints
binary_predicates_bench_test.go:51: [disabled under -short]
--- SKIP: BenchmarkIntersectsLargePolygonsAndPoints
PASS
----
{[] false true}