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  }