blob: 3d805ad7329567f722999abd3fc5c6ca32ca2822 [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 (
"flag"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/google/pprof/profile"
"golang.org/x/benchmarks/sweet/benchmarks/internal/cgroups"
"golang.org/x/benchmarks/sweet/benchmarks/internal/driver"
"golang.org/x/benchmarks/sweet/common"
"golang.org/x/benchmarks/sweet/common/diagnostics"
sprofile "golang.org/x/benchmarks/sweet/common/profile"
)
var (
goTool string
tmpDir string
toolexec bool
benchName string
)
func init() {
driver.SetFlags(flag.CommandLine)
flag.StringVar(&goTool, "go", "", "path to cmd/go binary")
flag.StringVar(&tmpDir, "tmp", "", "work directory (cleared before use)")
flag.BoolVar(&toolexec, "toolexec", false, "run as a toolexec binary")
flag.StringVar(&benchName, "bench-name", "", "for -toolexec")
}
func tmpResultsDir() string {
return filepath.Join(tmpDir, "results")
}
var benchOpts = []driver.RunOption{
driver.DoTime(true),
}
func run(pkgPath string) error {
// Clear any stale results from previous runs and recreate
// the directory.
if err := os.RemoveAll(tmpResultsDir()); err != nil {
return err
}
if err := os.MkdirAll(tmpResultsDir(), 0777); err != nil {
return err
}
name := "GoBuild" + strings.Title(filepath.Base(pkgPath))
cmdArgs := []string{"build", "-a"}
// Build a command comprised of this binary to pass to -toolexec.
selfPath, err := filepath.Abs(os.Args[0])
if err != nil {
return err
}
selfCmd := []string{
selfPath, "-toolexec",
"-bench-name", name,
}
flag.CommandLine.Visit(func(f *flag.Flag) {
if f.Name == "go" || f.Name == "bench-name" || strings.HasPrefix(f.Name, "perf") {
// No need to pass this along.
return
}
selfCmd = append(selfCmd, "-"+f.Name, f.Value.String())
})
cmdArgs = append(cmdArgs, "-toolexec", strings.Join(selfCmd, " "))
var baseCmd *exec.Cmd
if driver.DiagnosticEnabled(diagnostics.Perf) {
perfArgs := []string{"record", "-o", filepath.Join(tmpDir, "perf.data")}
perfArgs = append(perfArgs, driver.PerfFlags()...)
perfArgs = append(perfArgs, goTool)
perfArgs = append(perfArgs, cmdArgs...)
baseCmd = exec.Command("perf", perfArgs...)
} else {
baseCmd = exec.Command(goTool, cmdArgs...)
}
baseCmd.Dir = pkgPath
baseCmd.Env = common.NewEnvFromEnviron().MustSet("GOROOT=" + filepath.Dir(filepath.Dir(goTool))).Collapse()
baseCmd.Stdout = os.Stdout
baseCmd.Stderr = os.Stderr
cmd, err := cgroups.WrapCommand(baseCmd, "test.scope")
if err != nil {
return err
}
err = driver.RunBenchmark(name, func(d *driver.B) error {
return cmd.Run()
}, append(benchOpts, driver.DoAvgRSS(cmd.RSSFunc()))...)
if err != nil {
return err
}
// Handle any CPU profiles produced, and merge them.
// Then, write them out to the canonical profiles above.
if driver.DiagnosticEnabled(diagnostics.CPUProfile) {
compileProfile, err := mergePprofProfiles(tmpDir, profilePrefix("compile", diagnostics.CPUProfile))
if err != nil {
return err
}
if err := driver.WritePprofProfile(compileProfile, diagnostics.CPUProfile, name+"Compile"); err != nil {
return err
}
linkProfile, err := mergePprofProfiles(tmpDir, profilePrefix("link", diagnostics.CPUProfile))
if err != nil {
return err
}
if err := driver.WritePprofProfile(linkProfile, diagnostics.CPUProfile, name+"Link"); err != nil {
return err
}
}
if driver.DiagnosticEnabled(diagnostics.MemProfile) {
if err := copyPprofProfiles(tmpDir, "compile", diagnostics.MemProfile, name+"Compile"); err != nil {
return err
}
if err := copyPprofProfiles(tmpDir, "link", diagnostics.MemProfile, name+"Link"); err != nil {
return err
}
}
if driver.DiagnosticEnabled(diagnostics.Perf) {
if err := driver.CopyDiagnosticData(filepath.Join(tmpDir, "perf.data"), diagnostics.Perf, name); err != nil {
return err
}
}
if driver.DiagnosticEnabled(diagnostics.Trace) {
entries, err := os.ReadDir(tmpDir)
if err != nil {
return err
}
for _, entry := range entries {
if !strings.HasPrefix(entry.Name(), profilePrefix("compile", diagnostics.Trace)) {
continue
}
if err := driver.CopyDiagnosticData(filepath.Join(tmpDir, entry.Name()), diagnostics.Trace, name+"Compile"); err != nil {
return err
}
}
}
return printOtherResults(tmpResultsDir())
}
func mergePprofProfiles(dir, prefix string) (*profile.Profile, error) {
profiles, err := sprofile.ReadDirPprof(dir, func(name string) bool {
return strings.HasPrefix(name, prefix)
})
if err != nil {
return nil, err
}
return profile.Merge(profiles)
}
func copyPprofProfiles(dir, bin string, typ diagnostics.Type, finalPrefix string) error {
prefix := profilePrefix(bin, typ)
profiles, err := sprofile.ReadDirPprof(dir, func(name string) bool {
return strings.HasPrefix(name, prefix)
})
if err != nil {
return err
}
for _, profile := range profiles {
if err := driver.WritePprofProfile(profile, typ, finalPrefix); err != nil {
return err
}
}
return nil
}
func profilePrefix(bin string, typ diagnostics.Type) string {
return bin + "-prof." + string(typ)
}
func printOtherResults(dir string) error {
entries, err := os.ReadDir(dir)
if err != nil {
return err
}
for _, entry := range entries {
name := entry.Name()
path := filepath.Join(dir, name)
if strings.HasSuffix(name, ".results") {
f, err := os.Open(path)
if err != nil {
return err
}
if _, err := io.Copy(os.Stderr, f); err != nil {
f.Close()
return err
}
f.Close()
}
}
return nil
}
func runToolexec() error {
var benchSuffix string
benchmark := false
bin := filepath.Base(flag.Arg(0))
switch bin {
case "compile":
case "link":
benchSuffix = "Link"
benchmark = true
default:
cmd := exec.Command(flag.Args()[0], flag.Args()[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
var extraFlags []string
for _, typ := range []diagnostics.Type{diagnostics.CPUProfile, diagnostics.MemProfile, diagnostics.Trace} {
if driver.DiagnosticEnabled(typ) {
if bin == "link" && typ == diagnostics.Trace {
// TODO(mknyszek): Traces are not supported for the linker.
continue
}
// Stake a claim for a filename.
f, err := os.CreateTemp(tmpDir, profilePrefix(bin, typ))
if err != nil {
return err
}
f.Close()
flag := "-" + string(typ)
if typ == diagnostics.Trace {
flag += "profile" // The compiler flag is -traceprofile.
}
extraFlags = append(extraFlags, flag, f.Name())
}
}
cmd := exec.Command(flag.Args()[0], append(extraFlags, flag.Args()[1:]...)...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if benchmark {
name := benchName + benchSuffix
f, err := os.Create(filepath.Join(tmpResultsDir(), name+".results"))
if err != nil {
return err
}
defer f.Close()
return driver.RunBenchmark(name, func(d *driver.B) error {
return cmd.Run()
}, append(benchOpts, driver.WriteResultsTo(f))...)
}
return cmd.Run()
}
func main() {
flag.Parse()
if toolexec {
if err := runToolexec(); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
return
}
if flag.NArg() != 1 {
fmt.Fprintf(os.Stderr, "error: expected one argument\n")
os.Exit(1)
}
if err := run(flag.Arg(0)); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}