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;">&#128513;</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>