golang.org/x/build@v0.0.0-20240506185731-218518f32b70/perf/app/dashboard/index.html (about) 1 <!-- 2 Copyright 2022 The Go Authors. All rights reserved. 3 Use of this source code is governed by a BSD-style 4 license that can be found in the LICENSE file. 5 --> 6 7 <!DOCTYPE html> 8 <html lang="en"> 9 <head> 10 <title>Go Performance Dashboard</title> 11 <link rel="icon" href="https://go.dev/favicon.ico"/> 12 <link rel="stylesheet" href="./static/style.css"/> 13 <script src="https://ajax.googleapis.com/ajax/libs/d3js/7.4.2/d3.min.js"></script> 14 <script src="./third_party/bandchart/bandchart.js"></script> 15 <script src="./static/range.js"></script> 16 </head> 17 18 <body class="Dashboard"> 19 <!-- header class="Dashboard-topbar" style="background: antiquewhite;"> 20 <div> 21 A banner isn't actively displayed at this time. This serves as a placeholder 22 that can be used if a banner does need to be displayed. 23 </div> 24 </header --> 25 <header class="Dashboard-topbar"> 26 <h1> 27 <a href="https://farmer.golang.org/">Go Build Coordinator</a> 28 </h1> 29 <nav> 30 <ul> 31 <li><a href="https://build.golang.org/">Build Dashboard</a></li> 32 <li><a href="/dashboard">Performance Dashboard</a></li> 33 <li><a href="https://farmer.golang.org/builders">Builders</a></li> 34 </ul> 35 </nav> 36 </header> 37 38 <nav class="Dashboard-controls"> 39 <form autocomplete="off" action="./"> 40 <ul> 41 <li> 42 <div class="Dashboard-search-benchmark"> 43 <input id="benchmark-input" type="text" name="benchmark" placeholder="Type benchmark name..." /> 44 </div> 45 <div class="Dashboard-search-unit"> 46 <input id="unit-input" type="text" name="unit" placeholder="Unit (optional)" /> 47 </div> 48 <input type="submit" /> 49 </li> 50 <li><a href="?benchmark=all">All benchmarks</a></li> 51 <li><a href="?benchmark=regressions">Regressions first</a></li> 52 <span class="left-separator"></span> 53 <li> 54 Repository: 55 <select id="repository-select" name="repository"> 56 <option>go</option> 57 <option>tools</option> 58 </select> 59 Go branch: 60 <select id="branch-select" name="branch"> 61 <option>master</option> 62 <option>release-branch.go1.19</option> 63 <option>release-branch.go1.20</option> 64 <option>release-branch.go1.21</option> 65 <option>release-branch.go1.22</option> 66 </select> 67 Duration (days): 68 <div class="Dashboard-duration"> 69 <input id="days-input" type="number" name="days" value="30" /> 70 </div> 71 End (UTC): <input id="end-input" type="datetime-local" name="end" /> 72 <input type="submit"> 73 </li> 74 </ul> 75 </form> 76 </nav> 77 78 <div class="Dashboard-documentation"> 79 <p> 80 Each graph displays benchmark results relative to its baseline 81 commit, which is the latest stable release (e.g., 1.18.3) at 82 the time of testing. The 95% confidence interval is displayed 83 in light gray. On hover, the graph displays the benchmarked 84 commit at that point (click to view full commit). 85 </p> 86 <p> 87 Note that some commits are not tested, so there could be 88 multiple commits (not shown) between two points on the graph. 89 See the <code>gotip-linux-amd64_debian12-perf_vs_release</code> 90 column on the 91 <a href="https://ci.chromium.org/p/golang/g/go-gotip/console">build dashboard</a>. 92 Individually tested commits have their own green box. A single 93 box that covers multiple commits indicates that only the latest 94 commit in the range was tested. 95 </p> 96 <p> 97 The 'Go branch' selection above is the Go branch that benchmarking 98 ran against on 99 <a href="https://ci.chromium.org/ui/p/golang">the build dashboard</a>. 100 </p> 101 <p> 102 Using the 'unit' search box above leads to a page that shows much 103 more detail about each individual point in the selected time range. 104 Another way of reaching this page is to click the unit name that acts 105 as each graph's title, i.e. "sec/op". 106 </p> 107 <p> 108 Further documentation is available on the 109 <a href=https://go.dev/wiki/PerformanceMonitoring>wiki</a>. 110 </p> 111 </div> 112 113 <grid id="dashboard"> 114 <grid id="loading" class="Dashboard-section Dashboard-section-expand"> 115 <h2 class="Dashboard-title" id="loading">Loading...</h2> 116 </grid> 117 </grid> 118 119 <script> 120 // minViewPercentDelta represents the minimum range we're willing to 121 // let the Y axis have for charts and X axis for ranges in the per-unit 122 // view. In both cases the unit of this value is a delta percent, hence 123 // the name. 124 // 125 // This constant exists because allowing the axis' range to be arbitrarily 126 // small produces results that are really noisy visually, even though they 127 // represent virtually no change. For instance, a relatively low-noise 128 // series of benchmark results, with a min delta of -0.05% and +0.05% 129 // might appear really noisy if we "zoom" in too far, when in actuality 130 // the amount of noise is incredibly low. 131 const minViewDeltaPercent = 0.025; 132 133 // HTML to inject when there's no data found. 134 const noDataHTML = ` 135 <grid class="Dashboard-section Dashboard-section-expand"> 136 <h2 class="Dashboard-title">No data</h2> 137 <p class="Dashboard-documentation"> 138 Found no data. Consider trying a different time range. Note also that subrepositories like tools have 139 no data against the Go master branch, in which case, try picking a release branch. 140 </p> 141 </grid> 142 ` 143 144 function removeLoadingMessage() { 145 let loading = document.getElementById("loading"); 146 loading.parentNode.removeChild(loading); 147 } 148 149 function addCharts(benchmarks, repository) { 150 let dashboard = document.getElementById("dashboard"); 151 152 removeLoadingMessage(); 153 154 let prevName = ""; 155 let grid = null; 156 let addedChart = false; 157 for (const b in benchmarks) { 158 const bench = benchmarks[b]; 159 160 if (bench.Name != prevName) { 161 prevName = bench.Name; 162 163 let section = document.createElement("grid"); 164 section.classList.add("Dashboard-section"); 165 dashboard.appendChild(section); 166 167 let link = document.createElement("a"); 168 link.href = "?benchmark=" + bench.Name; 169 link.textContent = bench.Name; 170 171 let title = document.createElement("h2"); 172 title.classList.add("Dashboard-title"); 173 title.appendChild(link); 174 section.appendChild(title); 175 176 grid = document.createElement("grid"); 177 grid.classList.add("Dashboard-grid"); 178 section.appendChild(grid); 179 } 180 181 let item = document.createElement("div"); 182 item.classList.add("Dashboard-grid-item"); 183 if (bench.Regression) { 184 const p = document.createElement("p"); 185 p.classList.add("Dashboard-regression-description"); 186 const r = bench.Regression; 187 if (r.DeltaIndex >= 0) { 188 // Generate some text indicating the regression. 189 const rd = bench.Values[r.DeltaIndex]; 190 const regression = (Math.abs(r.Change)*100).toFixed(2); 191 const shortCommit = rd.CommitHash.slice(0, 7); 192 let diffText = "regression"; 193 let isRegression = true; 194 if (r.Change < 0) { 195 // Note: r.Change already has its sign flipped for HigherIsBetter. 196 // Positive always means regression, negative always means improvement. 197 diffText = "improvement"; 198 isRegression = false; 199 } 200 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>.`; 201 202 // Add a link to file a bug. 203 if (isRegression) { 204 const title = `affected/package: ${regression}% regression in ${bench.Name} ${bench.Unit} at ${shortCommit}`; 205 const body = `Discovered a regression in ${bench.Unit} of ${regression}% for benchmark ${bench.Name} at ${shortCommit}.\n\n<ADD MORE DETAILS>.` 206 let query = `?title=${encodeURIComponent(title)}&body=${encodeURIComponent(body)}&labels=Performance`; 207 p.innerHTML += ` <a href="https://github.com/golang/go/issues/new${query}">File an issue</a>.`; 208 } else { 209 // Include a grinning emoji if it's an improvement. 210 p.innerHTML += ` <span style="font-style: normal;">😁</span>`; 211 } 212 } else { 213 p.textContext = `Not ranked because ${r.IgnoredBecause}.`; 214 } 215 item.appendChild(p); 216 } 217 item.appendChild(BandChart(bench.Values, { 218 benchmark: bench.Name, 219 unit: bench.Unit, 220 repository: repository, 221 minViewDeltaPercent: minViewDeltaPercent, 222 higherIsBetter: bench.HigherIsBetter, 223 })); 224 grid.appendChild(item); 225 addedChart = true; 226 } 227 if (!addedChart) { 228 dashboard.innerHTML = noDataHTML; 229 } 230 } 231 232 function commitToId(commitHash) { 233 return "commit" + commitHash; 234 } 235 236 function idToCommit(id) { 237 if (id && id.startsWith("commit")) { 238 return id.slice(6); 239 } 240 return null; 241 } 242 243 function addTable(bench, unit, repository) { 244 let commitSelected = idToCommit(window.location.hash.slice(1)); 245 let dashboard = document.getElementById("dashboard"); 246 247 removeLoadingMessage(); 248 249 let section = document.createElement("grid"); 250 section.classList.add("Dashboard-section"); 251 section.classList.add("Dashboard-section-expand"); 252 dashboard.appendChild(section); 253 254 let link = document.createElement("a"); 255 link.href = "?benchmark=" + bench.Name + "&unit=" + unit; 256 link.textContent = bench.Name + " (" + unit + ")"; 257 258 let title = document.createElement("h2"); 259 title.classList.add("Dashboard-title"); 260 title.appendChild(link); 261 section.appendChild(title); 262 263 const table = document.createElement("table"); 264 table.classList.add("Dashboard-table"); 265 section.appendChild(table); 266 267 const createCell = function(text, header) { 268 let elemType = "td"; 269 if (header) { 270 elemType = "th"; 271 } 272 const elem = document.createElement(elemType); 273 elem.textContent = text; 274 return elem; 275 } 276 277 const createCommitCell = function(commit, repository) { 278 const commitHash = createCell("", false); 279 const commitLink = document.createElement("a"); 280 commitLink.href = "https://go.googlesource.com/" + repository + "/+show/" + commit; 281 commitLink.textContent = commit.slice(0, 7); 282 commitHash.appendChild(commitLink); 283 commitHash.classList.add("Dashboard-table-commit"); 284 return commitHash; 285 } 286 287 // Create the header. 288 const header = document.createElement("tr"); 289 header.appendChild(createCell("Date", true)); 290 header.appendChild(createCell("Experiment commit", true)); 291 header.appendChild(createCell("Delta", true)); 292 header.appendChild(createCell("Baseline commit", true)); 293 header.appendChild(createCell("x/benchmarks commit", true)); 294 table.appendChild(header); 295 296 // Find the min and max. 297 let min = bench.Values[0].Low; 298 let max = bench.Values[0].High; 299 for (let i = 1; i < bench.Values.length; i++) { 300 if (bench.Values[i].Low < min) { 301 min = bench.Values[i].Low; 302 } 303 if (bench.Values[i].High > max) { 304 max = bench.Values[i].High; 305 } 306 } 307 308 // Clamp for presentation. 309 if (min < 0 && min > -minViewDeltaPercent) { 310 min = -minViewDeltaPercent 311 } 312 if (max > 0 && max < minViewDeltaPercent) { 313 max = minViewDeltaPercent 314 } 315 if (max-min < 2*minViewDeltaPercent) { 316 const amt = (2*minViewDeltaPercent-(max-min))/2; 317 max += amt; 318 min -= amt; 319 } 320 321 // Iterate backwards, showing the most recent first. 322 for (let i = bench.Values.length-1; i >= 0; i--) { 323 const v = bench.Values[i]; 324 325 // Create a row per value. 326 const row = document.createElement("tr"); 327 if (commitSelected && commitSelected === v.CommitHash) { 328 row.classList.add("selected"); 329 } 330 331 // Commit date. 332 row.appendChild(createCell(Intl.DateTimeFormat([], { 333 dateStyle: "long", 334 timeStyle: "short", 335 }).format(v.CommitDate), false)); 336 337 // Commit hash. 338 const commitCell = createCommitCell(v.CommitHash, repository); 339 commitCell.id = commitToId(v.CommitHash); 340 row.appendChild(commitCell); 341 342 // Range visualization. 343 const range = createCell("", false); 344 range.appendChild(Range(v.Low, v.Center, v.High, min, max, 640, 48, bench.Unit, bench.HigherIsBetter)); 345 range.classList.add("Dashboard-table-range") 346 row.appendChild(range); 347 348 // Baseline commit hash. 349 row.appendChild(createCommitCell(v.BaselineCommitHash, repository)); 350 351 // Benchmarks commit hash. 352 row.appendChild(createCommitCell(v.BenchmarksCommitHash, "benchmarks")); 353 354 table.appendChild(row); 355 } 356 357 if (commitSelected) { 358 // Now that we've generated anchors for every commit, let's scroll to the 359 // right one. The browser won't do this automatically because the anchors 360 // don't exist when the page is loaded. 361 const anchor = document.querySelector("#" + commitToId(commitSelected)); 362 window.scrollTo({ 363 top: anchor.getBoundingClientRect().top + window.pageYOffset - 20, 364 }) 365 } 366 } 367 368 function failure(name, response) { 369 let dashboard = document.getElementById("dashboard"); 370 371 removeLoadingMessage(); 372 373 let title = document.createElement("h2"); 374 title.classList.add("Dashboard-title"); 375 title.textContent = "Benchmark \"" + name + "\" not found."; 376 dashboard.appendChild(title); 377 378 let message = document.createElement("p"); 379 message.classList.add("Dashboard-documentation"); 380 response.text().then(function(error) { 381 message.textContent = error; 382 }); 383 dashboard.appendChild(message); 384 } 385 386 // Fill search boxes from query params. 387 function prefillSearch() { 388 let params = new URLSearchParams(window.location.search); 389 390 let benchmark = params.get('benchmark'); 391 if (benchmark) { 392 let input = document.getElementById('benchmark-input'); 393 input.value = benchmark; 394 } 395 396 let unit = params.get('unit'); 397 if (unit) { 398 let input = document.getElementById('unit-input'); 399 input.value = unit; 400 } 401 402 let repository = params.get('repository'); 403 if (repository) { 404 let select = document.getElementById('repository-select'); 405 select.value = repository; 406 } 407 408 let branch = params.get('branch'); 409 if (branch) { 410 let select = document.getElementById('branch-select'); 411 select.value = branch; 412 } 413 414 let days = params.get('days'); 415 if (days) { 416 let input = document.getElementById('days-input'); 417 input.value = days; 418 } 419 420 let end = params.get('end'); 421 let input = document.getElementById('end-input'); 422 if (end) { 423 input.value = end; 424 } else { 425 // Default to now. 426 let now = new Date(); 427 428 // toISOString always uses UTC, then we just chop off the end 429 // of string to get the datetime-local format of 430 // 2000-12-31T15:00. 431 // 432 // Yes, this is really the suggested approach... 433 input.value = now.toISOString().slice(0, 16); 434 } 435 } 436 prefillSearch() 437 438 // Grab the repository so we can plumb it into UI elements. 439 // prefillSearch set this up for us just now. 440 const repository = document.getElementById('repository-select').value; 441 442 // Fetch content. 443 let benchmark = (new URLSearchParams(window.location.search)).get('benchmark'); 444 let unit = (new URLSearchParams(window.location.search)).get('unit'); 445 let dataURL = './data.json' + window.location.search; // Pass through all URL params. 446 fetch(dataURL) 447 .then(response => { 448 if (!response.ok) { 449 failure(benchmark, response); 450 throw new Error("Data fetch failed"); 451 } 452 return response.json(); 453 }) 454 .then(function(benchmarks) { 455 // Convert CommitDate to a proper date. 456 benchmarks.forEach(function(b) { 457 b.Values.forEach(function(v) { 458 v.CommitDate = new Date(v.CommitDate); 459 }); 460 }); 461 462 // If we have an explicit unit, then there should be just one result. 463 if (unit) { 464 if (benchmarks.length !== 1) { 465 failure(benchmark, "got more that one benchmark when a unit was specified"); 466 throw new Error("Data fetch failed"); 467 } 468 addTable(benchmarks[0], unit, repository); 469 } else { 470 addCharts(benchmarks, repository); 471 } 472 }); 473 </script> 474 475 </body> 476 </html>