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  }