Files
Testdrive/lab.php
T

953 lines
39 KiB
PHP

<?php
/**
* Web & PC Studio - Webserver Benchmark & Stress Suite
* Diepgaande diagnostische suite voor CPU, NVMe Disk I/O, RAM en HTTP Concurrency.
*/
require_once __DIR__ . '/auth.php';
require_auth();
// Helper functies voor benchmarks
function benchmarkCPU() {
$start = microtime(true);
$iterations = 80000;
$val = 1.0;
for ($i = 1; $i <= $iterations; $i++) {
$val = sqrt($val + $i) * sin($i);
}
$hash = 'webenpcstudio';
for ($i = 0; $i < 40000; $i++) {
$hash = hash('sha256', $hash . $i);
}
$duration = (microtime(true) - $start) * 1000;
$ops = round(($iterations + 40000) / (max(0.001, $duration) / 1000));
return [
'status' => 'success',
'duration_ms' => round($duration, 2),
'ops_per_sec' => number_format($ops, 0, ',', '.'),
'raw_ops' => $ops
];
}
function benchmarkMemory() {
$start = microtime(true);
$startMem = memory_get_usage();
$arr = [];
for ($i = 0; $i < 50000; $i++) {
$arr['key_' . $i] = [
'id' => $i,
'val' => $i * 1.5,
'studio' => 'WebenPCStudio'
];
}
ksort($arr);
$serialized = serialize($arr);
$unserialized = unserialize($serialized);
unset($arr, $serialized, $unserialized);
$duration = (microtime(true) - $start) * 1000;
$peakMem = (memory_get_peak_usage() - $startMem) / (1024 * 1024);
$throughput = round(150000 / (max(0.001, $duration) / 1000));
return [
'status' => 'success',
'duration_ms' => round($duration, 2),
'peak_mem_mb' => round(max(0.1, $peakMem), 2),
'ops_per_sec' => number_format($throughput, 0, ',', '.'),
'raw_ops' => $throughput
];
}
function benchmarkDiskIO() {
$tempDir = sys_get_temp_dir();
$tempFile = tempnam($tempDir, 'wpc_bench_');
if (!$tempFile) {
return ['status' => 'error', 'message' => 'Kan tijdelijk bestand niet aanmaken'];
}
$chunkSize = 64 * 1024;
$chunks = 48; // ~3 MB
$dummyData = str_repeat('WPCStudio2026_TEST_STRING_ABCD!', 2048);
$writeStart = microtime(true);
$fp = fopen($tempFile, 'wb');
if (!$fp) {
@unlink($tempFile);
return ['status' => 'error', 'message' => 'Kan bestand niet openen'];
}
for ($i = 0; $i < $chunks; $i++) {
fwrite($fp, $dummyData);
}
fflush($fp);
fclose($fp);
$writeTime = max(0.0005, microtime(true) - $writeStart);
$readStart = microtime(true);
$fp = fopen($tempFile, 'rb');
if ($fp) {
while (!feof($fp)) {
fread($fp, $chunkSize);
}
fclose($fp);
}
$readTime = max(0.0005, microtime(true) - $readStart);
$totalMB = ($chunks * $chunkSize) / (1024 * 1024);
$writeSpeed = round($totalMB / $writeTime, 1);
$readSpeed = round($totalMB / $readTime, 1);
@unlink($tempFile);
return [
'status' => 'success',
'file_size_mb' => round($totalMB, 2),
'write_speed_mb_s' => $writeSpeed,
'read_speed_mb_s' => $readSpeed,
'write_time_ms' => round($writeTime * 1000, 1),
'read_time_ms' => round($readTime * 1000, 1)
];
}
function benchmarkStringJSON() {
$start = microtime(true);
$data = [];
for ($i = 0; $i < 15000; $i++) {
$data[] = [
'id' => $i,
'email' => "user{$i}@webenpcstudio.nl",
'tags' => ['git', 'php', 'speed', 'server'],
'active' => ($i % 2 === 0)
];
}
$json = json_encode($data);
$decoded = json_decode($json, true);
preg_match_all('/[a-zA-Z0-9._%+-]+@webenpcstudio\.nl/', $json, $matches);
$duration = (microtime(true) - $start) * 1000;
$throughput = round(15000 / (max(0.001, $duration) / 1000));
return [
'status' => 'success',
'duration_ms' => round($duration, 2),
'records_per_sec' => number_format($throughput, 0, ',', '.'),
'json_size_kb' => round(strlen($json) / 1024, 1),
'raw_ops' => $throughput
];
}
function benchmarkDatabase() {
if (!extension_loaded('pdo_sqlite')) {
return [
'status' => 'skipped',
'message' => 'PDO SQLite niet actief',
'tps' => 0,
'duration_ms' => 0
];
}
try {
$start = microtime(true);
$pdo = new PDO('sqlite::memory:');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->exec('CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT, val REAL)');
$pdo->beginTransaction();
$stmt = $pdo->prepare('INSERT INTO test (name, val) VALUES (?, ?)');
for ($i = 0; $i < 2500; $i++) {
$stmt->execute(['item_' . $i, mt_rand(1, 1000) / 10]);
}
$pdo->commit();
$stmt2 = $pdo->query('SELECT AVG(val), COUNT(*) FROM test WHERE val > 500');
$stmt2->fetch();
$duration = (microtime(true) - $start) * 1000;
$tps = round(2500 / (max(0.001, $duration) / 1000));
return [
'status' => 'success',
'duration_ms' => round($duration, 2),
'tps' => number_format($tps, 0, ',', '.'),
'records' => 2500,
'raw_tps' => $tps
];
} catch (Exception $e) {
return [
'status' => 'error',
'message' => $e->getMessage()
];
}
}
function getOpcacheInfo() {
if (function_exists('opcache_get_status') && ($status = @opcache_get_status(false))) {
$mem = $status['memory_usage'] ?? [];
return [
'enabled' => true,
'used_mb' => round(($mem['used_memory'] ?? 0) / (1024 * 1024), 1),
'free_mb' => round(($mem['free_memory'] ?? 0) / (1024 * 1024), 1),
'hit_rate' => round($status['opcache_statistics']['opcache_hit_rate'] ?? 0, 1) . '%',
'jit' => !empty($status['jit']['enabled']) ? 'Actief' : 'Inactief'
];
}
return [
'enabled' => false,
'message' => 'Niet geactiveerd'
];
}
// Routing voor AJAX verzoeken
if (isset($_GET['action'])) {
header('Content-Type: application/json; charset=utf-8');
if ($_GET['action'] === 'ping') {
echo json_encode([
'status' => 'pong',
'timestamp' => microtime(true),
'server_time' => date('H:i:s.u')
]);
exit;
}
if ($_GET['action'] === 'bench_cpu') { echo json_encode(benchmarkCPU()); exit; }
if ($_GET['action'] === 'bench_memory') { echo json_encode(benchmarkMemory()); exit; }
if ($_GET['action'] === 'bench_disk') { echo json_encode(benchmarkDiskIO()); exit; }
if ($_GET['action'] === 'bench_string') { echo json_encode(benchmarkStringJSON()); exit; }
if ($_GET['action'] === 'bench_db') { echo json_encode(benchmarkDatabase()); exit; }
if ($_GET['action'] === 'bench_all') {
$cpu = benchmarkCPU();
$mem = benchmarkMemory();
$disk = benchmarkDiskIO();
$str = benchmarkStringJSON();
$db = benchmarkDatabase();
$opcache = getOpcacheInfo();
$score = 0;
$score += min(25, round((($cpu['raw_ops'] ?? 0) / 150000) * 25));
$score += min(20, round((($mem['raw_ops'] ?? 0) / 200000) * 20));
$score += min(25, round((($disk['write_speed_mb_s'] ?? 0) / 300) * 25));
$score += min(15, round((($str['raw_ops'] ?? 0) / 35000) * 15));
if ($db['status'] === 'success') {
$score += min(15, round((($db['raw_tps'] ?? 0) / 20000) * 15));
} else {
$score += ($opcache['enabled'] ? 12 : 8);
}
$finalScore = max(35, min(100, $score));
if ($finalScore >= 90) {
$grade = 'A+'; $rating = 'Superieur (Enterprise Snelheid)'; $color = '#34d399';
} elseif ($finalScore >= 80) {
$grade = 'A'; $rating = 'Uitstekend (Snelle Server)'; $color = '#38bdf8';
} elseif ($finalScore >= 70) {
$grade = 'B'; $rating = 'Goed (Solide Webhosting)'; $color = '#fbbf24';
} else {
$grade = 'C'; $rating = 'Voldoende (Basis)'; $color = '#f87171';
}
echo json_encode([
'status' => 'success',
'score' => $finalScore,
'grade' => $grade,
'rating' => $rating,
'color' => $color,
'cpu' => $cpu,
'memory' => $mem,
'disk' => $disk,
'string_json' => $str,
'db' => $db,
'opcache' => $opcache,
'timestamp' => date('d-m-Y H:i:s')
]);
exit;
}
echo json_encode(['status' => 'error', 'message' => 'Onbekend verzoek']);
exit;
}
date_default_timezone_set('Europe/Amsterdam');
$phpVersion = PHP_VERSION;
$serverSoftware = $_SERVER['SERVER_SOFTWARE'] ?? 'Apache / Nginx';
?>
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web &amp; PC Studio - Webserver Benchmark</title>
<link rel="stylesheet" href="style.css">
<script src="theme.js"></script>
<style>
.bench-master {
background: linear-gradient(135deg, rgba(15, 23, 42, 0.9), rgba(11, 15, 25, 0.95));
border: 1px solid var(--border-accent);
border-radius: var(--radius-xl);
padding: 28px 32px;
margin-bottom: 28px;
box-shadow: 0 20px 40px -15px rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
flex-wrap: wrap;
}
[data-theme="light"] .bench-master {
background: linear-gradient(135deg, rgba(255, 255, 255, 0.95), rgba(241, 245, 249, 0.95));
box-shadow: 0 15px 35px -10px rgba(0, 0, 0, 0.08);
}
.score-box {
display: flex;
align-items: center;
gap: 18px;
background: rgba(0, 0, 0, 0.4);
border: 1px solid var(--border-subtle);
padding: 16px 22px;
border-radius: var(--radius-lg);
}
[data-theme="light"] .score-box {
background: rgba(241, 245, 249, 0.85);
}
.score-circle {
width: 76px;
height: 76px;
border-radius: 50%;
border: 4px solid #38bdf8;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-shadow: 0 0 20px rgba(56, 189, 248, 0.25);
font-weight: 800;
}
.progress-bar-wrap {
width: 100%;
height: 6px;
background: rgba(255, 255, 255, 0.08);
border-radius: 999px;
margin-top: 18px;
overflow: hidden;
display: none;
}
.progress-bar-fill {
height: 100%;
width: 0%;
background: linear-gradient(to right, var(--primary), var(--accent));
transition: width 0.3s ease;
}
.conc-pill {
background: rgba(15, 23, 42, 0.9);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 6px;
padding: 4px 8px;
font-size: 0.75rem;
font-family: var(--font-mono);
}
.conc-pill.success { border-color: #10b981; color: #34d399; }
.conc-pill.slow { border-color: #f59e0b; color: #fbbf24; }
.conc-pill.error { border-color: #ef4444; color: #f87171; }
@media print {
.navbar, .btn, .nav-status, .sound-toggle, footer { display: none !important; }
body { background: #fff !important; color: #000 !important; }
.card, .bench-master { box-shadow: none !important; border: 1px solid #ccc !important; }
}
</style>
</head>
<body>
<div class="main-wrapper">
<!-- Bovenste Menu Balk -->
<nav class="navbar">
<a href="index.php" class="nav-brand">
<div class="nav-logo">⚡</div>
<div class="nav-title-group">
<span class="nav-title">Web &amp; PC Studio</span>
<span class="nav-tagline">Testdrive Environment</span>
</div>
</a>
<div class="nav-menu">
<a href="index.php" class="nav-item">📊 Dashboard</a>
<a href="lab.php" class="nav-item active">🚀 Benchmark</a>
<a href="tools.php" class="nav-item">🛠️ Dev Tools</a>
</div>
<div class="nav-status">
<button class="btn btn-secondary btn-sm theme-btn" onclick="toggleTheme()">🌙 Thema</button>
<button class="btn btn-secondary btn-sm" id="sound-btn" onclick="toggleAudio()" title="Schakel geluid in/uit">
<span id="sound-icon">🔈</span> Geluid: <span id="sound-status">Uit</span>
</button>
<a href="logout.php" class="btn btn-secondary btn-sm" style="color: #f87171;" title="Veilig uitloggen">🚪 Uitloggen</a>
</div>
</nav>
<!-- Hero -->
<header class="hero">
<h1>Webserver Performance Suite</h1>
<p>
Meet CPU rekenkracht, NVMe Disk I/O schrijf/leessnelheid, RAM geheugendoorvoer en gelijktijdige request-capaciteit van de server.
</p>
</header>
<!-- Master Benchmark Banner -->
<div class="bench-master">
<div style="flex: 1; min-width: 280px;">
<h2 style="font-size: 1.3rem; color: var(--text-main); margin-bottom: 6px;">⚡ Volledige Server Benchmark</h2>
<p style="font-size: 0.88rem; color: var(--text-muted); margin-bottom: 18px;">
Voert in één geautomatiseerde cyclus alle 5 hardware- &amp; softwaretests uit op <strong>server.webenpcstudio.nl</strong>.
</p>
<div style="display: flex; gap: 8px; align-items: center; flex-wrap: wrap;">
<button class="btn btn-primary" id="btn-run-all" onclick="runFullBenchmark()">
▶ Start Volledige Benchmark
</button>
<button class="btn btn-secondary btn-sm" onclick="copyReport()">
📋 Kopieer
</button>
<button class="btn btn-secondary btn-sm" onclick="downloadJSONReport()">
📥 Download JSON
</button>
<button class="btn btn-secondary btn-sm" onclick="printReport()">
🖨️ PDF / Print
</button>
</div>
<div class="progress-bar-wrap" id="p-bar-wrap">
<div class="progress-bar-fill" id="p-bar-fill"></div>
</div>
</div>
<!-- Score Display -->
<div class="score-box">
<div class="score-circle" id="score-circle">
<span style="font-size: 1.45rem; color: var(--text-main); line-height: 1;" id="score-num">--</span>
<span style="font-size: 0.65rem; color: var(--text-muted);">/ 100</span>
</div>
<div>
<div style="font-size: 1.35rem; font-weight: 800; color: #38bdf8;" id="score-grade">Klaar</div>
<div style="font-size: 0.8rem; color: var(--text-muted);" id="score-label">Klik start voor meting</div>
</div>
</div>
</div>
<!-- Historische Vergelijking & Trend Grafiek -->
<div class="card" style="margin-bottom: 24px;">
<div class="card-header">
<div>
<div class="card-title"><span>📈</span> Historische Benchmark Trend (Opeenvolgende Runs)</div>
<p class="card-desc" style="margin-bottom: 0;">Vergelijk eerdere benchmark scores over tijd om server-stabiliteit te monitoren.</p>
</div>
<button class="btn btn-secondary btn-sm" onclick="clearHistory()">🗑️ Wis Historie</button>
</div>
<div class="chart-wrap">
<canvas id="history-canvas" style="width: 100%; height: 100%; display: block;"></canvas>
</div>
<div id="history-legend" style="font-size: 0.78rem; color: var(--text-muted); display: flex; justify-content: space-between;">
<span>Eerste meting</span>
<span id="history-count">0 eerdere benchmarks opgeslagen</span>
<span>Laatste meting</span>
</div>
</div>
<!-- 6 Test Cards -->
<div class="grid-3">
<!-- CPU -->
<div class="card">
<div class="card-header">
<div class="card-title"><span>🚀</span> CPU &amp; Math</div>
<span class="badge-pill">80k Math + 40k SHA</span>
</div>
<p class="card-desc">Rekenkundige algoritmes en cryptografische hashing cycli.</p>
<div class="metric-grid">
<div class="metric-box">
<div class="metric-label">Bewerkingen / s</div>
<div class="metric-val" id="cpu-ops">--</div>
</div>
<div class="metric-box">
<div class="metric-label">Executietijd</div>
<div class="metric-val" id="cpu-time">-- ms</div>
</div>
</div>
<button class="btn btn-secondary btn-sm" onclick="runSingleTest('bench_cpu')">▶ Test Alleen CPU</button>
</div>
<!-- Disk I/O -->
<div class="card">
<div class="card-header">
<div class="card-title"><span>💾</span> Disk I/O Snelheid</div>
<span class="badge-pill">3 MB Chunk R/W</span>
</div>
<p class="card-desc">Fysieke schrijf- en leessnelheid naar server temp opslag (NVMe/SSD).</p>
<div class="metric-grid">
<div class="metric-box">
<div class="metric-label">Schrijfsnelheid</div>
<div class="metric-val" id="disk-write">-- MB/s</div>
</div>
<div class="metric-box">
<div class="metric-label">Leessnelheid</div>
<div class="metric-val" id="disk-read">-- MB/s</div>
</div>
</div>
<button class="btn btn-secondary btn-sm" onclick="runSingleTest('bench_disk')">▶ Test Alleen Disk</button>
</div>
<!-- RAM -->
<div class="card">
<div class="card-header">
<div class="card-title"><span>🧠</span> RAM Geheugen</div>
<span class="badge-pill">50k Array Nodes</span>
</div>
<p class="card-desc">Allocatie, sorteren en serialisatie in PHP werkgeheugen.</p>
<div class="metric-grid">
<div class="metric-box">
<div class="metric-label">Doorvoer / s</div>
<div class="metric-val" id="mem-ops">--</div>
</div>
<div class="metric-box">
<div class="metric-label">Piekgeheugen</div>
<div class="metric-val" id="mem-peak">-- MB</div>
</div>
</div>
<button class="btn btn-secondary btn-sm" onclick="runSingleTest('bench_memory')">▶ Test Alleen RAM</button>
</div>
<!-- JSON & String -->
<div class="card">
<div class="card-header">
<div class="card-title"><span>📦</span> String &amp; JSON</div>
<span class="badge-pill">15k JSON Records</span>
</div>
<p class="card-desc">JSON encoding, decoding en RegEx patroonvergelijking.</p>
<div class="metric-grid">
<div class="metric-box">
<div class="metric-label">Records / s</div>
<div class="metric-val" id="str-ops">--</div>
</div>
<div class="metric-box">
<div class="metric-label">Payload</div>
<div class="metric-val" id="str-size">-- KB</div>
</div>
</div>
<button class="btn btn-secondary btn-sm" onclick="runSingleTest('bench_string')">▶ Test Alleen JSON</button>
</div>
<!-- Database -->
<div class="card">
<div class="card-header">
<div class="card-title"><span>🗄️</span> Database RAM I/O</div>
<span class="badge-pill">SQLite :memory:</span>
</div>
<p class="card-desc">2.500 transacties met inserts, indexeringen en aggregaties.</p>
<div class="metric-grid">
<div class="metric-box">
<div class="metric-label">Transacties / s</div>
<div class="metric-val" id="db-tps">--</div>
</div>
<div class="metric-box">
<div class="metric-label">Query Tijd</div>
<div class="metric-val" id="db-time">-- ms</div>
</div>
</div>
<button class="btn btn-secondary btn-sm" onclick="runSingleTest('bench_db')">▶ Test Alleen Database</button>
</div>
<!-- Server Specs -->
<div class="card">
<div class="card-header">
<div class="card-title"><span>⚙️</span> Server Info</div>
<span class="badge-pill">PHP <?= htmlspecialchars($phpVersion); ?></span>
</div>
<table class="data-table">
<tr>
<td class="label">Webserver:</td>
<td class="val" style="max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"><?= htmlspecialchars($serverSoftware); ?></td>
</tr>
<tr>
<td class="label">Memory Limit:</td>
<td class="val"><?= ini_get('memory_limit'); ?></td>
</tr>
<tr>
<td class="label">Execution Time:</td>
<td class="val"><?= ini_get('max_execution_time'); ?>s</td>
</tr>
<tr>
<td class="label">OPcache:</td>
<td class="val" id="spec-opcache">Actief</td>
</tr>
</table>
</div>
</div>
<!-- Concurrency Stress Test -->
<div class="card" style="margin-bottom: 24px;">
<div class="card-header">
<div>
<div class="card-title"><span>🌐</span> HTTP Concurrency &amp; RPS Burst Test</div>
<p class="card-desc" style="margin-bottom: 0;">Meet de afhandeling van gelijktijdige aanroepen onder piekdruk.</p>
</div>
<div style="display: flex; gap: 8px; align-items: center;">
<button class="btn btn-secondary btn-sm" onclick="setConc(5, this)">5 Calls</button>
<button class="btn btn-secondary btn-sm" style="border-color: var(--primary);" onclick="setConc(12, this)">12 Calls</button>
<button class="btn btn-secondary btn-sm" onclick="setConc(24, this)">24 Calls</button>
<button class="btn btn-primary btn-sm" id="btn-conc" onclick="runConcurrencyTest()">⚡ Start Burst</button>
</div>
</div>
<div id="conc-visualizer" style="display: flex; gap: 6px; flex-wrap: wrap; margin: 12px 0; min-height: 32px; align-items: center;">
<span style="font-size: 0.8rem; color: var(--text-muted);">Klik op "Start Burst" om gelijktijdige calls af te vuren.</span>
</div>
<div class="metric-grid">
<div class="metric-box">
<div class="metric-label">Requests / Seconde</div>
<div class="metric-val" id="conc-rps">-- RPS</div>
</div>
<div class="metric-box">
<div class="metric-label">Totale Duur</div>
<div class="metric-val" id="conc-total-time">-- ms</div>
</div>
<div class="metric-box">
<div class="metric-label">Snelste Call</div>
<div class="metric-val" id="conc-min">-- ms</div>
</div>
<div class="metric-box">
<div class="metric-label">Gemiddelde Latency</div>
<div class="metric-val" id="conc-avg">-- ms</div>
</div>
</div>
</div>
<!-- Footer -->
<footer class="footer">
&copy; <?= date('Y'); ?> <a href="index.php">Web &amp; PC Studio</a> &bull; Gekoppeld aan git.webenpcstudio.nl &bull; Alle systemen operationeel.
</footer>
</div>
<!-- Script Logic -->
<script>
// Web Audio Synthesizer
let audioCtx = null;
let soundEnabled = false;
function toggleAudio() {
if (!audioCtx) {
const AudioClass = window.AudioContext || window.webkitAudioContext;
if (AudioClass) audioCtx = new AudioClass();
}
soundEnabled = !soundEnabled;
document.getElementById('sound-status').textContent = soundEnabled ? 'Aan' : 'Uit';
document.getElementById('sound-icon').textContent = soundEnabled ? '🔊' : '🔈';
if (soundEnabled) playTone(600, 'sine', 0.1);
}
function playTone(freq, type = 'sine', duration = 0.1, gain = 0.08) {
if (!soundEnabled || !audioCtx) return;
try {
if (audioCtx.state === 'suspended') audioCtx.resume();
const osc = audioCtx.createOscillator();
const g = audioCtx.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
g.gain.setValueAtTime(gain, audioCtx.currentTime);
g.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + duration);
osc.connect(g);
g.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + duration);
} catch (e) {}
}
let lastReport = null;
// Historie beheer
function getHistory() {
try {
return JSON.parse(localStorage.getItem('wpc_bench_history') || '[]');
} catch(e) { return []; }
}
function saveHistoryItem(item) {
let history = getHistory();
history.push(item);
if (history.length > 20) history.shift();
localStorage.setItem('wpc_bench_history', JSON.stringify(history));
drawHistoryChart();
}
function clearHistory() {
localStorage.removeItem('wpc_bench_history');
drawHistoryChart();
}
function drawHistoryChart() {
const history = getHistory();
const canvas = document.getElementById('history-canvas');
const ctx = canvas.getContext('2d');
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
ctx.clearRect(0, 0, canvas.width, canvas.height);
document.getElementById('history-count').textContent = `${history.length} benchmark runs opgeslagen`;
if (history.length === 0) {
ctx.fillStyle = '#94a3b8';
ctx.font = '12px var(--font-sans)';
ctx.textAlign = 'center';
ctx.fillText('Nog geen eerdere benchmarks opgeslagen. Voer een test uit!', canvas.width / 2, canvas.height / 2);
return;
}
const stepX = canvas.width / Math.max(1, history.length - 1);
// Gradient fill
const grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
grad.addColorStop(0, 'rgba(14, 165, 233, 0.4)');
grad.addColorStop(1, 'rgba(14, 165, 233, 0.0)');
ctx.beginPath();
history.forEach((h, i) => {
const x = history.length === 1 ? canvas.width / 2 : i * stepX;
const y = canvas.height - ((h.score / 100) * (canvas.height - 30)) - 15;
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
});
if (history.length > 1) {
ctx.lineTo((history.length - 1) * stepX, canvas.height);
ctx.lineTo(0, canvas.height);
ctx.fillStyle = grad;
ctx.fill();
}
// Stroke
ctx.beginPath();
history.forEach((h, i) => {
const x = history.length === 1 ? canvas.width / 2 : i * stepX;
const y = canvas.height - ((h.score / 100) * (canvas.height - 30)) - 15;
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
});
ctx.strokeStyle = '#0ea5e9';
ctx.lineWidth = 2.5;
ctx.stroke();
// Dots & labels
history.forEach((h, i) => {
const x = history.length === 1 ? canvas.width / 2 : i * stepX;
const y = canvas.height - ((h.score / 100) * (canvas.height - 30)) - 15;
ctx.beginPath();
ctx.arc(x, y, 4, 0, Math.PI * 2);
ctx.fillStyle = '#fff';
ctx.fill();
ctx.fillStyle = '#38bdf8';
ctx.font = '10px var(--font-mono)';
ctx.textAlign = 'center';
ctx.fillText(h.score, x, y - 8);
});
}
async function runFullBenchmark() {
const btn = document.getElementById('btn-run-all');
const pWrap = document.getElementById('p-bar-wrap');
const pFill = document.getElementById('p-bar-fill');
btn.disabled = true;
btn.textContent = 'Meting loopt...';
pWrap.style.display = 'block';
pFill.style.width = '30%';
playTone(440, 'sine', 0.1);
try {
pFill.style.width = '60%';
const res = await fetch('lab.php?action=bench_all&t=' + Date.now());
const data = await res.json();
lastReport = data;
pFill.style.width = '100%';
document.getElementById('score-num').textContent = data.score;
document.getElementById('score-grade').textContent = 'Graad ' + data.grade;
document.getElementById('score-grade').style.color = data.color;
document.getElementById('score-label').textContent = data.rating;
document.getElementById('score-circle').style.borderColor = data.color;
document.getElementById('cpu-ops').textContent = data.cpu.ops_per_sec;
document.getElementById('cpu-time').textContent = data.cpu.duration_ms + ' ms';
document.getElementById('disk-write').textContent = data.disk.write_speed_mb_s + ' MB/s';
document.getElementById('disk-read').textContent = data.disk.read_speed_mb_s + ' MB/s';
document.getElementById('mem-ops').textContent = data.memory.ops_per_sec;
document.getElementById('mem-peak').textContent = data.memory.peak_mem_mb + ' MB';
document.getElementById('str-ops').textContent = data.string_json.records_per_sec;
document.getElementById('str-size').textContent = data.string_json.json_size_kb + ' KB';
if (data.db.status === 'success') {
document.getElementById('db-tps').textContent = data.db.tps;
document.getElementById('db-time').textContent = data.db.duration_ms + ' ms';
}
// Opslaan in historie
saveHistoryItem({
score: data.score,
grade: data.grade,
time: data.timestamp
});
playTone(880, 'triangle', 0.2);
} catch (err) {
alert('Benchmark fout: ' + err.message);
} finally {
btn.disabled = false;
btn.textContent = '▶ Start Volledige Benchmark';
setTimeout(() => pWrap.style.display = 'none', 800);
}
}
async function runSingleTest(action) {
playTone(520, 'sine', 0.08);
try {
const res = await fetch('lab.php?action=' + action + '&t=' + Date.now());
const data = await res.json();
if (action === 'bench_cpu') {
document.getElementById('cpu-ops').textContent = data.ops_per_sec;
document.getElementById('cpu-time').textContent = data.duration_ms + ' ms';
} else if (action === 'bench_disk') {
document.getElementById('disk-write').textContent = data.write_speed_mb_s + ' MB/s';
document.getElementById('disk-read').textContent = data.read_speed_mb_s + ' MB/s';
} else if (action === 'bench_memory') {
document.getElementById('mem-ops').textContent = data.ops_per_sec;
document.getElementById('mem-peak').textContent = data.peak_mem_mb + ' MB';
} else if (action === 'bench_string') {
document.getElementById('str-ops').textContent = data.records_per_sec;
document.getElementById('str-size').textContent = data.json_size_kb + ' KB';
} else if (action === 'bench_db') {
if (data.status === 'success') {
document.getElementById('db-tps').textContent = data.tps;
document.getElementById('db-time').textContent = data.duration_ms + ' ms';
}
}
playTone(740, 'triangle', 0.1);
} catch (e) {
alert('Test mislukt: ' + e.message);
}
}
let concCount = 12;
function setConc(num, btn) {
concCount = num;
btn.parentElement.querySelectorAll('button').forEach(b => {
if (b.id !== 'btn-conc') {
b.style.borderColor = 'rgba(255,255,255,0.1)';
b.style.color = 'var(--text-main)';
}
});
btn.style.borderColor = 'var(--primary)';
btn.style.color = '#fff';
}
async function runConcurrencyTest() {
const btn = document.getElementById('btn-conc');
const vis = document.getElementById('conc-visualizer');
btn.disabled = true;
vis.innerHTML = '';
const startTime = performance.now();
const requests = [];
for (let i = 0; i < concCount; i++) {
const reqStart = performance.now();
const req = fetch('lab.php?action=ping&call=' + i + '&t=' + Math.random())
.then(res => res.json())
.then(() => ({ ok: true, latency: Math.round(performance.now() - reqStart) }))
.catch(() => ({ ok: false, latency: Math.round(performance.now() - reqStart) }));
requests.push(req);
}
playTone(480, 'sine', 0.08);
const results = await Promise.all(requests);
const totalDuration = Math.round(performance.now() - startTime);
let latencies = [];
results.forEach((r, idx) => {
latencies.push(r.latency);
const pill = document.createElement('div');
pill.className = 'conc-pill ' + (r.ok ? (r.latency < 80 ? 'success' : 'slow') : 'error');
pill.textContent = `#${idx + 1}: ${r.latency}ms`;
vis.appendChild(pill);
});
const minLat = Math.min(...latencies);
const avgLat = Math.round(latencies.reduce((a, b) => a + b, 0) / latencies.length);
const rps = Math.round((concCount / (totalDuration / 1000)));
document.getElementById('conc-rps').textContent = rps + ' RPS';
document.getElementById('conc-total-time').textContent = totalDuration + ' ms';
document.getElementById('conc-min').textContent = minLat + ' ms';
document.getElementById('conc-avg').textContent = avgLat + ' ms';
playTone(720, 'triangle', 0.15);
btn.disabled = false;
}
function copyReport() {
let report = "=== WEB & PC STUDIO - WEBSERVER BENCHMARK RAPPORT ===\n" +
`Domein: server.webenpcstudio.nl\n` +
`Datum: ${new Date().toLocaleString('nl-NL')}\n\n`;
if (lastReport) {
report += `Score: ${lastReport.score}/100 (Graad ${lastReport.grade} - ${lastReport.rating})\n` +
`- CPU Math & Hash: ${lastReport.cpu.ops_per_sec} ops/sec (${lastReport.cpu.duration_ms} ms)\n` +
`- NVMe/Disk Write: ${lastReport.disk.write_speed_mb_s} MB/s\n` +
`- NVMe/Disk Read: ${lastReport.disk.read_speed_mb_s} MB/s\n` +
`- RAM Doorvoer: ${lastReport.memory.ops_per_sec} ops/sec\n` +
`- JSON & String: ${lastReport.string_json.records_per_sec} rec/sec\n` +
`- Database TPS: ${lastReport.db.tps || 'N/A'}\n`;
} else {
report += "Draai eerst een benchmark voor gedetailleerde resultaten.\n";
}
navigator.clipboard.writeText(report).then(() => {
alert('Benchmark rapport gekopieerd naar het klembord!');
playTone(660, 'sine', 0.1);
});
}
function downloadJSONReport() {
if (!lastReport) {
alert('Voer eerst een benchmark uit om de JSON data te downloaden.');
return;
}
const blob = new Blob([JSON.stringify(lastReport, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `benchmark_webenpcstudio_${Date.now()}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
function printReport() {
window.print();
}
window.addEventListener('resize', drawHistoryChart);
document.addEventListener('DOMContentLoaded', drawHistoryChart);
</script>
</body>
</html>