blaze-performance-tester 3.1.4 → 3.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +155 -43
- package/dist/index.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -209,7 +209,7 @@ async function runBlazeCoreEngine(config) {
|
|
|
209
209
|
const steadyStateRequests = rawRequests.slice(warmUpCutoff);
|
|
210
210
|
const metrics = processMetricsTelemetry(steadyStateRequests.length > 0 ? steadyStateRequests : rawRequests, config.durationSec);
|
|
211
211
|
const rawErrors = rawRequests.filter((r) => r.errorMessage).map((r) => r.errorMessage);
|
|
212
|
-
resolve2({ metrics, rawErrors });
|
|
212
|
+
resolve2({ metrics, rawErrors, rawRequests: steadyStateRequests.length > 0 ? steadyStateRequests : rawRequests });
|
|
213
213
|
}, 1e3);
|
|
214
214
|
});
|
|
215
215
|
}
|
|
@@ -217,7 +217,8 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
217
217
|
console.log("\u{1F680} Initializing Intelligent Autonomous Load Agent...");
|
|
218
218
|
const history = [];
|
|
219
219
|
let aggregateErrorLogs = [];
|
|
220
|
-
let
|
|
220
|
+
let lastRawRequests = [];
|
|
221
|
+
let total1xx = 0, total2xx = 0, total3xx = 0, total4xx = 0, total5xx = 0;
|
|
221
222
|
let globalMetricsAccumulator = { dns: [], tcp: [], tls: [], ttfb: [], latencies: [], bandwidths: [] };
|
|
222
223
|
let currentThreads = Math.min(config.maxThreads, Math.max(5, Math.floor(config.maxThreads * 0.2)));
|
|
223
224
|
let baseStep = config.stepSize;
|
|
@@ -226,10 +227,12 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
226
227
|
let saturationKneePoint = null;
|
|
227
228
|
while (currentThreads <= config.maxThreads && isStable) {
|
|
228
229
|
console.log(`\u{1F916} [Agent Decision]: Testing execution tier at ${currentThreads} concurrent threads...`);
|
|
229
|
-
const { metrics, rawErrors } = await runBlazeCoreEngine({ ...config, threads: currentThreads });
|
|
230
|
+
const { metrics, rawErrors, rawRequests } = await runBlazeCoreEngine({ ...config, threads: currentThreads });
|
|
230
231
|
if (config.clusterLogs) {
|
|
231
232
|
aggregateErrorLogs = aggregateErrorLogs.concat(rawErrors);
|
|
232
233
|
}
|
|
234
|
+
lastRawRequests = rawRequests;
|
|
235
|
+
total1xx += metrics.c1xx;
|
|
233
236
|
total2xx += metrics.c2xx;
|
|
234
237
|
total3xx += metrics.c3xx;
|
|
235
238
|
total4xx += metrics.c4xx;
|
|
@@ -290,6 +293,8 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
290
293
|
const analyzer = new LogAnalyzer();
|
|
291
294
|
semanticReport = analyzer.process(aggregateErrorLogs);
|
|
292
295
|
}
|
|
296
|
+
const totalTestSeconds = history.length * config.durationSec;
|
|
297
|
+
const vuRampUpVelocity = history.length > 1 && totalTestSeconds > 0 ? (history[history.length - 1].threads - history[0].threads) / totalTestSeconds : config.threads / config.durationSec;
|
|
293
298
|
const finalSummaryCards = {
|
|
294
299
|
apdex: history[history.length - 1]?.apdex || 0,
|
|
295
300
|
throughput: history[history.length - 1]?.throughput || 0,
|
|
@@ -299,14 +304,15 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
299
304
|
tcp: mathUtils.mean(globalMetricsAccumulator.tcp),
|
|
300
305
|
tls: mathUtils.mean(globalMetricsAccumulator.tls),
|
|
301
306
|
stdDev: mathUtils.stdDev(globalMetricsAccumulator.latencies, mathUtils.mean(globalMetricsAccumulator.latencies)),
|
|
302
|
-
saturationKneePoint: saturationKneePoint || history[history.length - 1]?.threads || 50
|
|
307
|
+
saturationKneePoint: saturationKneePoint || history[history.length - 1]?.threads || 50,
|
|
308
|
+
vuRampUpVelocity
|
|
303
309
|
};
|
|
304
310
|
generateFinalAgentReport(history);
|
|
305
|
-
exportHtmlDashboard(history, config, verdictReason, semanticReport, { total2xx, total3xx, total4xx, total5xx }, finalSummaryCards);
|
|
311
|
+
exportHtmlDashboard(history, config, verdictReason, semanticReport, { total1xx, total2xx, total3xx, total4xx, total5xx }, finalSummaryCards, lastRawRequests);
|
|
306
312
|
}
|
|
307
313
|
async function runStandardStressTest(config) {
|
|
308
314
|
console.log(`\u{1F3CB}\uFE0F Running Standard Stress Test [Threads: ${config.threads} | Duration: ${config.durationSec}s]`);
|
|
309
|
-
const { metrics, rawErrors } = await runBlazeCoreEngine(config);
|
|
315
|
+
const { metrics, rawErrors, rawRequests } = await runBlazeCoreEngine(config);
|
|
310
316
|
printMatrixDashboard(metrics, config.threads);
|
|
311
317
|
const history = [{
|
|
312
318
|
threads: config.threads,
|
|
@@ -334,16 +340,18 @@ async function runStandardStressTest(config) {
|
|
|
334
340
|
tcp: metrics.avgTcpMs,
|
|
335
341
|
tls: metrics.avgTlsMs,
|
|
336
342
|
stdDev: metrics.stdDevMs,
|
|
337
|
-
saturationKneePoint: config.threads
|
|
343
|
+
saturationKneePoint: config.threads,
|
|
344
|
+
vuRampUpVelocity: config.threads / config.durationSec
|
|
338
345
|
};
|
|
339
346
|
const isPassed = metrics.apdexScore >= config.targetApdex && metrics.errorRate <= config.targetErrorRate;
|
|
340
347
|
const verdictReason = isPassed ? "Static single-wave baseline checks concluded successfully without triggering health boundaries." : "Static verification loop completed with values exceeding defined quality thresholds.";
|
|
341
348
|
exportHtmlDashboard(history, config, verdictReason, semanticReport, {
|
|
349
|
+
total1xx: metrics.c1xx,
|
|
342
350
|
total2xx: metrics.c2xx,
|
|
343
351
|
total3xx: metrics.c3xx,
|
|
344
352
|
total4xx: metrics.c4xx,
|
|
345
353
|
total5xx: metrics.c5xx
|
|
346
|
-
}, finalSummaryCards);
|
|
354
|
+
}, finalSummaryCards, rawRequests);
|
|
347
355
|
}
|
|
348
356
|
function getFallbackClusterLogs() {
|
|
349
357
|
const logs = [];
|
|
@@ -396,9 +404,10 @@ function processMetricsTelemetry(requests, durationSec) {
|
|
|
396
404
|
const avgTcpMs = mathUtils.mean(requests.map((r) => r.tcpTimeMs || 0));
|
|
397
405
|
const avgTlsMs = mathUtils.mean(requests.map((r) => r.tlsTimeMs || 0));
|
|
398
406
|
const avgTtfbMs = avgDnsMs + avgTcpMs + avgTlsMs + avgLatencyMs * 0.3;
|
|
399
|
-
let c2xx = 0, c3xx = 0, c4xx = 0, c5xx = 0;
|
|
407
|
+
let c1xx = 0, c2xx = 0, c3xx = 0, c4xx = 0, c5xx = 0;
|
|
400
408
|
requests.forEach((r) => {
|
|
401
|
-
if (r.statusCode >=
|
|
409
|
+
if (r.statusCode >= 100 && r.statusCode < 200) c1xx++;
|
|
410
|
+
else if (r.statusCode >= 200 && r.statusCode < 300) c2xx++;
|
|
402
411
|
else if (r.statusCode >= 300 && r.statusCode < 400) c3xx++;
|
|
403
412
|
else if (r.statusCode >= 400 && r.statusCode < 500) c4xx++;
|
|
404
413
|
else if (r.statusCode >= 500) c5xx++;
|
|
@@ -418,6 +427,7 @@ function processMetricsTelemetry(requests, durationSec) {
|
|
|
418
427
|
p95LatencyMs,
|
|
419
428
|
errorRate,
|
|
420
429
|
apdexScore,
|
|
430
|
+
c1xx,
|
|
421
431
|
c2xx,
|
|
422
432
|
c3xx,
|
|
423
433
|
c4xx,
|
|
@@ -450,7 +460,7 @@ function generateFinalAgentReport(history) {
|
|
|
450
460
|
console.log(`\u26A1 Peak Achieved Cluster Velocity : ${peakThroughput.toFixed(0)} req/sec`);
|
|
451
461
|
console.log("=======================================================\n");
|
|
452
462
|
}
|
|
453
|
-
function exportHtmlDashboard(history, config, verdictReason, semanticReport, responseMatrix, cards) {
|
|
463
|
+
function exportHtmlDashboard(history, config, verdictReason, semanticReport, responseMatrix, cards, rawRequests = []) {
|
|
454
464
|
const labels = history.map((h, i) => `${i + 1}s`);
|
|
455
465
|
const successRequestsData = history.map((h) => h.successRequests);
|
|
456
466
|
const failedWorkloadsData = history.map((h) => h.failedRequests);
|
|
@@ -462,6 +472,11 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
462
472
|
const estimatedMonthlyCost = (cards.throughput * 0.045 * 24 * 30).toFixed(2);
|
|
463
473
|
const recommendedCpuCores = Math.max(2, Math.ceil(cards.saturationKneePoint / 75));
|
|
464
474
|
const recommendedRamGb = recommendedCpuCores * 2;
|
|
475
|
+
const firstTimestamp = rawRequests[0]?.timestampMs || Date.now();
|
|
476
|
+
const scatterPoints = rawRequests.slice(0, 180).map((r) => ({
|
|
477
|
+
x: Number(((r.timestampMs - firstTimestamp) / 1e3).toFixed(2)),
|
|
478
|
+
y: Number(((r.dnsTimeMs || 0) + (r.tcpTimeMs || 0) + (r.tlsTimeMs || 0) + r.durationMs * 0.3).toFixed(1))
|
|
479
|
+
}));
|
|
465
480
|
let logClustersHtml = "";
|
|
466
481
|
if (semanticReport) {
|
|
467
482
|
const totalLogCount = semanticReport.clusters.reduce((sum, c) => sum + c.count, 0);
|
|
@@ -469,23 +484,14 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
469
484
|
logClustersHtml = `
|
|
470
485
|
<div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
|
|
471
486
|
<h3 style="border-bottom: none; padding-bottom: 0rem; margin-bottom: 0.25rem; display: flex; align-items: center; gap: 0.5rem; color: #ffffff; font-size: 1.2rem;">
|
|
472
|
-
\u{1F9E0}
|
|
487
|
+
\u{1F9E0} Interactive Remediation Playbooks & Log Classification
|
|
473
488
|
</h3>
|
|
474
489
|
<div style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 1.5rem;">
|
|
475
|
-
Captured <strong style="color: #f8fafc;">${totalLogCount}</strong> raw failures grouped into <strong style="color: #f8fafc;">${uniquePatternsCount}</strong> unique structural patterns.
|
|
490
|
+
Captured <strong style="color: #f8fafc;">${totalLogCount}</strong> raw failures grouped into <strong style="color: #f8fafc;">${uniquePatternsCount}</strong> unique structural patterns. Expand a playbook to inspect automated remediation strategies and diagnostics confidence ratings.
|
|
476
491
|
</div>
|
|
477
492
|
|
|
478
|
-
<
|
|
479
|
-
|
|
480
|
-
<tr style="border-bottom: 1px solid #1e293b;">
|
|
481
|
-
<th style="width: 8%; padding: 0.75rem; text-align: left; color: #64748b; font-size: 0.85rem; font-weight: bold; text-transform: none;">Count</th>
|
|
482
|
-
<th style="width: 22%; padding: 0.75rem; text-align: left; color: #64748b; font-size: 0.85rem; font-weight: bold; text-transform: none;">Classification Category</th>
|
|
483
|
-
<th style="width: 10%; padding: 0.75rem; text-align: left; color: #64748b; font-size: 0.85rem; font-weight: bold; text-transform: none;">Severity</th>
|
|
484
|
-
<th style="width: 60%; padding: 0.75rem; text-align: left; color: #64748b; font-size: 0.85rem; font-weight: bold; text-transform: none;">Structural Abstract Blueprint / Dynamic Log Fingerprint</th>
|
|
485
|
-
</tr>
|
|
486
|
-
</thead>
|
|
487
|
-
<tbody>
|
|
488
|
-
${semanticReport.clusters.map((c) => {
|
|
493
|
+
<div style="display: flex; flex-direction: column; gap: 1rem;">
|
|
494
|
+
${semanticReport.clusters.map((c, idx) => {
|
|
489
495
|
let catColor = "#fb923c";
|
|
490
496
|
if (c.category === "Authentication_Error") catColor = "#c084fc";
|
|
491
497
|
if (c.category === "Product_Error") catColor = "#f87171";
|
|
@@ -493,25 +499,53 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
493
499
|
let sevBg = c.severity === "CRITICAL" ? "#dc2626" : "#ea580c";
|
|
494
500
|
const rawTemplate = c.template || "";
|
|
495
501
|
const cleanedTemplate = rawTemplate.replace(/\[\d{4}-\d{2}-\d{2}.*?\]\s*/, "");
|
|
502
|
+
const clarityBonus = c.severity === "CRITICAL" ? 15 : 8;
|
|
503
|
+
const freqScore = Math.min(25, c.count * 0.5);
|
|
504
|
+
const confidenceScore = Math.min(99, Math.round(65 + freqScore + clarityBonus));
|
|
505
|
+
let confColor = "#10b981";
|
|
506
|
+
if (confidenceScore < 80) confColor = "#f59e0b";
|
|
507
|
+
if (confidenceScore < 70) confColor = "#ef4444";
|
|
508
|
+
let fixTitle = "Infrastructure Pool & Tuning Fix";
|
|
509
|
+
let fixCode = "const pool = new Pool({ max: 50, idleTimeoutMillis: 30000 });";
|
|
510
|
+
let explanation = "High concurrency is causing socket/connection starvation. Increase connection limits or enable pooling keep-alives.";
|
|
511
|
+
if (c.category === "Authentication_Error") {
|
|
512
|
+
fixTitle = "Auth Token / JWT Strategy";
|
|
513
|
+
fixCode = "// Implement token caching and silent rotation\napp.use(authMiddleware({ cacheTtl: 300, allowGracePeriod: 60 }));";
|
|
514
|
+
explanation = "Frequent token verification failures under load. Extend signature verification cache or decouple auth validation check.";
|
|
515
|
+
} else if (c.category === "Product_Error") {
|
|
516
|
+
fixTitle = "Application Null-Safety / Guard Fix";
|
|
517
|
+
fixCode = "const configId = payload?.config_id ?? defaultConfigurationId;\nif (!configId) throw new ValidationError('Missing config_id');";
|
|
518
|
+
explanation = "Runtime reference error for undefined payload field under race condition spikes. Add optional chaining and input contracts.";
|
|
519
|
+
}
|
|
520
|
+
const snippetId = `snippet-${idx}`;
|
|
496
521
|
return `
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
<
|
|
500
|
-
|
|
501
|
-
<span style="
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
522
|
+
<details style="background: #090d16; border: 1px solid #1e293b; border-radius: 6px; overflow: hidden;">
|
|
523
|
+
<summary style="padding: 1rem; cursor: pointer; display: flex; align-items: center; justify-content: space-between; user-select: none;">
|
|
524
|
+
<div style="display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;">
|
|
525
|
+
<span style="font-size: 1.1rem; font-weight: bold; color: #f8fafc; background: #131c2e; padding: 0.2rem 0.6rem; border-radius: 4px;">${c.count}x</span>
|
|
526
|
+
<span style="color: ${catColor}; font-weight: 600; font-size: 0.95rem;">${c.category}</span>
|
|
527
|
+
<span style="background: ${sevBg}; color: #ffffff; padding: 0.15rem 0.4rem; border-radius: 4px; font-size: 0.65rem; font-weight: bold; letter-spacing: 0.05em;">${c.severity}</span>
|
|
528
|
+
<span style="background: rgba(16, 185, 129, 0.15); color: ${confColor}; border: 1px solid ${confColor}40; padding: 0.15rem 0.5rem; border-radius: 4px; font-size: 0.7rem; font-weight: bold;">AI Confidence: ${confidenceScore}%</span>
|
|
529
|
+
</div>
|
|
530
|
+
<div style="color: #64748b; font-size: 0.85rem; font-family: monospace;">Expand Playbook \u25BC</div>
|
|
531
|
+
</summary>
|
|
532
|
+
<div style="padding: 1.25rem; border-top: 1px solid #1e293b; background: #0f172a;">
|
|
533
|
+
<div style="margin-bottom: 1rem;">
|
|
534
|
+
<div style="color: #64748b; font-size: 0.75rem; text-transform: uppercase; font-weight: bold; margin-bottom: 0.25rem;">Detected Log Fingerprint</div>
|
|
535
|
+
<code style="color: #cbd5e1; font-family: monospace; font-size: 0.85rem; word-break: break-all;">${cleanedTemplate}</code>
|
|
536
|
+
</div>
|
|
537
|
+
<div style="margin-bottom: 1.25rem;">
|
|
538
|
+
<div style="color: #38bdf8; font-size: 0.9rem; font-weight: bold; margin-bottom: 0.25rem;">\u{1F4A1} Playbook Strategy: ${fixTitle}</div>
|
|
539
|
+
<div style="color: #94a3b8; font-size: 0.85rem; line-height: 1.4; margin-bottom: 0.75rem;">${explanation}</div>
|
|
540
|
+
<div style="position: relative; background: #0b111e; border: 1px solid #1e293b; border-radius: 4px; padding: 0.75rem;">
|
|
541
|
+
<button onclick="copyToClipboard('${snippetId}')" style="position: absolute; top: 0.5rem; right: 0.5rem; background: #1e293b; color: #38bdf8; border: none; padding: 0.2rem 0.5rem; border-radius: 3px; font-size: 0.7rem; cursor: pointer;">Copy Fix</button>
|
|
542
|
+
<pre id="${snippetId}" style="margin: 0; color: #4ade80; font-family: monospace; font-size: 0.85rem; white-space: pre-wrap;">${fixCode}</pre>
|
|
509
543
|
</div>
|
|
510
|
-
</
|
|
511
|
-
</
|
|
544
|
+
</div>
|
|
545
|
+
</div>
|
|
546
|
+
</details>`;
|
|
512
547
|
}).join("")}
|
|
513
|
-
|
|
514
|
-
</table>
|
|
548
|
+
</div>
|
|
515
549
|
</div>`;
|
|
516
550
|
}
|
|
517
551
|
const liveAdjusterHtml = `
|
|
@@ -569,7 +603,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
569
603
|
h1 { color: #38bdf8; margin: 0; font-size: 2rem; }
|
|
570
604
|
.meta { color: #94a3b8; font-size: 0.9rem; margin-top: 0.5rem; }
|
|
571
605
|
|
|
572
|
-
.cards-wrapper { display: grid; grid-template-columns: repeat(
|
|
606
|
+
.cards-wrapper { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
|
|
573
607
|
.metric-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 6px; padding: 1.25rem; position: relative; }
|
|
574
608
|
.card-title { font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; }
|
|
575
609
|
.card-value { font-size: 1.6rem; font-weight: 700; color: #ffffff; }
|
|
@@ -577,6 +611,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
577
611
|
.card-value.green { color: #10b981; }
|
|
578
612
|
.card-value.orange { color: #f97316; }
|
|
579
613
|
.card-value.purple { color: #a855f7; }
|
|
614
|
+
.card-value.cyan { color: #38bdf8; }
|
|
580
615
|
.card-subtext { font-size: 0.8rem; color: #10b981; font-weight: 500; margin-left: 0.4rem; display: inline-block; }
|
|
581
616
|
|
|
582
617
|
.top-layout-grid { display: grid; grid-template-columns: 1fr 400px; gap: 1.5rem; margin-bottom: 2rem; }
|
|
@@ -592,13 +627,14 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
592
627
|
.matrix-box .matrix-row:last-child { border-bottom: none; }
|
|
593
628
|
.matrix-label { font-size: 0.9rem; color: #f8fafc; display: flex; align-items: center; gap: 0.5rem; }
|
|
594
629
|
.matrix-badge { font-size: 0.7rem; font-weight: bold; padding: 0.1rem 0.3rem; border-radius: 4px; }
|
|
630
|
+
.badge-1xx { background: rgba(56, 189, 248, 0.2); color: #38bdf8; }
|
|
595
631
|
.badge-2xx { background: rgba(22, 163, 74, 0.2); color: #4ade80; }
|
|
596
632
|
.badge-3xx { background: rgba(148, 163, 184, 0.2); color: #cbd5e1; }
|
|
597
633
|
.badge-4xx { background: rgba(234, 179, 8, 0.2); color: #fde047; }
|
|
598
634
|
.badge-5xx { background: rgba(220, 38, 38, 0.2); color: #f87171; }
|
|
599
635
|
.matrix-value { font-size: 1rem; font-weight: bold; color: #ffffff; }
|
|
600
636
|
|
|
601
|
-
.charts-grid { display: grid; grid-template-columns:
|
|
637
|
+
.charts-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.5rem; margin-bottom: 2rem; }
|
|
602
638
|
.graph-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 8px; padding: 1.5rem; }
|
|
603
639
|
.graph-title { font-size: 1.1rem; font-weight: 700; color: #ffffff; margin-bottom: 1.2rem; display: flex; align-items: center; gap: 0.5rem; }
|
|
604
640
|
|
|
@@ -634,6 +670,10 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
634
670
|
<div class="card-title">Avg Time To First Byte</div>
|
|
635
671
|
<div class="card-value">${cards.ttfb.toFixed(1)}<span class="unit">ms</span></div>
|
|
636
672
|
</div>
|
|
673
|
+
<div class="metric-card">
|
|
674
|
+
<div class="card-title">VU Ramp-up Velocity</div>
|
|
675
|
+
<div class="card-value cyan">${cards.vuRampUpVelocity.toFixed(1)}<span class="unit">VUs/s</span></div>
|
|
676
|
+
</div>
|
|
637
677
|
<div class="metric-card">
|
|
638
678
|
<div class="card-title">DNS Lookup</div>
|
|
639
679
|
<div class="card-value">${cards.dns.toFixed(2)}<span class="unit">ms</span></div>
|
|
@@ -661,6 +701,10 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
661
701
|
|
|
662
702
|
<div class="matrix-box">
|
|
663
703
|
<div class="matrix-title">\u{1F4CB} HTTP Response Matrix</div>
|
|
704
|
+
<div class="matrix-row">
|
|
705
|
+
<div class="matrix-label">Informational <span class="matrix-badge badge-1xx">1xx</span></div>
|
|
706
|
+
<div class="matrix-value">${responseMatrix.total1xx}</div>
|
|
707
|
+
</div>
|
|
664
708
|
<div class="matrix-row">
|
|
665
709
|
<div class="matrix-label">Successful <span class="matrix-badge badge-2xx">2xx</span></div>
|
|
666
710
|
<div class="matrix-value">${responseMatrix.total2xx}</div>
|
|
@@ -689,11 +733,25 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
689
733
|
</div>
|
|
690
734
|
|
|
691
735
|
<div class="graph-card">
|
|
692
|
-
<div class="graph-title">\u{1F504} Active Concurrency (VUs) vs.
|
|
736
|
+
<div class="graph-title">\u{1F504} Active Concurrency (VUs) vs. TTF Trend</div>
|
|
693
737
|
<div style="height: 280px; position: relative;">
|
|
694
738
|
<canvas id="concurrencyChart"></canvas>
|
|
695
739
|
</div>
|
|
696
740
|
</div>
|
|
741
|
+
|
|
742
|
+
<div class="graph-card">
|
|
743
|
+
<div class="graph-title">\u{1F369} HTTP Status Code Proportions</div>
|
|
744
|
+
<div style="height: 280px; position: relative;">
|
|
745
|
+
<canvas id="statusDonutChart"></canvas>
|
|
746
|
+
</div>
|
|
747
|
+
</div>
|
|
748
|
+
|
|
749
|
+
<div class="graph-card">
|
|
750
|
+
<div class="graph-title">\u{1F4C8} Time-to-First-Byte Scatter Plot</div>
|
|
751
|
+
<div style="height: 280px; position: relative;">
|
|
752
|
+
<canvas id="ttfbScatterChart"></canvas>
|
|
753
|
+
</div>
|
|
754
|
+
</div>
|
|
697
755
|
</div>
|
|
698
756
|
|
|
699
757
|
<div class="card">
|
|
@@ -730,6 +788,15 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
730
788
|
</div>
|
|
731
789
|
|
|
732
790
|
<script>
|
|
791
|
+
function copyToClipboard(containerId) {
|
|
792
|
+
const text = document.getElementById(containerId).innerText;
|
|
793
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
794
|
+
alert('Copied remediation snippet to clipboard!');
|
|
795
|
+
}).catch(err => {
|
|
796
|
+
console.error('Failed to copy text: ', err);
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
|
|
733
800
|
const labels = ${JSON.stringify(labels)};
|
|
734
801
|
const baseThroughput = ${cards.throughput};
|
|
735
802
|
const baseLatency = ${cards.ttfb};
|
|
@@ -824,6 +891,51 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
824
891
|
}
|
|
825
892
|
}
|
|
826
893
|
});
|
|
894
|
+
|
|
895
|
+
new Chart(document.getElementById('statusDonutChart'), {
|
|
896
|
+
type: 'doughnut',
|
|
897
|
+
data: {
|
|
898
|
+
labels: ['1xx Info', '2xx Success', '3xx Redirect', '4xx Client Err', '5xx Server Err'],
|
|
899
|
+
datasets: [{
|
|
900
|
+
data: [${responseMatrix.total1xx}, ${responseMatrix.total2xx}, ${responseMatrix.total3xx}, ${responseMatrix.total4xx}, ${responseMatrix.total5xx}],
|
|
901
|
+
backgroundColor: ['#38bdf8', '#4ade80', '#cbd5e1', '#fde047', '#f87171'],
|
|
902
|
+
borderWidth: 1,
|
|
903
|
+
borderColor: '#1e293b'
|
|
904
|
+
}]
|
|
905
|
+
},
|
|
906
|
+
options: {
|
|
907
|
+
responsive: true,
|
|
908
|
+
maintainAspectRatio: false,
|
|
909
|
+
plugins: {
|
|
910
|
+
legend: { position: 'top', labels: { color: '#94a3b8', font: { size: 11 } } }
|
|
911
|
+
},
|
|
912
|
+
cutout: '65%'
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
new Chart(document.getElementById('ttfbScatterChart'), {
|
|
917
|
+
type: 'scatter',
|
|
918
|
+
data: {
|
|
919
|
+
datasets: [{
|
|
920
|
+
label: 'Request Processing Delay',
|
|
921
|
+
data: ${JSON.stringify(scatterPoints)},
|
|
922
|
+
backgroundColor: '#a855f7',
|
|
923
|
+
pointRadius: 4,
|
|
924
|
+
pointHoverRadius: 6
|
|
925
|
+
}]
|
|
926
|
+
},
|
|
927
|
+
options: {
|
|
928
|
+
responsive: true,
|
|
929
|
+
maintainAspectRatio: false,
|
|
930
|
+
plugins: {
|
|
931
|
+
legend: { position: 'top', labels: { color: '#94a3b8', font: { size: 11 } } }
|
|
932
|
+
},
|
|
933
|
+
scales: {
|
|
934
|
+
x: { grid: { color: '#1e293b' }, ticks: { color: '#64748b' }, title: { display: true, text: 'Time offset (s)', color: '#64748b' } },
|
|
935
|
+
y: { grid: { color: '#1e293b' }, ticks: { color: '#64748b' }, title: { display: true, text: 'TTFB / Delay (ms)', color: '#64748b' } }
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
});
|
|
827
939
|
</script></body></html>`;
|
|
828
940
|
try {
|
|
829
941
|
fs.writeFileSync(config.outputHtml, htmlContent, "utf-8");
|
|
Binary file
|