mirror of
https://github.com/golang/go
synced 2024-11-06 15:36:24 -07:00
cd91e8d096
They were from a time before we had the os/exec package, if memory serves. Also, make verbose also mean that the main build's stdout and stderr go to the real stdout and stderr as well. I'll want that for the Docker-based builder. LGTM=adg R=adg CC=golang-codereviews https://golang.org/cl/135000043
255 lines
7.6 KiB
Go
255 lines
7.6 KiB
Go
// Copyright 2013 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 (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// benchHash benchmarks a single commit.
|
|
func (b *Builder) benchHash(hash string, benchs []string) error {
|
|
if *verbose {
|
|
log.Println(b.name, "benchmarking", hash)
|
|
}
|
|
|
|
res := &PerfResult{Hash: hash, Benchmark: "meta-done"}
|
|
|
|
// Create place in which to do work.
|
|
workpath := filepath.Join(*buildroot, b.name+"-"+hash[:12])
|
|
// Prepare a workpath if we don't have one we can reuse.
|
|
update := false
|
|
if b.lastWorkpath != workpath {
|
|
if err := os.Mkdir(workpath, mkdirPerm); err != nil {
|
|
return err
|
|
}
|
|
buildLog, _, err := b.buildRepoOnHash(workpath, hash, makeCmd)
|
|
if err != nil {
|
|
removePath(workpath)
|
|
// record failure
|
|
res.Artifacts = append(res.Artifacts, PerfArtifact{"log", buildLog})
|
|
return b.recordPerfResult(res)
|
|
}
|
|
b.lastWorkpath = workpath
|
|
update = true
|
|
}
|
|
|
|
// Build the benchmark binary.
|
|
benchBin, buildLog, err := b.buildBenchmark(workpath, update)
|
|
if err != nil {
|
|
// record failure
|
|
res.Artifacts = append(res.Artifacts, PerfArtifact{"log", buildLog})
|
|
return b.recordPerfResult(res)
|
|
}
|
|
|
|
benchmark, procs, affinity, last := chooseBenchmark(benchBin, benchs)
|
|
if benchmark != "" {
|
|
res.Benchmark = fmt.Sprintf("%v-%v", benchmark, procs)
|
|
res.Metrics, res.Artifacts, res.OK = b.executeBenchmark(workpath, hash, benchBin, benchmark, procs, affinity)
|
|
if err = b.recordPerfResult(res); err != nil {
|
|
return fmt.Errorf("recordResult: %s", err)
|
|
}
|
|
}
|
|
|
|
if last {
|
|
// All benchmarks have beed executed, don't need workpath anymore.
|
|
removePath(b.lastWorkpath)
|
|
b.lastWorkpath = ""
|
|
// Notify the app.
|
|
res = &PerfResult{Hash: hash, Benchmark: "meta-done", OK: true}
|
|
if err = b.recordPerfResult(res); err != nil {
|
|
return fmt.Errorf("recordResult: %s", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// buildBenchmark builds the benchmark binary.
|
|
func (b *Builder) buildBenchmark(workpath string, update bool) (benchBin, log string, err error) {
|
|
goroot := filepath.Join(workpath, "go")
|
|
gobin := filepath.Join(goroot, "bin", "go") + exeExt
|
|
gopath := filepath.Join(*buildroot, "gopath")
|
|
env := append([]string{
|
|
"GOROOT=" + goroot,
|
|
"GOPATH=" + gopath},
|
|
b.envv()...)
|
|
// First, download without installing.
|
|
args := []string{"get", "-d"}
|
|
if update {
|
|
args = append(args, "-u")
|
|
}
|
|
args = append(args, *benchPath)
|
|
var buildlog bytes.Buffer
|
|
runOpts := []runOpt{runTimeout(*buildTimeout), runEnv(env), allOutput(&buildlog), runDir(workpath)}
|
|
err = run(exec.Command(gobin, args...), runOpts...)
|
|
if err != nil {
|
|
fmt.Fprintf(&buildlog, "go get -d %s failed: %s", *benchPath, err)
|
|
return "", buildlog.String(), err
|
|
}
|
|
// Then, build into workpath.
|
|
benchBin = filepath.Join(workpath, "benchbin") + exeExt
|
|
args = []string{"build", "-o", benchBin, *benchPath}
|
|
buildlog.Reset()
|
|
err = run(exec.Command(gobin, args...), runOpts...)
|
|
if err != nil {
|
|
fmt.Fprintf(&buildlog, "go build %s failed: %s", *benchPath, err)
|
|
return "", buildlog.String(), err
|
|
}
|
|
return benchBin, "", nil
|
|
}
|
|
|
|
// chooseBenchmark chooses the next benchmark to run
|
|
// based on the list of available benchmarks, already executed benchmarks
|
|
// and -benchcpu list.
|
|
func chooseBenchmark(benchBin string, doneBenchs []string) (bench string, procs, affinity int, last bool) {
|
|
out, err := exec.Command(benchBin).CombinedOutput()
|
|
if err != nil {
|
|
log.Printf("Failed to query benchmark list: %v\n%s", err, out)
|
|
last = true
|
|
return
|
|
}
|
|
outStr := string(out)
|
|
nlIdx := strings.Index(outStr, "\n")
|
|
if nlIdx < 0 {
|
|
log.Printf("Failed to parse benchmark list (no new line): %s", outStr)
|
|
last = true
|
|
return
|
|
}
|
|
localBenchs := strings.Split(outStr[:nlIdx], ",")
|
|
benchsMap := make(map[string]bool)
|
|
for _, b := range doneBenchs {
|
|
benchsMap[b] = true
|
|
}
|
|
cnt := 0
|
|
// We want to run all benchmarks with GOMAXPROCS=1 first.
|
|
for i, procs1 := range benchCPU {
|
|
for _, bench1 := range localBenchs {
|
|
if benchsMap[fmt.Sprintf("%v-%v", bench1, procs1)] {
|
|
continue
|
|
}
|
|
cnt++
|
|
if cnt == 1 {
|
|
bench = bench1
|
|
procs = procs1
|
|
if i < len(benchAffinity) {
|
|
affinity = benchAffinity[i]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
last = cnt <= 1
|
|
return
|
|
}
|
|
|
|
// executeBenchmark runs a single benchmark and parses its output.
|
|
func (b *Builder) executeBenchmark(workpath, hash, benchBin, bench string, procs, affinity int) (metrics []PerfMetric, artifacts []PerfArtifact, ok bool) {
|
|
// Benchmarks runs mutually exclusive with other activities.
|
|
benchMutex.RUnlock()
|
|
defer benchMutex.RLock()
|
|
benchMutex.Lock()
|
|
defer benchMutex.Unlock()
|
|
|
|
log.Printf("%v executing benchmark %v-%v on %v", b.name, bench, procs, hash)
|
|
|
|
// The benchmark executes 'go build'/'go tool',
|
|
// so we need properly setup env.
|
|
env := append([]string{
|
|
"GOROOT=" + filepath.Join(workpath, "go"),
|
|
"PATH=" + filepath.Join(workpath, "go", "bin") + string(os.PathListSeparator) + os.Getenv("PATH"),
|
|
"GODEBUG=gctrace=1", // since Go1.2
|
|
"GOGCTRACE=1", // before Go1.2
|
|
fmt.Sprintf("GOMAXPROCS=%v", procs)},
|
|
b.envv()...)
|
|
args := []string{
|
|
"-bench", bench,
|
|
"-benchmem", strconv.Itoa(*benchMem),
|
|
"-benchtime", benchTime.String(),
|
|
"-benchnum", strconv.Itoa(*benchNum),
|
|
"-tmpdir", workpath}
|
|
if affinity != 0 {
|
|
args = append(args, "-affinity", strconv.Itoa(affinity))
|
|
}
|
|
benchlog := new(bytes.Buffer)
|
|
err := run(exec.Command(benchBin, args...), runEnv(env), allOutput(benchlog), runDir(workpath))
|
|
if strip := benchlog.Len() - 512<<10; strip > 0 {
|
|
// Leave the last 512K, that part contains metrics.
|
|
benchlog = bytes.NewBuffer(benchlog.Bytes()[strip:])
|
|
}
|
|
artifacts = []PerfArtifact{{Type: "log", Body: benchlog.String()}}
|
|
if err != nil {
|
|
if err != nil {
|
|
log.Printf("Failed to execute benchmark '%v': %v", bench, err)
|
|
ok = false
|
|
}
|
|
return
|
|
}
|
|
|
|
metrics1, artifacts1, err := parseBenchmarkOutput(benchlog)
|
|
if err != nil {
|
|
log.Printf("Failed to parse benchmark output: %v", err)
|
|
ok = false
|
|
return
|
|
}
|
|
metrics = metrics1
|
|
artifacts = append(artifacts, artifacts1...)
|
|
return
|
|
}
|
|
|
|
// parseBenchmarkOutput fetches metrics and artifacts from benchmark output.
|
|
func parseBenchmarkOutput(out io.Reader) (metrics []PerfMetric, artifacts []PerfArtifact, err error) {
|
|
s := bufio.NewScanner(out)
|
|
metricRe := regexp.MustCompile("^GOPERF-METRIC:([a-z,0-9,-]+)=([0-9]+)$")
|
|
fileRe := regexp.MustCompile("^GOPERF-FILE:([a-z,0-9,-]+)=(.+)$")
|
|
for s.Scan() {
|
|
ln := s.Text()
|
|
if ss := metricRe.FindStringSubmatch(ln); ss != nil {
|
|
var v uint64
|
|
v, err = strconv.ParseUint(ss[2], 10, 64)
|
|
if err != nil {
|
|
err = fmt.Errorf("Failed to parse metric '%v=%v': %v", ss[1], ss[2], err)
|
|
return
|
|
}
|
|
metrics = append(metrics, PerfMetric{Type: ss[1], Val: v})
|
|
} else if ss := fileRe.FindStringSubmatch(ln); ss != nil {
|
|
var buf []byte
|
|
buf, err = ioutil.ReadFile(ss[2])
|
|
if err != nil {
|
|
err = fmt.Errorf("Failed to read file '%v': %v", ss[2], err)
|
|
return
|
|
}
|
|
artifacts = append(artifacts, PerfArtifact{ss[1], string(buf)})
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// needsBenchmarking determines whether the commit needs benchmarking.
|
|
func needsBenchmarking(log *HgLog) bool {
|
|
// Do not benchmark branch commits, they are usually not interesting
|
|
// and fall out of the trunk succession.
|
|
if log.Branch != "" {
|
|
return false
|
|
}
|
|
// Do not benchmark commits that do not touch source files (e.g. CONTRIBUTORS).
|
|
for _, f := range strings.Split(log.Files, " ") {
|
|
if (strings.HasPrefix(f, "include") || strings.HasPrefix(f, "src")) &&
|
|
!strings.HasSuffix(f, "_test.go") && !strings.Contains(f, "testdata") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|