golang.org/x/build@v0.0.0-20240506185731-218518f32b70/cmd/perfrun/perfrun.go (about)

     1  // Copyright 2016 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  // perfrun interacts with the buildlet coordinator to run the go1
     6  // benchmarks on a buildlet follower for the most recent successful
     7  // commits according to the build dashboard.
     8  package main
     9  
    10  import (
    11  	"bytes"
    12  	"context"
    13  	"encoding/json"
    14  	"flag"
    15  	"fmt"
    16  	"io"
    17  	"log"
    18  	"net/http"
    19  	"os"
    20  	"path"
    21  	"strings"
    22  	"time"
    23  
    24  	"golang.org/x/build/buildenv"
    25  	"golang.org/x/build/buildlet"
    26  	"golang.org/x/build/types"
    27  )
    28  
    29  var (
    30  	buildletBench = flag.String("buildlet", "", "name of buildlet to use for benchmarks")
    31  	buildletSrc   = flag.String("buildlet_src", "", "name of builder to get binaries from (defaults to the same as buildlet)")
    32  	buildEnv      *buildenv.Environment
    33  )
    34  
    35  // runBench runs the benchmarks from each of the revisions in
    36  // commits. It uses the tarballs built by the "src" buildlet and runs
    37  // the benchmarks on the "bench" buildlet. It writes a log to out in
    38  // the standard benchmark format
    39  // (https://github.com/golang/proposal/blob/master/design/14313-benchmark-format.md).
    40  func runBench(out io.Writer, bench, src string, commits []string) error {
    41  	ctx := context.TODO()
    42  	bc, err := namedClient(*buildletBench)
    43  	if err != nil {
    44  		return err
    45  	}
    46  	log.Printf("Using buildlet %s", bc.RemoteName())
    47  	workDir, err := bc.WorkDir(ctx)
    48  	if err != nil {
    49  		log.Printf("Getting WorkDir: %v", err)
    50  		return err
    51  	}
    52  	for _, rev := range commits {
    53  		log.Printf("Installing prebuilt rev %s", rev)
    54  		dir := fmt.Sprintf("go-%s", rev)
    55  		// Copy pre-built trees
    56  		if err := bc.PutTarFromURL(ctx, buildEnv.SnapshotURL(src, rev), dir); err != nil {
    57  			log.Printf("failed to extract snapshot for %s: %v", rev, err)
    58  			return err
    59  		}
    60  		// Build binaries
    61  		log.Printf("Building bench binary for rev %s", rev)
    62  		var buf bytes.Buffer
    63  		remoteErr, err := bc.Exec(ctx, path.Join(dir, "bin", "go"), buildlet.ExecOpts{
    64  			Output:   &buf,
    65  			ExtraEnv: []string{"GOROOT=" + path.Join(workDir, dir)},
    66  			Args:     []string{"test", "-c"},
    67  			Dir:      path.Join(dir, "test/bench/go1"),
    68  		})
    69  		if remoteErr != nil {
    70  			log.Printf("failed to compile bench for %s: %v", rev, remoteErr)
    71  			log.Printf("output: %s", buf.Bytes())
    72  			return remoteErr
    73  		}
    74  		if err != nil {
    75  			log.Printf("Exec error: %v", err)
    76  			log.Printf("output: %s", buf.Bytes())
    77  			return err
    78  		}
    79  	}
    80  	// Loop over commits and run N times interleaved, grabbing output
    81  	// TODO: Overhead of multiple Exec calls might be significant; should we ship over a shell script to do this in one go?
    82  	for i := 0; i < 10; i++ {
    83  		log.Printf("Starting bench run %d", i)
    84  		for _, rev := range commits {
    85  			var buf bytes.Buffer
    86  			remoteErr, err := bc.Exec(context.Background(), path.Join("go-"+rev, "test/bench/go1/go1.test"), buildlet.ExecOpts{
    87  				Output: &buf,
    88  				Args:   []string{"-test.bench", ".", "-test.benchmem"},
    89  			})
    90  			if remoteErr != nil {
    91  				log.Printf("failed to run %d-%s: %v", i, rev, remoteErr)
    92  				log.Printf("output: %s", buf.Bytes())
    93  				return remoteErr
    94  			}
    95  			if err != nil {
    96  				log.Printf("Exec error: %v", err)
    97  				log.Printf("output: %s", buf.Bytes())
    98  				return err
    99  			}
   100  			log.Printf("%d-%s: %s", i, rev, buf.Bytes()) // XXX
   101  			fmt.Fprintf(out, "commit: %s\niteration: %d\nstart: %s", rev, i, time.Now().UTC().Format(time.RFC3339))
   102  			out.Write(buf.Bytes())
   103  			out.Write([]byte{'\n'})
   104  		}
   105  	}
   106  
   107  	// Destroy client
   108  	// TODO: defer this so we don't leak clients?
   109  	if err := bc.Close(); err != nil {
   110  		return err
   111  	}
   112  	return nil
   113  }
   114  
   115  func namedClient(name string) (buildlet.RemoteClient, error) {
   116  	if strings.Contains(name, ":") {
   117  		return buildlet.NewClient(name, buildlet.NoKeyPair), nil
   118  	}
   119  	cc, err := buildlet.NewCoordinatorClientFromFlags()
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  	return cc.CreateBuildlet(name)
   124  	// TODO(quentin): Figure out a way to detect if there's an already running builder with this name.
   125  	//return cc.NamedBuildlet(name)
   126  }
   127  
   128  // findCommits finds all the recent successful commits for the given builder
   129  func findCommits(name string) ([]string, error) {
   130  	var bs types.BuildStatus
   131  	res, err := http.Get(buildEnv.DashBase() + "?mode=json")
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  	defer res.Body.Close()
   136  	if err := json.NewDecoder(res.Body).Decode(&bs); err != nil {
   137  		return nil, err
   138  	}
   139  	if res.StatusCode != 200 {
   140  		return nil, fmt.Errorf("unexpected http status %v", res.Status)
   141  	}
   142  
   143  	var commits []string
   144  
   145  	for builderIdx := 0; builderIdx < len(bs.Builders); builderIdx++ {
   146  		if bs.Builders[builderIdx] == name {
   147  			for _, br := range bs.Revisions {
   148  				if br.Repo != "go" {
   149  					// Only process go repo for now
   150  					continue
   151  				}
   152  				if br.Results[builderIdx] == "ok" {
   153  					commits = append(commits, br.Revision)
   154  				}
   155  			}
   156  			return commits, nil
   157  		}
   158  	}
   159  	return nil, fmt.Errorf("builder %q not found", name)
   160  }
   161  
   162  func usage() {
   163  	fmt.Fprintf(os.Stderr, `Usage of perfrun: perfrun [flags] <commits>
   164  
   165  Flags:
   166  `)
   167  	flag.PrintDefaults()
   168  	os.Exit(1)
   169  }
   170  
   171  func main() {
   172  	buildlet.RegisterFlags()
   173  	flag.Usage = usage
   174  	flag.Parse()
   175  	buildEnv = buildenv.FromFlags()
   176  	args := flag.Args()
   177  	if *buildletBench == "" {
   178  		usage()
   179  	}
   180  	if *buildletSrc == "" {
   181  		*buildletSrc = *buildletBench
   182  	}
   183  	if len(args) == 0 {
   184  		res, err := findCommits(*buildletSrc)
   185  		args = res
   186  		if err != nil {
   187  			fmt.Fprintf(os.Stderr, "Failed finding commits to build: %v", err)
   188  			os.Exit(1)
   189  		}
   190  	}
   191  	log.Printf("Running bench on %v", args)
   192  	out, err := os.Create(fmt.Sprintf("perfrun-%s-%s-%s.log", *buildletSrc, *buildletBench, time.Now().Format("20060102150405")))
   193  	if err != nil {
   194  		fmt.Fprintf(os.Stderr, "Creating log failed: %v", err)
   195  	}
   196  	defer func() {
   197  		if err := out.Close(); err != nil {
   198  			fmt.Fprintf(os.Stderr, "Failed writing log: %v", err)
   199  			os.Exit(1)
   200  		}
   201  	}()
   202  	if err := runBench(out, *buildletBench, *buildletSrc, args); err != nil {
   203  		fmt.Fprintf(os.Stderr, "Failed running bench: %v", err)
   204  		os.Exit(1)
   205  	}
   206  }