| <!-- |
| 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;">😁</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> |