blob: a9b1f137ffd18934772e087f28708f5887d79476 [file] [log] [blame]
<!--
Copyright 2022 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.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<title>Go Performance Dashboard</title>
<link rel="icon" href="https://go.dev/favicon.ico"/>
<link rel="stylesheet" href="./static/style.css"/>
<script src="https://ajax.googleapis.com/ajax/libs/d3js/7.4.2/d3.min.js"></script>
<script src="./third_party/bandchart/bandchart.js"></script>
<script src="./static/range.js"></script>
</head>
<body class="Dashboard">
<header class="Dashboard-topbar">
<h1>
<a href="https://farmer.golang.org/">Go Build Coordinator</a>
</h1>
<nav>
<ul>
<li><a href="https://build.golang.org/">Build Dashboard</a></li>
<li><a href="/dashboard">Performance Dashboard</a></li>
<li><a href="https://farmer.golang.org/builders">Builders</a></li>
</ul>
</nav>
</header>
<nav class="Dashboard-controls">
<form autocomplete="off" action="./">
<ul>
<li>
<div class="Dashboard-search-benchmark">
<input id="benchmark-input" type="text" name="benchmark" placeholder="Type benchmark name..." />
</div>
<div class="Dashboard-search-unit">
<input id="unit-input" type="text" name="unit" placeholder="Unit (optional)" />
</div>
<input type="submit" />
</li>
<li><a href="?benchmark=all">All benchmarks</a></li>
<li><a href="?benchmark=regressions">Regressions first</a></li>
<span class="left-separator"></span>
<li>
Repository:
<select id="repository-select" name="repository">
<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" />
</div>
End (UTC): <input id="end-input" type="datetime-local" name="end" />
<input type="submit">
</li>
</ul>
</form>
</nav>
<div class="Dashboard-documentation">
<p>
Each graph displays benchmark results relative to its baseline
commit, which is the latest stable release (e.g., 1.18.3) at
the time of testing. The 95% confidence interval is displayed
in light gray. On hover, the graph displays the benchmarked
commit at that point (click to view full commit).
</p>
<p>
Note that some commits are not tested, so there could be
multiple commits (not shown) between two points on the graph.
See the <code>linux-amd64-perf</code> column on the
<a href="https://build.golang.org/?repo=golang.org%2fx%2fbenchmarks">x/benchmarks build dashboard</a>
for tested ("ok") / untested (empty) commits.
</p>
<p>
The 'branch' selection above is the Go branch that benchmarking
ran against on <a href=https://build.golang.org>the build dashboard</a>,
not the subrepo branch.
</p>
<p>
Using the 'unit' search box above leads to a page that shows much
more detail about each individual point in the selected time range.
Another way of reaching this page is to click the unit name that acts
as each graph's title, i.e. "sec/op".
</p>
</div>
<grid id="dashboard">
<grid id="loading" class="Dashboard-section Dashboard-section-expand">
<h2 class="Dashboard-title" id="loading">Loading...</h2>
</grid>
</grid>
<script>
// minViewPercentDelta represents the minimum range we're willing to
// let the Y axis have for charts and X axis for ranges in the per-unit
// view. In both cases the unit of this value is a delta percent, hence
// the name.
//
// This constant exists because allowing the axis' range to be arbitrarily
// small produces results that are really noisy visually, even though they
// represent virtually no change. For instance, a relatively low-noise
// series of benchmark results, with a min delta of -0.05% and and +0.05%
// might appear really noisy if we "zoom" in too far, when in actuality
// the amount of noise is incredibly low.
const minViewDeltaPercent = 0.025;
function removeLoadingMessage() {
let loading = document.getElementById("loading");
loading.parentNode.removeChild(loading);
}
function addCharts(benchmarks, repository) {
let dashboard = document.getElementById("dashboard");
removeLoadingMessage();
let prevName = "";
let grid = null;
for (const b in benchmarks) {
const bench = benchmarks[b];
if (bench.Name != prevName) {
prevName = bench.Name;
let section = document.createElement("grid");
section.classList.add("Dashboard-section");
dashboard.appendChild(section);
let link = document.createElement("a");
link.href = "?benchmark=" + bench.Name;
link.textContent = bench.Name;
let title = document.createElement("h2");
title.classList.add("Dashboard-title");
title.appendChild(link);
section.appendChild(title);
grid = document.createElement("grid");
grid.classList.add("Dashboard-grid");
section.appendChild(grid);
}
let item = document.createElement("div");
item.classList.add("Dashboard-grid-item");
if (bench.Regression) {
const p = document.createElement("p");
p.classList.add("Dashboard-regression-description");
const r = bench.Regression;
if (r.DeltaIndex >= 0) {
// Generate some text indicating the regression.
const rd = bench.Values[r.DeltaIndex];
const regression = (Math.abs(r.Change)*100).toFixed(2);
const shortCommit = rd.CommitHash.slice(0, 7);
let diffText = "regression";
let isRegression = true;
if (r.Change < 0) {
// Note: r.Change already has its sign flipped for HigherIsBetter.
// Positive always means regression, negative always means improvement.
diffText = "improvement";
isRegression = false;
}
p.innerHTML = `${regression}% ${diffText}, ${(r.Delta*100).toFixed(2)}%-point change at <a href="?benchmark=${bench.Name}&unit=${bench.Unit}#${commitToId(rd.CommitHash)}">${shortCommit}</a>.`;
// Add a link to file a bug.
if (isRegression) {
const title = `affected/package: ${regression}% regression in ${bench.Name} ${bench.Unit} at ${shortCommit}`;
const body = `Discovered a regression in ${bench.Unit} of ${regression}% for benchmark ${bench.Name} at ${shortCommit}.\n\n<ADD MORE DETAILS>.`
let query = `?title=${encodeURIComponent(title)}&body=${encodeURIComponent(body)}&labels=Performance`;
p.innerHTML += ` <a href="https://github.com/golang/go/issues/new${query}">File an issue</a>.`;
} else {
// Include a grinning emoji if it's an improvement.
p.innerHTML += ` <span style="font-style: normal;">&#128513;</span>`;
}
} else {
p.textContext = `Not ranked because ${r.IgnoredBecause}.`;
}
item.appendChild(p);
}
item.appendChild(BandChart(bench.Values, {
benchmark: bench.Name,
unit: bench.Unit,
repository: repository,
minViewDeltaPercent: minViewDeltaPercent,
higherIsBetter: bench.HigherIsBetter,
}));
grid.appendChild(item);
}
}
function commitToId(commitHash) {
return "commit" + commitHash;
}
function idToCommit(id) {
if (id && id.startsWith("commit")) {
return id.slice(6);
}
return null;
}
function addTable(bench, unit, repository) {
let commitSelected = idToCommit(window.location.hash.slice(1));
let dashboard = document.getElementById("dashboard");
removeLoadingMessage();
let section = document.createElement("grid");
section.classList.add("Dashboard-section");
section.classList.add("Dashboard-section-expand");
dashboard.appendChild(section);
let link = document.createElement("a");
link.href = "?benchmark=" + bench.Name + "&unit=" + unit;
link.textContent = bench.Name + " (" + unit + ")";
let title = document.createElement("h2");
title.classList.add("Dashboard-title");
title.appendChild(link);
section.appendChild(title);
const table = document.createElement("table");
table.classList.add("Dashboard-table");
section.appendChild(table);
const createCell = function(text, header) {
let elemType = "td";
if (header) {
elemType = "th";
}
const elem = document.createElement(elemType);
elem.textContent = text;
return elem;
}
// Create the header.
const header = document.createElement("tr");
header.appendChild(createCell("Date", true));
header.appendChild(createCell("Commit", true));
header.appendChild(createCell("Delta", true));
table.appendChild(header);
// Find the min and max.
let min = bench.Values[0].Low;
let max = bench.Values[0].High;
for (let i = 1; i < bench.Values.length; i++) {
if (bench.Values[i].Low < min) {
min = bench.Values[i].Low;
}
if (bench.Values[i].High > max) {
max = bench.Values[i].High;
}
}
// Clamp for presentation.
if (min < 0 && min > -minViewDeltaPercent) {
min = -minViewDeltaPercent
}
if (max > 0 && max < minViewDeltaPercent) {
max = minViewDeltaPercent
}
if (max-min < 2*minViewDeltaPercent) {
const amt = (2*minViewDeltaPercent-(max-min))/2;
max += amt;
min -= amt;
}
// Iterate backwards, showing the most recent first.
for (let i = bench.Values.length-1; i >= 0; i--) {
const v = bench.Values[i];
// Create a row per value.
const row = document.createElement("tr");
if (commitSelected && commitSelected === v.CommitHash) {
row.classList.add("selected");
}
// Commit date.
row.appendChild(createCell(Intl.DateTimeFormat([], {
dateStyle: "long",
timeStyle: "short",
}).format(v.CommitDate), false));
// Commit hash.
const commitHash = createCell("", false);
const commitLink = document.createElement("a");
commitLink.href = "https://go.googlesource.com/" + repository + "/+show/" + v.CommitHash;
commitLink.id = commitToId(v.CommitHash);
commitLink.textContent = v.CommitHash.slice(0, 7);
commitHash.appendChild(commitLink);
commitHash.classList.add("Dashboard-table-commit")
row.appendChild(commitHash);
// Range visualization.
const range = createCell("", false);
range.appendChild(Range(v.Low, v.Center, v.High, min, max, 640, 48, bench.Unit, bench.HigherIsBetter));
range.classList.add("Dashboard-table-range")
row.appendChild(range);
table.appendChild(row);
}
if (commitSelected) {
// Now that we've generated anchors for every commit, let's scroll to the
// right one. The browser won't do this automatically because the anchors
// don't exist when the page is loaded.
const anchor = document.querySelector("#" + commitToId(commitSelected));
window.scrollTo({
top: anchor.getBoundingClientRect().top + window.pageYOffset - 20,
})
}
}
function failure(name, response) {
let dashboard = document.getElementById("dashboard");
removeLoadingMessage();
let title = document.createElement("h2");
title.classList.add("Dashboard-title");
title.textContent = "Benchmark \"" + name + "\" not found.";
dashboard.appendChild(title);
let message = document.createElement("p");
message.classList.add("Dashboard-documentation");
response.text().then(function(error) {
message.textContent = error;
});
dashboard.appendChild(message);
}
// Fill search boxes from query params.
function prefillSearch() {
let params = new URLSearchParams(window.location.search);
let benchmark = params.get('benchmark');
if (benchmark) {
let input = document.getElementById('benchmark-input');
input.value = benchmark;
}
let unit = params.get('unit');
if (unit) {
let input = document.getElementById('unit-input');
input.value = unit;
}
let repository = params.get('repository');
if (repository) {
let select = document.getElementById('repository-select');
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');
input.value = days;
}
let end = params.get('end');
let input = document.getElementById('end-input');
if (end) {
input.value = end;
} else {
// Default to now.
let now = new Date();
// toISOString always uses UTC, then we just chop off the end
// of string to get the datetime-local format of
// 2000-12-31T15:00.
//
// Yes, this is really the suggested approach...
input.value = now.toISOString().slice(0, 16);
}
}
prefillSearch()
// Grab the repository so we can plumb it into UI elements.
// prefillSearch set this up for us just now.
const repository = document.getElementById('repository-select').value;
// Fetch content.
let benchmark = (new URLSearchParams(window.location.search)).get('benchmark');
let unit = (new URLSearchParams(window.location.search)).get('unit');
let dataURL = './data.json' + window.location.search; // Pass through all URL params.
fetch(dataURL)
.then(response => {
if (!response.ok) {
failure(benchmark, response);
throw new Error("Data fetch failed");
}
return response.json();
})
.then(function(benchmarks) {
// Convert CommitDate to a proper date.
benchmarks.forEach(function(b) {
b.Values.forEach(function(v) {
v.CommitDate = new Date(v.CommitDate);
});
});
// If we have an explicit unit, then there should be just one result.
if (unit) {
if (benchmarks.length !== 1) {
failure(benchmark, "got more that one benchmark when a unit was specified");
throw new Error("Data fetch failed");
}
addTable(benchmarks[0], unit, repository);
} else {
addCharts(benchmarks, repository);
}
});
</script>
</body>
</html>