blob: a9a59ce3651ce5dbe8416fbd883a2365e825785b [file] [log] [blame]
Dmitriy Vyukov052b6392014-05-13 11:00:11 +04001// Copyright 2013 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package main
6
7import (
8 "bufio"
9 "bytes"
10 "fmt"
11 "io"
12 "io/ioutil"
13 "log"
14 "os"
15 "os/exec"
16 "path/filepath"
17 "regexp"
18 "strconv"
19 "strings"
20)
21
22// benchHash benchmarks a single commit.
23func (b *Builder) benchHash(hash string, benchs []string) error {
24 if *verbose {
25 log.Println(b.name, "benchmarking", hash)
26 }
27
28 res := &PerfResult{Hash: hash, Benchmark: "meta-done"}
29
30 // Create place in which to do work.
31 workpath := filepath.Join(*buildroot, b.name+"-"+hash[:12])
32 // Prepare a workpath if we don't have one we can reuse.
33 update := false
34 if b.lastWorkpath != workpath {
35 if err := os.Mkdir(workpath, mkdirPerm); err != nil {
36 return err
37 }
38 buildLog, _, err := b.buildRepoOnHash(workpath, hash, makeCmd)
39 if err != nil {
40 removePath(workpath)
41 // record failure
42 res.Artifacts = append(res.Artifacts, PerfArtifact{"log", buildLog})
43 return b.recordPerfResult(res)
44 }
45 b.lastWorkpath = workpath
46 update = true
47 }
48
49 // Build the benchmark binary.
50 benchBin, buildLog, err := b.buildBenchmark(workpath, update)
51 if err != nil {
52 // record failure
53 res.Artifacts = append(res.Artifacts, PerfArtifact{"log", buildLog})
54 return b.recordPerfResult(res)
55 }
56
57 benchmark, procs, affinity, last := chooseBenchmark(benchBin, benchs)
58 if benchmark != "" {
59 res.Benchmark = fmt.Sprintf("%v-%v", benchmark, procs)
60 res.Metrics, res.Artifacts, res.OK = b.executeBenchmark(workpath, hash, benchBin, benchmark, procs, affinity)
61 if err = b.recordPerfResult(res); err != nil {
62 return fmt.Errorf("recordResult: %s", err)
63 }
64 }
65
66 if last {
67 // All benchmarks have beed executed, don't need workpath anymore.
68 removePath(b.lastWorkpath)
69 b.lastWorkpath = ""
70 // Notify the app.
71 res = &PerfResult{Hash: hash, Benchmark: "meta-done", OK: true}
72 if err = b.recordPerfResult(res); err != nil {
73 return fmt.Errorf("recordResult: %s", err)
74 }
75 }
76
77 return nil
78}
79
80// buildBenchmark builds the benchmark binary.
81func (b *Builder) buildBenchmark(workpath string, update bool) (benchBin, log string, err error) {
82 goroot := filepath.Join(workpath, "go")
83 gobin := filepath.Join(goroot, "bin", "go") + exeExt
84 gopath := filepath.Join(*buildroot, "gopath")
85 env := append([]string{
86 "GOROOT=" + goroot,
87 "GOPATH=" + gopath},
88 b.envv()...)
89 // First, download without installing.
Brad Fitzpatrick6baf2f12014-08-28 14:58:15 -070090 args := []string{"get", "-d"}
Dmitriy Vyukov052b6392014-05-13 11:00:11 +040091 if update {
Brad Fitzpatrick6baf2f12014-08-28 14:58:15 -070092 args = append(args, "-u")
Dmitriy Vyukov052b6392014-05-13 11:00:11 +040093 }
Brad Fitzpatrick6baf2f12014-08-28 14:58:15 -070094 args = append(args, *benchPath)
Dmitriy Vyukov052b6392014-05-13 11:00:11 +040095 var buildlog bytes.Buffer
Brad Fitzpatrick6baf2f12014-08-28 14:58:15 -070096 runOpts := []runOpt{runTimeout(*buildTimeout), runEnv(env), allOutput(&buildlog), runDir(workpath)}
97 err = run(exec.Command(gobin, args...), runOpts...)
98 if err != nil {
Dmitriy Vyukov052b6392014-05-13 11:00:11 +040099 fmt.Fprintf(&buildlog, "go get -d %s failed: %s", *benchPath, err)
100 return "", buildlog.String(), err
101 }
102 // Then, build into workpath.
103 benchBin = filepath.Join(workpath, "benchbin") + exeExt
Brad Fitzpatrick6baf2f12014-08-28 14:58:15 -0700104 args = []string{"build", "-o", benchBin, *benchPath}
Dmitriy Vyukov052b6392014-05-13 11:00:11 +0400105 buildlog.Reset()
Brad Fitzpatrick6baf2f12014-08-28 14:58:15 -0700106 err = run(exec.Command(gobin, args...), runOpts...)
107 if err != nil {
Dmitriy Vyukov052b6392014-05-13 11:00:11 +0400108 fmt.Fprintf(&buildlog, "go build %s failed: %s", *benchPath, err)
109 return "", buildlog.String(), err
110 }
111 return benchBin, "", nil
112}
113
114// chooseBenchmark chooses the next benchmark to run
115// based on the list of available benchmarks, already executed benchmarks
116// and -benchcpu list.
117func chooseBenchmark(benchBin string, doneBenchs []string) (bench string, procs, affinity int, last bool) {
Dmitriy Vyukovcdce0b52014-10-24 21:05:24 +0400118 var out bytes.Buffer
119 err := run(exec.Command(benchBin), allOutput(&out))
Dmitriy Vyukov052b6392014-05-13 11:00:11 +0400120 if err != nil {
Emil Hessmanb362f172014-12-29 06:15:53 +0100121 log.Printf("Failed to query benchmark list: %v\n%s", err, &out)
Dmitriy Vyukov052b6392014-05-13 11:00:11 +0400122 last = true
123 return
124 }
Dmitriy Vyukovcdce0b52014-10-24 21:05:24 +0400125 outStr := out.String()
Dmitriy Vyukov052b6392014-05-13 11:00:11 +0400126 nlIdx := strings.Index(outStr, "\n")
127 if nlIdx < 0 {
128 log.Printf("Failed to parse benchmark list (no new line): %s", outStr)
129 last = true
130 return
131 }
132 localBenchs := strings.Split(outStr[:nlIdx], ",")
133 benchsMap := make(map[string]bool)
134 for _, b := range doneBenchs {
135 benchsMap[b] = true
136 }
137 cnt := 0
138 // We want to run all benchmarks with GOMAXPROCS=1 first.
139 for i, procs1 := range benchCPU {
140 for _, bench1 := range localBenchs {
141 if benchsMap[fmt.Sprintf("%v-%v", bench1, procs1)] {
142 continue
143 }
144 cnt++
145 if cnt == 1 {
146 bench = bench1
147 procs = procs1
148 if i < len(benchAffinity) {
149 affinity = benchAffinity[i]
150 }
151 }
152 }
153 }
154 last = cnt <= 1
155 return
156}
157
158// executeBenchmark runs a single benchmark and parses its output.
159func (b *Builder) executeBenchmark(workpath, hash, benchBin, bench string, procs, affinity int) (metrics []PerfMetric, artifacts []PerfArtifact, ok bool) {
160 // Benchmarks runs mutually exclusive with other activities.
161 benchMutex.RUnlock()
162 defer benchMutex.RLock()
163 benchMutex.Lock()
164 defer benchMutex.Unlock()
165
166 log.Printf("%v executing benchmark %v-%v on %v", b.name, bench, procs, hash)
167
168 // The benchmark executes 'go build'/'go tool',
169 // so we need properly setup env.
170 env := append([]string{
171 "GOROOT=" + filepath.Join(workpath, "go"),
172 "PATH=" + filepath.Join(workpath, "go", "bin") + string(os.PathListSeparator) + os.Getenv("PATH"),
173 "GODEBUG=gctrace=1", // since Go1.2
174 "GOGCTRACE=1", // before Go1.2
175 fmt.Sprintf("GOMAXPROCS=%v", procs)},
176 b.envv()...)
Brad Fitzpatrick6baf2f12014-08-28 14:58:15 -0700177 args := []string{
Dmitriy Vyukov052b6392014-05-13 11:00:11 +0400178 "-bench", bench,
179 "-benchmem", strconv.Itoa(*benchMem),
180 "-benchtime", benchTime.String(),
181 "-benchnum", strconv.Itoa(*benchNum),
182 "-tmpdir", workpath}
183 if affinity != 0 {
Brad Fitzpatrick6baf2f12014-08-28 14:58:15 -0700184 args = append(args, "-affinity", strconv.Itoa(affinity))
Dmitriy Vyukov052b6392014-05-13 11:00:11 +0400185 }
186 benchlog := new(bytes.Buffer)
Brad Fitzpatrick6baf2f12014-08-28 14:58:15 -0700187 err := run(exec.Command(benchBin, args...), runEnv(env), allOutput(benchlog), runDir(workpath))
Dmitriy Vyukov052b6392014-05-13 11:00:11 +0400188 if strip := benchlog.Len() - 512<<10; strip > 0 {
189 // Leave the last 512K, that part contains metrics.
190 benchlog = bytes.NewBuffer(benchlog.Bytes()[strip:])
191 }
192 artifacts = []PerfArtifact{{Type: "log", Body: benchlog.String()}}
Brad Fitzpatrick6baf2f12014-08-28 14:58:15 -0700193 if err != nil {
Dmitriy Vyukov052b6392014-05-13 11:00:11 +0400194 if err != nil {
195 log.Printf("Failed to execute benchmark '%v': %v", bench, err)
196 ok = false
197 }
198 return
199 }
200
201 metrics1, artifacts1, err := parseBenchmarkOutput(benchlog)
202 if err != nil {
203 log.Printf("Failed to parse benchmark output: %v", err)
204 ok = false
205 return
206 }
207 metrics = metrics1
208 artifacts = append(artifacts, artifacts1...)
Dmitriy Vyukov9ddd8b62014-10-17 13:51:23 +0400209 ok = true
Dmitriy Vyukov052b6392014-05-13 11:00:11 +0400210 return
211}
212
213// parseBenchmarkOutput fetches metrics and artifacts from benchmark output.
214func parseBenchmarkOutput(out io.Reader) (metrics []PerfMetric, artifacts []PerfArtifact, err error) {
215 s := bufio.NewScanner(out)
216 metricRe := regexp.MustCompile("^GOPERF-METRIC:([a-z,0-9,-]+)=([0-9]+)$")
217 fileRe := regexp.MustCompile("^GOPERF-FILE:([a-z,0-9,-]+)=(.+)$")
218 for s.Scan() {
219 ln := s.Text()
220 if ss := metricRe.FindStringSubmatch(ln); ss != nil {
221 var v uint64
222 v, err = strconv.ParseUint(ss[2], 10, 64)
223 if err != nil {
224 err = fmt.Errorf("Failed to parse metric '%v=%v': %v", ss[1], ss[2], err)
225 return
226 }
227 metrics = append(metrics, PerfMetric{Type: ss[1], Val: v})
228 } else if ss := fileRe.FindStringSubmatch(ln); ss != nil {
229 var buf []byte
230 buf, err = ioutil.ReadFile(ss[2])
231 if err != nil {
232 err = fmt.Errorf("Failed to read file '%v': %v", ss[2], err)
233 return
234 }
235 artifacts = append(artifacts, PerfArtifact{ss[1], string(buf)})
236 }
237 }
238 return
239}
240
241// needsBenchmarking determines whether the commit needs benchmarking.
242func needsBenchmarking(log *HgLog) bool {
243 // Do not benchmark branch commits, they are usually not interesting
244 // and fall out of the trunk succession.
245 if log.Branch != "" {
246 return false
247 }
248 // Do not benchmark commits that do not touch source files (e.g. CONTRIBUTORS).
249 for _, f := range strings.Split(log.Files, " ") {
250 if (strings.HasPrefix(f, "include") || strings.HasPrefix(f, "src")) &&
251 !strings.HasSuffix(f, "_test.go") && !strings.Contains(f, "testdata") {
252 return true
253 }
254 }
255 return false
256}