sweet/benchmarks/go-build: use new diagnostics framework

Change-Id: I77f8b37288ee6bb72a34a46aac4450e40559caaf
Cq-Include-Trybots: luci.golang.try:x_benchmarks-gotip-linux-amd64-longtest
Reviewed-on: https://go-review.googlesource.com/c/benchmarks/+/600068
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
diff --git a/sweet/benchmarks/go-build/main.go b/sweet/benchmarks/go-build/main.go
index 3d805ad..01cf6a8 100644
--- a/sweet/benchmarks/go-build/main.go
+++ b/sweet/benchmarks/go-build/main.go
@@ -8,17 +8,16 @@
 	"flag"
 	"fmt"
 	"io"
+	"math/rand"
 	"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 (
@@ -26,6 +25,8 @@
 	tmpDir    string
 	toolexec  bool
 	benchName string
+
+	diag *driver.Diagnostics
 )
 
 func init() {
@@ -34,6 +35,10 @@
 	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")
+	flag.Func("diagnostics", "for -toolexec", func(s string) error {
+		diag = new(driver.Diagnostics)
+		return diag.UnmarshalText([]byte(s))
+	})
 }
 
 func tmpResultsDir() string {
@@ -56,7 +61,7 @@
 
 	name := "GoBuild" + strings.Title(filepath.Base(pkgPath))
 
-	cmdArgs := []string{"build", "-a"}
+	cmdArgs := []string{goTool, "build", "-a"}
 
 	// Build a command comprised of this binary to pass to -toolexec.
 	selfPath, err := filepath.Abs(os.Args[0])
@@ -75,17 +80,28 @@
 		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...)
+	diag = driver.NewDiagnostics(name)
+	diagFlag, err := diag.MarshalText()
+	if err != nil {
+		return err
 	}
+	selfCmd = append(selfCmd, "-diagnostics", string(diagFlag))
+
+	cmdArgs = append(cmdArgs, "-toolexec", strings.Join(selfCmd, " "))
+
+	if df, err := diag.Create(diagnostics.Perf); err != nil {
+		fmt.Fprintf(os.Stderr, "failed to create %s diagnostics: %s\n", diagnostics.Perf, err)
+	} else if df != nil {
+		df.Close()
+		defer df.Commit()
+
+		perfArgs := []string{"perf", "record", "-o", df.Name()}
+		perfArgs = append(perfArgs, driver.PerfFlags()...)
+		perfArgs = append(perfArgs, cmdArgs...)
+		cmdArgs = perfArgs
+	}
+
+	baseCmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
 	baseCmd.Dir = pkgPath
 	baseCmd.Env = common.NewEnvFromEnviron().MustSet("GOROOT=" + filepath.Dir(filepath.Dir(goTool))).Collapse()
 	baseCmd.Stdout = os.Stdout
@@ -95,91 +111,15 @@
 		return err
 	}
 	err = driver.RunBenchmark(name, func(d *driver.B) error {
+		defer diag.Commit(d)
 		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 {
@@ -220,22 +160,29 @@
 	}
 	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()
+		if bin == "link" && typ == diagnostics.Trace {
+			// TODO(mknyszek): Traces are not supported for the linker.
+			continue
+		}
+
+		subName := ""
+		if !typ.CanMerge() {
+			// Create a unique name for this diagnostic file.
+			subName = fmt.Sprintf("%08x", rand.Uint32())
+		}
+		df, err := diag.CreateNamed(typ, subName)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "failed to create %s diagnostics: %s\n", typ, err)
+			continue
+		} else if df != nil {
+			df.Close()
+			defer df.Commit()
+
 			flag := "-" + string(typ)
 			if typ == diagnostics.Trace {
-				flag += "profile" // The compiler flag is -traceprofile.
+				flag = "-traceprofile"
 			}
-			extraFlags = append(extraFlags, flag, f.Name())
+			extraFlags = append(extraFlags, flag, df.Name())
 		}
 	}
 	cmd := exec.Command(flag.Args()[0], append(extraFlags, flag.Args()[1:]...)...)
diff --git a/sweet/benchmarks/internal/driver/diagnostics.go b/sweet/benchmarks/internal/driver/diagnostics.go
index c910a87..a9d51a3 100644
--- a/sweet/benchmarks/internal/driver/diagnostics.go
+++ b/sweet/benchmarks/internal/driver/diagnostics.go
@@ -47,6 +47,28 @@
 	return &Diagnostics{name: name}
 }
 
+// MarshalText marshals this Diagnostics configuration into text that can be
+// passed to another process (e.g., via a flag) and unmarshaled into a new
+// Diagnostics with UnmarshalText. That other process may call Create* on the
+// result and use the resulting [DiagnosticsFile]s as usual, but it must not
+// call [Diagnostics.Commit].
+func (d *Diagnostics) MarshalText() (text []byte, err error) {
+	tmpDir, err := d.getTmpDir()
+	return []byte(tmpDir), err
+}
+
+func (d *Diagnostics) UnmarshalText(text []byte) error {
+	// Clear the tmpDir once.
+	first := false
+	d.once.Do(func() { first = true })
+	if !first {
+		return fmt.Errorf("Diagnostics.UnmarshalText requires an unused Diagnostics")
+	}
+	d.tmpDir = string(text)
+	d.tmpDirErr = nil
+	return nil
+}
+
 func safeFileName(name string) string {
 	// The following characters are disallowed by either VFAT, NTFS, APFS, or
 	// most Unix file systems: