perf: dashboard support for multiple branches
First and foremost, we must ingest uploads on alternative branches.
Right now ingest only branch master.
On the frontend, add a dropdown for branch, very similar to the
repository dropdown. In the future, we should probably fetch a list of
branches rather than hard-coding them.
Note that the branch here is the Go branch that the perf builder
triggered against, which is a bit confusing for subrepos, but I've added
a note to try to explain.
For golang/go#53538.
Change-Id: Ib5b74b9e5d7b67ce2b04bc2bb22e3521dffaee36
Reviewed-on: https://go-review.googlesource.com/c/build/+/459155
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/perf/app/dashboard.go b/perf/app/dashboard.go
index 4b3b116..fa6d764 100644
--- a/perf/app/dashboard.go
+++ b/perf/app/dashboard.go
@@ -94,7 +94,7 @@
// validateRe is an allowlist of characters for a Flux string literal. The
// string will be quoted, so we must not allow ending the quote sequence.
-var validateRe = regexp.MustCompile(`^[a-zA-Z0-9(),=/_:;-]+$`)
+var validateRe = regexp.MustCompile(`^[a-zA-Z0-9(),=/_:;.-]+$`)
func validateFluxString(s string) error {
if !validateRe.MatchString(s) {
@@ -111,10 +111,13 @@
var errBenchmarkNotFound = errors.New("benchmark not found")
// fetchNamedUnitBenchmark queries Influx for a specific name + unit benchmark.
-func fetchNamedUnitBenchmark(ctx context.Context, qc api.QueryAPI, start, end time.Time, repository string, name, unit string) (*BenchmarkJSON, error) {
+func fetchNamedUnitBenchmark(ctx context.Context, qc api.QueryAPI, start, end time.Time, repository, branch, name, unit string) (*BenchmarkJSON, error) {
if err := validateFluxString(repository); err != nil {
return nil, fmt.Errorf("invalid repository name: %w", err)
}
+ if err := validateFluxString(branch); err != nil {
+ return nil, fmt.Errorf("invalid branch name: %w", err)
+ }
if err := validateFluxString(name); err != nil {
return nil, fmt.Errorf("invalid benchmark name: %w", err)
}
@@ -131,14 +134,14 @@
|> filter(fn: (r) => r["_measurement"] == "benchmark-result")
|> filter(fn: (r) => r["name"] == "%s")
|> filter(fn: (r) => r["unit"] == "%s")
- |> filter(fn: (r) => r["branch"] == "master")
+ |> filter(fn: (r) => r["branch"] == "%s")
|> filter(fn: (r) => r["goos"] == "linux")
|> filter(fn: (r) => r["goarch"] == "amd64")
|> fill(column: "repository", value: "go")
|> filter(fn: (r) => r["repository"] == "%s")
|> pivot(columnKey: ["_field"], rowKey: ["_time"], valueColumn: "_value")
|> yield(name: "last")
-`, start.Format(time.RFC3339), end.Format(time.RFC3339), name, unit, repository)
+`, start.Format(time.RFC3339), end.Format(time.RFC3339), name, unit, branch, repository)
res, err := influxQuery(ctx, qc, query)
if err != nil {
@@ -159,7 +162,7 @@
}
// fetchDefaultBenchmarks queries Influx for the default benchmark set.
-func fetchDefaultBenchmarks(ctx context.Context, qc api.QueryAPI, start, end time.Time, repository string) ([]*BenchmarkJSON, error) {
+func fetchDefaultBenchmarks(ctx context.Context, qc api.QueryAPI, start, end time.Time, repository, branch string) ([]*BenchmarkJSON, error) {
if repository != "go" {
// No defaults defined for other subrepos yet, just return an
// empty set.
@@ -225,7 +228,7 @@
ret := make([]*BenchmarkJSON, 0, len(benchmarks))
for _, bench := range benchmarks {
- b, err := fetchNamedUnitBenchmark(ctx, qc, start, end, repository, bench.name, bench.unit)
+ b, err := fetchNamedUnitBenchmark(ctx, qc, start, end, repository, branch, bench.name, bench.unit)
if err != nil {
return nil, fmt.Errorf("error fetching benchmark %s/%s: %w", bench.name, bench.unit, err)
}
@@ -237,10 +240,13 @@
// fetchNamedBenchmark queries Influx for all benchmark results with the passed
// name (for all units).
-func fetchNamedBenchmark(ctx context.Context, qc api.QueryAPI, start, end time.Time, repository string, name string) ([]*BenchmarkJSON, error) {
+func fetchNamedBenchmark(ctx context.Context, qc api.QueryAPI, start, end time.Time, repository, branch, name string) ([]*BenchmarkJSON, error) {
if err := validateFluxString(repository); err != nil {
return nil, fmt.Errorf("invalid repository name: %w", err)
}
+ if err := validateFluxString(branch); err != nil {
+ return nil, fmt.Errorf("invalid branch name: %w", err)
+ }
if err := validateFluxString(name); err != nil {
return nil, fmt.Errorf("invalid benchmark name: %w", err)
}
@@ -253,14 +259,14 @@
|> range(start: %s, stop: %s)
|> filter(fn: (r) => r["_measurement"] == "benchmark-result")
|> filter(fn: (r) => r["name"] == "%s")
- |> filter(fn: (r) => r["branch"] == "master")
+ |> filter(fn: (r) => r["branch"] == "%s")
|> filter(fn: (r) => r["goos"] == "linux")
|> filter(fn: (r) => r["goarch"] == "amd64")
|> fill(column: "repository", value: "go")
|> filter(fn: (r) => r["repository"] == "%s")
|> pivot(columnKey: ["_field"], rowKey: ["_time"], valueColumn: "_value")
|> yield(name: "last")
-`, start.Format(time.RFC3339), end.Format(time.RFC3339), name, repository)
+`, start.Format(time.RFC3339), end.Format(time.RFC3339), name, branch, repository)
res, err := influxQuery(ctx, qc, query)
if err != nil {
@@ -278,10 +284,13 @@
}
// fetchAllBenchmarks queries Influx for all benchmark results.
-func fetchAllBenchmarks(ctx context.Context, qc api.QueryAPI, regressions bool, start, end time.Time, repository string) ([]*BenchmarkJSON, error) {
+func fetchAllBenchmarks(ctx context.Context, qc api.QueryAPI, regressions bool, start, end time.Time, repository, branch string) ([]*BenchmarkJSON, error) {
if err := validateFluxString(repository); err != nil {
return nil, fmt.Errorf("invalid repository name: %w", err)
}
+ if err := validateFluxString(branch); err != nil {
+ return nil, fmt.Errorf("invalid branch name: %w", err)
+ }
// Note that very old points are missing the "repository" field. fill()
// sets repository=go on all points missing that field, as they were
@@ -290,14 +299,14 @@
from(bucket: "perf")
|> range(start: %s, stop: %s)
|> filter(fn: (r) => r["_measurement"] == "benchmark-result")
- |> filter(fn: (r) => r["branch"] == "master")
+ |> filter(fn: (r) => r["branch"] == "%s")
|> filter(fn: (r) => r["goos"] == "linux")
|> filter(fn: (r) => r["goarch"] == "amd64")
|> fill(column: "repository", value: "go")
|> filter(fn: (r) => r["repository"] == "%s")
|> pivot(columnKey: ["_field"], rowKey: ["_time"], valueColumn: "_value")
|> yield(name: "last")
-`, start.Format(time.RFC3339), end.Format(time.RFC3339), repository)
+`, start.Format(time.RFC3339), end.Format(time.RFC3339), branch, repository)
res, err := influxQuery(ctx, qc, query)
if err != nil {
@@ -640,17 +649,21 @@
if repository == "" {
repository = "go"
}
+ branch := r.FormValue("branch")
+ if branch == "" {
+ branch = "master"
+ }
benchmark := r.FormValue("benchmark")
var benchmarks []*BenchmarkJSON
if benchmark == "" {
- benchmarks, err = fetchDefaultBenchmarks(ctx, qc, start, end, repository)
+ benchmarks, err = fetchDefaultBenchmarks(ctx, qc, start, end, repository, branch)
} else if benchmark == "all" {
- benchmarks, err = fetchAllBenchmarks(ctx, qc, false, start, end, repository)
+ benchmarks, err = fetchAllBenchmarks(ctx, qc, false, start, end, repository, branch)
} else if benchmark == "regressions" {
- benchmarks, err = fetchAllBenchmarks(ctx, qc, true, start, end, repository)
+ benchmarks, err = fetchAllBenchmarks(ctx, qc, true, start, end, repository, branch)
} else {
- benchmarks, err = fetchNamedBenchmark(ctx, qc, start, end, repository, benchmark)
+ benchmarks, err = fetchNamedBenchmark(ctx, qc, start, end, repository, branch, benchmark)
}
if err == errBenchmarkNotFound {
log.Printf("Benchmark not found: %q", benchmark)
diff --git a/perf/app/dashboard/index.html b/perf/app/dashboard/index.html
index f5d2d8c..3d3a19f 100644
--- a/perf/app/dashboard/index.html
+++ b/perf/app/dashboard/index.html
@@ -46,6 +46,14 @@
<option>go</option>
<option>tools</option>
</select>
+ Branch:
+ <select id="branch-select" name="branch">
+ <option>master</option>
+ <option>release-branch.go1.17</option>
+ <option>release-branch.go1.18</option>
+ <option>release-branch.go1.19</option>
+ <option>release-branch.go1.20</option>
+ </select>
Duration (days):
<div class="Dashboard-duration">
<input id="days-input" type="number" name="days" value="30" />
@@ -72,6 +80,11 @@
<a href="https://build.golang.org/?repo=golang.org%2fx%2fbenchmarks">x/benchmarks build dashboard</a>
for tested ("ok") / untested (empty) commits.
</p>
+ <p>
+ Also note that the 'branch' selection above is the Go branch
+ that benchmarking ran against on https://build.golang.org, not
+ the subrepo branch.
+ </p>
</div>
<div id="dashboard">
@@ -154,6 +167,12 @@
select.value = repository;
}
+ let branch = params.get('branch');
+ if (branch) {
+ let select = document.getElementById('branch-select');
+ select.value = branch;
+ }
+
let days = params.get('days');
if (days) {
let input = document.getElementById('days-input');
diff --git a/perf/app/influx.go b/perf/app/influx.go
index 0bedf3c..ae7219c 100644
--- a/perf/app/influx.go
+++ b/perf/app/influx.go
@@ -214,9 +214,6 @@
"by:coordinator@symbolic-datum-552.iam.gserviceaccount.com",
// Only take results that were generated from post-submit runs, not trybots.
"post-submit:true",
- // Limit to just the master branch for now.
- // TODO(mknyszek): Support other branches.
- "branch:master",
}, " ")
uploadList := a.StorageClient.ListUploads(
ctx,