github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/tools/build_perf/build_perf.go (about) 1 // Copyright 2017 the u-root 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 // Measure the performance of building all the Go commands under various GOGC 6 // values. The output is four csv files: 7 // 8 // - build_perf_real.csv 9 // - build_perf_user.csv 10 // - build_perf_sys.csv 11 // - build_perf_max_rss.csv 12 package main 13 14 import ( 15 "encoding/csv" 16 "fmt" 17 "log" 18 "os" 19 "os/exec" 20 "path/filepath" 21 "sync" 22 "syscall" 23 "time" 24 ) 25 26 const ( 27 cmdsPath = "$GOPATH/src/github.com/mvdan/u-root-coreutils/cmds" 28 gogcBegin = 50 29 gogcEnd = 2000 30 gogcStep = 50 31 ) 32 33 // The fields profiled by a single `go build`. 34 type measurement struct { 35 realTime float64 // seconds 36 userTime float64 // seconds 37 sysTime float64 // seconds 38 maxRss int64 // KiB 39 } 40 41 // Each CSV file only stores one field of the measurement. 42 // This struct describes a single CSV file. 43 type csvDesc struct { 44 filename string 45 field func(measurement) string 46 // Measurements are sent from the `measureBuilds` to the `writeCsv` 47 // functions via this channel. 48 c chan []*measurement 49 } 50 51 var descs = []csvDesc{ 52 { 53 "build_perf_real.csv", 54 func(m measurement) string { return fmt.Sprint(m.realTime) }, 55 make(chan []*measurement), 56 }, { 57 "build_perf_user.csv", 58 func(m measurement) string { return fmt.Sprint(m.userTime) }, 59 make(chan []*measurement), 60 }, { 61 "build_perf_sys.csv", 62 func(m measurement) string { return fmt.Sprint(m.sysTime) }, 63 make(chan []*measurement), 64 }, { 65 "build_perf_max_rss.csv", 66 func(m measurement) string { return fmt.Sprint(m.maxRss) }, 67 make(chan []*measurement), 68 }, 69 } 70 71 var wg sync.WaitGroup 72 73 // Return a list of command names. 74 func getCmdNames() ([]string, error) { 75 files, err := os.ReadDir(os.ExpandEnv(cmdsPath)) 76 if err != nil { 77 return nil, err 78 } 79 cmds := []string{} 80 for _, file := range files { 81 if file.IsDir() { 82 cmds = append(cmds, file.Name()) 83 } 84 } 85 return cmds, nil 86 } 87 88 func buildCmd(cmd string, gogc int) (*measurement, error) { 89 start := time.Now() 90 c := exec.Command("go", "build") 91 c.Dir = filepath.Join(os.ExpandEnv(cmdsPath), cmd) 92 c.Env = append(os.Environ(), fmt.Sprintf("GOGC=%d", gogc)) 93 if err := c.Run(); err != nil { 94 return nil, err 95 } 96 return &measurement{ 97 realTime: time.Since(start).Seconds(), 98 userTime: c.ProcessState.UserTime().Seconds(), 99 sysTime: c.ProcessState.SystemTime().Seconds(), 100 maxRss: c.ProcessState.SysUsage().(*syscall.Rusage).Maxrss, 101 }, nil 102 } 103 104 func measureBuilds(cmds []string, gogcs []int) { 105 for _, cmd := range cmds { 106 measurements := make([]*measurement, len(gogcs)) 107 for i, gogc := range gogcs { 108 m, err := buildCmd(cmd, gogc) 109 if err != nil { 110 log.Printf("%v: %v", cmd, err) 111 } 112 measurements[i] = m 113 } 114 // Write to all csv files. 115 for _, d := range descs { 116 d.c <- measurements 117 } 118 fmt.Print(".") 119 } 120 } 121 122 func writeCsv(cmds []string, gogcs []int, d csvDesc) { 123 // Create the csv writer. 124 f, err := os.Create(d.filename) 125 if err != nil { 126 log.Fatal(err) 127 } 128 defer f.Close() 129 w := csv.NewWriter(f) 130 131 // Write header. 132 header := make([]string, len(gogcs)+1) 133 header[0] = "cmd \\ gogc" 134 for i, gogc := range gogcs { 135 header[i+1] = fmt.Sprint(gogc) 136 } 137 w.Write(header) 138 139 // Iterator over all the measurements. 140 row := 0 141 for measurements := range d.c { 142 record := make([]string, len(measurements)+1) 143 record[0] = cmds[row] 144 // Iterate over measurements for a single command. 145 for i, m := range measurements { 146 if m == nil { 147 record[i+1] = "err" 148 } else { 149 record[i+1] = d.field(*m) 150 } 151 } 152 153 // Write to CSV. 154 if err := w.Write(record); err != nil { 155 log.Fatalln("error writing record to csv:", err) 156 } 157 w.Flush() 158 if err := w.Error(); err != nil { 159 log.Fatalln("error flushing csv:", err) 160 } 161 row++ 162 } 163 wg.Done() 164 } 165 166 func main() { 167 // Get list of commands. 168 cmds, err := getCmdNames() 169 if err != nil { 170 log.Fatal("Cannot get list of commands:", err) 171 } 172 173 // Create range of GOGC values. 174 gogcs := []int{} 175 for i := gogcBegin; i <= gogcEnd; i += gogcStep { 176 gogcs = append(gogcs, i) 177 } 178 179 wg.Add(len(descs)) 180 for _, d := range descs { 181 go writeCsv(cmds, gogcs, d) 182 } 183 184 measureBuilds(cmds, gogcs) 185 186 for _, d := range descs { 187 close(d.c) 188 } 189 wg.Wait() 190 fmt.Println("\nDone!") 191 }