Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201028153306-37f0764111ff / cmd / compilebench / main.go
1 // Copyright 2015 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 // Compilebench benchmarks the speed of the Go compiler.
6 //
7 // Usage:
8 //
9 //      compilebench [options]
10 //
11 // It times the compilation of various packages and prints results in
12 // the format used by package testing (and expected by golang.org/x/perf/cmd/benchstat).
13 //
14 // The options are:
15 //
16 //      -alloc
17 //              Report allocations.
18 //
19 //      -compile exe
20 //              Use exe as the path to the cmd/compile binary.
21 //
22 //      -compileflags 'list'
23 //              Pass the space-separated list of flags to the compilation.
24 //
25 //      -link exe
26 //              Use exe as the path to the cmd/link binary.
27 //
28 //      -linkflags 'list'
29 //              Pass the space-separated list of flags to the linker.
30 //
31 //      -count n
32 //              Run each benchmark n times (default 1).
33 //
34 //      -cpuprofile file
35 //              Write a CPU profile of the compiler to file.
36 //
37 //      -go path
38 //              Path to "go" command (default "go").
39 //
40 //      -memprofile file
41 //              Write a memory profile of the compiler to file.
42 //
43 //      -memprofilerate rate
44 //              Set runtime.MemProfileRate during compilation.
45 //
46 //      -obj
47 //              Report object file statistics.
48 //
49 //      -pkg pkg
50 //              Benchmark compiling a single package.
51 //
52 //      -run regexp
53 //              Only run benchmarks with names matching regexp.
54 //
55 //      -short
56 //              Skip long-running benchmarks.
57 //
58 // Although -cpuprofile and -memprofile are intended to write a
59 // combined profile for all the executed benchmarks to file,
60 // today they write only the profile for the last benchmark executed.
61 //
62 // The default memory profiling rate is one profile sample per 512 kB
63 // allocated (see ``go doc runtime.MemProfileRate'').
64 // Lowering the rate (for example, -memprofilerate 64000) produces
65 // a more fine-grained and therefore accurate profile, but it also incurs
66 // execution cost. For benchmark comparisons, never use timings
67 // obtained with a low -memprofilerate option.
68 //
69 // Example
70 //
71 // Assuming the base version of the compiler has been saved with
72 // ``toolstash save,'' this sequence compares the old and new compiler:
73 //
74 //      compilebench -count 10 -compile $(toolstash -n compile) >old.txt
75 //      compilebench -count 10 >new.txt
76 //      benchstat old.txt new.txt
77 //
78 package main
79
80 import (
81         "bytes"
82         "encoding/json"
83         "flag"
84         "fmt"
85         "io/ioutil"
86         "log"
87         "os"
88         "os/exec"
89         "path/filepath"
90         "regexp"
91         "strconv"
92         "strings"
93         "time"
94 )
95
96 var (
97         goroot   string
98         compiler string
99         linker   string
100         runRE    *regexp.Regexp
101         is6g     bool
102 )
103
104 var (
105         flagGoCmd          = flag.String("go", "go", "path to \"go\" command")
106         flagAlloc          = flag.Bool("alloc", false, "report allocations")
107         flagObj            = flag.Bool("obj", false, "report object file stats")
108         flagCompiler       = flag.String("compile", "", "use `exe` as the cmd/compile binary")
109         flagCompilerFlags  = flag.String("compileflags", "", "additional `flags` to pass to compile")
110         flagLinker         = flag.String("link", "", "use `exe` as the cmd/link binary")
111         flagLinkerFlags    = flag.String("linkflags", "", "additional `flags` to pass to link")
112         flagRun            = flag.String("run", "", "run benchmarks matching `regexp`")
113         flagCount          = flag.Int("count", 1, "run benchmarks `n` times")
114         flagCpuprofile     = flag.String("cpuprofile", "", "write CPU profile to `file`")
115         flagMemprofile     = flag.String("memprofile", "", "write memory profile to `file`")
116         flagMemprofilerate = flag.Int64("memprofilerate", -1, "set memory profile `rate`")
117         flagPackage        = flag.String("pkg", "", "if set, benchmark the package at path `pkg`")
118         flagShort          = flag.Bool("short", false, "skip long-running benchmarks")
119 )
120
121 type test struct {
122         name string
123         r    runner
124 }
125
126 type runner interface {
127         long() bool
128         run(name string, count int) error
129 }
130
131 var tests = []test{
132         {"BenchmarkTemplate", compile{"html/template"}},
133         {"BenchmarkUnicode", compile{"unicode"}},
134         {"BenchmarkGoTypes", compile{"go/types"}},
135         {"BenchmarkCompiler", compile{"cmd/compile/internal/gc"}},
136         {"BenchmarkSSA", compile{"cmd/compile/internal/ssa"}},
137         {"BenchmarkFlate", compile{"compress/flate"}},
138         {"BenchmarkGoParser", compile{"go/parser"}},
139         {"BenchmarkReflect", compile{"reflect"}},
140         {"BenchmarkTar", compile{"archive/tar"}},
141         {"BenchmarkXML", compile{"encoding/xml"}},
142         {"BenchmarkLinkCompiler", link{"cmd/compile", ""}},
143         {"BenchmarkExternalLinkCompiler", link{"cmd/compile", "-linkmode=external"}},
144         {"BenchmarkLinkWithoutDebugCompiler", link{"cmd/compile", "-w"}},
145         {"BenchmarkStdCmd", goBuild{[]string{"std", "cmd"}}},
146         {"BenchmarkHelloSize", size{"$GOROOT/test/helloworld.go", false}},
147         {"BenchmarkCmdGoSize", size{"cmd/go", true}},
148 }
149
150 func usage() {
151         fmt.Fprintf(os.Stderr, "usage: compilebench [options]\n")
152         fmt.Fprintf(os.Stderr, "options:\n")
153         flag.PrintDefaults()
154         os.Exit(2)
155 }
156
157 func main() {
158         log.SetFlags(0)
159         log.SetPrefix("compilebench: ")
160         flag.Usage = usage
161         flag.Parse()
162         if flag.NArg() != 0 {
163                 usage()
164         }
165
166         s, err := exec.Command(*flagGoCmd, "env", "GOROOT").CombinedOutput()
167         if err != nil {
168                 log.Fatalf("%s env GOROOT: %v", *flagGoCmd, err)
169         }
170         goroot = strings.TrimSpace(string(s))
171         os.Setenv("GOROOT", goroot) // for any subcommands
172
173         compiler = *flagCompiler
174         if compiler == "" {
175                 var foundTool string
176                 foundTool, compiler = toolPath("compile", "6g")
177                 if foundTool == "6g" {
178                         is6g = true
179                 }
180         }
181
182         linker = *flagLinker
183         if linker == "" && !is6g { // TODO: Support 6l
184                 _, linker = toolPath("link")
185         }
186
187         if is6g {
188                 *flagMemprofilerate = -1
189                 *flagAlloc = false
190                 *flagCpuprofile = ""
191                 *flagMemprofile = ""
192         }
193
194         if *flagRun != "" {
195                 r, err := regexp.Compile(*flagRun)
196                 if err != nil {
197                         log.Fatalf("invalid -run argument: %v", err)
198                 }
199                 runRE = r
200         }
201
202         if *flagPackage != "" {
203                 tests = []test{
204                         {"BenchmarkPkg", compile{*flagPackage}},
205                         {"BenchmarkPkgLink", link{*flagPackage, ""}},
206                 }
207                 runRE = nil
208         }
209
210         for i := 0; i < *flagCount; i++ {
211                 for _, tt := range tests {
212                         if tt.r.long() && *flagShort {
213                                 continue
214                         }
215                         if runRE == nil || runRE.MatchString(tt.name) {
216                                 if err := tt.r.run(tt.name, i); err != nil {
217                                         log.Printf("%s: %v", tt.name, err)
218                                 }
219                         }
220                 }
221         }
222 }
223
224 func toolPath(names ...string) (found, path string) {
225         var out1 []byte
226         var err1 error
227         for i, name := range names {
228                 out, err := exec.Command(*flagGoCmd, "tool", "-n", name).CombinedOutput()
229                 if err == nil {
230                         return name, strings.TrimSpace(string(out))
231                 }
232                 if i == 0 {
233                         out1, err1 = out, err
234                 }
235         }
236         log.Fatalf("go tool -n %s: %v\n%s", names[0], err1, out1)
237         return "", ""
238 }
239
240 type Pkg struct {
241         Dir     string
242         GoFiles []string
243 }
244
245 func goList(dir string) (*Pkg, error) {
246         var pkg Pkg
247         out, err := exec.Command(*flagGoCmd, "list", "-json", dir).Output()
248         if err != nil {
249                 return nil, fmt.Errorf("go list -json %s: %v", dir, err)
250         }
251         if err := json.Unmarshal(out, &pkg); err != nil {
252                 return nil, fmt.Errorf("go list -json %s: unmarshal: %v", dir, err)
253         }
254         return &pkg, nil
255 }
256
257 func runCmd(name string, cmd *exec.Cmd) error {
258         start := time.Now()
259         out, err := cmd.CombinedOutput()
260         if err != nil {
261                 return fmt.Errorf("%v\n%s", err, out)
262         }
263         fmt.Printf("%s 1 %d ns/op\n", name, time.Since(start).Nanoseconds())
264         return nil
265 }
266
267 type goBuild struct{ pkgs []string }
268
269 func (goBuild) long() bool { return true }
270
271 func (r goBuild) run(name string, count int) error {
272         args := []string{"build", "-a"}
273         if *flagCompilerFlags != "" {
274                 args = append(args, "-gcflags", *flagCompilerFlags)
275         }
276         args = append(args, r.pkgs...)
277         cmd := exec.Command(*flagGoCmd, args...)
278         cmd.Dir = filepath.Join(goroot, "src")
279         return runCmd(name, cmd)
280 }
281
282 type size struct {
283         // path is either a path to a file ("$GOROOT/test/helloworld.go") or a package path ("cmd/go").
284         path   string
285         isLong bool
286 }
287
288 func (r size) long() bool { return r.isLong }
289
290 func (r size) run(name string, count int) error {
291         if strings.HasPrefix(r.path, "$GOROOT/") {
292                 r.path = goroot + "/" + r.path[len("$GOROOT/"):]
293         }
294
295         cmd := exec.Command(*flagGoCmd, "build", "-o", "_compilebenchout_", r.path)
296         cmd.Stdout = os.Stderr
297         cmd.Stderr = os.Stderr
298         if err := cmd.Run(); err != nil {
299                 return err
300         }
301         defer os.Remove("_compilebenchout_")
302         info, err := os.Stat("_compilebenchout_")
303         if err != nil {
304                 return err
305         }
306         out, err := exec.Command("size", "_compilebenchout_").CombinedOutput()
307         if err != nil {
308                 return fmt.Errorf("size: %v\n%s", err, out)
309         }
310         lines := strings.Split(string(out), "\n")
311         if len(lines) < 2 {
312                 return fmt.Errorf("not enough output from size: %s", out)
313         }
314         f := strings.Fields(lines[1])
315         if strings.HasPrefix(lines[0], "__TEXT") && len(f) >= 2 { // OS X
316                 fmt.Printf("%s 1 %s text-bytes %s data-bytes %v exe-bytes\n", name, f[0], f[1], info.Size())
317         } else if strings.Contains(lines[0], "bss") && len(f) >= 3 {
318                 fmt.Printf("%s 1 %s text-bytes %s data-bytes %s bss-bytes %v exe-bytes\n", name, f[0], f[1], f[2], info.Size())
319         }
320         return nil
321 }
322
323 type compile struct{ dir string }
324
325 func (compile) long() bool { return false }
326
327 func (c compile) run(name string, count int) error {
328         // Make sure dependencies needed by go tool compile are installed to GOROOT/pkg.
329         out, err := exec.Command(*flagGoCmd, "build", "-i", c.dir).CombinedOutput()
330         if err != nil {
331                 return fmt.Errorf("go build -i %s: %v\n%s", c.dir, err, out)
332         }
333
334         // Find dir and source file list.
335         pkg, err := goList(c.dir)
336         if err != nil {
337                 return err
338         }
339
340         args := []string{"-o", "_compilebench_.o"}
341         args = append(args, strings.Fields(*flagCompilerFlags)...)
342         args = append(args, pkg.GoFiles...)
343         if err := runBuildCmd(name, count, pkg.Dir, compiler, args); err != nil {
344                 return err
345         }
346
347         opath := pkg.Dir + "/_compilebench_.o"
348         if *flagObj {
349                 // TODO(josharian): object files are big; just read enough to find what we seek.
350                 data, err := ioutil.ReadFile(opath)
351                 if err != nil {
352                         log.Print(err)
353                 }
354                 // Find start of export data.
355                 i := bytes.Index(data, []byte("\n$$B\n")) + len("\n$$B\n")
356                 // Count bytes to end of export data.
357                 nexport := bytes.Index(data[i:], []byte("\n$$\n"))
358                 fmt.Printf(" %d object-bytes %d export-bytes", len(data), nexport)
359         }
360         fmt.Println()
361
362         os.Remove(opath)
363         return nil
364 }
365
366 type link struct{ dir, flags string }
367
368 func (link) long() bool { return false }
369
370 func (r link) run(name string, count int) error {
371         if linker == "" {
372                 // No linker. Skip the test.
373                 return nil
374         }
375
376         // Build dependencies.
377         out, err := exec.Command(*flagGoCmd, "build", "-i", "-o", "/dev/null", r.dir).CombinedOutput()
378         if err != nil {
379                 return fmt.Errorf("go build -i %s: %v\n%s", r.dir, err, out)
380         }
381
382         // Build the main package.
383         pkg, err := goList(r.dir)
384         if err != nil {
385                 return err
386         }
387         args := []string{"-o", "_compilebench_.o"}
388         args = append(args, pkg.GoFiles...)
389         cmd := exec.Command(compiler, args...)
390         cmd.Dir = pkg.Dir
391         cmd.Stdout = os.Stderr
392         cmd.Stderr = os.Stderr
393         err = cmd.Run()
394         if err != nil {
395                 return fmt.Errorf("compiling: %v", err)
396         }
397         defer os.Remove(pkg.Dir + "/_compilebench_.o")
398
399         // Link the main package.
400         args = []string{"-o", "_compilebench_.exe"}
401         args = append(args, strings.Fields(*flagLinkerFlags)...)
402         args = append(args, strings.Fields(r.flags)...)
403         args = append(args, "_compilebench_.o")
404         if err := runBuildCmd(name, count, pkg.Dir, linker, args); err != nil {
405                 return err
406         }
407         fmt.Println()
408         defer os.Remove(pkg.Dir + "/_compilebench_.exe")
409
410         return err
411 }
412
413 // runBuildCmd runs "tool args..." in dir, measures standard build
414 // tool metrics, and prints a benchmark line. The caller may print
415 // additional metrics and then must print a newline.
416 //
417 // This assumes tool accepts standard build tool flags like
418 // -memprofilerate, -memprofile, and -cpuprofile.
419 func runBuildCmd(name string, count int, dir, tool string, args []string) error {
420         var preArgs []string
421         if *flagMemprofilerate >= 0 {
422                 preArgs = append(preArgs, "-memprofilerate", fmt.Sprint(*flagMemprofilerate))
423         }
424         if *flagAlloc || *flagCpuprofile != "" || *flagMemprofile != "" {
425                 if *flagAlloc || *flagMemprofile != "" {
426                         preArgs = append(preArgs, "-memprofile", "_compilebench_.memprof")
427                 }
428                 if *flagCpuprofile != "" {
429                         preArgs = append(preArgs, "-cpuprofile", "_compilebench_.cpuprof")
430                 }
431         }
432         cmd := exec.Command(tool, append(preArgs, args...)...)
433         cmd.Dir = dir
434         cmd.Stdout = os.Stderr
435         cmd.Stderr = os.Stderr
436         start := time.Now()
437         err := cmd.Run()
438         if err != nil {
439                 return err
440         }
441         end := time.Now()
442
443         haveAllocs, haveRSS := false, false
444         var allocs, allocbytes, rssbytes int64
445         if *flagAlloc || *flagMemprofile != "" {
446                 out, err := ioutil.ReadFile(dir + "/_compilebench_.memprof")
447                 if err != nil {
448                         log.Print("cannot find memory profile after compilation")
449                 }
450                 for _, line := range strings.Split(string(out), "\n") {
451                         f := strings.Fields(line)
452                         if len(f) < 4 || f[0] != "#" || f[2] != "=" {
453                                 continue
454                         }
455                         val, err := strconv.ParseInt(f[3], 0, 64)
456                         if err != nil {
457                                 continue
458                         }
459                         haveAllocs = true
460                         switch f[1] {
461                         case "TotalAlloc":
462                                 allocbytes = val
463                         case "Mallocs":
464                                 allocs = val
465                         case "MaxRSS":
466                                 haveRSS = true
467                                 rssbytes = val
468                         }
469                 }
470                 if !haveAllocs {
471                         log.Println("missing stats in memprof (golang.org/issue/18641)")
472                 }
473
474                 if *flagMemprofile != "" {
475                         outpath := *flagMemprofile
476                         if *flagCount != 1 {
477                                 outpath = fmt.Sprintf("%s_%d", outpath, count)
478                         }
479                         if err := ioutil.WriteFile(outpath, out, 0666); err != nil {
480                                 log.Print(err)
481                         }
482                 }
483                 os.Remove(dir + "/_compilebench_.memprof")
484         }
485
486         if *flagCpuprofile != "" {
487                 out, err := ioutil.ReadFile(dir + "/_compilebench_.cpuprof")
488                 if err != nil {
489                         log.Print(err)
490                 }
491                 outpath := *flagCpuprofile
492                 if *flagCount != 1 {
493                         outpath = fmt.Sprintf("%s_%d", outpath, count)
494                 }
495                 if err := ioutil.WriteFile(outpath, out, 0666); err != nil {
496                         log.Print(err)
497                 }
498                 os.Remove(dir + "/_compilebench_.cpuprof")
499         }
500
501         wallns := end.Sub(start).Nanoseconds()
502         userns := cmd.ProcessState.UserTime().Nanoseconds()
503
504         fmt.Printf("%s 1 %d ns/op %d user-ns/op", name, wallns, userns)
505         if haveAllocs {
506                 fmt.Printf(" %d B/op %d allocs/op", allocbytes, allocs)
507         }
508         if haveRSS {
509                 fmt.Printf(" %d maxRSS/op", rssbytes)
510         }
511
512         return nil
513 }