cmd/compilebench: import from rsc.io

This CL is an import of compilebench from rsc.io/compilebench
at commit 1a5bec7724500bd6df47e053dbc42b41fe653372.

This CL brings in only main.go, unaltered.
The other files were:

* LICENSE: now unnecessary
* README.md: now unnecessary
* compilecmp: still available at its original home,
  soon to be superceded by a new tool in x/tools

Once compilecmp is available in x/tools,
I will replace rsc.io/compilebench with a breadcrumb repo.

All outstanding pull requests against rsc.io/compilebench are from me.
They will be closed, and new CLs sent here as appropriate.

Change-Id: Ic436abf4a662173c6c184bc765b1b9cab13b3cfb
Reviewed-on: https://go-review.googlesource.com/39712
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/compilebench/main.go b/compilebench/main.go
new file mode 100644
index 0000000..9050ac1
--- /dev/null
+++ b/compilebench/main.go
@@ -0,0 +1,319 @@
+// Copyright 2015 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.
+
+// Compilebench benchmarks the speed of the Go compiler.
+//
+// Usage:
+//
+//	compilebench [options]
+//
+// It times the compilation of various packages and prints results in
+// the format used by package testing (and expected by rsc.io/benchstat).
+//
+// The options are:
+//
+//	-alloc
+//		Report allocations.
+//
+//	-compile exe
+//		Use exe as the path to the cmd/compile binary.
+//
+//	-compileflags 'list'
+//		Pass the space-separated list of flags to the compilation.
+//
+//	-count n
+//		Run each benchmark n times (default 1).
+//
+//	-cpuprofile file
+//		Write a CPU profile of the compiler to file.
+//
+//	-memprofile file
+//		Write a memory profile of the compiler to file.
+//
+//	-memprofilerate rate
+//		Set runtime.MemProfileRate during compilation.
+//
+//	-run regexp
+//		Only run benchmarks with names matching regexp.
+//
+// Although -cpuprofile and -memprofile are intended to write a
+// combined profile for all the executed benchmarks to file,
+// today they write only the profile for the last benchmark executed.
+//
+// The default memory profiling rate is one profile sample per 512 kB
+// allocated (see ``go doc runtime.MemProfileRate'').
+// Lowering the rate (for example, -memprofilerate 64000) produces
+// a more fine-grained and therefore accurate profile, but it also incurs
+// execution cost. For benchmark comparisons, never use timings
+// obtained with a low -memprofilerate option.
+//
+// Example
+//
+// Assuming the base version of the compiler has been saved with
+// ``toolstash save,'' this sequence compares the old and new compiler:
+//
+//	compilebench -count 10 -compile $(toolstash -n compile) >old.txt
+//	compilebench -count 10 >new.txt
+//	benchstat old.txt new.txt
+//
+package main
+
+import (
+	"flag"
+	"fmt"
+	"go/build"
+	"io/ioutil"
+	"log"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"regexp"
+	"runtime"
+	"strconv"
+	"strings"
+	"time"
+)
+
+var (
+	goroot   = runtime.GOROOT()
+	compiler string
+	runRE    *regexp.Regexp
+	is6g     bool
+)
+
+var (
+	flagAlloc          = flag.Bool("alloc", false, "report allocations")
+	flagCompiler       = flag.String("compile", "", "use `exe` as the cmd/compile binary")
+	flagCompilerFlags  = flag.String("compileflags", "", "additional `flags` to pass to compile")
+	flagRun            = flag.String("run", "", "run benchmarks matching `regexp`")
+	flagCount          = flag.Int("count", 1, "run benchmarks `n` times")
+	flagCpuprofile     = flag.String("cpuprofile", "", "write CPU profile to `file`")
+	flagMemprofile     = flag.String("memprofile", "", "write memory profile to `file`")
+	flagMemprofilerate = flag.Int64("memprofilerate", -1, "set memory profile `rate`")
+	flagShort          = flag.Bool("short", false, "skip long-running benchmarks")
+)
+
+var tests = []struct {
+	name string
+	dir  string
+	long bool
+}{
+	{"BenchmarkTemplate", "html/template", false},
+	{"BenchmarkUnicode", "unicode", false},
+	{"BenchmarkGoTypes", "go/types", false},
+	{"BenchmarkCompiler", "cmd/compile/internal/gc", false},
+	{"BenchmarkMakeBash", "", true},
+	{"BenchmarkHelloSize", "", false},
+	{"BenchmarkCmdGoSize", "", true},
+}
+
+func usage() {
+	fmt.Fprintf(os.Stderr, "usage: compilebench [options]\n")
+	fmt.Fprintf(os.Stderr, "options:\n")
+	flag.PrintDefaults()
+	os.Exit(2)
+}
+
+func main() {
+	log.SetFlags(0)
+	log.SetPrefix("compilebench: ")
+	flag.Usage = usage
+	flag.Parse()
+	if flag.NArg() != 0 {
+		usage()
+	}
+
+	compiler = *flagCompiler
+	if compiler == "" {
+		out, err := exec.Command("go", "tool", "-n", "compile").CombinedOutput()
+		if err != nil {
+			out, err = exec.Command("go", "tool", "-n", "6g").CombinedOutput()
+			is6g = true
+			if err != nil {
+				out, err = exec.Command("go", "tool", "-n", "compile").CombinedOutput()
+				log.Fatalf("go tool -n compiler: %v\n%s", err, out)
+			}
+		}
+		compiler = strings.TrimSpace(string(out))
+	}
+
+	if *flagRun != "" {
+		r, err := regexp.Compile(*flagRun)
+		if err != nil {
+			log.Fatalf("invalid -run argument: %v", err)
+		}
+		runRE = r
+	}
+
+	for i := 0; i < *flagCount; i++ {
+		for _, tt := range tests {
+			if tt.long && *flagShort {
+				continue
+			}
+			if runRE == nil || runRE.MatchString(tt.name) {
+				runBuild(tt.name, tt.dir)
+			}
+		}
+	}
+}
+
+func runCmd(name string, cmd *exec.Cmd) {
+	start := time.Now()
+	out, err := cmd.CombinedOutput()
+	if err != nil {
+		log.Printf("%v: %v\n%s", name, err, out)
+		return
+	}
+	fmt.Printf("%s 1 %d ns/op\n", name, time.Since(start).Nanoseconds())
+}
+
+func runMakeBash() {
+	cmd := exec.Command("./make.bash")
+	cmd.Dir = filepath.Join(runtime.GOROOT(), "src")
+	runCmd("BenchmarkMakeBash", cmd)
+}
+
+func runCmdGoSize() {
+	runSize("BenchmarkCmdGoSize", filepath.Join(runtime.GOROOT(), "bin/go"))
+}
+
+func runHelloSize() {
+	cmd := exec.Command("go", "build", "-o", "_hello_", filepath.Join(runtime.GOROOT(), "test/helloworld.go"))
+	cmd.Stdout = os.Stderr
+	cmd.Stderr = os.Stderr
+	if err := cmd.Run(); err != nil {
+		log.Print(err)
+		return
+	}
+	defer os.Remove("_hello_")
+	runSize("BenchmarkHelloSize", "_hello_")
+}
+
+func runSize(name, file string) {
+	info, err := os.Stat(file)
+	if err != nil {
+		log.Print(err)
+		return
+	}
+	out, err := exec.Command("size", file).CombinedOutput()
+	if err != nil {
+		log.Printf("size: %v\n%s", err, out)
+		return
+	}
+	lines := strings.Split(string(out), "\n")
+	if len(lines) < 2 {
+		log.Printf("not enough output from size: %s", out)
+		return
+	}
+	f := strings.Fields(lines[1])
+	if strings.HasPrefix(lines[0], "__TEXT") && len(f) >= 2 { // OS X
+		fmt.Printf("%s 1 %s text-bytes %s data-bytes %v exe-bytes\n", name, f[0], f[1], info.Size())
+	} else if strings.Contains(lines[0], "bss") && len(f) >= 3 {
+		fmt.Printf("%s 1 %s text-bytes %s data-bytes %s bss-bytes %v exe-bytes\n", name, f[0], f[1], f[2], info.Size())
+	}
+}
+
+func runBuild(name, dir string) {
+	switch name {
+	case "BenchmarkMakeBash":
+		runMakeBash()
+		return
+	case "BenchmarkCmdGoSize":
+		runCmdGoSize()
+		return
+	case "BenchmarkHelloSize":
+		runHelloSize()
+		return
+	}
+
+	pkg, err := build.Import(dir, ".", 0)
+	if err != nil {
+		log.Print(err)
+		return
+	}
+	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)...)
+	if *flagAlloc || *flagCpuprofile != "" || *flagMemprofile != "" {
+		if *flagAlloc || *flagMemprofile != "" {
+			args = append(args, "-memprofile", "_compilebench_.memprof")
+		}
+		if *flagCpuprofile != "" {
+			args = append(args, "-cpuprofile", "_compilebench_.cpuprof")
+		}
+	}
+	args = append(args, pkg.GoFiles...)
+	cmd := exec.Command(compiler, args...)
+	cmd.Dir = pkg.Dir
+	cmd.Stdout = os.Stderr
+	cmd.Stderr = os.Stderr
+	start := time.Now()
+	err = cmd.Run()
+	if err != nil {
+		log.Printf("%v: %v", name, err)
+		return
+	}
+	end := time.Now()
+
+	var allocs, bytes int64
+	if *flagAlloc || *flagMemprofile != "" {
+		out, err := ioutil.ReadFile(pkg.Dir + "/_compilebench_.memprof")
+		if err != nil {
+			log.Print("cannot find memory profile after compilation")
+		}
+		for _, line := range strings.Split(string(out), "\n") {
+			f := strings.Fields(line)
+			if len(f) < 4 || f[0] != "#" || f[2] != "=" {
+				continue
+			}
+			val, err := strconv.ParseInt(f[3], 0, 64)
+			if err != nil {
+				continue
+			}
+			switch f[1] {
+			case "TotalAlloc":
+				bytes = val
+			case "Mallocs":
+				allocs = val
+			}
+		}
+
+		if *flagMemprofile != "" {
+			if err := ioutil.WriteFile(*flagMemprofile, out, 0666); err != nil {
+				log.Print(err)
+			}
+		}
+		os.Remove(pkg.Dir + "/_compilebench_.memprof")
+	}
+
+	if *flagCpuprofile != "" {
+		out, err := ioutil.ReadFile(pkg.Dir + "/_compilebench_.cpuprof")
+		if err != nil {
+			log.Print(err)
+		}
+		if err := ioutil.WriteFile(*flagCpuprofile, out, 0666); err != nil {
+			log.Print(err)
+		}
+		os.Remove(pkg.Dir + "/_compilebench_.cpuprof")
+	}
+
+	wallns := end.Sub(start).Nanoseconds()
+	userns := cmd.ProcessState.UserTime().Nanoseconds()
+
+	if *flagAlloc {
+		fmt.Printf("%s 1 %d ns/op %d user-ns/op %d B/op %d allocs/op\n", name, wallns, userns, bytes, allocs)
+	} else {
+		fmt.Printf("%s 1 %d ns/op %d user-ns/op\n", name, wallns, userns)
+	}
+
+	os.Remove(pkg.Dir + "/_compilebench_.o")
+}