/** * WalkGuide — k6 HTML Report Generator (Node.js) * Mengkonversi parsed-report.json menjadi HTML report yang rapi untuk submission. * * Usage: * node html-reporter.js [output.html] * * Output: HTML report siap lampir ke laporan final exam. */ const fs = require("fs"); const path = require("path"); const inputFile = process.argv[2]; const outputFile = process.argv[3] || "k6-results/load-test-report.html"; if (!inputFile || !fs.existsSync(inputFile)) { console.error( "Usage: node html-reporter.js [output.html]", ); process.exit(1); } const report = JSON.parse(fs.readFileSync(inputFile, "utf8")); // ── HTML Template ───────────────────────────────────────────────────────────── function badge(result) { if (!result) return 'SKIP'; if (result.includes("PASS")) return '✅ PASS'; if (result.includes("FAIL")) return '❌ FAIL'; return `${result}`; } function metricRow(label, val) { return `${label}${val || "N/A"}`; } function endpointTableRows() { const rows = []; for (const [ep, metrics] of Object.entries(report.endpoints || {})) { const dur = metrics["http_req_duration"]; if (!dur) continue; rows.push(` ${ep} ${dur.count || "—"} ${dur.avg || "—"} ${dur.p95 || "—"} ${dur.p99 || "—"} ${dur.max || "—"} `); } return rows.join(""); } function thresholdRows() { return (report.thresholdResults || []) .map( (t) => ` ${t.name} ${t.metric} ${t.threshold} ${t.actual} ${badge(t.result)} `, ) .join(""); } function wgMetricCards() { const wg = report.walkguideMetrics || {}; const cards = [ { label: "🔐 Auth", key: "authLatency", threshold: "< 800ms" }, { label: "📍 Location", key: "locationLatency", threshold: "< 300ms" }, { label: "🚧 Obstacle", key: "obstacleLatency", threshold: "< 400ms" }, { label: "🚨 SOS", key: "sosLatency", threshold: "< 200ms" }, { label: "📬 Notif", key: "notifLatency", threshold: "< 500ms" }, { label: "📊 Timeline", key: "timelineLatency", threshold: "< 1000ms" }, { label: "🔗 Pairing", key: "pairingLatency", threshold: "< 600ms" }, ]; return cards .map((c) => { const data = wg[c.key]; return `
${c.label}
${data ? data.p95 : "N/A"}
p95 | threshold ${c.threshold}
avg: ${data ? data.avg : "N/A"} | p99: ${data ? data.p99 : "N/A"}
`; }) .join(""); } const html = ` WalkGuide — k6 Load Test Report

🦺 WalkGuide — k6 Load Test Report

Spring Boot Backend Performance Benchmark — Pillar 3

Generated: ${report.generatedAt} | Input: ${path.basename(report.inputFile || "")} | Total Data Points: ${report.totalPoints}

📊 Key Metrics (Pillar 3 — 5 Required)

1. Throughput
${report.keyMetrics.throughput.value || "N/A"}
Requests per second
2. p95 Latency
${report.keyMetrics.p95Latency.value || "N/A"}
95th percentile response time
3. Error Rate
${report.keyMetrics.errorRate.value || "N/A"}
Non-2xx responses | ${report.keyMetrics.errorRate.passFail || "—"}
4. DB Query Time
${report.keyMetrics.dbQueryTime.value || "N/A"}
Estimated via write endpoint p95
5. JVM Heap
${report.keyMetrics.jvmHeap.value || "N/A"}
Spring Actuator jvm.memory.used

🏃 WalkGuide Endpoint Latency Breakdown

${wgMetricCards()}

✅ Threshold Evaluation

${thresholdRows()}
CheckMetricThresholdActualResult

📋 Per-Endpoint Latency (ms)

${endpointTableRows()}
EndpointRequestsAvgp95p99Max
`; // ── Write ───────────────────────────────────────────────────────────────────── const outDir = path.dirname(outputFile); if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true }); fs.writeFileSync(outputFile, html); console.log(`✅ HTML report written to: ${outputFile}`);