blaze-performance-tester 3.1.5 → 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 +476 -46
- package/dist/index.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -70,7 +70,7 @@ var blazeCore;
|
|
|
70
70
|
try {
|
|
71
71
|
blazeCore = require2(nativeBindingPath);
|
|
72
72
|
} catch (e) {
|
|
73
|
-
console.error(
|
|
73
|
+
console.error(`\u274C Failed to load native C++ bindings from ${nativeBindingPath}`);
|
|
74
74
|
process.exit(1);
|
|
75
75
|
}
|
|
76
76
|
var mathUtils = {
|
|
@@ -109,7 +109,7 @@ async function executeTestPipeline(config) {
|
|
|
109
109
|
})
|
|
110
110
|
}
|
|
111
111
|
];
|
|
112
|
-
console.log(
|
|
112
|
+
console.log(`Launching automated script transaction analysis for: ${config.targetScript}`);
|
|
113
113
|
const sanitizedSteps = transactionalScenario.map((step) => ({
|
|
114
114
|
...step,
|
|
115
115
|
body: typeof step.body === "string" ? step.body : step.body ? JSON.stringify(step.body) : ""
|
|
@@ -226,7 +226,7 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
226
226
|
let verdictReason = "The system successfully scaled and remained stable within parameters up to maximum configured capacity allocations.";
|
|
227
227
|
let saturationKneePoint = null;
|
|
228
228
|
while (currentThreads <= config.maxThreads && isStable) {
|
|
229
|
-
console.log(
|
|
229
|
+
console.log(`\u{1F916} [Agent Decision]: Testing execution tier at ${currentThreads} concurrent threads...`);
|
|
230
230
|
const { metrics, rawErrors, rawRequests } = await runBlazeCoreEngine({ ...config, threads: currentThreads });
|
|
231
231
|
if (config.clusterLogs) {
|
|
232
232
|
aggregateErrorLogs = aggregateErrorLogs.concat(rawErrors);
|
|
@@ -261,7 +261,7 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
261
261
|
const dLatency_dThreads = (current.avgLatency - previous.avgLatency) / (current.threads - previous.threads);
|
|
262
262
|
if (current.throughput <= previous.throughput * 1.02 && dLatency_dThreads > 4.5) {
|
|
263
263
|
saturationKneePoint = currentThreads;
|
|
264
|
-
verdictReason =
|
|
264
|
+
verdictReason = `Saturation knee-point identified at ${currentThreads} VUs where throughput flattened while latency spiked rapidly.`;
|
|
265
265
|
isStable = false;
|
|
266
266
|
break;
|
|
267
267
|
}
|
|
@@ -311,7 +311,7 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
311
311
|
exportHtmlDashboard(history, config, verdictReason, semanticReport, { total1xx, total2xx, total3xx, total4xx, total5xx }, finalSummaryCards, lastRawRequests);
|
|
312
312
|
}
|
|
313
313
|
async function runStandardStressTest(config) {
|
|
314
|
-
console.log(
|
|
314
|
+
console.log(`\u{1F3CB}\uFE0F Running Standard Stress Test [Threads: ${config.threads} | Duration: ${config.durationSec}s]`);
|
|
315
315
|
const { metrics, rawErrors, rawRequests } = await runBlazeCoreEngine(config);
|
|
316
316
|
printMatrixDashboard(metrics, config.threads);
|
|
317
317
|
const history = [{
|
|
@@ -356,7 +356,7 @@ async function runStandardStressTest(config) {
|
|
|
356
356
|
function getFallbackClusterLogs() {
|
|
357
357
|
const logs = [];
|
|
358
358
|
const add = (msg, count) => {
|
|
359
|
-
for (let i = 0; i < count; i++) logs.push(
|
|
359
|
+
for (let i = 0; i < count; i++) logs.push(`[2026-07-08T12:08:18.000Z] ${msg}`);
|
|
360
360
|
};
|
|
361
361
|
add("JsonWebTokenError: jwt expired at JMT.verify (\\app\\node_modules\\jsonwebtoken\\index.js:5)", 37);
|
|
362
362
|
add("Error: ECONNREFUSED 127.0.0.1:5432 at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1)", 33);
|
|
@@ -441,11 +441,11 @@ function processMetricsTelemetry(requests, durationSec) {
|
|
|
441
441
|
}
|
|
442
442
|
function printMatrixDashboard(m, threads) {
|
|
443
443
|
const pad = (val, size) => String(val).padEnd(size).substring(0, size);
|
|
444
|
-
console.log(
|
|
445
|
-
console.log(
|
|
446
|
-
console.log(
|
|
447
|
-
console.log(
|
|
448
|
-
console.log(
|
|
444
|
+
console.log(`-----------------------------------------------------------------------------------------`);
|
|
445
|
+
console.log(`| Threads | Samples | Avg Latency | P95 Latency | Throughput | Error Rate | Apdex |`);
|
|
446
|
+
console.log(`-----------------------------------------------------------------------------------------`);
|
|
447
|
+
console.log(`| ${pad(threads, 7)} | ${pad(m.totalRequests, 9)} | ${pad(m.avgLatencyMs.toFixed(1) + "ms", 11)} | ${pad(m.p95LatencyMs.toFixed(1) + "ms", 11)} | ${pad(m.requestsPerSecond.toFixed(0) + "/s", 10)} | ${pad((m.errorRate * 100).toFixed(2) + "%", 10)} | ${pad(m.apdexScore.toFixed(2), 9)} |`);
|
|
448
|
+
console.log(`-----------------------------------------------------------------------------------------`);
|
|
449
449
|
}
|
|
450
450
|
function generateFinalAgentReport(history) {
|
|
451
451
|
if (history.length === 0) return;
|
|
@@ -455,19 +455,19 @@ function generateFinalAgentReport(history) {
|
|
|
455
455
|
console.log("\n=======================================================");
|
|
456
456
|
console.log("\u{1F9E0} INTELLIGENT AUTONOMOUS LOAD AGENT FINAL REPORT");
|
|
457
457
|
console.log("=======================================================");
|
|
458
|
-
console.log(
|
|
459
|
-
console.log(
|
|
460
|
-
console.log(
|
|
458
|
+
console.log(`\u{1F3C6} Recommended Max Safe Concurrency : ${maxSafeThreads} allocation threads`);
|
|
459
|
+
console.log(`\u{1F4A5} Infrastructure Failure Boundary : ${history[history.length - 1].threads} concurrency allocation`);
|
|
460
|
+
console.log(`\u26A1 Peak Achieved Cluster Velocity : ${peakThroughput.toFixed(0)} req/sec`);
|
|
461
461
|
console.log("=======================================================\n");
|
|
462
462
|
}
|
|
463
463
|
function exportHtmlDashboard(history, config, verdictReason, semanticReport, responseMatrix, cards, rawRequests = []) {
|
|
464
|
-
const labels = history.map((h, i) => i + 1
|
|
464
|
+
const labels = history.map((h, i) => `${i + 1}s`);
|
|
465
465
|
const successRequestsData = history.map((h) => h.successRequests);
|
|
466
466
|
const failedWorkloadsData = history.map((h) => h.failedRequests);
|
|
467
467
|
const activeThreadsData = history.map((h) => h.threads);
|
|
468
468
|
const ttfTrendData = history.map((h) => h.avgLatency * 1.1);
|
|
469
469
|
const waveListItems = history.map((h) => {
|
|
470
|
-
return
|
|
470
|
+
return `<div class="wave-item">Wave (${h.threads} VUs): P95 ${h.p95Latency.toFixed(1)}ms, Errors ${(h.errorRate * 100).toFixed(1)}%</div>`;
|
|
471
471
|
}).join("");
|
|
472
472
|
const estimatedMonthlyCost = (cards.throughput * 0.045 * 24 * 30).toFixed(2);
|
|
473
473
|
const recommendedCpuCores = Math.max(2, Math.ceil(cards.saturationKneePoint / 75));
|
|
@@ -477,46 +477,476 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
477
477
|
x: Number(((r.timestampMs - firstTimestamp) / 1e3).toFixed(2)),
|
|
478
478
|
y: Number(((r.dnsTimeMs || 0) + (r.tcpTimeMs || 0) + (r.tlsTimeMs || 0) + r.durationMs * 0.3).toFixed(1))
|
|
479
479
|
}));
|
|
480
|
-
const naturalLanguageAssistantHtml = `
|
|
481
|
-
<div class="card" style="margin-top: 2rem; background: linear-gradient(135deg, #1e1b4b 0%, #111827 100%); border: 1px solid #4338ca;">
|
|
482
|
-
<h3 style="color: #818cf8; display: flex; align-items: center; gap: 0.6rem; font-size: 1.25rem; margin-bottom: 0.5rem; border-bottom: none; padding-bottom: 0;">
|
|
483
|
-
\u{1F52E} Ask the Test \u2014 Embedded Natural Language Assistant
|
|
484
|
-
</h3>
|
|
485
|
-
<div style="color: #94a3b8; font-size: 0.85rem; margin-bottom: 1.25rem;">
|
|
486
|
-
Query metrics, log topologies, and SLA compliance profiles instantly. Try asking: <span style="color: #a5b4fc; font-family: monospace; cursor: pointer;" onclick="document.getElementById('assistantInput').value=this.innerText; runAssistantQuery();">"What was our peak throughput capacity?"</span> or <span style="color: #a5b4fc; font-family: monospace; cursor: pointer;" onclick="document.getElementById('assistantInput').value=this.innerText; runAssistantQuery();">"Summarize the server logs and confidence ratings"</span>.
|
|
487
|
-
</div>
|
|
488
|
-
<div style="display: flex; gap: 0.75rem; margin-bottom: 1rem;">
|
|
489
|
-
<input type="text" id="assistantInput" placeholder="Ask a question regarding test telemetry..."
|
|
490
|
-
style="flex: 1; background: #030712; border: 1px solid #374151; border-radius: 6px; padding: 0.75rem 1rem; color: #f9fafb; font-size: 0.9rem; outline: none;"
|
|
491
|
-
onkeydown="if(event.key === 'Enter') runAssistantQuery();" />
|
|
492
|
-
<button onclick="runAssistantQuery()" style="background: #4f46e5; color: #ffffff; border: none; padding: 0 1.5rem; border-radius: 6px; font-weight: 600; font-size: 0.9rem; cursor: pointer; transition: background 0.2s;">
|
|
493
|
-
Query Engine
|
|
494
|
-
</button>
|
|
495
|
-
</div>
|
|
496
|
-
<div id="assistantResponseContainer" style="display: none; background: #030712; border: 1px solid #1f2937; border-radius: 6px; padding: 1.25rem;">
|
|
497
|
-
<div style="font-size: 0.7rem; font-weight: bold; color: #6366f1; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; display: flex; align-items: center; gap: 0.3rem;">
|
|
498
|
-
<span>\u{1F916} AI Assistant Response</span>
|
|
499
|
-
</div>
|
|
500
|
-
<div id="assistantResponseText" style="font-size: 0.9rem; line-height: 1.5; color: #e5e7eb; white-space: pre-wrap;"></div>
|
|
501
|
-
</div>
|
|
502
|
-
</div>`;
|
|
503
480
|
let logClustersHtml = "";
|
|
504
481
|
if (semanticReport) {
|
|
505
482
|
const totalLogCount = semanticReport.clusters.reduce((sum, c) => sum + c.count, 0);
|
|
506
483
|
const uniquePatternsCount = semanticReport.clusters.length;
|
|
507
|
-
logClustersHtml =
|
|
484
|
+
logClustersHtml = `
|
|
485
|
+
<div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
|
|
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;">
|
|
487
|
+
\u{1F9E0} Interactive Remediation Playbooks & Log Classification
|
|
488
|
+
</h3>
|
|
489
|
+
<div style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 1.5rem;">
|
|
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.
|
|
491
|
+
</div>
|
|
492
|
+
|
|
493
|
+
<div style="display: flex; flex-direction: column; gap: 1rem;">
|
|
494
|
+
${semanticReport.clusters.map((c, idx) => {
|
|
495
|
+
let catColor = "#fb923c";
|
|
496
|
+
if (c.category === "Authentication_Error") catColor = "#c084fc";
|
|
497
|
+
if (c.category === "Product_Error") catColor = "#f87171";
|
|
498
|
+
if (c.category === "Environment_Infrastructure_Error") catColor = "#fb923c";
|
|
499
|
+
let sevBg = c.severity === "CRITICAL" ? "#dc2626" : "#ea580c";
|
|
500
|
+
const rawTemplate = c.template || "";
|
|
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}`;
|
|
521
|
+
return `
|
|
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>
|
|
543
|
+
</div>
|
|
544
|
+
</div>
|
|
545
|
+
</div>
|
|
546
|
+
</details>`;
|
|
547
|
+
}).join("")}
|
|
548
|
+
</div>
|
|
549
|
+
</div>`;
|
|
508
550
|
}
|
|
509
|
-
const liveAdjusterHtml =
|
|
510
|
-
|
|
511
|
-
|
|
551
|
+
const liveAdjusterHtml = `
|
|
552
|
+
<div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
|
|
553
|
+
<h3 style="color: #38bdf8; display: flex; align-items: center; gap: 0.5rem; font-size: 1.2rem; margin-bottom: 1rem;">
|
|
554
|
+
\u{1F39B}\uFE0F Live Concurrency Adjuster (Mid-Test Simulation)
|
|
555
|
+
</h3>
|
|
556
|
+
<div style="display: flex; gap: 2rem; align-items: center; flex-wrap: wrap;">
|
|
557
|
+
<div style="flex: 1; min-width: 280px;">
|
|
558
|
+
<label for="concurrencySlider" style="display: block; font-size: 0.85rem; color: #94a3b8; margin-bottom: 0.5rem;">
|
|
559
|
+
Scale Virtual Users (VUs): <strong id="sliderValue" style="color: #38bdf8;">${config.threads}</strong> threads
|
|
560
|
+
</label>
|
|
561
|
+
<input type="range" id="concurrencySlider" min="5" max="${config.maxThreads || 300}" step="5" value="${config.threads}" style="width: 100%; accent-color: #38bdf8; cursor: pointer;">
|
|
562
|
+
</div>
|
|
563
|
+
<div style="display: flex; gap: 1.5rem; text-align: center;">
|
|
564
|
+
<div style="background: #090d16; padding: 0.75rem 1rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
565
|
+
<div style="font-size: 0.7rem; color: #64748b; text-transform: uppercase;">Sim. Throughput</div>
|
|
566
|
+
<div id="simThroughput" style="font-size: 1.2rem; font-weight: bold; color: #f97316;">${cards.throughput.toFixed(0)}/s</div>
|
|
567
|
+
</div>
|
|
568
|
+
<div style="background: #090d16; padding: 0.75rem 1rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
569
|
+
<div style="font-size: 0.7rem; color: #64748b; text-transform: uppercase;">Sim. Latency</div>
|
|
570
|
+
<div id="simLatency" style="font-size: 1.2rem; font-weight: bold; color: #10b981;">${cards.ttfb.toFixed(1)}ms</div>
|
|
571
|
+
</div>
|
|
572
|
+
</div>
|
|
573
|
+
</div>
|
|
574
|
+
</div>`;
|
|
575
|
+
const rightSizingHtml = `
|
|
576
|
+
<div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
|
|
577
|
+
<h3 style="color: #38bdf8; display: flex; align-items: center; gap: 0.5rem; font-size: 1.2rem;">
|
|
578
|
+
\u2601\uFE0F Infrastructure Right-Sizing & Cloud Cost Projections
|
|
579
|
+
</h3>
|
|
580
|
+
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-top: 1rem;">
|
|
581
|
+
<div style="background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
582
|
+
<div class="card-title">Est. Monthly Cloud Cost</div>
|
|
583
|
+
<div class="card-value green">$${estimatedMonthlyCost} <span class="unit">/mo</span></div>
|
|
584
|
+
</div>
|
|
585
|
+
<div style="background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
586
|
+
<div class="card-title">Recommended CPU Right-Sizing</div>
|
|
587
|
+
<div class="card-value purple">${recommendedCpuCores} <span class="unit">vCores</span></div>
|
|
588
|
+
</div>
|
|
589
|
+
<div style="background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
590
|
+
<div class="card-title">Recommended Memory Allocation</div>
|
|
591
|
+
<div class="card-value orange">${recommendedRamGb} <span class="unit">GB RAM</span></div>
|
|
592
|
+
</div>
|
|
593
|
+
</div>
|
|
594
|
+
</div>`;
|
|
595
|
+
const htmlContent = `<!DOCTYPE html><html lang="en"><head>
|
|
596
|
+
<meta charset="UTF-8">
|
|
597
|
+
<title>Blaze Core Performance Report</title>
|
|
598
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
599
|
+
<style>
|
|
600
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #0b111e; color: #f8fafc; margin: 0; padding: 2rem; }
|
|
601
|
+
.container { max-width: 1300px; margin: 0 auto; }
|
|
602
|
+
.header { border-bottom: 1px solid #1e293b; padding-bottom: 1rem; margin-bottom: 1.5rem; }
|
|
603
|
+
h1 { color: #38bdf8; margin: 0; font-size: 2rem; }
|
|
604
|
+
.meta { color: #94a3b8; font-size: 0.9rem; margin-top: 0.5rem; }
|
|
605
|
+
|
|
606
|
+
.cards-wrapper { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
|
|
607
|
+
.metric-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 6px; padding: 1.25rem; position: relative; }
|
|
608
|
+
.card-title { font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; }
|
|
609
|
+
.card-value { font-size: 1.6rem; font-weight: 700; color: #ffffff; }
|
|
610
|
+
.card-value .unit { font-size: 0.9rem; font-weight: 500; color: #94a3b8; margin-left: 0.2rem; }
|
|
611
|
+
.card-value.green { color: #10b981; }
|
|
612
|
+
.card-value.orange { color: #f97316; }
|
|
613
|
+
.card-value.purple { color: #a855f7; }
|
|
614
|
+
.card-value.cyan { color: #38bdf8; }
|
|
615
|
+
.card-subtext { font-size: 0.8rem; color: #10b981; font-weight: 500; margin-left: 0.4rem; display: inline-block; }
|
|
616
|
+
|
|
617
|
+
.top-layout-grid { display: grid; grid-template-columns: 1fr 400px; gap: 1.5rem; margin-bottom: 2rem; }
|
|
618
|
+
.verdict-box { background: #0f172a; border: 1px dashed #ea580c; border-radius: 8px; padding: 1.25rem; }
|
|
619
|
+
.verdict-title { text-transform: uppercase; font-size: 0.85rem; font-weight: 700; color: #f97316; margin-bottom: 0.5rem; }
|
|
620
|
+
.verdict-text { font-size: 0.95rem; line-height: 1.5; color: #e2e8f0; margin-bottom: 0.75rem; }
|
|
621
|
+
.verdict-waves { background: #1f2937; border-radius: 6px; padding: 0.75rem 1rem; font-family: monospace; color: #9ca3af; font-size: 0.9rem; }
|
|
622
|
+
.wave-item { margin: 0.25rem 0; }
|
|
623
|
+
|
|
624
|
+
.matrix-box { background: #131c2e; border: 1px solid #1e293b; border-radius: 8px; padding: 1.25rem; }
|
|
625
|
+
.matrix-title { font-size: 1.1rem; font-weight: bold; color: #ffffff; margin-bottom: 1rem; }
|
|
626
|
+
.matrix-box .matrix-row { display: flex; justify-content: space-between; align-items: center; padding: 0.6rem 0; border-bottom: 1px solid #1e293b; }
|
|
627
|
+
.matrix-box .matrix-row:last-child { border-bottom: none; }
|
|
628
|
+
.matrix-label { font-size: 0.9rem; color: #f8fafc; display: flex; align-items: center; gap: 0.5rem; }
|
|
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; }
|
|
631
|
+
.badge-2xx { background: rgba(22, 163, 74, 0.2); color: #4ade80; }
|
|
632
|
+
.badge-3xx { background: rgba(148, 163, 184, 0.2); color: #cbd5e1; }
|
|
633
|
+
.badge-4xx { background: rgba(234, 179, 8, 0.2); color: #fde047; }
|
|
634
|
+
.badge-5xx { background: rgba(220, 38, 38, 0.2); color: #f87171; }
|
|
635
|
+
.matrix-value { font-size: 1rem; font-weight: bold; color: #ffffff; }
|
|
636
|
+
|
|
637
|
+
.charts-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.5rem; margin-bottom: 2rem; }
|
|
638
|
+
.graph-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 8px; padding: 1.5rem; }
|
|
639
|
+
.graph-title { font-size: 1.1rem; font-weight: 700; color: #ffffff; margin-bottom: 1.2rem; display: flex; align-items: center; gap: 0.5rem; }
|
|
640
|
+
|
|
641
|
+
.card { background: #131c2e; border: 1px solid #1e293b; border-radius: 8px; padding: 1.5rem; }
|
|
642
|
+
h3 { margin-top: 0; color: #cbd5e1; border-bottom: 1px solid #1e293b; padding-bottom: 0.5rem; }
|
|
643
|
+
table { width: 100%; border-collapse: collapse; margin-top: 1rem; }
|
|
644
|
+
th, td { padding: 0.75rem; text-align: left; border-bottom: 1px solid #1e293b; }
|
|
645
|
+
th { color: #94a3b8; font-weight: 600; }
|
|
646
|
+
.badge { padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.8rem; font-weight: bold; }
|
|
647
|
+
.badge-success { background: #16a34a; color: #f0fdf4; }
|
|
648
|
+
.badge-fail { background: #dc2626; color: #fef2f2; }
|
|
649
|
+
</style></head><body>
|
|
650
|
+
<div class="container">
|
|
651
|
+
<div class="header">
|
|
652
|
+
<h1>\u{1F525} Blaze Core Performance Analysis</h1>
|
|
653
|
+
<div class="meta">Target Script: <code>${config.targetScript}</code></div>
|
|
654
|
+
</div>
|
|
655
|
+
|
|
656
|
+
<div class="cards-wrapper">
|
|
657
|
+
<div class="metric-card">
|
|
658
|
+
<div class="card-title">Apdex Score (T: 50ms)</div>
|
|
659
|
+
<div class="card-value green">${cards.apdex.toFixed(2)}<span class="card-subtext">(Good)</span></div>
|
|
660
|
+
</div>
|
|
661
|
+
<div class="metric-card">
|
|
662
|
+
<div class="card-title">Throughput</div>
|
|
663
|
+
<div class="card-value orange">${cards.throughput.toFixed(1)}<span class="unit">/s</span></div>
|
|
664
|
+
</div>
|
|
665
|
+
<div class="metric-card">
|
|
666
|
+
<div class="card-title">Network Bandwidth</div>
|
|
667
|
+
<div class="card-value purple">${cards.bandwidth.toFixed(2)}<span class="unit">MB/s</span></div>
|
|
668
|
+
</div>
|
|
669
|
+
<div class="metric-card">
|
|
670
|
+
<div class="card-title">Avg Time To First Byte</div>
|
|
671
|
+
<div class="card-value">${cards.ttfb.toFixed(1)}<span class="unit">ms</span></div>
|
|
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>
|
|
677
|
+
<div class="metric-card">
|
|
678
|
+
<div class="card-title">DNS Lookup</div>
|
|
679
|
+
<div class="card-value">${cards.dns.toFixed(2)}<span class="unit">ms</span></div>
|
|
680
|
+
</div>
|
|
681
|
+
<div class="metric-card">
|
|
682
|
+
<div class="card-title">TCP Connect</div>
|
|
683
|
+
<div class="card-value">${cards.tcp.toFixed(2)}<span class="unit">ms</span></div>
|
|
684
|
+
</div>
|
|
685
|
+
<div class="metric-card">
|
|
686
|
+
<div class="card-title">TLS Handshake</div>
|
|
687
|
+
<div class="card-value">${cards.tls.toFixed(2)}<span class="unit">ms</span></div>
|
|
688
|
+
</div>
|
|
689
|
+
<div class="metric-card">
|
|
690
|
+
<div class="card-title">Stability (Std Dev)</div>
|
|
691
|
+
<div class="card-value">±${cards.stdDev.toFixed(1)}<span class="unit">ms</span></div>
|
|
692
|
+
</div>
|
|
693
|
+
</div>
|
|
694
|
+
|
|
695
|
+
<div class="top-layout-grid">
|
|
696
|
+
<div class="verdict-box">
|
|
697
|
+
<div class="verdict-title">\u{1F916} AI Stress-Testing Agent Verdict</div>
|
|
698
|
+
<div class="verdict-text">${verdictReason}</div>
|
|
699
|
+
<div class="verdict-waves">${waveListItems}</div>
|
|
700
|
+
</div>
|
|
701
|
+
|
|
702
|
+
<div class="matrix-box">
|
|
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>
|
|
708
|
+
<div class="matrix-row">
|
|
709
|
+
<div class="matrix-label">Successful <span class="matrix-badge badge-2xx">2xx</span></div>
|
|
710
|
+
<div class="matrix-value">${responseMatrix.total2xx}</div>
|
|
711
|
+
</div>
|
|
712
|
+
<div class="matrix-row">
|
|
713
|
+
<div class="matrix-label">Redirects <span class="matrix-badge badge-3xx">3xx</span></div>
|
|
714
|
+
<div class="matrix-value">${responseMatrix.total3xx}</div>
|
|
715
|
+
</div>
|
|
716
|
+
<div class="matrix-row">
|
|
717
|
+
<div class="matrix-label">Client Error <span class="matrix-badge badge-4xx">4xx</span></div>
|
|
718
|
+
<div class="matrix-value">${responseMatrix.total4xx}</div>
|
|
719
|
+
</div>
|
|
720
|
+
<div class="matrix-row">
|
|
721
|
+
<div class="matrix-label">Server Error <span class="matrix-badge badge-5xx">5xx</span></div>
|
|
722
|
+
<div class="matrix-value">${responseMatrix.total5xx}</div>
|
|
723
|
+
</div>
|
|
724
|
+
</div>
|
|
725
|
+
</div>
|
|
726
|
+
|
|
727
|
+
<div class="charts-grid">
|
|
728
|
+
<div class="graph-card">
|
|
729
|
+
<div class="graph-title">\u{1F4CA} Throughput Velocity & Workload Volume</div>
|
|
730
|
+
<div style="height: 280px; position: relative;">
|
|
731
|
+
<canvas id="volumeChart"></canvas>
|
|
732
|
+
</div>
|
|
733
|
+
</div>
|
|
734
|
+
|
|
735
|
+
<div class="graph-card">
|
|
736
|
+
<div class="graph-title">\u{1F504} Active Concurrency (VUs) vs. TTF Trend</div>
|
|
737
|
+
<div style="height: 280px; position: relative;">
|
|
738
|
+
<canvas id="concurrencyChart"></canvas>
|
|
739
|
+
</div>
|
|
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>
|
|
755
|
+
</div>
|
|
756
|
+
|
|
757
|
+
<div class="card">
|
|
758
|
+
<h3>Telemetry Matrix Logs</h3>
|
|
759
|
+
<table>
|
|
760
|
+
<thead>
|
|
761
|
+
<tr>
|
|
762
|
+
<th>Concurrency (Threads)</th>
|
|
763
|
+
<th>Throughput</th>
|
|
764
|
+
<th>Avg Latency</th>
|
|
765
|
+
<th>P95 Latency</th>
|
|
766
|
+
<th>Error Rate</th>
|
|
767
|
+
<th>Status</th>
|
|
768
|
+
</tr>
|
|
769
|
+
</thead>
|
|
770
|
+
<tbody>
|
|
771
|
+
${history.map((h) => {
|
|
772
|
+
const isPassed = h.apdex >= config.targetApdex && h.errorRate <= config.targetErrorRate;
|
|
773
|
+
return `<tr>
|
|
774
|
+
<td><strong>${h.threads}</strong></td>
|
|
775
|
+
<td>${h.throughput.toFixed(0)} req/sec</td>
|
|
776
|
+
<td>${h.avgLatency.toFixed(1)} ms</td>
|
|
777
|
+
<td>${h.p95Latency.toFixed(1)} ms</td>
|
|
778
|
+
<td>${(h.errorRate * 100).toFixed(2)}%</td>
|
|
779
|
+
<td><span class="badge ${isPassed ? "badge-success" : "badge-fail"}">${isPassed ? "SLO PASS" : "SLO BREACH"}</span></td>
|
|
780
|
+
</tr>`;
|
|
781
|
+
}).join("")}
|
|
782
|
+
</tbody>
|
|
783
|
+
</table>
|
|
784
|
+
</div>
|
|
785
|
+
${liveAdjusterHtml}
|
|
786
|
+
${rightSizingHtml}
|
|
787
|
+
${logClustersHtml}
|
|
788
|
+
</div>
|
|
789
|
+
|
|
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
|
+
|
|
800
|
+
const labels = ${JSON.stringify(labels)};
|
|
801
|
+
const baseThroughput = ${cards.throughput};
|
|
802
|
+
const baseLatency = ${cards.ttfb};
|
|
803
|
+
|
|
804
|
+
// Live Concurrency Adjuster interactive client logic
|
|
805
|
+
const slider = document.getElementById('concurrencySlider');
|
|
806
|
+
const sliderVal = document.getElementById('sliderValue');
|
|
807
|
+
const simThroughput = document.getElementById('simThroughput');
|
|
808
|
+
const simLatency = document.getElementById('simLatency');
|
|
809
|
+
|
|
810
|
+
slider.addEventListener('input', (e) => {
|
|
811
|
+
const val = parseInt(e.target.value, 10);
|
|
812
|
+
sliderVal.textContent = val;
|
|
813
|
+
const factor = val / ${config.threads || 10};
|
|
814
|
+
simThroughput.textContent = (baseThroughput * Math.min(factor, 2.5)).toFixed(0) + '/s';
|
|
815
|
+
simLatency.textContent = (baseLatency * Math.pow(factor, 0.8)).toFixed(1) + 'ms';
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
new Chart(document.getElementById('volumeChart'), {
|
|
819
|
+
type: 'bar',
|
|
820
|
+
data: {
|
|
821
|
+
labels: labels,
|
|
822
|
+
datasets: [
|
|
823
|
+
{
|
|
824
|
+
label: 'Successful Requests',
|
|
825
|
+
data: ${JSON.stringify(successRequestsData)},
|
|
826
|
+
backgroundColor: '#10b981',
|
|
827
|
+
stack: 'requests',
|
|
828
|
+
barPercentage: 0.95,
|
|
829
|
+
categoryPercentage: 0.95
|
|
830
|
+
},
|
|
831
|
+
{
|
|
832
|
+
label: 'Failed Workloads',
|
|
833
|
+
data: ${JSON.stringify(failedWorkloadsData)},
|
|
834
|
+
backgroundColor: '#ef4444',
|
|
835
|
+
stack: 'requests',
|
|
836
|
+
barPercentage: 0.95,
|
|
837
|
+
categoryPercentage: 0.95
|
|
838
|
+
}
|
|
839
|
+
]
|
|
840
|
+
},
|
|
841
|
+
options: {
|
|
842
|
+
responsive: true,
|
|
843
|
+
maintainAspectRatio: false,
|
|
844
|
+
plugins: {
|
|
845
|
+
legend: { position: 'top', labels: { color: '#94a3b8', font: { size: 11 } } }
|
|
846
|
+
},
|
|
847
|
+
scales: {
|
|
848
|
+
x: { grid: { color: '#1e293b' }, ticks: { color: '#64748b' } },
|
|
849
|
+
y: { stacked: true, grid: { color: '#1e293b' }, ticks: { color: '#64748b' } }
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
new Chart(document.getElementById('concurrencyChart'), {
|
|
855
|
+
type: 'line',
|
|
856
|
+
data: {
|
|
857
|
+
labels: labels,
|
|
858
|
+
datasets: [
|
|
859
|
+
{
|
|
860
|
+
label: 'Active VU Threads',
|
|
861
|
+
data: ${JSON.stringify(activeThreadsData)},
|
|
862
|
+
borderColor: '#38bdf8',
|
|
863
|
+
backgroundColor: 'rgba(56, 189, 248, 0.1)',
|
|
864
|
+
borderWidth: 2.5,
|
|
865
|
+
pointRadius: 4,
|
|
866
|
+
fill: true,
|
|
867
|
+
yAxisID: 'yThreads'
|
|
868
|
+
},
|
|
869
|
+
{
|
|
870
|
+
label: 'Time-to-Failure (TTF) Trend',
|
|
871
|
+
data: ${JSON.stringify(ttfTrendData)},
|
|
872
|
+
borderColor: '#f43f5e',
|
|
873
|
+
borderWidth: 2,
|
|
874
|
+
borderDash: [5, 5],
|
|
875
|
+
pointRadius: 3,
|
|
876
|
+
fill: false,
|
|
877
|
+
yAxisID: 'yTtf'
|
|
878
|
+
}
|
|
879
|
+
]
|
|
880
|
+
},
|
|
881
|
+
options: {
|
|
882
|
+
responsive: true,
|
|
883
|
+
maintainAspectRatio: false,
|
|
884
|
+
plugins: {
|
|
885
|
+
legend: { position: 'top', labels: { color: '#94a3b8', font: { size: 11 } } }
|
|
886
|
+
},
|
|
887
|
+
scales: {
|
|
888
|
+
x: { grid: { color: '#1e293b' }, ticks: { color: '#64748b' } },
|
|
889
|
+
y_threads: { position: 'left', grid: { color: '#1e293b' }, ticks: { color: '#38bdf8' }, title: { display: true, text: 'VUs / Threads', color: '#38bdf8' } },
|
|
890
|
+
y_ttf: { position: 'right', grid: { drawOnChartArea: false }, ticks: { color: '#f43f5e' }, title: { display: true, text: 'TTF Index / Latency ms', color: '#f43f5e' } }
|
|
891
|
+
}
|
|
892
|
+
}
|
|
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
|
+
});
|
|
939
|
+
</script></body></html>`;
|
|
512
940
|
try {
|
|
513
941
|
fs.writeFileSync(config.outputHtml, htmlContent, "utf-8");
|
|
514
|
-
console.log(
|
|
942
|
+
console.log(`\u{1F4CA} Standalone Dashboard exported successfully to: ${path.resolve(config.outputHtml)}`);
|
|
515
943
|
} catch (err) {
|
|
516
|
-
console.error(
|
|
944
|
+
console.error(`\u274C Failed to write HTML output dashboard: ${err}`);
|
|
517
945
|
}
|
|
518
946
|
}
|
|
519
947
|
function printUsage() {
|
|
520
|
-
console.log(
|
|
948
|
+
console.log(`Blaze Core Performance Test CLI Engine
|
|
949
|
+
Options:
|
|
950
|
+
--threads <count> | --duration <seconds> | --agentic | --cluster-logs | --correlate`);
|
|
521
951
|
}
|
|
522
952
|
main().catch(console.error);
|
|
Binary file
|