Skip to content

Commit 6313293

Browse files
committed
[feature] Redesign JMH benchmark dashboards: grouping, search, dark mode
Replace the default github-action-benchmark template (one flat unsorted chart per benchmark+params, no navigation, light-only) with a grouped dashboard: charts are combined per class+method with one series per JMH parameter set, sections are collapsible with a sidebar table of contents, a search box filters by class/method/param, and each chart shows a percent-change badge against the previous run (direction-aware: ops/s-style units treat higher as better, */op units treat lower as better). Also adds dark mode (system-aware plus manual toggle), a log-scale y-axis for charts whose series span more than ~15x, and upgrades Chart.js 2.9.2 -> 4.4.4. exist-core-jmh drops from 168 individual charts to 29 grouped ones; exist-indexes-jmh from 60 to 20. The default template is only written by the action when index.html is missing, so these files are safe to hand-maintain going forward. Also restyles the root landing page to match.
1 parent b3e374c commit 6313293

5 files changed

Lines changed: 854 additions & 572 deletions

File tree

dev/bench/core/index.html

Lines changed: 62 additions & 280 deletions
Original file line numberDiff line numberDiff line change
@@ -1,281 +1,63 @@
1-
<!DOCTYPE html>
2-
<html>
3-
<head>
4-
<meta charset="utf-8" />
5-
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=yes" />
6-
<style>
7-
html {
8-
font-family: BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
9-
-webkit-font-smoothing: antialiased;
10-
background-color: #fff;
11-
font-size: 16px;
12-
}
13-
body {
14-
color: #4a4a4a;
15-
margin: 8px;
16-
font-size: 1em;
17-
font-weight: 400;
18-
}
19-
header {
20-
margin-bottom: 8px;
21-
display: flex;
22-
flex-direction: column;
23-
}
24-
main {
25-
width: 100%;
26-
display: flex;
27-
flex-direction: column;
28-
}
29-
a {
30-
color: #3273dc;
31-
cursor: pointer;
32-
text-decoration: none;
33-
}
34-
a:hover {
35-
color: #000;
36-
}
37-
button {
38-
color: #fff;
39-
background-color: #3298dc;
40-
border-color: transparent;
41-
cursor: pointer;
42-
text-align: center;
43-
}
44-
button:hover {
45-
background-color: #2793da;
46-
flex: none;
47-
}
48-
.spacer {
49-
flex: auto;
50-
}
51-
.small {
52-
font-size: 0.75rem;
53-
}
54-
footer {
55-
margin-top: 16px;
56-
display: flex;
57-
align-items: center;
58-
}
59-
.header-label {
60-
margin-right: 4px;
61-
}
62-
.benchmark-set {
63-
margin: 8px 0;
64-
width: 100%;
65-
display: flex;
66-
flex-direction: column;
67-
}
68-
.benchmark-title {
69-
font-size: 3rem;
70-
font-weight: 600;
71-
word-break: break-word;
72-
text-align: center;
73-
}
74-
.benchmark-graphs {
75-
display: flex;
76-
flex-direction: row;
77-
justify-content: space-around;
78-
align-items: center;
79-
flex-wrap: wrap;
80-
width: 100%;
81-
}
82-
.benchmark-chart {
83-
max-width: 1000px;
84-
}
85-
</style>
86-
<title>Benchmarks</title>
87-
</head>
88-
89-
<body>
90-
<header id="header">
91-
<div class="header-item">
92-
<strong class="header-label">Last Update:</strong>
93-
<span id="last-update"></span>
94-
</div>
95-
<div class="header-item">
96-
<strong class="header-label">Repository:</strong>
97-
<a id="repository-link" rel="noopener"></a>
98-
</div>
99-
</header>
100-
<main id="main"></main>
101-
<footer>
102-
<button id="dl-button">Download data as JSON</button>
103-
<div class="spacer"></div>
104-
<div class="small">Powered by <a rel="noopener" href="https://github.com/marketplace/actions/continuous-benchmark">github-action-benchmark</a></div>
105-
</footer>
106-
107-
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.2/dist/Chart.min.js"></script>
108-
<script src="data.js"></script>
109-
<script id="main-script">
110-
'use strict';
111-
(function() {
112-
// Colors from https://github.com/github/linguist/blob/master/lib/linguist/languages.yml
113-
const toolColors = {
114-
cargo: '#dea584',
115-
go: '#00add8',
116-
benchmarkjs: '#f1e05a',
117-
benchmarkluau: '#000080',
118-
pytest: '#3572a5',
119-
googlecpp: '#f34b7d',
120-
catch2: '#f34b7d',
121-
julia: '#a270ba',
122-
jmh: '#b07219',
123-
benchmarkdotnet: '#178600',
124-
customBiggerIsBetter: '#38ff38',
125-
customSmallerIsBetter: '#ff3838',
126-
_: '#333333'
127-
};
128-
129-
function init() {
130-
function collectBenchesPerTestCase(entries) {
131-
const map = new Map();
132-
for (const entry of entries) {
133-
const {commit, date, tool, benches} = entry;
134-
for (const bench of benches) {
135-
const result = { commit, date, tool, bench };
136-
const arr = map.get(bench.name);
137-
if (arr === undefined) {
138-
map.set(bench.name, [result]);
139-
} else {
140-
arr.push(result);
141-
}
142-
}
143-
}
144-
return map;
145-
}
146-
147-
const data = window.BENCHMARK_DATA;
148-
149-
// Render header
150-
document.getElementById('last-update').textContent = new Date(data.lastUpdate).toString();
151-
const repoLink = document.getElementById('repository-link');
152-
repoLink.href = data.repoUrl;
153-
repoLink.textContent = data.repoUrl;
154-
155-
// Render footer
156-
document.getElementById('dl-button').onclick = () => {
157-
const dataUrl = 'data:,' + JSON.stringify(data, null, 2);
158-
const a = document.createElement('a');
159-
a.href = dataUrl;
160-
a.download = 'benchmark_data.json';
161-
a.click();
162-
};
163-
164-
// Prepare data points for charts
165-
return Object.keys(data.entries).map(name => ({
166-
name,
167-
dataSet: collectBenchesPerTestCase(data.entries[name]),
168-
}));
169-
}
170-
171-
function renderAllChars(dataSets) {
172-
173-
function renderGraph(parent, name, dataset) {
174-
const canvas = document.createElement('canvas');
175-
canvas.className = 'benchmark-chart';
176-
parent.appendChild(canvas);
177-
178-
const color = toolColors[dataset.length > 0 ? dataset[0].tool : '_'];
179-
const data = {
180-
labels: dataset.map(d => d.commit.id.slice(0, 7)),
181-
datasets: [
182-
{
183-
label: name,
184-
data: dataset.map(d => d.bench.value),
185-
borderColor: color,
186-
backgroundColor: color + '60', // Add alpha for #rrggbbaa
187-
}
188-
],
189-
};
190-
const options = {
191-
scales: {
192-
xAxes: [
193-
{
194-
scaleLabel: {
195-
display: true,
196-
labelString: 'commit',
197-
},
198-
}
199-
],
200-
yAxes: [
201-
{
202-
scaleLabel: {
203-
display: true,
204-
labelString: dataset.length > 0 ? dataset[0].bench.unit : '',
205-
},
206-
ticks: {
207-
beginAtZero: true,
208-
}
209-
}
210-
],
211-
},
212-
tooltips: {
213-
callbacks: {
214-
afterTitle: items => {
215-
const {index} = items[0];
216-
const data = dataset[index];
217-
return '\n' + data.commit.message + '\n\n' + data.commit.timestamp + ' committed by @' + data.commit.committer.username + '\n';
218-
},
219-
label: item => {
220-
let label = item.value;
221-
const { range, unit } = dataset[item.index].bench;
222-
label += ' ' + unit;
223-
if (range) {
224-
label += ' (' + range + ')';
225-
}
226-
return label;
227-
},
228-
afterLabel: item => {
229-
const { extra } = dataset[item.index].bench;
230-
return extra ? '\n' + extra : '';
231-
}
232-
}
233-
},
234-
onClick: (_mouseEvent, activeElems) => {
235-
if (activeElems.length === 0) {
236-
return;
237-
}
238-
// XXX: Undocumented. How can we know the index?
239-
const index = activeElems[0]._index;
240-
const url = dataset[index].commit.url;
241-
window.open(url, '_blank');
242-
},
243-
};
244-
245-
new Chart(canvas, {
246-
type: 'line',
247-
data,
248-
options,
249-
});
250-
}
251-
252-
function renderBenchSet(name, benchSet, main) {
253-
const setElem = document.createElement('div');
254-
setElem.className = 'benchmark-set';
255-
main.appendChild(setElem);
256-
257-
const nameElem = document.createElement('h1');
258-
nameElem.className = 'benchmark-title';
259-
nameElem.textContent = name;
260-
setElem.appendChild(nameElem);
261-
262-
const graphsElem = document.createElement('div');
263-
graphsElem.className = 'benchmark-graphs';
264-
setElem.appendChild(graphsElem);
265-
266-
for (const [benchName, benches] of benchSet.entries()) {
267-
renderGraph(graphsElem, benchName, benches)
268-
}
269-
}
270-
271-
const main = document.getElementById('main');
272-
for (const {name, dataSet} of dataSets) {
273-
renderBenchSet(name, dataSet, main);
274-
}
275-
}
276-
277-
renderAllChars(init()); // Start
278-
})();
279-
</script>
280-
</body>
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=yes" />
6+
<title>exist-core-jmh Benchmarks — eXist-db</title>
7+
<link rel="stylesheet" href="../style.css" />
8+
</head>
9+
<body>
10+
<div class="layout" style="flex-direction: column; width: 100%;">
11+
<nav class="crumbs">
12+
<a href="../../">eXist-db Benchmarks</a>
13+
<span class="sep">/</span>
14+
<span>exist-core-jmh</span>
15+
<span class="sep">·</span>
16+
<a href="../indexes/">exist-indexes-jmh</a>
17+
<span class="sep">·</span>
18+
<a href="https://github.com/eXist-db/exist" rel="noopener">GitHub</a>
19+
</nav>
20+
<div class="layout" style="flex: 1;">
21+
<aside class="sidebar" id="sidebar"></aside>
22+
<main class="main">
23+
<div class="controlbar">
24+
<h1>exist-core-jmh</h1>
25+
<span class="meta" id="last-update"></span>
26+
<div class="spacer"></div>
27+
<input id="search" type="search" placeholder="Filter benchmarks…" aria-label="Filter benchmarks" />
28+
<button class="btn" id="theme-toggle" type="button"></button>
29+
<button class="btn" id="dl-button" type="button">Download JSON</button>
30+
</div>
31+
<div id="main"></div>
32+
<footer class="page-footer">
33+
<span>Data: <a id="repository-link" rel="noopener"></a></span>
34+
<span>Powered by <a rel="noopener" href="https://github.com/marketplace/actions/continuous-benchmark">github-action-benchmark</a> · rendered by a custom dashboard, see <a href="https://github.com/eXist-db/exist/blob/develop/.github/workflows/ci-benchmarks.yml">ci-benchmarks.yml</a></span>
35+
</footer>
36+
</main>
37+
</div>
38+
</div>
39+
40+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js"></script>
41+
<script src="../dashboard.js"></script>
42+
<script src="data.js"></script>
43+
<script>
44+
(function () {
45+
const data = window.BENCHMARK_DATA;
46+
document.getElementById('last-update').textContent = 'Last update: ' + new Date(data.lastUpdate).toLocaleString();
47+
const repoLink = document.getElementById('repository-link');
48+
repoLink.href = data.repoUrl;
49+
repoLink.textContent = data.repoUrl;
50+
document.getElementById('dl-button').onclick = () => {
51+
const a = document.createElement('a');
52+
a.href = 'data:,' + encodeURIComponent(JSON.stringify(data, null, 2));
53+
a.download = 'exist-core-jmh_benchmark_data.json';
54+
a.click();
55+
};
56+
57+
BenchDashboard.wireTheme(document.getElementById('theme-toggle'));
58+
BenchDashboard.render(document.getElementById('main'), document.getElementById('sidebar'), data, 'exist-core-jmh');
59+
BenchDashboard.wireSearch(document.getElementById('search'));
60+
})();
61+
</script>
62+
</body>
28163
</html>

0 commit comments

Comments
 (0)