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 }