github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/dashboard/builder/bench.go (about) 1 // 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 5 package main 6 7 import ( 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. 23 func (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. 81 func (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. 90 args := []string{"get", "-d"} 91 if update { 92 args = append(args, "-u") 93 } 94 args = append(args, *benchPath) 95 var buildlog bytes.Buffer 96 runOpts := []runOpt{runTimeout(*buildTimeout), runEnv(env), allOutput(&buildlog), runDir(workpath)} 97 err = run(exec.Command(gobin, args...), runOpts...) 98 if err != nil { 99 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 104 args = []string{"build", "-o", benchBin, *benchPath} 105 buildlog.Reset() 106 err = run(exec.Command(gobin, args...), runOpts...) 107 if err != nil { 108 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. 117 func chooseBenchmark(benchBin string, doneBenchs []string) (bench string, procs, affinity int, last bool) { 118 var out bytes.Buffer 119 err := run(exec.Command(benchBin), allOutput(&out)) 120 if err != nil { 121 log.Printf("Failed to query benchmark list: %v\n%s", err, out) 122 last = true 123 return 124 } 125 outStr := out.String() 126 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. 159 func (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()...) 177 args := []string{ 178 "-bench", bench, 179 "-benchmem", strconv.Itoa(*benchMem), 180 "-benchtime", benchTime.String(), 181 "-benchnum", strconv.Itoa(*benchNum), 182 "-tmpdir", workpath} 183 if affinity != 0 { 184 args = append(args, "-affinity", strconv.Itoa(affinity)) 185 } 186 benchlog := new(bytes.Buffer) 187 err := run(exec.Command(benchBin, args...), runEnv(env), allOutput(benchlog), runDir(workpath)) 188 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()}} 193 if err != nil { 194 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...) 209 ok = true 210 return 211 } 212 213 // parseBenchmarkOutput fetches metrics and artifacts from benchmark output. 214 func 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. 242 func 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 }