github.com/aclements/go-misc@v0.0.0-20240129233631-2f6ede80790c/benchplot/main.go (about)

     1  // Copyright 2016 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  // Command benchplot plots the results of benchmarks over time.
     6  //
     7  // benchplot takes an input file in Go benchmark format [1]. Each
     8  // benchmark result must have a "commit" configuration key that gives
     9  // the full commit hash of the revision that gave that result.
    10  // benchplot will cross-reference these hashes against the specified
    11  // Git repository and plot each metric over time for each benchmark.
    12  //
    13  // [1] https://github.com/golang/proposal/blob/master/design/14313-benchmark-format.md
    14  package main
    15  
    16  import (
    17  	"bytes"
    18  	"flag"
    19  	"fmt"
    20  	"log"
    21  	"os"
    22  	"os/exec"
    23  	"runtime"
    24  	"runtime/pprof"
    25  	"strings"
    26  
    27  	"github.com/aclements/go-gg/gg"
    28  	"github.com/aclements/go-gg/table"
    29  	"github.com/aclements/go-misc/bench"
    30  )
    31  
    32  func main() {
    33  	log.SetPrefix("benchplot: ")
    34  	log.SetFlags(0)
    35  
    36  	defaultGitDir, _ := exec.Command("git", "rev-parse", "--show-toplevel").Output()
    37  	defaultGitDir = bytes.TrimRight(defaultGitDir, "\n")
    38  	var (
    39  		flagCPUProfile = flag.String("cpuprofile", "", "write CPU profile to `file`")
    40  		flagMemProfile = flag.String("memprofile", "", "write heap profile to `file`")
    41  		flagGitDir     = flag.String("C", string(defaultGitDir), "run git in `dir`")
    42  		flagOut        = flag.String("o", "", "write output to `file` (default: stdout)")
    43  		flagTable      = flag.Bool("table", false, "output a table instead of a plot")
    44  	)
    45  	flag.Usage = func() {
    46  		fmt.Fprintf(os.Stderr, "Usage: %s [flags] [inputs...]\n", os.Args[0])
    47  		flag.PrintDefaults()
    48  	}
    49  	flag.Parse()
    50  
    51  	if *flagCPUProfile != "" {
    52  		f, err := os.Create(*flagCPUProfile)
    53  		if err != nil {
    54  			log.Fatal(err)
    55  		}
    56  		pprof.StartCPUProfile(f)
    57  		defer pprof.StopCPUProfile()
    58  	}
    59  
    60  	if *flagMemProfile != "" {
    61  		defer func() {
    62  			runtime.GC()
    63  			f, err := os.Create(*flagMemProfile)
    64  			if err != nil {
    65  				log.Fatal(err)
    66  			}
    67  			pprof.WriteHeapProfile(f)
    68  			f.Close()
    69  		}()
    70  	}
    71  
    72  	// Parse benchmark inputs.
    73  	paths := flag.Args()
    74  	if len(paths) == 0 {
    75  		paths = []string{"-"}
    76  	}
    77  	var benchmarks []*bench.Benchmark
    78  	for _, path := range paths {
    79  		func() {
    80  			f := os.Stdin
    81  			if path != "-" {
    82  				var err error
    83  				f, err = os.Open(path)
    84  				if err != nil {
    85  					log.Fatal(err)
    86  				}
    87  				defer f.Close()
    88  			}
    89  
    90  			bs, err := bench.Parse(f)
    91  			if err != nil {
    92  				log.Fatal(err)
    93  			}
    94  			benchmarks = append(benchmarks, bs...)
    95  		}()
    96  	}
    97  	bench.ParseValues(benchmarks, nil)
    98  
    99  	// Prepare gg tables.
   100  	var tab table.Grouping
   101  	btab, configCols, resultCols := benchmarksToTable(benchmarks)
   102  	if btab.Column("commit") == nil {
   103  		tab = btab
   104  	} else {
   105  		gtab := commitsToTable(Commits(*flagGitDir))
   106  		tab = table.Join(btab, "commit", gtab, "commit")
   107  	}
   108  
   109  	// Prepare for output.
   110  	f := os.Stdout
   111  	if *flagOut != "" {
   112  		var err error
   113  		f, err = os.Create(*flagOut)
   114  		if err != nil {
   115  			log.Fatal(err)
   116  		}
   117  		defer f.Close()
   118  	}
   119  
   120  	// Output table.
   121  	if *flagTable {
   122  		table.Fprint(f, tab)
   123  		return
   124  	}
   125  
   126  	// Plot.
   127  	//
   128  	// TODO: Collect nrows/ncols from the plot itself.
   129  	p, nrows, ncols := plot(tab, configCols, resultCols)
   130  	if !(len(paths) == 1 && paths[0] == "-") {
   131  		p.Add(gg.Title(strings.Join(paths, " ")))
   132  	}
   133  
   134  	// Render plot.
   135  	p.WriteSVG(f, 500*ncols, 350*nrows)
   136  }