in funcbench/main.go [55:225]
func main() {
cfg := struct {
userTestName string
verbose bool
nocomment bool
owner string
repo string
resultsDir string
workspaceDir string
ghPR int
benchTime time.Duration
benchTimeout time.Duration
compareTarget string
benchFuncRegex string
packagePath string
enablePerflock bool
}{}
app := kingpin.New(
filepath.Base(os.Args[0]),
`Benchmark and compare your Go code between sub benchmarks or commits.
* For BenchmarkFuncName, compare current with master: ./funcbench -v master BenchmarkFuncName
* For BenchmarkFunc.*, compare current with master: ./funcbench -v master BenchmarkFunc.*
* For all benchmarks, compare current with devel: ./funcbench -v devel .* or ./funcbench -v devel
* For BenchmarkFunc.*, compare current with 6d280 commit: ./funcbench -v 6d280 BenchmarkFunc.*
* For BenchmarkFunc.*, compare between sub-benchmarks of same benchmark on current commit: ./funcbench -v . BenchmarkFunc.*
* For BenchmarkFuncName, compare pr#35 with master: ./funcbench --nocomment --github-pr="35" master BenchmarkFuncName`,
)
// Options.
app.HelpFlag.Short('h')
app.Flag("verbose", "Verbose mode. Errors includes trace and commands output are logged.").
Short('v').BoolVar(&cfg.verbose)
app.Flag("nocomment", "Disable posting of comment using the GitHub API.").
BoolVar(&cfg.nocomment)
app.Flag("owner", "A Github owner or organisation name.").
Default("prometheus").StringVar(&cfg.owner)
app.Flag("repo", "This is the repository name.").
Default("prometheus").StringVar(&cfg.repo)
app.Flag("github-pr", "GitHub PR number to pull changes from and to post benchmark results.").
IntVar(&cfg.ghPR)
app.Flag("workspace", "Directory to clone GitHub PR.").
Default("/tmp/funcbench").
StringVar(&cfg.workspaceDir)
app.Flag("result-cache", "Directory to store benchmark results.").
Default("funcbench-results").
StringVar(&cfg.resultsDir)
app.Flag("user-test-name", "Name of the test to keep track of multiple benchmarks").
Default("default").
Short('n').
StringVar(&cfg.userTestName)
app.Flag("bench-time", "Run enough iterations of each benchmark to take t, specified "+
"as a time.Duration. The special syntax Nx means to run the benchmark N times").
Short('t').Default("1s").DurationVar(&cfg.benchTime)
app.Flag("timeout", "Benchmark timeout specified in time.Duration format, "+
"disabled if set to 0. If a test binary runs longer than duration d, panic.").
Short('d').Default("2h").DurationVar(&cfg.benchTimeout)
app.Flag("perflock", "Enable perflock (you must have perflock installed to use this)").
Short('l').
Default("false").
BoolVar(&cfg.enablePerflock)
app.Arg("target", "Can be one of '.', tag name, branch name or commit SHA of the branch "+
"to compare against. If set to '.', branch/commit is the same as the current one; "+
"funcbench will run once and try to compare between 2 sub-benchmarks. "+
"Errors out if there are no sub-benchmarks.").
Required().StringVar(&cfg.compareTarget)
app.Arg("bench-func-regex", "Function regex to use for benchmark."+
"Supports RE2 regexp and is fully anchored, by default will run all benchmarks.").
Default(".*").
StringVar(&cfg.benchFuncRegex) // TODO (geekodour) : validate regex?
app.Arg("packagepath", "Package to run benchmark against. Eg. ./tsdb, defaults to ./...").
Default("./...").
StringVar(&cfg.packagePath)
kingpin.MustParse(app.Parse(os.Args[1:]))
logger := &logger{
// Show file line with each log.
Logger: log.New(os.Stdout, "funcbech", log.Ltime|log.Lshortfile),
verbose: cfg.verbose,
}
var g run.Group
// Main routine.
{
ctx, cancel := context.WithCancel(context.Background())
g.Add(func() error {
var (
env Environment
err error
)
// Setup Environment.
e := environment{
logger: logger,
benchFunc: cfg.benchFuncRegex,
compareTarget: cfg.compareTarget,
}
if cfg.ghPR == 0 {
// Local Mode.
env, err = newLocalEnv(e)
if err != nil {
return errors.Wrap(err, "environment create")
}
} else {
// Github Mode.
ghClient, err := newGitHubClient(ctx, cfg.owner, cfg.repo, cfg.ghPR, cfg.nocomment)
if err != nil {
return errors.Wrapf(err, "github client")
}
env, err = newGitHubEnv(ctx, e, ghClient, cfg.workspaceDir)
if err != nil {
if err := ghClient.postComment(fmt.Sprintf("%v. Could not setup environment, please check logs.", err)); err != nil {
return errors.Wrap(err, "could not post error")
}
return errors.Wrap(err, "environment create")
}
}
// ( β_β)οΎ Start benchmarking!
benchmarker := newBenchmarker(logger, env,
&commander{verbose: cfg.verbose, ctx: ctx},
cfg.benchTime, cfg.benchTimeout,
path.Join(cfg.resultsDir, cfg.userTestName),
cfg.packagePath,
cfg.enablePerflock,
)
tables, err := startBenchmark(env, benchmarker)
if err != nil {
pErr := env.PostErr(
fmt.Sprintf(
"```\n%s\n```\nError:\n```\n%s\n```",
strings.Join(benchmarker.benchmarkArgs, " "),
err.Error(),
),
)
if pErr != nil {
return errors.Wrap(pErr, "could not log error")
}
return err
}
// Post results.
// TODO (geekodour): probably post some kind of funcbench summary(?)
return env.PostResults(
tables,
fmt.Sprintf("```\n%s\n```", strings.Join(benchmarker.benchmarkArgs, " ")),
)
}, func(err error) {
cancel()
})
}
// Listen for termination signals.
{
cancel := make(chan struct{})
g.Add(func() error {
return interrupt(logger, cancel)
}, func(error) {
close(cancel)
})
}
if err := g.Run(); err != nil {
logger.FatalError(errors.Wrap(err, "running command failed"))
}
logger.Println("exiting")
}