cmd/benchfilter: new command for filtering and tidying benchmark results
Change-Id: I66dac0c90f27f932abfabee0e86a216606d981d1
Reviewed-on: https://go-review.googlesource.com/c/perf/+/283618
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Austin Clements <austin@google.com>
Reviewed-by: David Chase <drchase@google.com>
diff --git a/README.md b/README.md
index 5a744c3..091947d 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,9 @@
[cmd/benchstat](cmd/benchstat) computes statistical summaries and A/B
comparisons of Go benchmarks.
+[cmd/benchfilter](cmd/benchfilter) filters the contents of benchmark
+result files.
+
[cmd/benchsave](cmd/benchsave) publishes benchmark results to
[perf.golang.org](https://perf.golang.org).
diff --git a/cmd/benchfilter/main.go b/cmd/benchfilter/main.go
new file mode 100644
index 0000000..5ede808
--- /dev/null
+++ b/cmd/benchfilter/main.go
@@ -0,0 +1,82 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// benchfilter reads Go benchmark results from input files, filters
+// them, and writes filtered benchmark results to stdout. If no inputs
+// are provided, it reads from stdin.
+//
+// The filter language is described at
+// https://pkg.go.dev/golang.org/x/perf/cmd/benchstat#Filtering
+package main
+
+import (
+ "flag"
+ "fmt"
+ "log"
+ "os"
+
+ "golang.org/x/perf/benchfmt"
+ "golang.org/x/perf/benchproc"
+)
+
+func usage() {
+ fmt.Fprintf(flag.CommandLine.Output(), `Usage: benchfilter query [inputs...]
+
+benchfilter reads Go benchmark results from input files, filters them,
+and writes filtered benchmark results to stdout. If no inputs are
+provided, it reads from stdin.
+
+The filter language is described at
+https://pkg.go.dev/golang.org/x/perf/cmd/benchstat#Filtering
+`)
+ flag.PrintDefaults()
+}
+
+func main() {
+ log.SetPrefix("")
+ log.SetFlags(0)
+
+ flag.Usage = usage
+ flag.Parse()
+ if flag.NArg() < 1 {
+ usage()
+ os.Exit(2)
+ }
+
+ // TODO: Consider adding filtering on values, like "@ns/op>=100".
+
+ filter, err := benchproc.NewFilter(flag.Arg(0))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ writer := benchfmt.NewWriter(os.Stdout)
+ files := benchfmt.Files{Paths: flag.Args()[1:], AllowStdin: true, AllowLabels: true}
+ for files.Scan() {
+ rec := files.Result()
+ switch rec := rec.(type) {
+ case *benchfmt.SyntaxError:
+ // Non-fatal result parse error. Warn
+ // but keep going.
+ fmt.Fprintln(os.Stderr, rec)
+ continue
+ case *benchfmt.Result:
+ if ok, err := filter.Apply(rec); !ok {
+ if err != nil {
+ // Print the reason we rejected this result.
+ fmt.Fprintln(os.Stderr, err)
+ }
+ continue
+ }
+ }
+
+ err = writer.Write(rec)
+ if err != nil {
+ log.Fatal("writing output: ", err)
+ }
+ }
+ if err := files.Err(); err != nil {
+ log.Fatal(err)
+ }
+}