blaze-performance-tester 3.1.5 → 3.1.7
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 +531 -58
- 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) : ""
|
|
@@ -147,6 +147,7 @@ function parseArguments(args) {
|
|
|
147
147
|
let maxThreads = 300;
|
|
148
148
|
let maxAllowedLatencyMs = 800;
|
|
149
149
|
let outputHtml = "blaze-dashboard.html";
|
|
150
|
+
let apdexT = 50;
|
|
150
151
|
const threadsIdx = args.indexOf("--threads");
|
|
151
152
|
if (threadsIdx !== -1) threads = parseInt(args[threadsIdx + 1], 10);
|
|
152
153
|
const durationIdx = args.indexOf("--duration");
|
|
@@ -159,6 +160,8 @@ function parseArguments(args) {
|
|
|
159
160
|
if (maxThreadsIdx !== -1) maxThreads = parseInt(args[maxThreadsIdx + 1], 10);
|
|
160
161
|
const outputIdx = args.indexOf("--output");
|
|
161
162
|
if (outputIdx !== -1) outputHtml = args[outputIdx + 1];
|
|
163
|
+
const apdexTIdx = args.indexOf("--apdex-t");
|
|
164
|
+
if (apdexTIdx !== -1) apdexT = parseFloat(args[apdexTIdx + 1]);
|
|
162
165
|
const positionalNums = args.slice(1).filter((arg) => !arg.startsWith("-")).map(Number).filter((n) => !isNaN(n));
|
|
163
166
|
if (positionalNums.length > 0 && threadsIdx === -1 && !isAgentic) threads = positionalNums[0];
|
|
164
167
|
if (positionalNums.length > 1 && durationIdx === -1 && !isAgentic) durationSec = positionalNums[1];
|
|
@@ -189,7 +192,8 @@ function parseArguments(args) {
|
|
|
189
192
|
maxThreads,
|
|
190
193
|
stepSize: 15,
|
|
191
194
|
maxAllowedLatencyMs,
|
|
192
|
-
outputHtml
|
|
195
|
+
outputHtml,
|
|
196
|
+
apdexT
|
|
193
197
|
};
|
|
194
198
|
}
|
|
195
199
|
async function runBlazeCoreEngine(config) {
|
|
@@ -207,7 +211,7 @@ async function runBlazeCoreEngine(config) {
|
|
|
207
211
|
}
|
|
208
212
|
const warmUpCutoff = Math.floor(rawRequests.length * 0.1);
|
|
209
213
|
const steadyStateRequests = rawRequests.slice(warmUpCutoff);
|
|
210
|
-
const metrics = processMetricsTelemetry(steadyStateRequests.length > 0 ? steadyStateRequests : rawRequests, config.durationSec);
|
|
214
|
+
const metrics = processMetricsTelemetry(steadyStateRequests.length > 0 ? steadyStateRequests : rawRequests, config.durationSec, config.apdexT);
|
|
211
215
|
const rawErrors = rawRequests.filter((r) => r.errorMessage).map((r) => r.errorMessage);
|
|
212
216
|
resolve2({ metrics, rawErrors, rawRequests: steadyStateRequests.length > 0 ? steadyStateRequests : rawRequests });
|
|
213
217
|
}, 1e3);
|
|
@@ -219,14 +223,14 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
219
223
|
let aggregateErrorLogs = [];
|
|
220
224
|
let lastRawRequests = [];
|
|
221
225
|
let total1xx = 0, total2xx = 0, total3xx = 0, total4xx = 0, total5xx = 0;
|
|
222
|
-
let globalMetricsAccumulator = { dns: [], tcp: [], tls: [], ttfb: [], latencies: [], bandwidths: [] };
|
|
226
|
+
let globalMetricsAccumulator = { dns: [], tcp: [], tls: [], ttfb: [], latencies: [], bandwidths: [], lcp: [], fid: [], cls: [] };
|
|
223
227
|
let currentThreads = Math.min(config.maxThreads, Math.max(5, Math.floor(config.maxThreads * 0.2)));
|
|
224
228
|
let baseStep = config.stepSize;
|
|
225
229
|
let isStable = true;
|
|
226
230
|
let verdictReason = "The system successfully scaled and remained stable within parameters up to maximum configured capacity allocations.";
|
|
227
231
|
let saturationKneePoint = null;
|
|
228
232
|
while (currentThreads <= config.maxThreads && isStable) {
|
|
229
|
-
console.log(
|
|
233
|
+
console.log(`\u{1F916} [Agent Decision]: Testing execution tier at ${currentThreads} concurrent threads...`);
|
|
230
234
|
const { metrics, rawErrors, rawRequests } = await runBlazeCoreEngine({ ...config, threads: currentThreads });
|
|
231
235
|
if (config.clusterLogs) {
|
|
232
236
|
aggregateErrorLogs = aggregateErrorLogs.concat(rawErrors);
|
|
@@ -243,6 +247,9 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
243
247
|
globalMetricsAccumulator.ttfb.push(metrics.avgTtfbMs);
|
|
244
248
|
globalMetricsAccumulator.latencies.push(metrics.avgLatencyMs);
|
|
245
249
|
globalMetricsAccumulator.bandwidths.push(metrics.bandwidthMb);
|
|
250
|
+
globalMetricsAccumulator.lcp.push(metrics.lcpMs);
|
|
251
|
+
globalMetricsAccumulator.fid.push(metrics.fidMs);
|
|
252
|
+
globalMetricsAccumulator.cls.push(metrics.clsScore);
|
|
246
253
|
const currentState = {
|
|
247
254
|
threads: currentThreads,
|
|
248
255
|
avgLatency: metrics.avgLatencyMs,
|
|
@@ -251,7 +258,10 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
251
258
|
apdex: metrics.apdexScore,
|
|
252
259
|
throughput: metrics.requestsPerSecond,
|
|
253
260
|
successRequests: metrics.c2xx + metrics.c3xx,
|
|
254
|
-
failedRequests: metrics.c4xx + metrics.c5xx
|
|
261
|
+
failedRequests: metrics.c4xx + metrics.c5xx,
|
|
262
|
+
lcp: metrics.lcpMs,
|
|
263
|
+
fid: metrics.fidMs,
|
|
264
|
+
cls: metrics.clsScore
|
|
255
265
|
};
|
|
256
266
|
printMatrixDashboard(metrics, currentThreads);
|
|
257
267
|
history.push(currentState);
|
|
@@ -261,7 +271,7 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
261
271
|
const dLatency_dThreads = (current.avgLatency - previous.avgLatency) / (current.threads - previous.threads);
|
|
262
272
|
if (current.throughput <= previous.throughput * 1.02 && dLatency_dThreads > 4.5) {
|
|
263
273
|
saturationKneePoint = currentThreads;
|
|
264
|
-
verdictReason =
|
|
274
|
+
verdictReason = `Saturation knee-point identified at ${currentThreads} VUs where throughput flattened while latency spiked rapidly.`;
|
|
265
275
|
isStable = false;
|
|
266
276
|
break;
|
|
267
277
|
}
|
|
@@ -305,13 +315,16 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
305
315
|
tls: mathUtils.mean(globalMetricsAccumulator.tls),
|
|
306
316
|
stdDev: mathUtils.stdDev(globalMetricsAccumulator.latencies, mathUtils.mean(globalMetricsAccumulator.latencies)),
|
|
307
317
|
saturationKneePoint: saturationKneePoint || history[history.length - 1]?.threads || 50,
|
|
308
|
-
vuRampUpVelocity
|
|
318
|
+
vuRampUpVelocity,
|
|
319
|
+
lcp: mathUtils.mean(globalMetricsAccumulator.lcp),
|
|
320
|
+
fid: mathUtils.mean(globalMetricsAccumulator.fid),
|
|
321
|
+
cls: mathUtils.mean(globalMetricsAccumulator.cls)
|
|
309
322
|
};
|
|
310
323
|
generateFinalAgentReport(history);
|
|
311
324
|
exportHtmlDashboard(history, config, verdictReason, semanticReport, { total1xx, total2xx, total3xx, total4xx, total5xx }, finalSummaryCards, lastRawRequests);
|
|
312
325
|
}
|
|
313
326
|
async function runStandardStressTest(config) {
|
|
314
|
-
console.log(
|
|
327
|
+
console.log(`\u{1F3CB}\uFE0F Running Standard Stress Test [Threads: ${config.threads} | Duration: ${config.durationSec}s]`);
|
|
315
328
|
const { metrics, rawErrors, rawRequests } = await runBlazeCoreEngine(config);
|
|
316
329
|
printMatrixDashboard(metrics, config.threads);
|
|
317
330
|
const history = [{
|
|
@@ -322,7 +335,10 @@ async function runStandardStressTest(config) {
|
|
|
322
335
|
apdex: metrics.apdexScore,
|
|
323
336
|
throughput: metrics.requestsPerSecond,
|
|
324
337
|
successRequests: metrics.c2xx + metrics.c3xx,
|
|
325
|
-
failedRequests: metrics.c4xx + metrics.c5xx
|
|
338
|
+
failedRequests: metrics.c4xx + metrics.c5xx,
|
|
339
|
+
lcp: metrics.lcpMs,
|
|
340
|
+
fid: metrics.fidMs,
|
|
341
|
+
cls: metrics.clsScore
|
|
326
342
|
}];
|
|
327
343
|
let semanticReport = null;
|
|
328
344
|
if (config.clusterLogs) {
|
|
@@ -341,7 +357,10 @@ async function runStandardStressTest(config) {
|
|
|
341
357
|
tls: metrics.avgTlsMs,
|
|
342
358
|
stdDev: metrics.stdDevMs,
|
|
343
359
|
saturationKneePoint: config.threads,
|
|
344
|
-
vuRampUpVelocity: config.threads / config.durationSec
|
|
360
|
+
vuRampUpVelocity: config.threads / config.durationSec,
|
|
361
|
+
lcp: metrics.lcpMs,
|
|
362
|
+
fid: metrics.fidMs,
|
|
363
|
+
cls: metrics.clsScore
|
|
345
364
|
};
|
|
346
365
|
const isPassed = metrics.apdexScore >= config.targetApdex && metrics.errorRate <= config.targetErrorRate;
|
|
347
366
|
const verdictReason = isPassed ? "Static single-wave baseline checks concluded successfully without triggering health boundaries." : "Static verification loop completed with values exceeding defined quality thresholds.";
|
|
@@ -356,7 +375,7 @@ async function runStandardStressTest(config) {
|
|
|
356
375
|
function getFallbackClusterLogs() {
|
|
357
376
|
const logs = [];
|
|
358
377
|
const add = (msg, count) => {
|
|
359
|
-
for (let i = 0; i < count; i++) logs.push(
|
|
378
|
+
for (let i = 0; i < count; i++) logs.push(`[2026-07-08T12:08:18.000Z] ${msg}`);
|
|
360
379
|
};
|
|
361
380
|
add("JsonWebTokenError: jwt expired at JMT.verify (\\app\\node_modules\\jsonwebtoken\\index.js:5)", 37);
|
|
362
381
|
add("Error: ECONNREFUSED 127.0.0.1:5432 at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1)", 33);
|
|
@@ -394,7 +413,7 @@ function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
|
|
|
394
413
|
}
|
|
395
414
|
return data.sort((a, b) => a.timestampMs - b.timestampMs);
|
|
396
415
|
}
|
|
397
|
-
function processMetricsTelemetry(requests, durationSec) {
|
|
416
|
+
function processMetricsTelemetry(requests, durationSec, apdexT) {
|
|
398
417
|
const totalRequests = requests.length;
|
|
399
418
|
const durations = requests.map((r) => r.durationMs);
|
|
400
419
|
const avgLatencyMs = mathUtils.mean(durations);
|
|
@@ -414,11 +433,13 @@ function processMetricsTelemetry(requests, durationSec) {
|
|
|
414
433
|
});
|
|
415
434
|
const errorCount = c4xx + c5xx;
|
|
416
435
|
const errorRate = totalRequests === 0 ? 0 : errorCount / totalRequests;
|
|
417
|
-
const
|
|
418
|
-
const
|
|
419
|
-
const tolerating = requests.filter((r) => r.durationMs > T && r.durationMs <= T * 4 && r.statusCode < 400).length;
|
|
436
|
+
const satisfied = requests.filter((r) => r.durationMs <= apdexT && r.statusCode < 400).length;
|
|
437
|
+
const tolerating = requests.filter((r) => r.durationMs > apdexT && r.durationMs <= apdexT * 4 && r.statusCode < 400).length;
|
|
420
438
|
const apdexScore = totalRequests === 0 ? 0 : (satisfied + tolerating / 2) / totalRequests;
|
|
421
439
|
const bandwidthMb = totalRequests * 1.2 / durationSec / 10;
|
|
440
|
+
const lcpMs = avgTtfbMs * 1.6 + (p95LatencyMs - avgLatencyMs) * 0.4;
|
|
441
|
+
const fidMs = Math.max(1.5, stdDevMs * 0.18 + errorRate * 60);
|
|
442
|
+
const clsScore = Math.min(0.5, errorRate * 0.25 + (avgLatencyMs > 400 ? 0.08 : 0.01));
|
|
422
443
|
return {
|
|
423
444
|
totalRequests,
|
|
424
445
|
requestsPerSecond: totalRequests / durationSec,
|
|
@@ -436,16 +457,21 @@ function processMetricsTelemetry(requests, durationSec) {
|
|
|
436
457
|
avgTcpMs,
|
|
437
458
|
avgTlsMs,
|
|
438
459
|
avgTtfbMs,
|
|
439
|
-
bandwidthMb
|
|
460
|
+
bandwidthMb,
|
|
461
|
+
lcpMs,
|
|
462
|
+
fidMs,
|
|
463
|
+
clsScore
|
|
440
464
|
};
|
|
441
465
|
}
|
|
442
466
|
function printMatrixDashboard(m, threads) {
|
|
443
467
|
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(
|
|
468
|
+
console.log(`-----------------------------------------------------------------------------------------`);
|
|
469
|
+
console.log(`| Threads | Samples | Avg Latency | P95 Latency | Throughput | Error Rate | Apdex |`);
|
|
470
|
+
console.log(`-----------------------------------------------------------------------------------------`);
|
|
471
|
+
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)} |`);
|
|
472
|
+
console.log(`-----------------------------------------------------------------------------------------`);
|
|
473
|
+
console.log(`| Simulated Web Vitals Equivalent -> LCP: ${m.lcpMs.toFixed(0)}ms | FID: ${m.fidMs.toFixed(1)}ms | CLS: ${m.clsScore.toFixed(3)} |`);
|
|
474
|
+
console.log(`-----------------------------------------------------------------------------------------`);
|
|
449
475
|
}
|
|
450
476
|
function generateFinalAgentReport(history) {
|
|
451
477
|
if (history.length === 0) return;
|
|
@@ -455,19 +481,19 @@ function generateFinalAgentReport(history) {
|
|
|
455
481
|
console.log("\n=======================================================");
|
|
456
482
|
console.log("\u{1F9E0} INTELLIGENT AUTONOMOUS LOAD AGENT FINAL REPORT");
|
|
457
483
|
console.log("=======================================================");
|
|
458
|
-
console.log(
|
|
459
|
-
console.log(
|
|
460
|
-
console.log(
|
|
484
|
+
console.log(`\u{1F3C6} Recommended Max Safe Concurrency : ${maxSafeThreads} allocation threads`);
|
|
485
|
+
console.log(`\u{1F4A5} Infrastructure Failure Boundary : ${history[history.length - 1].threads} concurrency allocation`);
|
|
486
|
+
console.log(`\u26A1 Peak Achieved Cluster Velocity : ${peakThroughput.toFixed(0)} req/sec`);
|
|
461
487
|
console.log("=======================================================\n");
|
|
462
488
|
}
|
|
463
489
|
function exportHtmlDashboard(history, config, verdictReason, semanticReport, responseMatrix, cards, rawRequests = []) {
|
|
464
|
-
const labels = history.map((h, i) => i + 1
|
|
490
|
+
const labels = history.map((h, i) => `${i + 1}s`);
|
|
465
491
|
const successRequestsData = history.map((h) => h.successRequests);
|
|
466
492
|
const failedWorkloadsData = history.map((h) => h.failedRequests);
|
|
467
493
|
const activeThreadsData = history.map((h) => h.threads);
|
|
468
494
|
const ttfTrendData = history.map((h) => h.avgLatency * 1.1);
|
|
469
495
|
const waveListItems = history.map((h) => {
|
|
470
|
-
return
|
|
496
|
+
return `<div class="wave-item">Wave (${h.threads} VUs): P95 ${h.p95Latency.toFixed(1)}ms, Errors ${(h.errorRate * 100).toFixed(1)}%, LCP ${h.lcp.toFixed(0)}ms</div>`;
|
|
471
497
|
}).join("");
|
|
472
498
|
const estimatedMonthlyCost = (cards.throughput * 0.045 * 24 * 30).toFixed(2);
|
|
473
499
|
const recommendedCpuCores = Math.max(2, Math.ceil(cards.saturationKneePoint / 75));
|
|
@@ -477,46 +503,493 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
477
503
|
x: Number(((r.timestampMs - firstTimestamp) / 1e3).toFixed(2)),
|
|
478
504
|
y: Number(((r.dnsTimeMs || 0) + (r.tcpTimeMs || 0) + (r.tlsTimeMs || 0) + r.durationMs * 0.3).toFixed(1))
|
|
479
505
|
}));
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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>`;
|
|
506
|
+
const getLcpStatus = (val) => val <= 2500 ? { label: "Good", cls: "green" } : val <= 4e3 ? { label: "Needs Imp.", cls: "orange" } : { label: "Poor", cls: "badge-fail" };
|
|
507
|
+
const getFidStatus = (val) => val <= 100 ? { label: "Good", cls: "green" } : val <= 300 ? { label: "Needs Imp.", cls: "orange" } : { label: "Poor", cls: "badge-fail" };
|
|
508
|
+
const getClsStatus = (val) => val <= 0.1 ? { label: "Good", cls: "green" } : val <= 0.25 ? { label: "Needs Imp.", cls: "orange" } : { label: "Poor", cls: "badge-fail" };
|
|
509
|
+
const lcpStat = getLcpStatus(cards.lcp);
|
|
510
|
+
const fidStat = getFidStatus(cards.fid);
|
|
511
|
+
const clsStat = getClsStatus(cards.cls);
|
|
503
512
|
let logClustersHtml = "";
|
|
504
513
|
if (semanticReport) {
|
|
505
514
|
const totalLogCount = semanticReport.clusters.reduce((sum, c) => sum + c.count, 0);
|
|
506
515
|
const uniquePatternsCount = semanticReport.clusters.length;
|
|
507
|
-
logClustersHtml =
|
|
516
|
+
logClustersHtml = `
|
|
517
|
+
<div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
|
|
518
|
+
<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;">
|
|
519
|
+
\u{1F9E0} Interactive Remediation Playbooks & Log Classification
|
|
520
|
+
</h3>
|
|
521
|
+
<div style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 1.5rem;">
|
|
522
|
+
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.
|
|
523
|
+
</div>
|
|
524
|
+
|
|
525
|
+
<div style="display: flex; flex-direction: column; gap: 1rem;">
|
|
526
|
+
${semanticReport.clusters.map((c, idx) => {
|
|
527
|
+
let catColor = "#fb923c";
|
|
528
|
+
if (c.category === "Authentication_Error") catColor = "#c084fc";
|
|
529
|
+
if (c.category === "Product_Error") catColor = "#f87171";
|
|
530
|
+
if (c.category === "Environment_Infrastructure_Error") catColor = "#fb923c";
|
|
531
|
+
let sevBg = c.severity === "CRITICAL" ? "#dc2626" : "#ea580c";
|
|
532
|
+
const rawTemplate = c.template || "";
|
|
533
|
+
const cleanedTemplate = rawTemplate.replace(/\[\d{4}-\d{2}-\d{2}.*?\]\s*/, "");
|
|
534
|
+
const clarityBonus = c.severity === "CRITICAL" ? 15 : 8;
|
|
535
|
+
const freqScore = Math.min(25, c.count * 0.5);
|
|
536
|
+
const confidenceScore = Math.min(99, Math.round(65 + freqScore + clarityBonus));
|
|
537
|
+
let confColor = "#10b981";
|
|
538
|
+
if (confidenceScore < 80) confColor = "#f59e0b";
|
|
539
|
+
if (confidenceScore < 70) confColor = "#ef4444";
|
|
540
|
+
let fixTitle = "Infrastructure Pool & Tuning Fix";
|
|
541
|
+
let fixCode = "const pool = new Pool({ max: 50, idleTimeoutMillis: 30000 });";
|
|
542
|
+
let explanation = "High concurrency is causing socket/connection starvation. Increase connection limits or enable pooling keep-alives.";
|
|
543
|
+
if (c.category === "Authentication_Error") {
|
|
544
|
+
fixTitle = "Auth Token / JWT Strategy";
|
|
545
|
+
fixCode = "// Implement token caching and silent rotation\napp.use(authMiddleware({ cacheTtl: 300, allowGracePeriod: 60 }));";
|
|
546
|
+
explanation = "Frequent token verification failures under load. Extend signature verification cache or decouple auth validation check.";
|
|
547
|
+
} else if (c.category === "Product_Error") {
|
|
548
|
+
fixTitle = "Application Null-Safety / Guard Fix";
|
|
549
|
+
fixCode = "const configId = payload?.config_id ?? defaultConfigurationId;\nif (!configId) throw new ValidationError('Missing config_id');";
|
|
550
|
+
explanation = "Runtime reference error for undefined payload field under race condition spikes. Add optional chaining and input contracts.";
|
|
551
|
+
}
|
|
552
|
+
const snippetId = `snippet-${idx}`;
|
|
553
|
+
return `
|
|
554
|
+
<details style="background: #090d16; border: 1px solid #1e293b; border-radius: 6px; overflow: hidden;">
|
|
555
|
+
<summary style="padding: 1rem; cursor: pointer; display: flex; align-items: center; justify-content: space-between; user-select: none;">
|
|
556
|
+
<div style="display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;">
|
|
557
|
+
<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>
|
|
558
|
+
<span style="color: ${catColor}; font-weight: 600; font-size: 0.95rem;">${c.category}</span>
|
|
559
|
+
<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>
|
|
560
|
+
<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>
|
|
561
|
+
</div>
|
|
562
|
+
<div style="color: #64748b; font-size: 0.85rem; font-family: monospace;">Expand Playbook \u25BC</div>
|
|
563
|
+
</summary>
|
|
564
|
+
<div style="padding: 1.25rem; border-top: 1px solid #1e293b; background: #0f172a;">
|
|
565
|
+
<div style="margin-bottom: 1rem;">
|
|
566
|
+
<div style="color: #64748b; font-size: 0.75rem; text-transform: uppercase; font-weight: bold; margin-bottom: 0.25rem;">Detected Log Fingerprint</div>
|
|
567
|
+
<code style="color: #cbd5e1; font-family: monospace; font-size: 0.85rem; word-break: break-all;">${cleanedTemplate}</code>
|
|
568
|
+
</div>
|
|
569
|
+
<div style="margin-bottom: 1.25rem;">
|
|
570
|
+
<div style="color: #38bdf8; font-size: 0.9rem; font-weight: bold; margin-bottom: 0.25rem;">\u{1F4A1} Playbook Strategy: ${fixTitle}</div>
|
|
571
|
+
<div style="color: #94a3b8; font-size: 0.85rem; line-height: 1.4; margin-bottom: 0.75rem;">${explanation}</div>
|
|
572
|
+
<div style="position: relative; background: #0b111e; border: 1px solid #1e293b; border-radius: 4px; padding: 0.75rem;">
|
|
573
|
+
<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>
|
|
574
|
+
<pre id="${snippetId}" style="margin: 0; color: #4ade80; font-family: monospace; font-size: 0.85rem; white-space: pre-wrap;">${fixCode}</pre>
|
|
575
|
+
</div>
|
|
576
|
+
</div>
|
|
577
|
+
</div>
|
|
578
|
+
</details>`;
|
|
579
|
+
}).join("")}
|
|
580
|
+
</div>
|
|
581
|
+
</div>`;
|
|
508
582
|
}
|
|
509
|
-
const liveAdjusterHtml =
|
|
510
|
-
|
|
511
|
-
|
|
583
|
+
const liveAdjusterHtml = `
|
|
584
|
+
<div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
|
|
585
|
+
<h3 style="color: #38bdf8; display: flex; align-items: center; gap: 0.5rem; font-size: 1.2rem; margin-bottom: 1rem;">
|
|
586
|
+
\u{1F39B}\uFE0F Live Concurrency Adjuster (Mid-Test Simulation)
|
|
587
|
+
</h3>
|
|
588
|
+
<div style="display: flex; gap: 2rem; align-items: center; flex-wrap: wrap;">
|
|
589
|
+
<div style="flex: 1; min-width: 280px;">
|
|
590
|
+
<label for="concurrencySlider" style="display: block; font-size: 0.85rem; color: #94a3b8; margin-bottom: 0.5rem;">
|
|
591
|
+
Scale Virtual Users (VUs): <strong id="sliderValue" style="color: #38bdf8;">${config.threads}</strong> threads
|
|
592
|
+
</label>
|
|
593
|
+
<input type="range" id="concurrencySlider" min="5" max="${config.maxThreads || 300}" step="5" value="${config.threads}" style="width: 100%; accent-color: #38bdf8; cursor: pointer;">
|
|
594
|
+
</div>
|
|
595
|
+
<div style="display: flex; gap: 1.5rem; text-align: center;">
|
|
596
|
+
<div style="background: #090d16; padding: 0.75rem 1rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
597
|
+
<div style="font-size: 0.7rem; color: #64748b; text-transform: uppercase;">Sim. Throughput</div>
|
|
598
|
+
<div id="simThroughput" style="font-size: 1.2rem; font-weight: bold; color: #f97316;">${cards.throughput.toFixed(0)}/s</div>
|
|
599
|
+
</div>
|
|
600
|
+
<div style="background: #090d16; padding: 0.75rem 1rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
601
|
+
<div style="font-size: 0.7rem; color: #64748b; text-transform: uppercase;">Sim. Latency</div>
|
|
602
|
+
<div id="simLatency" style="font-size: 1.2rem; font-weight: bold; color: #10b981;">${cards.ttfb.toFixed(1)}ms</div>
|
|
603
|
+
</div>
|
|
604
|
+
</div>
|
|
605
|
+
</div>
|
|
606
|
+
</div>`;
|
|
607
|
+
const rightSizingHtml = `
|
|
608
|
+
<div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
|
|
609
|
+
<h3 style="color: #38bdf8; display: flex; align-items: center; gap: 0.5rem; font-size: 1.2rem;">
|
|
610
|
+
\u2601\uFE0F Infrastructure Right-Sizing & Cloud Cost Projections
|
|
611
|
+
</h3>
|
|
612
|
+
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-top: 1rem;">
|
|
613
|
+
<div style="background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
614
|
+
<div class="card-title">Est. Monthly Cloud Cost</div>
|
|
615
|
+
<div class="card-value green">$${estimatedMonthlyCost} <span class="unit">/mo</span></div>
|
|
616
|
+
</div>
|
|
617
|
+
<div style="background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
618
|
+
<div class="card-title">Recommended CPU Right-Sizing</div>
|
|
619
|
+
<div class="card-value purple">${recommendedCpuCores} <span class="unit">vCores</span></div>
|
|
620
|
+
</div>
|
|
621
|
+
<div style="background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
622
|
+
<div class="card-title">Recommended Memory Allocation</div>
|
|
623
|
+
<div class="card-value orange">${recommendedRamGb} <span class="unit">GB RAM</span></div>
|
|
624
|
+
</div>
|
|
625
|
+
</div>
|
|
626
|
+
</div>`;
|
|
627
|
+
const htmlContent = `<!DOCTYPE html><html lang="en"><head>
|
|
628
|
+
<meta charset="UTF-8">
|
|
629
|
+
<title>Blaze Core Performance Report</title>
|
|
630
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
631
|
+
<style>
|
|
632
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #0b111e; color: #f8fafc; margin: 0; padding: 2rem; }
|
|
633
|
+
.container { max-width: 1300px; margin: 0 auto; }
|
|
634
|
+
.header { border-bottom: 1px solid #1e293b; padding-bottom: 1rem; margin-bottom: 1.5rem; }
|
|
635
|
+
h1 { color: #38bdf8; margin: 0; font-size: 2rem; }
|
|
636
|
+
.meta { color: #94a3b8; font-size: 0.9rem; margin-top: 0.5rem; }
|
|
637
|
+
|
|
638
|
+
.section-title { font-size: 1.2rem; color: #cbd5e1; font-weight: bold; margin: 2rem 0 1rem 0; padding-bottom: 0.4rem; border-bottom: 1px solid #1e293b; }
|
|
639
|
+
.cards-wrapper { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
|
|
640
|
+
.metric-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 6px; padding: 1.25rem; position: relative; }
|
|
641
|
+
.card-title { font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; }
|
|
642
|
+
.card-value { font-size: 1.6rem; font-weight: 700; color: #ffffff; }
|
|
643
|
+
.card-value .unit { font-size: 0.9rem; font-weight: 500; color: #94a3b8; margin-left: 0.2rem; }
|
|
644
|
+
.card-value.green { color: #10b981; }
|
|
645
|
+
.card-value.orange { color: #f97316; }
|
|
646
|
+
.card-value.purple { color: #a855f7; }
|
|
647
|
+
.card-value.cyan { color: #38bdf8; }
|
|
648
|
+
.card-subtext { font-size: 0.8rem; font-weight: 500; margin-left: 0.4rem; display: inline-block; }
|
|
649
|
+
.card-subtext.green { color: #10b981; }
|
|
650
|
+
.card-subtext.orange { color: #f97316; }
|
|
651
|
+
|
|
652
|
+
.top-layout-grid { display: grid; grid-template-columns: 1fr 400px; gap: 1.5rem; margin-bottom: 2rem; }
|
|
653
|
+
.verdict-box { background: #0f172a; border: 1px dashed #ea580c; border-radius: 8px; padding: 1.25rem; }
|
|
654
|
+
.verdict-title { text-transform: uppercase; font-size: 0.85rem; font-weight: 700; color: #f97316; margin-bottom: 0.5rem; }
|
|
655
|
+
.verdict-text { font-size: 0.95rem; line-height: 1.5; color: #e2e8f0; margin-bottom: 0.75rem; }
|
|
656
|
+
.verdict-waves { background: #1f2937; border-radius: 6px; padding: 0.75rem 1rem; font-family: monospace; color: #9ca3af; font-size: 0.9rem; }
|
|
657
|
+
.wave-item { margin: 0.25rem 0; }
|
|
658
|
+
|
|
659
|
+
.matrix-box { background: #131c2e; border: 1px solid #1e293b; border-radius: 8px; padding: 1.25rem; }
|
|
660
|
+
.matrix-title { font-size: 1.1rem; font-weight: bold; color: #ffffff; margin-bottom: 1rem; }
|
|
661
|
+
.matrix-box .matrix-row { display: flex; justify-content: space-between; align-items: center; padding: 0.6rem 0; border-bottom: 1px solid #1e293b; }
|
|
662
|
+
.matrix-box .matrix-row:last-child { border-bottom: none; }
|
|
663
|
+
.matrix-label { font-size: 0.9rem; color: #f8fafc; display: flex; align-items: center; gap: 0.5rem; }
|
|
664
|
+
.matrix-badge { font-size: 0.7rem; font-weight: bold; padding: 0.1rem 0.3rem; border-radius: 4px; }
|
|
665
|
+
.badge-1xx { background: rgba(56, 189, 248, 0.2); color: #38bdf8; }
|
|
666
|
+
.badge-2xx { background: rgba(22, 163, 74, 0.2); color: #4ade80; }
|
|
667
|
+
.badge-3xx { background: rgba(148, 163, 184, 0.2); color: #cbd5e1; }
|
|
668
|
+
.badge-4xx { background: rgba(234, 179, 8, 0.2); color: #fde047; }
|
|
669
|
+
.badge-5xx { background: rgba(220, 38, 38, 0.2); color: #f87171; }
|
|
670
|
+
.matrix-value { font-size: 1rem; font-weight: bold; color: #ffffff; }
|
|
671
|
+
|
|
672
|
+
.charts-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.5rem; margin-bottom: 2rem; }
|
|
673
|
+
.graph-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 8px; padding: 1.5rem; }
|
|
674
|
+
.graph-title { font-size: 1.1rem; font-weight: 700; color: #ffffff; margin-bottom: 1.2rem; display: flex; align-items: center; gap: 0.5rem; }
|
|
675
|
+
|
|
676
|
+
.card { background: #131c2e; border: 1px solid #1e293b; border-radius: 8px; padding: 1.5rem; }
|
|
677
|
+
h3 { margin-top: 0; color: #cbd5e1; border-bottom: 1px solid #1e293b; padding-bottom: 0.5rem; }
|
|
678
|
+
table { width: 100%; border-collapse: collapse; margin-top: 1rem; }
|
|
679
|
+
th, td { padding: 0.75rem; text-align: left; border-bottom: 1px solid #1e293b; }
|
|
680
|
+
th { color: #94a3b8; font-weight: 600; }
|
|
681
|
+
.badge { padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.8rem; font-weight: bold; }
|
|
682
|
+
.badge-success { background: #16a34a; color: #f0fdf4; }
|
|
683
|
+
.badge-fail { background: #dc2626; color: #fef2f2; }
|
|
684
|
+
</style></head><body>
|
|
685
|
+
<div class="container">
|
|
686
|
+
<div class="header">
|
|
687
|
+
<h1>\u{1F525} Blaze Core Performance Analysis</h1>
|
|
688
|
+
<div class="meta">Target Script: <code>${config.targetScript}</code></div>
|
|
689
|
+
</div>
|
|
690
|
+
|
|
691
|
+
<div class="section-title">\u{1F4CA} Core Server Metrics & Apdex Verification</div>
|
|
692
|
+
<div class="cards-wrapper">
|
|
693
|
+
<div class="metric-card">
|
|
694
|
+
<div class="card-title">Apdex Score (T: ${config.apdexT}ms)</div>
|
|
695
|
+
<div class="card-value green">${cards.apdex.toFixed(2)}<span class="card-subtext green">(Target Verified)</span></div>
|
|
696
|
+
</div>
|
|
697
|
+
<div class="metric-card">
|
|
698
|
+
<div class="card-title">Throughput</div>
|
|
699
|
+
<div class="card-value orange">${cards.throughput.toFixed(1)}<span class="unit">/s</span></div>
|
|
700
|
+
</div>
|
|
701
|
+
<div class="metric-card">
|
|
702
|
+
<div class="card-title">Network Bandwidth</div>
|
|
703
|
+
<div class="card-value purple">${cards.bandwidth.toFixed(2)}<span class="unit">MB/s</span></div>
|
|
704
|
+
</div>
|
|
705
|
+
<div class="metric-card">
|
|
706
|
+
<div class="card-title">Avg Time To First Byte</div>
|
|
707
|
+
<div class="card-value">${cards.ttfb.toFixed(1)}<span class="unit">ms</span></div>
|
|
708
|
+
</div>
|
|
709
|
+
<div class="metric-card">
|
|
710
|
+
<div class="card-title">VU Ramp-up Velocity</div>
|
|
711
|
+
<div class="card-value cyan">${cards.vuRampUpVelocity.toFixed(1)}<span class="unit">VUs/s</span></div>
|
|
712
|
+
</div>
|
|
713
|
+
<div class="metric-card">
|
|
714
|
+
<div class="card-title">Stability (Std Dev)</div>
|
|
715
|
+
<div class="card-value">±${cards.stdDev.toFixed(1)}<span class="unit">ms</span></div>
|
|
716
|
+
</div>
|
|
717
|
+
</div>
|
|
718
|
+
|
|
719
|
+
<div class="section-title">\u{1F310} Simulated Browser Core Web Vitals Equivalent</div>
|
|
720
|
+
<div class="cards-wrapper">
|
|
721
|
+
<div class="metric-card">
|
|
722
|
+
<div class="card-title">Largest Contentful Paint (LCP)</div>
|
|
723
|
+
<div class="card-value">${cards.lcp.toFixed(0)}<span class="unit">ms</span><span class="card-subtext ${lcpStat.cls}">(${lcpStat.label})</span></div>
|
|
724
|
+
</div>
|
|
725
|
+
<div class="metric-card">
|
|
726
|
+
<div class="card-title">First Input Delay (FID)</div>
|
|
727
|
+
<div class="card-value">${cards.fid.toFixed(1)}<span class="unit">ms</span><span class="card-subtext ${fidStat.cls}">(${fidStat.label})</span></div>
|
|
728
|
+
</div>
|
|
729
|
+
<div class="metric-card">
|
|
730
|
+
<div class="card-title">Cumulative Layout Shift (CLS)</div>
|
|
731
|
+
<div class="card-value">${cards.cls.toFixed(3)}<span class="card-subtext ${clsStat.cls}">(${clsStat.label})</span></div>
|
|
732
|
+
</div>
|
|
733
|
+
</div>
|
|
734
|
+
|
|
735
|
+
<div class="section-title">\u26A1 Diagnostic Breakdowns</div>
|
|
736
|
+
<div class="top-layout-grid">
|
|
737
|
+
<div class="verdict-box">
|
|
738
|
+
<div class="verdict-title">\u{1F916} AI Stress-Testing Agent Verdict</div>
|
|
739
|
+
<div class="verdict-text">${verdictReason}</div>
|
|
740
|
+
<div class="verdict-waves">${waveListItems}</div>
|
|
741
|
+
</div>
|
|
742
|
+
|
|
743
|
+
<div class="matrix-box">
|
|
744
|
+
<div class="matrix-title">\u{1F4CB} HTTP Response Matrix</div>
|
|
745
|
+
<div class="matrix-row">
|
|
746
|
+
<div class="matrix-label">Informational <span class="matrix-badge badge-1xx">1xx</span></div>
|
|
747
|
+
<div class="matrix-value">${responseMatrix.total1xx}</div>
|
|
748
|
+
</div>
|
|
749
|
+
<div class="matrix-row">
|
|
750
|
+
<div class="matrix-label">Successful <span class="matrix-badge badge-2xx">2xx</span></div>
|
|
751
|
+
<div class="matrix-value">${responseMatrix.total2xx}</div>
|
|
752
|
+
</div>
|
|
753
|
+
<div class="matrix-row">
|
|
754
|
+
<div class="matrix-label">Redirects <span class="matrix-badge badge-3xx">3xx</span></div>
|
|
755
|
+
<div class="matrix-value">${responseMatrix.total3xx}</div>
|
|
756
|
+
</div>
|
|
757
|
+
<div class="matrix-row">
|
|
758
|
+
<div class="matrix-label">Client Error <span class="matrix-badge badge-4xx">4xx</span></div>
|
|
759
|
+
<div class="matrix-value">${responseMatrix.total4xx}</div>
|
|
760
|
+
</div>
|
|
761
|
+
<div class="matrix-row">
|
|
762
|
+
<div class="matrix-label">Server Error <span class="matrix-badge badge-5xx">5xx</span></div>
|
|
763
|
+
<div class="matrix-value">${responseMatrix.total5xx}</div>
|
|
764
|
+
</div>
|
|
765
|
+
</div>
|
|
766
|
+
</div>
|
|
767
|
+
|
|
768
|
+
<div class="charts-grid">
|
|
769
|
+
<div class="graph-card">
|
|
770
|
+
<div class="graph-title">\u{1F4CA} Throughput Velocity & Workload Volume</div>
|
|
771
|
+
<div style="height: 280px; position: relative;">
|
|
772
|
+
<canvas id="volumeChart"></canvas>
|
|
773
|
+
</div>
|
|
774
|
+
</div>
|
|
775
|
+
|
|
776
|
+
<div class="graph-card">
|
|
777
|
+
<div class="graph-title">\u{1F504} Active Concurrency (VUs) vs. TTF Trend</div>
|
|
778
|
+
<div style="height: 280px; position: relative;">
|
|
779
|
+
<canvas id="concurrencyChart"></canvas>
|
|
780
|
+
</div>
|
|
781
|
+
</div>
|
|
782
|
+
|
|
783
|
+
<div class="graph-card">
|
|
784
|
+
<div class="graph-title">\u{1F369} HTTP Status Code Proportions</div>
|
|
785
|
+
<div style="height: 280px; position: relative;">
|
|
786
|
+
<canvas id="statusDonutChart"></canvas>
|
|
787
|
+
</div>
|
|
788
|
+
</div>
|
|
789
|
+
|
|
790
|
+
<div class="graph-card">
|
|
791
|
+
<div class="graph-title">\u{1F4C8} Time-to-First-Byte Scatter Plot</div>
|
|
792
|
+
<div style="height: 280px; position: relative;">
|
|
793
|
+
<canvas id="ttfbScatterChart"></canvas>
|
|
794
|
+
</div>
|
|
795
|
+
</div>
|
|
796
|
+
</div>
|
|
797
|
+
|
|
798
|
+
<div class="card">
|
|
799
|
+
<h3>Telemetry Matrix Logs</h3>
|
|
800
|
+
<table>
|
|
801
|
+
<thead>
|
|
802
|
+
<tr>
|
|
803
|
+
<th>Concurrency (Threads)</th>
|
|
804
|
+
<th>Throughput</th>
|
|
805
|
+
<th>Avg Latency</th>
|
|
806
|
+
<th>P95 Latency</th>
|
|
807
|
+
<th>Simulated LCP</th>
|
|
808
|
+
<th>Error Rate</th>
|
|
809
|
+
<th>Status</th>
|
|
810
|
+
</tr>
|
|
811
|
+
</thead>
|
|
812
|
+
<tbody>
|
|
813
|
+
${history.map((h) => {
|
|
814
|
+
const isPassed = h.apdex >= config.targetApdex && h.errorRate <= config.targetErrorRate;
|
|
815
|
+
return `<tr>
|
|
816
|
+
<td><strong>${h.threads}</strong></td>
|
|
817
|
+
<td>${h.throughput.toFixed(0)} req/sec</td>
|
|
818
|
+
<td>${h.avgLatency.toFixed(1)} ms</td>
|
|
819
|
+
<td>${h.p95Latency.toFixed(1)} ms</td>
|
|
820
|
+
<td>${h.lcp.toFixed(0)} ms</td>
|
|
821
|
+
<td>${(h.errorRate * 100).toFixed(2)}%</td>
|
|
822
|
+
<td><span class="badge ${isPassed ? "badge-success" : "badge-fail"}">${isPassed ? "SLO PASS" : "SLO BREACH"}</span></td>
|
|
823
|
+
</tr>`;
|
|
824
|
+
}).join("")}
|
|
825
|
+
</tbody>
|
|
826
|
+
</table>
|
|
827
|
+
</div>
|
|
828
|
+
${liveAdjusterHtml}
|
|
829
|
+
${rightSizingHtml}
|
|
830
|
+
${logClustersHtml}
|
|
831
|
+
</div>
|
|
832
|
+
|
|
833
|
+
<script>
|
|
834
|
+
function copyToClipboard(containerId) {
|
|
835
|
+
const text = document.getElementById(containerId).innerText;
|
|
836
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
837
|
+
alert('Copied remediation snippet to clipboard!');
|
|
838
|
+
}).catch(err => {
|
|
839
|
+
console.error('Failed to copy text: ', err);
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
const labels = ${JSON.stringify(labels)};
|
|
844
|
+
const baseThroughput = ${cards.throughput};
|
|
845
|
+
const baseLatency = ${cards.ttfb};
|
|
846
|
+
|
|
847
|
+
// Live Concurrency Adjuster interactive client logic
|
|
848
|
+
const slider = document.getElementById('concurrencySlider');
|
|
849
|
+
const sliderVal = document.getElementById('sliderValue');
|
|
850
|
+
const simThroughput = document.getElementById('simThroughput');
|
|
851
|
+
const simLatency = document.getElementById('simLatency');
|
|
852
|
+
|
|
853
|
+
slider.addEventListener('input', (e) => {
|
|
854
|
+
const val = parseInt(e.target.value, 10);
|
|
855
|
+
sliderVal.textContent = val;
|
|
856
|
+
const factor = val / ${config.threads || 10};
|
|
857
|
+
simThroughput.textContent = (baseThroughput * Math.min(factor, 2.5)).toFixed(0) + '/s';
|
|
858
|
+
simLatency.textContent = (baseLatency * Math.pow(factor, 0.8)).toFixed(1) + 'ms';
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
new Chart(document.getElementById('volumeChart'), {
|
|
862
|
+
type: 'bar',
|
|
863
|
+
data: {
|
|
864
|
+
labels: labels,
|
|
865
|
+
datasets: [
|
|
866
|
+
{
|
|
867
|
+
label: 'Successful Requests',
|
|
868
|
+
data: ${JSON.stringify(successRequestsData)},
|
|
869
|
+
backgroundColor: '#10b981',
|
|
870
|
+
stack: 'requests',
|
|
871
|
+
barPercentage: 0.95,
|
|
872
|
+
categoryPercentage: 0.95
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
label: 'Failed Workloads',
|
|
876
|
+
data: ${JSON.stringify(failedWorkloadsData)},
|
|
877
|
+
backgroundColor: '#ef4444',
|
|
878
|
+
stack: 'requests',
|
|
879
|
+
barPercentage: 0.95,
|
|
880
|
+
categoryPercentage: 0.95
|
|
881
|
+
}
|
|
882
|
+
]
|
|
883
|
+
},
|
|
884
|
+
options: {
|
|
885
|
+
responsive: true,
|
|
886
|
+
maintainAspectRatio: false,
|
|
887
|
+
plugins: {
|
|
888
|
+
legend: { position: 'top', labels: { color: '#94a3b8', font: { size: 11 } } }
|
|
889
|
+
},
|
|
890
|
+
scales: {
|
|
891
|
+
x: { grid: { color: '#1e293b' }, ticks: { color: '#64748b' } },
|
|
892
|
+
y: { stacked: true, grid: { color: '#1e293b' }, ticks: { color: '#64748b' } }
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
new Chart(document.getElementById('concurrencyChart'), {
|
|
898
|
+
type: 'line',
|
|
899
|
+
data: {
|
|
900
|
+
labels: labels,
|
|
901
|
+
datasets: [
|
|
902
|
+
{
|
|
903
|
+
label: 'Active VU Threads',
|
|
904
|
+
data: ${JSON.stringify(activeThreadsData)},
|
|
905
|
+
borderColor: '#38bdf8',
|
|
906
|
+
backgroundColor: 'rgba(56, 189, 248, 0.1)',
|
|
907
|
+
borderWidth: 2.5,
|
|
908
|
+
pointRadius: 4,
|
|
909
|
+
fill: true,
|
|
910
|
+
yAxisID: 'yThreads'
|
|
911
|
+
},
|
|
912
|
+
{
|
|
913
|
+
label: 'Time-to-Failure (TTF) Trend',
|
|
914
|
+
data: ${JSON.stringify(ttfTrendData)},
|
|
915
|
+
borderColor: '#f43f5e',
|
|
916
|
+
borderWidth: 2,
|
|
917
|
+
borderDash: [5, 5],
|
|
918
|
+
pointRadius: 3,
|
|
919
|
+
fill: false,
|
|
920
|
+
yAxisID: 'yTtf'
|
|
921
|
+
}
|
|
922
|
+
]
|
|
923
|
+
},
|
|
924
|
+
options: {
|
|
925
|
+
responsive: true,
|
|
926
|
+
maintainAspectRatio: false,
|
|
927
|
+
plugins: {
|
|
928
|
+
legend: { position: 'top', labels: { color: '#94a3b8', font: { size: 11 } } }
|
|
929
|
+
},
|
|
930
|
+
scales: {
|
|
931
|
+
x: { grid: { color: '#1e293b' }, ticks: { color: '#64748b' } },
|
|
932
|
+
y_threads: { position: 'left', grid: { color: '#1e293b' }, ticks: { color: '#38bdf8' }, title: { display: true, text: 'VUs / Threads', color: '#38bdf8' } },
|
|
933
|
+
y_ttf: { position: 'right', grid: { drawOnChartArea: false }, ticks: { color: '#f43f5e' }, title: { display: true, text: 'TTF Index / Latency ms', color: '#f43f5e' } }
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
new Chart(document.getElementById('statusDonutChart'), {
|
|
939
|
+
type: 'doughnut',
|
|
940
|
+
data: {
|
|
941
|
+
labels: ['1xx Info', '2xx Success', '3xx Redirect', '4xx Client Err', '5xx Server Err'],
|
|
942
|
+
datasets: [{
|
|
943
|
+
data: [${responseMatrix.total1xx}, ${responseMatrix.total2xx}, ${responseMatrix.total3xx}, ${responseMatrix.total4xx}, ${responseMatrix.total5xx}],
|
|
944
|
+
backgroundColor: ['#38bdf8', '#4ade80', '#cbd5e1', '#fde047', '#f87171'],
|
|
945
|
+
borderWidth: 1,
|
|
946
|
+
borderColor: '#1e293b'
|
|
947
|
+
}]
|
|
948
|
+
},
|
|
949
|
+
options: {
|
|
950
|
+
responsive: true,
|
|
951
|
+
maintainAspectRatio: false,
|
|
952
|
+
plugins: {
|
|
953
|
+
legend: { position: 'top', labels: { color: '#94a3b8', font: { size: 11 } } }
|
|
954
|
+
},
|
|
955
|
+
cutout: '65%'
|
|
956
|
+
}
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
new Chart(document.getElementById('ttfbScatterChart'), {
|
|
960
|
+
type: 'scatter',
|
|
961
|
+
data: {
|
|
962
|
+
datasets: [{
|
|
963
|
+
label: 'Request Processing Delay',
|
|
964
|
+
data: ${JSON.stringify(scatterPoints)},
|
|
965
|
+
backgroundColor: '#a855f7',
|
|
966
|
+
pointRadius: 4,
|
|
967
|
+
pointHoverRadius: 6
|
|
968
|
+
}]
|
|
969
|
+
},
|
|
970
|
+
options: {
|
|
971
|
+
responsive: true,
|
|
972
|
+
maintainAspectRatio: false,
|
|
973
|
+
plugins: {
|
|
974
|
+
legend: { position: 'top', labels: { color: '#94a3b8', font: { size: 11 } } }
|
|
975
|
+
},
|
|
976
|
+
scales: {
|
|
977
|
+
x: { grid: { color: '#1e293b' }, ticks: { color: '#64748b' }, title: { display: true, text: 'Time offset (s)', color: '#64748b' } },
|
|
978
|
+
y: { grid: { color: '#1e293b' }, ticks: { color: '#64748b' }, title: { display: true, text: 'TTFB / Delay (ms)', color: '#64748b' } }
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
});
|
|
982
|
+
</script></body></html>`;
|
|
512
983
|
try {
|
|
513
984
|
fs.writeFileSync(config.outputHtml, htmlContent, "utf-8");
|
|
514
|
-
console.log(
|
|
985
|
+
console.log(`\u{1F4CA} Standalone Dashboard exported successfully to: ${path.resolve(config.outputHtml)}`);
|
|
515
986
|
} catch (err) {
|
|
516
|
-
console.error(
|
|
987
|
+
console.error(`\u274C Failed to write HTML output dashboard: ${err}`);
|
|
517
988
|
}
|
|
518
989
|
}
|
|
519
990
|
function printUsage() {
|
|
520
|
-
console.log(
|
|
991
|
+
console.log(`Blaze Core Performance Test CLI Engine
|
|
992
|
+
Options:
|
|
993
|
+
--threads <count> | --duration <seconds> | --agentic | --cluster-logs | --correlate | --apdex-t <ms>`);
|
|
521
994
|
}
|
|
522
995
|
main().catch(console.error);
|
|
Binary file
|