SysDeck/plugins/sysdeck-benchmark/benchmark.js

135 lines
6.0 KiB
JavaScript
Executable File

/*
* SysDeck - Benchmark Panel (v0.0.11)
* Author: Jeremy Anderson (https://dcos.net)
*
* System benchmarking via sysbench. Attributes the cockpit-benchmark
* plugin (MIT, ealier) whose standalone plugin lives at
* https://github.com/ealier/cockpit-benchmark.
*
* This panel invokes sysbench via cockpit.spawn as a separate process —
* no cockpit-benchmark or sysbench code is bundled. sysbench is GPL-2.0
* licensed; the suite (MIT) and sysbench remain independent programs.
*
* Shows available tests, run controls, and results history.
*
* v0.1.4 SECURITY: raw sysbench output (result.raw — stdout+stderr,
* includes echoed test names) and spawn error messages are untrusted
* tool output; both previously landed in innerHTML unescaped (0.3.0
* audit). Now escaped; the numeric fields are formatted to strings
* before escaping too.
*/
function escapeHtml(s) {
return String(s == null ? '' : s)
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
export async function mount(panel, { bridge, EventBus }) {
panel.innerHTML = renderSkeleton();
let tests = [];
try {
tests = await bridge.benchmark.listTests();
} catch (err) {
panel.innerHTML = renderError(err);
return;
}
panel.innerHTML = `
<header>
<h2 class="suite-panel-title">System Benchmark</h2>
<p class="suite-panel-subtitle">sysbench + cockpit-benchmark integration</p>
<span class="suite-badge info">MIT · ealier</span>
</header>
<div class="suite-card">
<div class="suite-card-header">
<h3 class="suite-card-title">Available Tests</h3>
<button class="suite-btn suite-btn-ghost" id="btn-benchmark-refresh">↻ Refresh</button>
</div>
<table class="suite-table">
<thead><tr><th>Test</th><th>Tool</th><th>Actions</th></tr></thead>
<tbody>
${tests.map((t) => `<tr>
<td class="suite-table-mono">${escapeHtml(t.name)}</td>
<td>${escapeHtml(t.tool)}</td>
<td><button class="suite-btn suite-btn-primary" data-test="${escapeHtml(t.name)}">Run</button></td>
</tr>`).join('') || '<tr><td colspan="3" class="suite-muted">No benchmarks available. Install sysbench.</td></tr>'}
</tbody>
</table>
</div>
<div class="suite-card">
<div class="suite-card-header">
<h3 class="suite-card-title">Quick Benchmarks</h3>
</div>
<div class="suite-row">
<button class="suite-btn suite-btn-primary" id="btn-run-cpu">CPU Benchmark</button>
<button class="suite-btn suite-btn-primary" id="btn-run-memory">Memory Benchmark</button>
<button class="suite-btn suite-btn-primary" id="btn-run-io">File I/O Benchmark</button>
</div>
<div id="benchmark-results" class="suite-muted">No results yet. Click a benchmark to run.</div>
</div>
`;
const resultsDiv = panel.querySelector('#benchmark-results');
const runBench = async (name, resultFn) => {
resultsDiv.innerHTML = `<em>Running ${escapeHtml(name)} benchmark...</em>`;
try {
const result = await resultFn();
EventBus.emit('benchmark.run', { test: name, result });
resultsDiv.innerHTML = `
<h4>${escapeHtml(name)} Results</h4>
<p>Events/sec: <strong>${escapeHtml(result.events_per_sec ?? 'N/A')}</strong></p>
<p>Avg latency: <strong>${result.latency_ms != null ? escapeHtml(result.latency_ms.toFixed(2)) + ' ms' : 'N/A'}</strong></p>
<details><summary>Raw output</summary><pre>${escapeHtml(result.raw || 'N/A')}</pre></details>
`;
} catch (err) {
resultsDiv.innerHTML = `<span class="suite-badge warn">Error: ${escapeHtml(err.message || err)}</span>`;
}
};
panel.querySelector('#btn-run-cpu')?.addEventListener('click', () => runBench('CPU', () => bridge.benchmark.runCpu()));
panel.querySelector('#btn-run-memory')?.addEventListener('click', () => runBench('Memory', () => bridge.benchmark.runMemory()));
panel.querySelector('#btn-run-io')?.addEventListener('click', () => runBench('File I/O', () => bridge.benchmark.runIo()));
panel.querySelectorAll('[data-test]').forEach((btn) => {
btn.addEventListener('click', async () => {
const test = btn.dataset.test;
btn.disabled = true;
try {
const result = await bridge.benchmark.runTest(test);
EventBus.emit('benchmark.run', { test });
resultsDiv.innerHTML = `<h4>${escapeHtml(test)} completed</h4><pre>${escapeHtml(result)}</pre>`;
} catch (err) {
resultsDiv.innerHTML = `<span class="suite-badge warn">${escapeHtml(err.message || err)}</span>`;
}
btn.disabled = false;
});
});
panel.querySelector('#btn-benchmark-refresh')?.addEventListener('click', () => {
mount(panel, { bridge, EventBus });
});
}
function renderSkeleton() {
return `<div class="suite-skeleton">
<div class="suite-skeleton-line w-1/3"></div>
<div class="suite-skeleton-line w-2/3"></div>
<div class="suite-skeleton-line w-1/2"></div>
</div>`;
}
function renderError(err) {
return `<div class="suite-card">
<h3 class="suite-card-title">Benchmark tools unavailable</h3>
<p class="suite-card-body suite-muted">${escapeHtml(err.message || err)}. Install sysbench for system benchmarking.</p>
<p class="suite-muted">cockpit-benchmark (MIT) by ealier — <a href="https://github.com/ealier/cockpit-benchmark">https://github.com/ealier/cockpit-benchmark</a></p>
<p class="suite-muted">sysbench (GPL-2.0) — <a href="https://github.com/akopytov/sysbench">https://github.com/akopytov/sysbench</a></p>
</div>`;
}