| // Copyright 2016 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. |
| |
| // perfrun interacts with the buildlet coordinator to run the go1 |
| // benchmarks on a buildlet follower for the most recent successful |
| // commits according to the build dashboard. |
| package main |
| |
| import ( |
| "bytes" |
| "context" |
| "encoding/json" |
| "flag" |
| "fmt" |
| "io" |
| "log" |
| "net/http" |
| "os" |
| "path" |
| "strings" |
| "time" |
| |
| "golang.org/x/build/buildenv" |
| "golang.org/x/build/buildlet" |
| "golang.org/x/build/types" |
| ) |
| |
| var ( |
| buildletBench = flag.String("buildlet", "", "name of buildlet to use for benchmarks") |
| buildletSrc = flag.String("buildlet_src", "", "name of builder to get binaries from (defaults to the same as buildlet)") |
| buildEnv *buildenv.Environment |
| ) |
| |
| // runBench runs the benchmarks from each of the revisions in |
| // commits. It uses the tarballs built by the "src" buildlet and runs |
| // the benchmarks on the "bench" buildlet. It writes a log to out in |
| // the standard benchmark format |
| // (https://github.com/golang/proposal/blob/master/design/14313-benchmark-format.md). |
| func runBench(out io.Writer, bench, src string, commits []string) error { |
| ctx := context.TODO() |
| bc, err := namedClient(*buildletBench) |
| if err != nil { |
| return err |
| } |
| log.Printf("Using buildlet %s", bc.RemoteName()) |
| workDir, err := bc.WorkDir(ctx) |
| if err != nil { |
| log.Printf("Getting WorkDir: %v", err) |
| return err |
| } |
| for _, rev := range commits { |
| log.Printf("Installing prebuilt rev %s", rev) |
| dir := fmt.Sprintf("go-%s", rev) |
| // Copy pre-built trees |
| if err := bc.PutTarFromURL(ctx, buildEnv.SnapshotURL(src, rev), dir); err != nil { |
| log.Printf("failed to extract snapshot for %s: %v", rev, err) |
| return err |
| } |
| // Build binaries |
| log.Printf("Building bench binary for rev %s", rev) |
| var buf bytes.Buffer |
| remoteErr, err := bc.Exec(ctx, path.Join(dir, "bin", "go"), buildlet.ExecOpts{ |
| Output: &buf, |
| ExtraEnv: []string{"GOROOT=" + path.Join(workDir, dir)}, |
| Args: []string{"test", "-c"}, |
| Dir: path.Join(dir, "test/bench/go1"), |
| }) |
| if remoteErr != nil { |
| log.Printf("failed to compile bench for %s: %v", rev, remoteErr) |
| log.Printf("output: %s", buf.Bytes()) |
| return remoteErr |
| } |
| if err != nil { |
| log.Printf("Exec error: %v", err) |
| log.Printf("output: %s", buf.Bytes()) |
| return err |
| } |
| } |
| // Loop over commits and run N times interleaved, grabbing output |
| // TODO: Overhead of multiple Exec calls might be significant; should we ship over a shell script to do this in one go? |
| for i := 0; i < 10; i++ { |
| log.Printf("Starting bench run %d", i) |
| for _, rev := range commits { |
| var buf bytes.Buffer |
| remoteErr, err := bc.Exec(context.Background(), path.Join("go-"+rev, "test/bench/go1/go1.test"), buildlet.ExecOpts{ |
| Output: &buf, |
| Args: []string{"-test.bench", ".", "-test.benchmem"}, |
| }) |
| if remoteErr != nil { |
| log.Printf("failed to run %d-%s: %v", i, rev, remoteErr) |
| log.Printf("output: %s", buf.Bytes()) |
| return remoteErr |
| } |
| if err != nil { |
| log.Printf("Exec error: %v", err) |
| log.Printf("output: %s", buf.Bytes()) |
| return err |
| } |
| log.Printf("%d-%s: %s", i, rev, buf.Bytes()) // XXX |
| fmt.Fprintf(out, "commit: %s\niteration: %d\nstart: %s", rev, i, time.Now().UTC().Format(time.RFC3339)) |
| out.Write(buf.Bytes()) |
| out.Write([]byte{'\n'}) |
| } |
| } |
| |
| // Destroy client |
| // TODO: defer this so we don't leak clients? |
| if err := bc.Close(); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| func namedClient(name string) (buildlet.Client, error) { |
| if strings.Contains(name, ":") { |
| return buildlet.NewClient(name, buildlet.NoKeyPair), nil |
| } |
| cc, err := buildlet.NewCoordinatorClientFromFlags() |
| if err != nil { |
| return nil, err |
| } |
| return cc.CreateBuildlet(name) |
| // TODO(quentin): Figure out a way to detect if there's an already running builder with this name. |
| //return cc.NamedBuildlet(name) |
| } |
| |
| // findCommits finds all the recent successful commits for the given builder |
| func findCommits(name string) ([]string, error) { |
| var bs types.BuildStatus |
| res, err := http.Get(buildEnv.DashBase() + "?mode=json") |
| if err != nil { |
| return nil, err |
| } |
| defer res.Body.Close() |
| if err := json.NewDecoder(res.Body).Decode(&bs); err != nil { |
| return nil, err |
| } |
| if res.StatusCode != 200 { |
| return nil, fmt.Errorf("unexpected http status %v", res.Status) |
| } |
| |
| var commits []string |
| |
| for builderIdx := 0; builderIdx < len(bs.Builders); builderIdx++ { |
| if bs.Builders[builderIdx] == name { |
| for _, br := range bs.Revisions { |
| if br.Repo != "go" { |
| // Only process go repo for now |
| continue |
| } |
| if br.Results[builderIdx] == "ok" { |
| commits = append(commits, br.Revision) |
| } |
| } |
| return commits, nil |
| } |
| } |
| return nil, fmt.Errorf("builder %q not found", name) |
| } |
| |
| func usage() { |
| fmt.Fprintf(os.Stderr, `Usage of perfrun: perfrun [flags] <commits> |
| |
| Flags: |
| `) |
| flag.PrintDefaults() |
| os.Exit(1) |
| } |
| |
| func main() { |
| buildlet.RegisterFlags() |
| flag.Usage = usage |
| flag.Parse() |
| buildEnv = buildenv.FromFlags() |
| args := flag.Args() |
| if *buildletBench == "" { |
| usage() |
| } |
| if *buildletSrc == "" { |
| *buildletSrc = *buildletBench |
| } |
| if len(args) == 0 { |
| res, err := findCommits(*buildletSrc) |
| args = res |
| if err != nil { |
| fmt.Fprintf(os.Stderr, "Failed finding commits to build: %v", err) |
| os.Exit(1) |
| } |
| } |
| log.Printf("Running bench on %v", args) |
| out, err := os.Create(fmt.Sprintf("perfrun-%s-%s-%s.log", *buildletSrc, *buildletBench, time.Now().Format("20060102150405"))) |
| if err != nil { |
| fmt.Fprintf(os.Stderr, "Creating log failed: %v", err) |
| } |
| defer func() { |
| if err := out.Close(); err != nil { |
| fmt.Fprintf(os.Stderr, "Failed writing log: %v", err) |
| os.Exit(1) |
| } |
| }() |
| if err := runBench(out, *buildletBench, *buildletSrc, args); err != nil { |
| fmt.Fprintf(os.Stderr, "Failed running bench: %v", err) |
| os.Exit(1) |
| } |
| } |