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