| <!DOCTYPE html> |
| <html> |
| <head> |
| <meta charset="utf-8"> |
| <title>R8 perf</title> |
| </head> |
| <body> |
| <select id="benchmark-selector"></select> |
| <div> |
| <canvas id="myChart"></canvas> |
| </div> |
| <div> |
| <div style="float: left; width: 50%"> |
| <button type="button" id="show-more-left" disabled>⇐</button> |
| <button type="button" id="show-less-left">⇒</button> |
| </div> |
| <div style="float: left; text-align: right; width: 50%"> |
| <button type="button" id="show-less-right">⇐</button> |
| <button type="button" id="show-more-right" disabled>⇒</button> |
| </div> |
| </div> |
| <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script> |
| <script type="module"> |
| // Utility methods. |
| Array.prototype.any = function(predicate) { |
| for (const element of this.values()) { |
| if (predicate(element)) { |
| return true; |
| } |
| } |
| return false; |
| }; |
| Array.prototype.first = function() { |
| return this[0]; |
| }; |
| Array.prototype.avg = function() { |
| return this.reduce(function(x, y) { return x + y; }, 0) / this.length; |
| }; |
| Array.prototype.min = function() { |
| return this.reduce(function(x, y) { return x === null ? y : Math.min(x, y); }, null); |
| }; |
| Array.prototype.reverseInPlace = function() { |
| for (var i = 0; i < Math.floor(this.length / 2); i++) { |
| var temp = this[i]; |
| this[i] = this[this.length - i - 1]; |
| this[this.length - i - 1] = temp; |
| } |
| }; |
| Number.prototype.ns_to_s = function() { |
| const seconds = this/10E8; |
| const seconds_with_one_decimal = Math.round(seconds*10)/10; |
| return seconds; |
| }; |
| |
| // Import and reverse commits so that newest are last. |
| import commits from "./benchmark_data.json" with { type: "json" }; |
| commits.reverseInPlace() |
| |
| // Amend the commits with their unique index. |
| for (var i = 0; i < commits.length; i++) { |
| commits[i].index = i; |
| } |
| |
| // DOM references. |
| const benchmarkSelector = document.getElementById('benchmark-selector') |
| const canvas = document.getElementById('myChart'); |
| const showMoreLeft = document.getElementById('show-more-left'); |
| const showLessLeft = document.getElementById('show-less-left'); |
| const showLessRight = document.getElementById('show-less-right'); |
| const showMoreRight = document.getElementById('show-more-right'); |
| |
| // Initialize benchmark selector. |
| const benchmarks = new Set(); |
| for (const commit of commits.values()) { |
| for (const benchmark in commit.benchmarks) { |
| benchmarks.add(benchmark) |
| } |
| } |
| var selectedBenchmark = window.location.hash.substring(1) |
| if (!benchmarks.has(selectedBenchmark)) { |
| selectedBenchmark = benchmarks.values().next().value; |
| } |
| for (const benchmark of benchmarks.values()) { |
| const opt = document.createElement('option'); |
| opt.value = benchmark; |
| opt.innerHTML = benchmark; |
| benchmarkSelector.appendChild(opt); |
| if (benchmark == selectedBenchmark) { |
| benchmarkSelector.selectedIndex = benchmarkSelector.options.length - 1 |
| } |
| } |
| benchmarkSelector.onchange = function(event) { |
| selectedBenchmark = |
| benchmarkSelector.options[benchmarkSelector.selectedIndex].value; |
| updateChart(); |
| window.location.hash = selectedBenchmark; |
| }; |
| |
| // Chart data provider. |
| function getData(start = 0, end = commits.length) { |
| const filteredCommits = |
| commits |
| .slice(start, end); |
| //.filter( |
| // commit => |
| // selectedBenchmark in commit.benchmarks |
| // && commit.benchmarks[selectedBenchmark].results.length > 0); |
| const labels = filteredCommits.map((c, i) => i); |
| const codeSizeData = |
| filteredCommits.map( |
| (c, i) => |
| selectedBenchmark in filteredCommits[i].benchmarks |
| ? filteredCommits[i] |
| .benchmarks[selectedBenchmark] |
| .results |
| .first() |
| .code_size |
| : NaN); |
| const codeSizeScatterData = []; |
| for (const commit of filteredCommits.values()) { |
| if (!(selectedBenchmark in commit.benchmarks)) { |
| continue; |
| } |
| const codeSizes = |
| commit.benchmarks[selectedBenchmark].results.map(result => result.code_size) |
| const expectedCodeSize = codeSizes.first(); |
| if (codeSizes.any(codeSize => codeSize != expectedCodeSize)) { |
| const seen = new Set(); |
| seen.add(expectedCodeSize); |
| for (const codeSize of codeSizes.values()) { |
| if (!seen.has(codeSize)) { |
| codeSizeScatterData.push({ x: commit.index, y: codeSize }); |
| seen.add(codeSize); |
| } |
| } |
| } |
| } |
| const runtimeData = |
| filteredCommits.map( |
| (c, i) => |
| selectedBenchmark in filteredCommits[i].benchmarks |
| ? filteredCommits[i] |
| .benchmarks[selectedBenchmark] |
| .results |
| .map(result => result.runtime) |
| .min() |
| .ns_to_s() |
| : NaN); |
| const runtimeScatterData = []; |
| for (const commit of filteredCommits.values()) { |
| if (!(selectedBenchmark in commit.benchmarks)) { |
| continue; |
| } |
| const runtimes = |
| commit.benchmarks[selectedBenchmark].results.map(result => result.runtime) |
| for (const runtime of runtimes.values()) { |
| runtimeScatterData.push({ x: commit.index, y: runtime.ns_to_s() }); |
| } |
| } |
| |
| const skipped = (ctx, value) => ctx.p0.skip || ctx.p1.skip ? value : undefined; |
| return { |
| labels: labels, |
| datasets: [ |
| { |
| type: 'line', |
| label: 'Code size', |
| data: codeSizeData, |
| tension: 0.1, |
| segment: { |
| borderColor: ctx => |
| skipped(ctx, 'rgba(54, 162, 235, 0.5)'), |
| borderDash: ctx => skipped(ctx, [6, 6]), |
| }, |
| spanGaps: true |
| }, |
| { |
| type: 'scatter', |
| label: 'Nondeterminism', |
| data: codeSizeScatterData, |
| pointBackgroundColor: 'red' |
| }, |
| { |
| type: 'line', |
| label: 'Runtime', |
| data: runtimeData, |
| tension: 0.1, |
| yAxisID: 'y2', |
| segment: { |
| borderColor: ctx => |
| skipped(ctx, 'rgba(255, 160, 64, 0.5)'), |
| borderDash: ctx => skipped(ctx, [6, 6]), |
| }, |
| spanGaps: true |
| }, |
| { |
| type: 'scatter', |
| label: 'Runtime variance', |
| data: runtimeScatterData, |
| yAxisID: 'y2' |
| } |
| ], |
| }; |
| } |
| |
| // Chart options. |
| const options = { |
| onHover: (event, chartElement) => |
| event.native.target.style.cursor = |
| chartElement[0] ? 'pointer' : 'default', |
| plugins: { |
| tooltip: { |
| callbacks: { |
| title: (context) => { |
| const elementInfo = context[0]; |
| var commit; |
| if (elementInfo.dataset.type == 'line') { |
| commit = commits[elementInfo.dataIndex]; |
| } else { |
| console.assert(elementInfo.dataset.type == 'scatter'); |
| commit = commits[elementInfo.raw.x]; |
| } |
| return commit.title; |
| }, |
| footer: (context) => { |
| const elementInfo = context[0]; |
| var commit; |
| if (elementInfo.dataset.type == 'line') { |
| commit = commits[elementInfo.dataIndex]; |
| } else { |
| console.assert(elementInfo.dataset.type == 'scatter'); |
| commit = commits[elementInfo.raw.x]; |
| } |
| return `Author: ${commit.author}\n` |
| + `Submitted: ${new Date(commit.submitted * 1000).toLocaleString()}\n` |
| + `Hash: ${commit.hash}`; |
| } |
| } |
| } |
| }, |
| responsive: true, |
| scales: { |
| x: {}, |
| y: { |
| position: 'left', |
| title: { |
| display: true, |
| text: 'Code size (bytes)' |
| } |
| }, |
| y2: { |
| position: 'right', |
| title: { |
| display: true, |
| text: 'Runtime (seconds)' |
| } |
| } |
| } |
| }; |
| |
| // Create chart. |
| const myChart = new Chart(canvas, { |
| data: getData(), |
| options: options |
| }); |
| |
| // Setup click handler. |
| canvas.onclick = function (event) { |
| const points = |
| myChart.getElementsAtEventForMode( |
| event, 'nearest', { intersect: true }, true); |
| if (points.length > 0) { |
| const point = points[0]; |
| const commit = commits[point.index]; |
| window.open('https://r8.googlesource.com/r8/+/' + commit.hash, '_blank'); |
| } |
| }; |
| |
| // Setup chart navigation. |
| var left = 0; |
| var right = commits.length; |
| |
| showMoreLeft.onclick = function (event) { |
| if (left == 0) { |
| return; |
| } |
| const currentSize = right - left; |
| left = left - currentSize; |
| if (left < 0) { |
| left = 0; |
| } |
| updateChart(); |
| }; |
| |
| showLessLeft.onclick = function (event) { |
| const currentSize = right - left; |
| left = left + Math.floor(currentSize / 2); |
| if (left >= right) { |
| left = right - 1; |
| } |
| updateChart(); |
| }; |
| |
| showLessRight.onclick = function (event) { |
| if (right == 0) { |
| return; |
| } |
| const currentSize = right - left; |
| right = right - Math.floor(currentSize / 2); |
| if (right < left) { |
| right = left; |
| } |
| updateChart(); |
| }; |
| |
| showMoreRight.onclick = function (event) { |
| const currentSize = right - left; |
| right = right + currentSize; |
| if (right > commits.length) { |
| right = commits.length; |
| } |
| updateChart(); |
| }; |
| |
| function updateChart() { |
| console.assert(left <= right); |
| const newData = getData(left, right); |
| Object.assign(myChart.data, newData); |
| myChart.update(); |
| showMoreLeft.disabled = left == 0; |
| showLessLeft.disabled = left == right - 1; |
| showLessRight.disabled = left == right - 1; |
| showMoreRight.disabled = right == commits.length; |
| } |
| </script> |
| </body> |
| </html> |