Skip to content

Commit 95c3b61

Browse files
committed
vizualize
1 parent 8ec31e1 commit 95c3b61

File tree

4 files changed

+213
-1
lines changed

4 files changed

+213
-1
lines changed

db-service/bench/cqn4sql/performance-benchmarks.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict'
22

33
const cds = require('@sap/cds')
4-
const { writeDump } = require('./utils/format-bench')
4+
const { writeDump } = require('./utils/format-benchmarks')
55

66
let cqn4sql = require('../../lib/cqn4sql')
77

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<meta charset="utf-8" />
4+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
5+
<title>Perf Benchmarks by Commit</title>
6+
<style>
7+
:root { --fg:#111; --muted:#666; }
8+
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Arial; margin: 24px; color: var(--fg); }
9+
h1 { margin: 0 0 8px; font-size: 20px; }
10+
#chart { width: 100%; height: 72vh; }
11+
.meta { margin-top: 8px; color: var(--muted); font-size: 12px; }
12+
label { font-size: 12px; margin-right: 8px; color: var(--muted); }
13+
select { font-size: 12px; }
14+
</style>
15+
<h1>Perf Benchmarks (requests/second) by Commit</h1>
16+
<div class="meta">
17+
Data: cqn4sql-benchmarks.json • Commits: 2 • Metric: requests.average
18+
</div>
19+
<div style="margin:8px 0">
20+
<label for="filter">Filter benchmark:</label>
21+
<select id="filter"><option value="__all__">All</option></select>
22+
</div>
23+
<div id="chart"></div>
24+
25+
<script src="https://cdn.plot.ly/plotly-2.35.2.min.js"></script>
26+
<script>
27+
const SERIES = [{"name":"select simple","x":["8ea5c2b9","19731f02"],"y":[87328,87520]},{"name":"expand simple","x":["8ea5c2b9","19731f02"],"y":[20440,20016]},{"name":"expand recursive (depth 3)","x":["8ea5c2b9","19731f02"],"y":[7926,8123]},{"name":"exists simple","x":["8ea5c2b9","19731f02"],"y":[8144,8352]},{"name":"exists recursive (depth 3)","x":["8ea5c2b9","19731f02"],"y":[46192,46080]},{"name":"assoc2join simple","x":["8ea5c2b9","19731f02"],"y":[53792,54800]},{"name":"assoc2join recursive (depth 3)","x":["8ea5c2b9","19731f02"],"y":[42480,45888]}];
28+
const COMMITS = ["8ea5c2b9","19731f02"];
29+
const COMMIT_DATES = ["2025-08-27T15:03:45.583Z","2025-08-27T15:06:07.491Z"];
30+
31+
// Populate filter
32+
const filterEl = document.getElementById('filter');
33+
SERIES.forEach(s => {
34+
const opt = document.createElement('option');
35+
opt.value = s.name; opt.textContent = s.name;
36+
filterEl.appendChild(opt);
37+
});
38+
39+
function makeTraces(showName) {
40+
const base = {
41+
mode: 'lines+markers',
42+
connectgaps: false,
43+
x: COMMITS,
44+
customdata: COMMIT_DATES, // one per x
45+
hovertemplate:
46+
'<b>%{fullData.name}</b><br>' +
47+
'commit: %{x}<br>' +
48+
'rps: %{y:.0f}<br>' +
49+
'%{customdata|%Y-%m-%d %H:%M:%S}<extra></extra>'
50+
};
51+
const chosen = showName === '__all__'
52+
? SERIES
53+
: SERIES.filter(s => s.name === showName);
54+
return chosen.map(s => Object.assign({}, base, { name: s.name, y: s.y }));
55+
}
56+
57+
const layout = {
58+
xaxis: {
59+
title: 'Commit',
60+
type: 'category',
61+
tickangle: -45,
62+
automargin: true
63+
},
64+
yaxis: { title: 'Requests / second', rangemode: 'tozero' },
65+
hovermode: 'x unified',
66+
legend: { orientation: 'h' },
67+
margin: { l: 60, r: 20, t: 10, b: 80 }
68+
};
69+
70+
function render() {
71+
const name = filterEl.value;
72+
const traces = makeTraces(name);
73+
Plotly.newPlot('chart', traces, layout, { displayModeBar: true, responsive: true });
74+
}
75+
76+
filterEl.addEventListener('change', render);
77+
render();
78+
</script>
79+
</html>
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
#!/usr/bin/env node
2+
'use strict'
3+
4+
const fs = require('fs')
5+
const path = require('path')
6+
7+
const dumpPath = process.argv[2] || 'perf-benchmarks.json'
8+
const outPath = process.argv[3] || 'perf-benchmarks.html'
9+
const metric = process.argv[4] || 'average' // requests[metric], e.g. average | mean | p50
10+
11+
if (!fs.existsSync(dumpPath)) {
12+
console.error(`❌ Cannot find ${dumpPath}`)
13+
process.exit(1)
14+
}
15+
16+
const dump = JSON.parse(fs.readFileSync(dumpPath, 'utf8'))
17+
18+
// Normalize + sort commits by date (not shown on axis, but used for order)
19+
const entries = Object.entries(dump)
20+
.map(([commit, v]) => ({
21+
commit,
22+
dateISO: v.date,
23+
date: new Date(v.date),
24+
benchmarks: v.benchmarks || {}
25+
}))
26+
.sort((a, b) => a.date - b.date)
27+
28+
// X-axis: commit IDs (category axis)
29+
const commits = entries.map(e => e.commit)
30+
const commitDates = entries.map(e => e.dateISO)
31+
32+
// Collect all benchmark names across commits
33+
const benchNames = Array.from(
34+
entries.reduce((s, e) => {
35+
Object.keys(e.benchmarks).forEach(k => s.add(k))
36+
return s
37+
}, new Set())
38+
)
39+
40+
// Build series aligned to commits (null for missing)
41+
const series = benchNames.map(name => {
42+
const y = entries.map(e => {
43+
const req = e.benchmarks[name]
44+
if (!req) return null
45+
const v = req[metric]
46+
return typeof v === 'number' ? v : (v != null ? Number(v) : null)
47+
})
48+
return { name, x: commits, y }
49+
})
50+
51+
// HTML with Plotly (commit = x, equally spaced)
52+
const html = `<!doctype html>
53+
<html lang="en">
54+
<meta charset="utf-8" />
55+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
56+
<title>Perf Benchmarks by Commit</title>
57+
<style>
58+
:root { --fg:#111; --muted:#666; }
59+
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Arial; margin: 24px; color: var(--fg); }
60+
h1 { margin: 0 0 8px; font-size: 20px; }
61+
#chart { width: 100%; height: 72vh; }
62+
.meta { margin-top: 8px; color: var(--muted); font-size: 12px; }
63+
label { font-size: 12px; margin-right: 8px; color: var(--muted); }
64+
select { font-size: 12px; }
65+
</style>
66+
<h1>Perf Benchmarks (requests/second) by Commit</h1>
67+
<div class="meta">
68+
Data: ${path.basename(dumpPath)} • Commits: ${entries.length} • Metric: requests.${metric}
69+
</div>
70+
<div style="margin:8px 0">
71+
<label for="filter">Filter benchmark:</label>
72+
<select id="filter"><option value="__all__">All</option></select>
73+
</div>
74+
<div id="chart"></div>
75+
76+
<script src="https://cdn.plot.ly/plotly-2.35.2.min.js"></script>
77+
<script>
78+
const SERIES = ${JSON.stringify(series)};
79+
const COMMITS = ${JSON.stringify(commits)};
80+
const COMMIT_DATES = ${JSON.stringify(commitDates)};
81+
82+
// Populate filter
83+
const filterEl = document.getElementById('filter');
84+
SERIES.forEach(s => {
85+
const opt = document.createElement('option');
86+
opt.value = s.name; opt.textContent = s.name;
87+
filterEl.appendChild(opt);
88+
});
89+
90+
function makeTraces(showName) {
91+
const base = {
92+
mode: 'lines+markers',
93+
connectgaps: false,
94+
x: COMMITS,
95+
customdata: COMMIT_DATES, // one per x
96+
hovertemplate:
97+
'<b>%{fullData.name}</b><br>' +
98+
'commit: %{x}<br>' +
99+
'rps: %{y:.0f}<br>' +
100+
'%{customdata|%Y-%m-%d %H:%M:%S}<extra></extra>'
101+
};
102+
const chosen = showName === '__all__'
103+
? SERIES
104+
: SERIES.filter(s => s.name === showName);
105+
return chosen.map(s => Object.assign({}, base, { name: s.name, y: s.y }));
106+
}
107+
108+
const layout = {
109+
xaxis: {
110+
title: 'Commit',
111+
type: 'category',
112+
tickangle: -45,
113+
automargin: true
114+
},
115+
yaxis: { title: 'Requests / second', rangemode: 'tozero' },
116+
hovermode: 'x unified',
117+
legend: { orientation: 'h' },
118+
margin: { l: 60, r: 20, t: 10, b: 80 }
119+
};
120+
121+
function render() {
122+
const name = filterEl.value;
123+
const traces = makeTraces(name);
124+
Plotly.newPlot('chart', traces, layout, { displayModeBar: true, responsive: true });
125+
}
126+
127+
filterEl.addEventListener('change', render);
128+
render();
129+
</script>
130+
</html>`
131+
132+
fs.writeFileSync(outPath, html, 'utf8')
133+
console.log(`✅ Wrote \${outPath} (\${series.length} series, \${entries.length} commits) using requests.\${metric}\``)

0 commit comments

Comments
 (0)