| <!-- |
| 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" style="background: antiquewhite;"> |
| <div> |
| A banner isn't actively displayed at this time. This serves as a placeholder |
| that can be used if a banner does need to be displayed. |
| </div> |
| </header --> |
| <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> |
| <span class="left-separator"></span> |
| <li><a class="alt-nav-button" href="?repository=tools&benchmark=all&branch=latest-release">x/tools Dashboard</a> |
| <li><a class="alt-nav-button" href="?benchmark=regressions&platform=linux/amd64">linux/amd64 Regressions</a> |
| <li><a class="alt-nav-button" href="?benchmark=regressions&platform=linux/arm64">linux/arm64 Regressions</a> |
| <li><a class="alt-nav-button" href="?repository=tools&benchmark=regressions&branch=latest-release">x/tools Regressions</a> |
| </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="button" class="alt-nav-button" onclick="setBenchmarkInput('all')" value="All"> |
| <input type="button" class="alt-nav-button" onclick="setBenchmarkInput('regressions')" value="Regressions first"> |
| </li> |
| <span class="left-separator"></span> |
| <li> |
| Repository: |
| <select id="repository-select" name="repository"> |
| <option>go</option> |
| <option>tools</option> |
| </select> |
| Go branch: |
| <select id="branch-select" name="branch"></select> |
| Platform: |
| <select id="platform-select" name="platform"> |
| <option>all</option> |
| <option>linux/amd64</option> |
| <option>linux/arm64</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>gotip-linux-amd64_debian12-perf_vs_release</code> |
| column on the |
| <a href="https://ci.chromium.org/p/golang/g/go-gotip/console">build dashboard</a>. |
| Individually tested commits have their own green box. A single |
| box that covers multiple commits indicates that only the latest |
| commit in the range was tested. |
| </p> |
| <p> |
| The 'Go branch' selection above is the Go branch that benchmarking |
| ran against on |
| <a href="https://ci.chromium.org/ui/p/golang">the build dashboard</a>. |
| </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> |
| <p> |
| Further documentation is available on the |
| <a href=https://go.dev/wiki/PerformanceMonitoring>wiki</a>. |
| </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 +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; |
| |
| // HTML to inject when there's no data found. |
| const noDataHTML = ` |
| <grid class="Dashboard-section Dashboard-section-expand"> |
| <h2 class="Dashboard-title">No data</h2> |
| <p class="Dashboard-documentation"> |
| Found no data. Consider trying a different time range. Note also that subrepositories like tools have |
| no data against the Go master branch, in which case, try picking a release branch. |
| </p> |
| </grid> |
| ` |
| |
| function removeLoadingMessage() { |
| let loading = document.getElementById("loading"); |
| loading.parentNode.removeChild(loading); |
| } |
| |
| function setBenchmarkInput(benchmark) { |
| let input = document.getElementById('benchmark-input'); |
| input.value = benchmark; |
| } |
| |
| function addCharts(benchmarks, repository, goBranch, commits) { |
| let dashboard = document.getElementById("dashboard"); |
| |
| removeLoadingMessage(); |
| |
| let prevName = ""; |
| let prevPlatform = ""; |
| let grid = null; |
| let addedChart = false; |
| for (const b in benchmarks) { |
| const bench = benchmarks[b]; |
| |
| if (bench.Name != prevName || bench.Platform != prevPlatform) { |
| prevName = bench.Name; |
| prevPlatform = bench.Platform; |
| |
| let section = document.createElement("grid"); |
| section.classList.add("Dashboard-section"); |
| dashboard.appendChild(section); |
| |
| let link = document.createElement("a"); |
| link.href = "?benchmark=" + bench.Name + "&platform=" + bench.Platform + "&repository=" + repository + "&branch=" + goBranch; |
| link.textContent = bench.Name + " (" + bench.Platform + ")"; |
| |
| 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}&platform=${bench.Platform}#${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} on ${bench.Platform} 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, |
| platform: bench.Platform, |
| repository: repository, |
| minViewDeltaPercent: minViewDeltaPercent, |
| higherIsBetter: bench.HigherIsBetter, |
| history: commits, |
| })); |
| grid.appendChild(item); |
| addedChart = true; |
| } |
| if (!addedChart) { |
| dashboard.innerHTML = noDataHTML; |
| } |
| } |
| |
| function commitToId(commitHash) { |
| return "commit" + commitHash; |
| } |
| |
| function idToCommit(id) { |
| if (id && id.startsWith("commit")) { |
| return id.slice(6); |
| } |
| return null; |
| } |
| |
| function addTable(bench, unit, platform, repository, goBranch, commits) { |
| 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 + "&platform=" + platform + "&repository=" + repository + "&branch=" + goBranch; |
| link.textContent = bench.Name + " (" + unit + ", " + platform + ")"; |
| |
| 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; |
| } |
| |
| const createCommitCell = function(commit, repository) { |
| const commitHash = createCell("", false); |
| const commitLink = document.createElement("a"); |
| commitLink.href = "https://go.googlesource.com/" + repository + "/+show/" + commit; |
| commitLink.textContent = commit.slice(0, 7); |
| commitHash.appendChild(commitLink); |
| commitHash.classList.add("Dashboard-table-commit"); |
| return commitHash; |
| } |
| |
| // Create the header. |
| const header = document.createElement("tr"); |
| header.appendChild(createCell("Date", true)); |
| header.appendChild(createCell("Experiment commit", true)); |
| header.appendChild(createCell("Delta", true)); |
| header.appendChild(createCell("Baseline commit", true)); |
| header.appendChild(createCell("x/benchmarks commit", 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; |
| } |
| |
| // Create a map of hashes to values. |
| let valuesByCommit = new Map(); |
| for (let i = 0; i < bench.Values.length; i++) { |
| valuesByCommit.set(bench.Values[i].CommitHash, bench.Values[i]) |
| } |
| |
| // Iterate backwards, showing the most recent first. |
| for (let i = commits.length-1; i >= 0; i--) { |
| const c = commits[i]; |
| |
| // Create a row per value. |
| const row = document.createElement("tr"); |
| if (commitSelected && commitSelected === c.Hash) { |
| row.classList.add("selected"); |
| } |
| |
| // Commit date. |
| row.appendChild(createCell(Intl.DateTimeFormat([], { |
| dateStyle: "long", |
| timeStyle: "short", |
| }).format(c.Date), false)); |
| |
| // Commit hash. |
| const commitCell = createCommitCell(c.Hash, repository); |
| commitCell.id = commitToId(c.Hash); |
| row.appendChild(commitCell); |
| |
| if (valuesByCommit.has(c.Hash)) { |
| const v = valuesByCommit.get(c.Hash); |
| |
| // 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); |
| |
| // Baseline commit hash. |
| row.appendChild(createCommitCell(v.BaselineCommitHash, repository)); |
| |
| // Benchmarks commit hash. |
| row.appendChild(createCommitCell(v.BenchmarksCommitHash, "benchmarks")); |
| } else { |
| // Row without info. |
| const range = createCell("", false); |
| range.appendChild(NoDataRange(min, max, 640, 48)); |
| range.classList.add("Dashboard-table-range"); |
| row.appendChild(range); |
| |
| const baselineNA = createCell("N/A", false); |
| baselineNA.classList.add("Dashboard-table-commit"); |
| row.appendChild(baselineNA); |
| |
| const benchmarksNA = createCell("N/A", false); |
| benchmarksNA.classList.add("Dashboard-table-commit"); |
| row.appendChild(benchmarksNA); |
| } |
| |
| 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); |
| } |
| |
| let now = new Date(); |
| |
| // Fill search boxes from query params. |
| function prefillSearch(formFields) { |
| 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 platform = params.get('platform'); |
| if (platform) { |
| let select = document.getElementById('platform-select'); |
| select.value = platform; |
| } |
| |
| let branch = params.get('branch'); |
| if (branch) { |
| let select = document.getElementById('branch-select'); |
| if (branch === "latest-release") { |
| select.value = formFields.LatestReleaseBranch; |
| } else { |
| 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 { |
| // 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); |
| } |
| } |
| fetch('./formfields.json') |
| .then(response => { |
| if (!response.ok) { |
| throw new Error("Form fields fetch failed"); |
| } |
| return response.json(); |
| }) |
| .then(function(formFields) { |
| let select = document.getElementById('branch-select'); |
| for (const i in formFields.Branches) { |
| var opt = document.createElement('option'); |
| opt.value = formFields.Branches[i]; |
| opt.innerText = formFields.Branches[i]; |
| select.appendChild(opt); |
| } |
| var opt = document.createElement('option'); |
| opt.value = "latest-release"; |
| opt.innerText = "Latest release branch"; |
| select.appendChild(opt); |
| prefillSearch(formFields); |
| }); |
| |
| |
| // Grab the repository so we can plumb it into UI elements. |
| let repository = (new URLSearchParams(window.location.search)).get('repository'); |
| if (!repository) { |
| repository = "go"; |
| } |
| |
| // Fetch content. |
| let benchmark = (new URLSearchParams(window.location.search)).get('benchmark'); |
| let unit = (new URLSearchParams(window.location.search)).get('unit'); |
| let platform = (new URLSearchParams(window.location.search)).get('platform'); |
| let goBranch = (new URLSearchParams(window.location.search)).get('branch'); |
| 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(data) { |
| let benchmarks = data.Benchmarks; |
| let commits = data.Commits; |
| |
| // Convert CommitDate and Date to a proper dates. |
| benchmarks.forEach(function(b) { |
| b.Values.forEach(function(v) { |
| v.CommitDate = new Date(v.CommitDate); |
| }); |
| }); |
| commits.forEach(function(c) { |
| c.Date = new Date(c.Date); |
| }) |
| |
| // Figure out the date range we care about. |
| |
| // If we have an explicit unit, then there should be just one result. |
| if (unit && platform && platform != "all") { |
| 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, platform, repository, goBranch, commits); |
| } else { |
| addCharts(benchmarks, repository, goBranch, commits); |
| } |
| }); |
| </script> |
| |
| </body> |
| </html> |