compilebench: factor running build tool commands

We do a lot of sophisticated things around running the compile command
to gather benchmark metrics from it. We're about to add a benchmark
for the linker and will want all of the same mechanism. This CL
factors this out into a function.

Change-Id: I499d15d30417618f372982404ceb3e0f9c762e5c
Reviewed-on: https://go-review.googlesource.com/c/tools/+/175800
Run-TryBot: Austin Clements <austin@google.com>
Reviewed-by: Russ Cox <rsc@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
diff --git a/cmd/compilebench/main.go b/cmd/compilebench/main.go
index b627287..0233821 100644
--- a/cmd/compilebench/main.go
+++ b/cmd/compilebench/main.go
@@ -172,6 +172,13 @@
 		compiler = strings.TrimSpace(string(out))
 	}
 
+	if is6g {
+		*flagMemprofilerate = -1
+		*flagAlloc = false
+		*flagCpuprofile = ""
+		*flagMemprofile = ""
+	}
+
 	if *flagRun != "" {
 		r, err := regexp.Compile(*flagRun)
 		if err != nil {
@@ -292,31 +299,56 @@
 	}
 
 	args := []string{"-o", "_compilebench_.o"}
-	if is6g {
-		*flagMemprofilerate = -1
-		*flagAlloc = false
-		*flagCpuprofile = ""
-		*flagMemprofile = ""
-	}
-	if *flagMemprofilerate >= 0 {
-		args = append(args, "-memprofilerate", fmt.Sprint(*flagMemprofilerate))
-	}
 	args = append(args, strings.Fields(*flagCompilerFlags)...)
+	args = append(args, pkg.GoFiles...)
+	if err := runBuildCmd(name, count, pkg.Dir, compiler, args); err != nil {
+		return err
+	}
+
+	opath := pkg.Dir + "/_compilebench_.o"
+	if *flagObj {
+		// TODO(josharian): object files are big; just read enough to find what we seek.
+		data, err := ioutil.ReadFile(opath)
+		if err != nil {
+			log.Print(err)
+		}
+		// Find start of export data.
+		i := bytes.Index(data, []byte("\n$$B\n")) + len("\n$$B\n")
+		// Count bytes to end of export data.
+		nexport := bytes.Index(data[i:], []byte("\n$$\n"))
+		fmt.Printf(" %d object-bytes %d export-bytes", len(data), nexport)
+	}
+	fmt.Println()
+
+	os.Remove(opath)
+	return nil
+}
+
+// runBuildCmd runs "tool args..." in dir, measures standard build
+// tool metrics, and prints a benchmark line. The caller may print
+// additional metrics and then must print a newline.
+//
+// This assumes tool accepts standard build tool flags like
+// -memprofilerate, -memprofile, and -cpuprofile.
+func runBuildCmd(name string, count int, dir, tool string, args []string) error {
+	var preArgs []string
+	if *flagMemprofilerate >= 0 {
+		preArgs = append(preArgs, "-memprofilerate", fmt.Sprint(*flagMemprofilerate))
+	}
 	if *flagAlloc || *flagCpuprofile != "" || *flagMemprofile != "" {
 		if *flagAlloc || *flagMemprofile != "" {
-			args = append(args, "-memprofile", "_compilebench_.memprof")
+			preArgs = append(preArgs, "-memprofile", "_compilebench_.memprof")
 		}
 		if *flagCpuprofile != "" {
-			args = append(args, "-cpuprofile", "_compilebench_.cpuprof")
+			preArgs = append(preArgs, "-cpuprofile", "_compilebench_.cpuprof")
 		}
 	}
-	args = append(args, pkg.GoFiles...)
-	cmd := exec.Command(compiler, args...)
-	cmd.Dir = pkg.Dir
+	cmd := exec.Command(tool, append(preArgs, args...)...)
+	cmd.Dir = dir
 	cmd.Stdout = os.Stderr
 	cmd.Stderr = os.Stderr
 	start := time.Now()
-	err = cmd.Run()
+	err := cmd.Run()
 	if err != nil {
 		return err
 	}
@@ -324,7 +356,7 @@
 
 	var allocs, allocbytes int64
 	if *flagAlloc || *flagMemprofile != "" {
-		out, err := ioutil.ReadFile(pkg.Dir + "/_compilebench_.memprof")
+		out, err := ioutil.ReadFile(dir + "/_compilebench_.memprof")
 		if err != nil {
 			log.Print("cannot find memory profile after compilation")
 		}
@@ -354,11 +386,11 @@
 				log.Print(err)
 			}
 		}
-		os.Remove(pkg.Dir + "/_compilebench_.memprof")
+		os.Remove(dir + "/_compilebench_.memprof")
 	}
 
 	if *flagCpuprofile != "" {
-		out, err := ioutil.ReadFile(pkg.Dir + "/_compilebench_.cpuprof")
+		out, err := ioutil.ReadFile(dir + "/_compilebench_.cpuprof")
 		if err != nil {
 			log.Print(err)
 		}
@@ -369,7 +401,7 @@
 		if err := ioutil.WriteFile(outpath, out, 0666); err != nil {
 			log.Print(err)
 		}
-		os.Remove(pkg.Dir + "/_compilebench_.cpuprof")
+		os.Remove(dir + "/_compilebench_.cpuprof")
 	}
 
 	wallns := end.Sub(start).Nanoseconds()
@@ -380,21 +412,5 @@
 		fmt.Printf(" %d B/op %d allocs/op", allocbytes, allocs)
 	}
 
-	opath := pkg.Dir + "/_compilebench_.o"
-	if *flagObj {
-		// TODO(josharian): object files are big; just read enough to find what we seek.
-		data, err := ioutil.ReadFile(opath)
-		if err != nil {
-			log.Print(err)
-		}
-		// Find start of export data.
-		i := bytes.Index(data, []byte("\n$$B\n")) + len("\n$$B\n")
-		// Count bytes to end of export data.
-		nexport := bytes.Index(data[i:], []byte("\n$$\n"))
-		fmt.Printf(" %d object-bytes %d export-bytes", len(data), nexport)
-	}
-	fmt.Println()
-
-	os.Remove(opath)
 	return nil
 }