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  }