blob: 25152feb12254a219145af2487f9519223a67b79 [file] [log] [blame]
// Copyright 2021 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 main
import (
"bytes"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"text/template"
)
// TODO(prattmic): refactor bent to export Todo so we can directly build this
// in Go.
var configurationTmpl = template.Must(template.New("configuration").Parse(`
{{- range . -}}
[[Configurations]]
Name = "{{.Name}}"
Root = "{{.GOROOT}}"
AfterBuild = ["benchsize"]
{{end -}}
`))
func writeBentConfiguration(filename string, tcs []*toolchain) error {
var buf bytes.Buffer
if err := configurationTmpl.Execute(&buf, tcs); err != nil {
return fmt.Errorf("error generating configuration: %w", err)
}
log.Printf("bent configuration:\n%s", buf.String())
if err := os.WriteFile(filename, buf.Bytes(), 0644); err != nil {
return fmt.Errorf("error creating configurations.toml: %w", err)
}
return nil
}
// removeAllIncludingReadonly is like os.RemoveAll except that it'll
// also try to change permissions to work around permission errors
// when deleting.
func removeAllIncludingReadonly(dir string) error {
err := os.RemoveAll(dir)
if err == nil || !os.IsPermission(err) || runtime.GOOS == "windows" /* different fs permission model */ {
return err
}
// Make a best effort (ignoring errors) attempt to make all
// files and directories writable before we try to delete them
// all again.
filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
const ownerWritable = 0200
if err != nil || fi.Mode().Perm()&ownerWritable != 0 {
return nil
}
os.Chmod(path, fi.Mode().Perm()|ownerWritable)
return nil
})
return os.RemoveAll(dir)
}
func bent(tcs []*toolchain) (err error) {
dir, err := os.MkdirTemp("", "bent")
if err != nil {
return fmt.Errorf("error creating temporary directory: %w", err)
}
defer func() {
r := removeAllIncludingReadonly(dir)
if r != nil && err == nil {
err = fmt.Errorf("error removing temporary directory: %w", err)
} else if r != nil {
log.Printf("error removing temporary directory: %v", err)
}
}()
log.Printf("Bent temporary directory: %s", dir)
log.Printf("Building bent...")
// Build bent itself. Just pick any toolchain we have; it doesn't matter which.
// We're not benchmarking bent itself, it's just a driver.
bentPath := filepath.Join(dir, "bent")
if err := tcs[0].BuildPackage("golang.org/x/benchmarks/cmd/bent", bentPath); err != nil {
return fmt.Errorf("building bent: %w", err)
}
log.Printf("Initializing bent...")
// Initialize scratch dir for bent.
cmd := exec.Command(bentPath, "-I")
cmd.Dir = dir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("error running bent -I: %w", err)
}
confFile := filepath.Join(dir, "configurations.toml")
if err := writeBentConfiguration(confFile, tcs); err != nil {
return fmt.Errorf("error writing configuration: %w", err)
}
log.Printf("Running bent...")
// Finally we can actually run the benchmarks.
// N.B. bent prints the "toolchain" tag to indicate which toolchain is being used.
// It's passed to bent via the TOML configuration.
cmd = exec.Command(bentPath,
"-N", "10",
"-C", confFile,
"-B", filepath.Join(dir, "benchmarks-50.toml"),
"-report-build-time=false", // We only run builds once, which won't yield statistically significant results.
"-v",
)
cmd.Dir = dir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("error running bent: %w", err)
}
return nil
}