analysis/app: group benchstat results if a name label is in the query
Change-Id: Ia8761c7709e09196dbb2499bcec76ab5bfc0c715
Reviewed-on: https://go-review.googlesource.com/35949
Reviewed-by: Russ Cox <rsc@golang.org>
diff --git a/analysis/app/compare.go b/analysis/app/compare.go
index 21a89ea..9780d95 100644
--- a/analysis/app/compare.go
+++ b/analysis/app/compare.go
@@ -6,14 +6,18 @@
import (
"bytes"
+ "fmt"
"html/template"
"io/ioutil"
"net/http"
"sort"
+ "strconv"
"strings"
+ "unicode"
"golang.org/x/perf/analysis/internal/benchstat"
"golang.org/x/perf/storage/benchfmt"
+ "golang.org/x/perf/storage/query"
)
// A resultGroup holds a list of results and tracks the distinct labels found in that list.
@@ -165,6 +169,56 @@
CommonLabels benchfmt.Labels
}
+// queryKeys returns the keys that are exact-matched by q.
+func queryKeys(q string) map[string]bool {
+ out := make(map[string]bool)
+ for _, part := range query.SplitWords(q) {
+ // TODO(quentin): This func is shared with db.go; refactor?
+ i := strings.IndexFunc(part, func(r rune) bool {
+ return r == ':' || r == '>' || r == '<' || unicode.IsSpace(r) || unicode.IsUpper(r)
+ })
+ if i >= 0 && part[i] == ':' {
+ out[part[:i]] = true
+ }
+ }
+ return out
+}
+
+// elideKeyValues returns content, a benchmark format line, with the
+// values of any keys in keys elided.
+func elideKeyValues(content string, keys map[string]bool) string {
+ var end string
+ if i := strings.IndexFunc(content, unicode.IsSpace); i >= 0 {
+ content, end = content[:i], content[i:]
+ }
+ // Check for gomaxprocs value
+ if i := strings.LastIndex(content, "-"); i >= 0 {
+ _, err := strconv.Atoi(content[i+1:])
+ if err == nil {
+ if keys["gomaxprocs"] {
+ content, end = content[:i], "-*"+end
+ } else {
+ content, end = content[:i], content[i:]+end
+ }
+ }
+ }
+ parts := strings.Split(content, "/")
+ for i, part := range parts {
+ if equals := strings.Index(part, "="); equals >= 0 {
+ if keys[part[:equals]] {
+ parts[i] = part[:equals] + "=*"
+ }
+ } else if i == 0 {
+ if keys["name"] {
+ parts[i] = "Benchmark*"
+ }
+ } else if keys[fmt.Sprintf("sub%d", i)] {
+ parts[i] = "*"
+ }
+ }
+ return strings.Join(parts, "/") + end
+}
+
func (a *App) compareQuery(q string) *compareData {
// Parse query
prefix, queries := parseQueryString(q)
@@ -174,13 +228,16 @@
var groups []*resultGroup
var found int
for _, qPart := range queries {
+ keys := queryKeys(qPart)
group := &resultGroup{}
if prefix != "" {
qPart = prefix + " " + qPart
}
res := a.StorageClient.Query(qPart)
for res.Next() {
- group.add(res.Result())
+ result := res.Result()
+ result.Content = elideKeyValues(result.Content, keys)
+ group.add(result)
found++
}
err := res.Err()
diff --git a/analysis/app/compare_test.go b/analysis/app/compare_test.go
index 1728ae0..5552c53 100644
--- a/analysis/app/compare_test.go
+++ b/analysis/app/compare_test.go
@@ -131,3 +131,27 @@
})
}
}
+
+func TestElideKeyValues(t *testing.T) {
+ type sb map[string]bool
+ tests := []struct {
+ content string
+ keys sb
+ want string
+ }{
+ {"BenchmarkOne/key=1-1 1 ns/op", sb{"key": true}, "BenchmarkOne/key=*-1 1 ns/op"},
+ {"BenchmarkOne/key=1-2 1 ns/op", sb{"other": true}, "BenchmarkOne/key=1-2 1 ns/op"},
+ {"BenchmarkOne/key=1/key2=2-3 1 ns/op", sb{"key": true}, "BenchmarkOne/key=*/key2=2-3 1 ns/op"},
+ {"BenchmarkOne/foo/bar-4 1 ns/op", sb{"sub1": true}, "BenchmarkOne/*/bar-4 1 ns/op"},
+ {"BenchmarkOne/foo/bar-5 1 ns/op", sb{"gomaxprocs": true}, "BenchmarkOne/foo/bar-* 1 ns/op"},
+ {"BenchmarkOne/foo/bar-6 1 ns/op", sb{"name": true}, "Benchmark*/foo/bar-6 1 ns/op"},
+ }
+ for i, test := range tests {
+ t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
+ have := elideKeyValues(test.content, test.keys)
+ if have != test.want {
+ t.Errorf("elideKeys(%q, %#v) = %q, want %q", test.content, map[string]bool(test.keys), have, test.want)
+ }
+ })
+ }
+}