package main import ( "bytes" "encoding/csv" "flag" "fmt" "os" "os/exec" "runtime" "runtime/pprof" "strconv" "strings" "time" ) func main() { if err := run(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } func run() error { if os.Getenv("WORKER") == "" { return leader() } else { return worker() } } func leader() error { var ( blockprofilerates = flagIntSlice("blockprofilerates", []int{0, 1, 10, 100, 1000, 10000, 100000, 1000000}, "The runtime.SetBlockProfileRate() values to benchmark.") bufsizes = flagIntSlice("bufsizes", []int{0, 64}, "The buffer sizes to use for channel operations (not applicable to all workloads).") depths = flagIntSlice("depths", []int{2, 4, 8, 16, 32}, "The different frame depths values to use for each workload.") goroutines = flagIntSlice("goroutines", []int{runtime.NumCPU()}, "The number of goroutine values to use for each workloads.") ops = flag.Int("ops", 1000, "The number of operations to perform for each workload.") runs = flag.Int("runs", 3, "The number of times to repeat the same benchmark to understand variance.") workloads = flagStringSlice("workloads", []string{"mutex", "chan"}, "The workloads to benchmark.") ) flag.Parse() cw := csv.NewWriter(os.Stdout) cw.Write(Headers()) cw.Flush() for _, workload := range *workloads { for _, goroutine := range *goroutines { for _, blockprofilerate := range *blockprofilerates { for _, depth := range *depths { for _, bufsize := range *bufsizes { for run := 1; run <= *runs; run++ { cmd := exec.Command(os.Args[0], "-run", fmt.Sprintf("%d", run), "-blockprofilerate", fmt.Sprintf("%d", blockprofilerate), "-ops", fmt.Sprintf("%d", *ops), "-goroutines", fmt.Sprintf("%d", goroutine), "-depth", fmt.Sprintf("%d", depth), "-bufsize", fmt.Sprintf("%d", bufsize), "-workload", workload, ) buf := &bytes.Buffer{} cmd.Stdout = buf cmd.Stderr = os.Stderr cmd.Env = append(cmd.Env, "WORKER=yeah") if err := cmd.Run(); err != nil { return err } buf.WriteTo(os.Stdout) } } } } } } return nil } func worker() error { var ( blockprofilerate = flag.Int("blockprofilerate", 1, "The block profile rate to use.") bufsize = flag.Int("bufsize", 0, "The buffer size to use for channel operations (not applicable to all workloads).") depth = flag.Int("depth", 16, "The stack depth at which to perform blocking events.") goroutines = flag.Int("goroutines", runtime.NumCPU(), "The number of goroutines to utilize.") ops = flag.Int("ops", 100000, "The number of operations to perform.") out = flag.String("blockprofile", "", "Path to a file for writing the block profile.") run = flag.Int("run", 1, "The number of run. Has no impact on the benchmark, but gets included in the csv output line.") workload = flag.String("workload", "mutex", "The workload to simulate.") ) flag.Parse() if *blockprofilerate > 0 { runtime.SetBlockProfileRate(*blockprofilerate) } start := time.Now() switch *workload { case "mutex": if err := mutexWorkload(*goroutines, *ops, *depth); err != nil { return err } case "chan": if err := chanWorkload(*goroutines, *ops, *depth, *bufsize); err != nil { return err } default: return fmt.Errorf("unknown workload: %q", *workload) } duration := time.Since(start) if *blockprofilerate > 0 && *out != "" { f, err := os.Create(*out) if err != nil { return err } defer f.Close() if err := pprof.Lookup("block").WriteTo(f, 0); err != nil { return err } } cw := csv.NewWriter(os.Stdout) record, err := (&Record{ Blockprofilerate: *blockprofilerate, Bufsize: *bufsize, Depth: *depth, Duration: duration, Goroutines: *goroutines, Ops: *ops, Run: *run, Workload: *workload, }).MarshalRecord() if err != nil { return err } cw.Write(record) cw.Flush() return cw.Error() } func atStackDepth(depth int, fn func()) { pcs := make([]uintptr, depth*10) n := runtime.Callers(1, pcs) if n > depth { panic("depth exceeded") } else if n < depth { atStackDepth(depth, fn) return } fn() } func flagIntSlice(name string, value []int, usage string) *[]int { val := &intSlice{vals: value} flag.Var(val, name, usage) return &val.vals } type intSlice struct { vals []int } func (i *intSlice) Set(val string) error { var vals []int for _, val := range strings.Split(val, ",") { num, err := strconv.Atoi(val) if err != nil { return err } vals = append(vals, num) } i.vals = vals return nil } func (i *intSlice) String() string { return fmt.Sprintf("%v", i.vals) } func flagStringSlice(name string, value []string, usage string) *[]string { val := &strSlice{vals: value} flag.Var(val, name, usage) return &val.vals } type strSlice struct { vals []string } func (s *strSlice) Set(val string) error { s.vals = strings.Split(val, ",") return nil } func (s *strSlice) String() string { return fmt.Sprintf("%v", s.vals) }