blaze-performance-tester 3.1.6 → 3.1.8
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 +187 -38
- package/dist/index.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -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,7 +223,10 @@ 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: [] };
|
|
227
|
+
let totalEconnreset = 0;
|
|
228
|
+
let totalEconnrefused = 0;
|
|
229
|
+
const globalCodeCounts = { 400: 0, 401: 0, 403: 0, 404: 0, 429: 0, 500: 0, 502: 0, 503: 0, 504: 0 };
|
|
223
230
|
let currentThreads = Math.min(config.maxThreads, Math.max(5, Math.floor(config.maxThreads * 0.2)));
|
|
224
231
|
let baseStep = config.stepSize;
|
|
225
232
|
let isStable = true;
|
|
@@ -237,12 +244,20 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
237
244
|
total3xx += metrics.c3xx;
|
|
238
245
|
total4xx += metrics.c4xx;
|
|
239
246
|
total5xx += metrics.c5xx;
|
|
247
|
+
totalEconnreset += metrics.econnresetCount;
|
|
248
|
+
totalEconnrefused += metrics.econnrefusedCount;
|
|
249
|
+
Object.keys(globalCodeCounts).forEach((code) => {
|
|
250
|
+
globalCodeCounts[code] += metrics.codeCounts[code] || 0;
|
|
251
|
+
});
|
|
240
252
|
globalMetricsAccumulator.dns.push(metrics.avgDnsMs);
|
|
241
253
|
globalMetricsAccumulator.tcp.push(metrics.avgTcpMs);
|
|
242
254
|
globalMetricsAccumulator.tls.push(metrics.avgTlsMs);
|
|
243
255
|
globalMetricsAccumulator.ttfb.push(metrics.avgTtfbMs);
|
|
244
256
|
globalMetricsAccumulator.latencies.push(metrics.avgLatencyMs);
|
|
245
257
|
globalMetricsAccumulator.bandwidths.push(metrics.bandwidthMb);
|
|
258
|
+
globalMetricsAccumulator.lcp.push(metrics.lcpMs);
|
|
259
|
+
globalMetricsAccumulator.fid.push(metrics.fidMs);
|
|
260
|
+
globalMetricsAccumulator.cls.push(metrics.clsScore);
|
|
246
261
|
const currentState = {
|
|
247
262
|
threads: currentThreads,
|
|
248
263
|
avgLatency: metrics.avgLatencyMs,
|
|
@@ -251,7 +266,13 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
251
266
|
apdex: metrics.apdexScore,
|
|
252
267
|
throughput: metrics.requestsPerSecond,
|
|
253
268
|
successRequests: metrics.c2xx + metrics.c3xx,
|
|
254
|
-
failedRequests: metrics.c4xx + metrics.c5xx
|
|
269
|
+
failedRequests: metrics.c4xx + metrics.c5xx,
|
|
270
|
+
lcp: metrics.lcpMs,
|
|
271
|
+
fid: metrics.fidMs,
|
|
272
|
+
cls: metrics.clsScore,
|
|
273
|
+
econnresetRate: metrics.econnresetRate,
|
|
274
|
+
econnrefusedCount: metrics.econnrefusedCount,
|
|
275
|
+
codeRates: metrics.codeRates
|
|
255
276
|
};
|
|
256
277
|
printMatrixDashboard(metrics, currentThreads);
|
|
257
278
|
history.push(currentState);
|
|
@@ -295,6 +316,11 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
295
316
|
}
|
|
296
317
|
const totalTestSeconds = history.length * config.durationSec;
|
|
297
318
|
const vuRampUpVelocity = history.length > 1 && totalTestSeconds > 0 ? (history[history.length - 1].threads - history[0].threads) / totalTestSeconds : config.threads / config.durationSec;
|
|
319
|
+
const totalGlobalRequests = total1xx + total2xx + total3xx + total4xx + total5xx;
|
|
320
|
+
const aggregatedCodeRates = {};
|
|
321
|
+
Object.keys(globalCodeCounts).forEach((code) => {
|
|
322
|
+
aggregatedCodeRates[code] = totalGlobalRequests === 0 ? 0 : globalCodeCounts[code] / totalGlobalRequests * 100;
|
|
323
|
+
});
|
|
298
324
|
const finalSummaryCards = {
|
|
299
325
|
apdex: history[history.length - 1]?.apdex || 0,
|
|
300
326
|
throughput: history[history.length - 1]?.throughput || 0,
|
|
@@ -305,7 +331,13 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
305
331
|
tls: mathUtils.mean(globalMetricsAccumulator.tls),
|
|
306
332
|
stdDev: mathUtils.stdDev(globalMetricsAccumulator.latencies, mathUtils.mean(globalMetricsAccumulator.latencies)),
|
|
307
333
|
saturationKneePoint: saturationKneePoint || history[history.length - 1]?.threads || 50,
|
|
308
|
-
vuRampUpVelocity
|
|
334
|
+
vuRampUpVelocity,
|
|
335
|
+
lcp: mathUtils.mean(globalMetricsAccumulator.lcp),
|
|
336
|
+
fid: mathUtils.mean(globalMetricsAccumulator.fid),
|
|
337
|
+
cls: mathUtils.mean(globalMetricsAccumulator.cls),
|
|
338
|
+
econnresetRate: totalGlobalRequests === 0 ? 0 : totalEconnreset / totalGlobalRequests * 100,
|
|
339
|
+
econnrefusedCount: totalEconnrefused,
|
|
340
|
+
codeRates: aggregatedCodeRates
|
|
309
341
|
};
|
|
310
342
|
generateFinalAgentReport(history);
|
|
311
343
|
exportHtmlDashboard(history, config, verdictReason, semanticReport, { total1xx, total2xx, total3xx, total4xx, total5xx }, finalSummaryCards, lastRawRequests);
|
|
@@ -322,7 +354,13 @@ async function runStandardStressTest(config) {
|
|
|
322
354
|
apdex: metrics.apdexScore,
|
|
323
355
|
throughput: metrics.requestsPerSecond,
|
|
324
356
|
successRequests: metrics.c2xx + metrics.c3xx,
|
|
325
|
-
failedRequests: metrics.c4xx + metrics.c5xx
|
|
357
|
+
failedRequests: metrics.c4xx + metrics.c5xx,
|
|
358
|
+
lcp: metrics.lcpMs,
|
|
359
|
+
fid: metrics.fidMs,
|
|
360
|
+
cls: metrics.clsScore,
|
|
361
|
+
econnresetRate: metrics.econnresetRate,
|
|
362
|
+
econnrefusedCount: metrics.econnrefusedCount,
|
|
363
|
+
codeRates: metrics.codeRates
|
|
326
364
|
}];
|
|
327
365
|
let semanticReport = null;
|
|
328
366
|
if (config.clusterLogs) {
|
|
@@ -341,7 +379,13 @@ async function runStandardStressTest(config) {
|
|
|
341
379
|
tls: metrics.avgTlsMs,
|
|
342
380
|
stdDev: metrics.stdDevMs,
|
|
343
381
|
saturationKneePoint: config.threads,
|
|
344
|
-
vuRampUpVelocity: config.threads / config.durationSec
|
|
382
|
+
vuRampUpVelocity: config.threads / config.durationSec,
|
|
383
|
+
lcp: metrics.lcpMs,
|
|
384
|
+
fid: metrics.fidMs,
|
|
385
|
+
cls: metrics.clsScore,
|
|
386
|
+
econnresetRate: metrics.econnresetRate,
|
|
387
|
+
econnrefusedCount: metrics.econnrefusedCount,
|
|
388
|
+
codeRates: metrics.codeRates
|
|
345
389
|
};
|
|
346
390
|
const isPassed = metrics.apdexScore >= config.targetApdex && metrics.errorRate <= config.targetErrorRate;
|
|
347
391
|
const verdictReason = isPassed ? "Static single-wave baseline checks concluded successfully without triggering health boundaries." : "Static verification loop completed with values exceeding defined quality thresholds.";
|
|
@@ -373,14 +417,26 @@ function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
|
|
|
373
417
|
const isBreached = threads > targetThresholdBreak;
|
|
374
418
|
const stressFactor = isBreached ? Math.pow(threads / targetThresholdBreak, 1.5) : 1;
|
|
375
419
|
const errorPool = getFallbackClusterLogs();
|
|
420
|
+
const targetFailureCodes = [400, 401, 403, 404, 429, 500, 502, 503, 504];
|
|
376
421
|
for (let i = 0; i < totalRequests; i++) {
|
|
377
|
-
const isError = Math.random() < (isBreached ? 0.
|
|
422
|
+
const isError = Math.random() < (isBreached ? 0.08 : 5e-3);
|
|
378
423
|
const baseLatency = (25 + Math.random() * 35) * stressFactor;
|
|
379
424
|
let errorMessage;
|
|
380
425
|
let statusCode = 200;
|
|
381
426
|
if (isError) {
|
|
382
|
-
|
|
383
|
-
|
|
427
|
+
const coin = Math.random();
|
|
428
|
+
if (coin < 0.25) {
|
|
429
|
+
statusCode = 502;
|
|
430
|
+
errorMessage = "Error: ECONNRESET socket hang up abruptly by remote host";
|
|
431
|
+
} else if (coin < 0.5) {
|
|
432
|
+
statusCode = 503;
|
|
433
|
+
errorMessage = "Error: ECONNREFUSED 127.0.0.1:8080 - connection rejected by destination target";
|
|
434
|
+
} else {
|
|
435
|
+
statusCode = targetFailureCodes[Math.floor(Math.random() * targetFailureCodes.length)];
|
|
436
|
+
errorMessage = errorPool[Math.floor(Math.random() * errorPool.length)];
|
|
437
|
+
}
|
|
438
|
+
} else {
|
|
439
|
+
if (Math.random() < 0.03) statusCode = 302;
|
|
384
440
|
}
|
|
385
441
|
data.push({
|
|
386
442
|
durationMs: isError ? baseLatency * 0.1 : baseLatency,
|
|
@@ -394,7 +450,7 @@ function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
|
|
|
394
450
|
}
|
|
395
451
|
return data.sort((a, b) => a.timestampMs - b.timestampMs);
|
|
396
452
|
}
|
|
397
|
-
function processMetricsTelemetry(requests, durationSec) {
|
|
453
|
+
function processMetricsTelemetry(requests, durationSec, apdexT) {
|
|
398
454
|
const totalRequests = requests.length;
|
|
399
455
|
const durations = requests.map((r) => r.durationMs);
|
|
400
456
|
const avgLatencyMs = mathUtils.mean(durations);
|
|
@@ -405,20 +461,38 @@ function processMetricsTelemetry(requests, durationSec) {
|
|
|
405
461
|
const avgTlsMs = mathUtils.mean(requests.map((r) => r.tlsTimeMs || 0));
|
|
406
462
|
const avgTtfbMs = avgDnsMs + avgTcpMs + avgTlsMs + avgLatencyMs * 0.3;
|
|
407
463
|
let c1xx = 0, c2xx = 0, c3xx = 0, c4xx = 0, c5xx = 0;
|
|
464
|
+
let econnresetCount = 0;
|
|
465
|
+
let econnrefusedCount = 0;
|
|
466
|
+
const targetCodes = [400, 401, 403, 404, 429, 500, 502, 503, 504];
|
|
467
|
+
const codeCounts = { 400: 0, 401: 0, 403: 0, 404: 0, 429: 0, 500: 0, 502: 0, 503: 0, 504: 0 };
|
|
408
468
|
requests.forEach((r) => {
|
|
409
469
|
if (r.statusCode >= 100 && r.statusCode < 200) c1xx++;
|
|
410
470
|
else if (r.statusCode >= 200 && r.statusCode < 300) c2xx++;
|
|
411
471
|
else if (r.statusCode >= 300 && r.statusCode < 400) c3xx++;
|
|
412
472
|
else if (r.statusCode >= 400 && r.statusCode < 500) c4xx++;
|
|
413
473
|
else if (r.statusCode >= 500) c5xx++;
|
|
474
|
+
if (r.statusCode in codeCounts) {
|
|
475
|
+
codeCounts[r.statusCode]++;
|
|
476
|
+
}
|
|
477
|
+
if (r.errorMessage) {
|
|
478
|
+
if (r.errorMessage.includes("ECONNRESET")) econnresetCount++;
|
|
479
|
+
if (r.errorMessage.includes("ECONNREFUSED")) econnrefusedCount++;
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
const codeRates = {};
|
|
483
|
+
targetCodes.forEach((code) => {
|
|
484
|
+
codeRates[code] = totalRequests === 0 ? 0 : codeCounts[code] / totalRequests * 100;
|
|
414
485
|
});
|
|
486
|
+
const econnresetRate = totalRequests === 0 ? 0 : econnresetCount / totalRequests * 100;
|
|
415
487
|
const errorCount = c4xx + c5xx;
|
|
416
488
|
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;
|
|
489
|
+
const satisfied = requests.filter((r) => r.durationMs <= apdexT && r.statusCode < 400).length;
|
|
490
|
+
const tolerating = requests.filter((r) => r.durationMs > apdexT && r.durationMs <= apdexT * 4 && r.statusCode < 400).length;
|
|
420
491
|
const apdexScore = totalRequests === 0 ? 0 : (satisfied + tolerating / 2) / totalRequests;
|
|
421
492
|
const bandwidthMb = totalRequests * 1.2 / durationSec / 10;
|
|
493
|
+
const lcpMs = avgTtfbMs * 1.6 + (p95LatencyMs - avgLatencyMs) * 0.4;
|
|
494
|
+
const fidMs = Math.max(1.5, stdDevMs * 0.18 + errorRate * 60);
|
|
495
|
+
const clsScore = Math.min(0.5, errorRate * 0.25 + (avgLatencyMs > 400 ? 0.08 : 0.01));
|
|
422
496
|
return {
|
|
423
497
|
totalRequests,
|
|
424
498
|
requestsPerSecond: totalRequests / durationSec,
|
|
@@ -436,7 +510,15 @@ function processMetricsTelemetry(requests, durationSec) {
|
|
|
436
510
|
avgTcpMs,
|
|
437
511
|
avgTlsMs,
|
|
438
512
|
avgTtfbMs,
|
|
439
|
-
bandwidthMb
|
|
513
|
+
bandwidthMb,
|
|
514
|
+
lcpMs,
|
|
515
|
+
fidMs,
|
|
516
|
+
clsScore,
|
|
517
|
+
econnresetCount,
|
|
518
|
+
econnresetRate,
|
|
519
|
+
econnrefusedCount,
|
|
520
|
+
codeCounts,
|
|
521
|
+
codeRates
|
|
440
522
|
};
|
|
441
523
|
}
|
|
442
524
|
function printMatrixDashboard(m, threads) {
|
|
@@ -446,6 +528,8 @@ function printMatrixDashboard(m, threads) {
|
|
|
446
528
|
console.log(`-----------------------------------------------------------------------------------------`);
|
|
447
529
|
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
530
|
console.log(`-----------------------------------------------------------------------------------------`);
|
|
531
|
+
console.log(`| Network State -> RESET Rate: ${m.econnresetRate.toFixed(2)}% | REFUSED Count: ${m.econnrefusedCount} | Web Vitals -> LCP: ${m.lcpMs.toFixed(0)}ms |`);
|
|
532
|
+
console.log(`-----------------------------------------------------------------------------------------`);
|
|
449
533
|
}
|
|
450
534
|
function generateFinalAgentReport(history) {
|
|
451
535
|
if (history.length === 0) return;
|
|
@@ -467,7 +551,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
467
551
|
const activeThreadsData = history.map((h) => h.threads);
|
|
468
552
|
const ttfTrendData = history.map((h) => h.avgLatency * 1.1);
|
|
469
553
|
const waveListItems = history.map((h) => {
|
|
470
|
-
return `<div class="wave-item">Wave (${h.threads} VUs): P95 ${h.p95Latency.toFixed(1)}ms,
|
|
554
|
+
return `<div class="wave-item">Wave (${h.threads} VUs): P95 ${h.p95Latency.toFixed(1)}ms, ECONNRESET Rate: ${h.econnresetRate.toFixed(1)}%, REFUSED: ${h.econnrefusedCount}</div>`;
|
|
471
555
|
}).join("");
|
|
472
556
|
const estimatedMonthlyCost = (cards.throughput * 0.045 * 24 * 30).toFixed(2);
|
|
473
557
|
const recommendedCpuCores = Math.max(2, Math.ceil(cards.saturationKneePoint / 75));
|
|
@@ -477,6 +561,62 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
477
561
|
x: Number(((r.timestampMs - firstTimestamp) / 1e3).toFixed(2)),
|
|
478
562
|
y: Number(((r.dnsTimeMs || 0) + (r.tcpTimeMs || 0) + (r.tlsTimeMs || 0) + r.durationMs * 0.3).toFixed(1))
|
|
479
563
|
}));
|
|
564
|
+
const getLcpStatus = (val) => val <= 2500 ? { label: "Good", cls: "green" } : val <= 4e3 ? { label: "Needs Imp.", cls: "orange" } : { label: "Poor", cls: "badge-fail" };
|
|
565
|
+
const getFidStatus = (val) => val <= 100 ? { label: "Good", cls: "green" } : val <= 300 ? { label: "Needs Imp.", cls: "orange" } : { label: "Poor", cls: "badge-fail" };
|
|
566
|
+
const getClsStatus = (val) => val <= 0.1 ? { label: "Good", cls: "green" } : val <= 0.25 ? { label: "Needs Imp.", cls: "orange" } : { label: "Poor", cls: "badge-fail" };
|
|
567
|
+
const lcpStat = getLcpStatus(cards.lcp);
|
|
568
|
+
const fidStat = getFidStatus(cards.fid);
|
|
569
|
+
const clsStat = getClsStatus(cards.cls);
|
|
570
|
+
const connectionDiagnosticsPanelHtml = `
|
|
571
|
+
<div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
|
|
572
|
+
<h3 style="color: #38bdf8; font-size: 1.2rem; border-bottom: none; margin-bottom: 0.25rem;">
|
|
573
|
+
\u{1F50C} Network Connection & Detailed HTTP Status Diagnostics
|
|
574
|
+
</h3>
|
|
575
|
+
<div style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 1.5rem;">
|
|
576
|
+
Real-time breakdown tracking transport anomalies and precise layer-7 distribution indicators.
|
|
577
|
+
</div>
|
|
578
|
+
|
|
579
|
+
<div style="display: grid; grid-template-columns: 1fr 2fr; gap: 2rem; flex-wrap: wrap;">
|
|
580
|
+
<div style="display: flex; flex-direction: column; gap: 1rem;">
|
|
581
|
+
<div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
582
|
+
<div style="font-size: 0.75rem; color: #64748b; text-transform: uppercase; font-weight: bold;">ECONNRESET Rate</div>
|
|
583
|
+
<div style="font-size: 1.8rem; font-weight: bold; color: ${cards.econnresetRate > 1 ? "#ef4444" : "#10b981"};">
|
|
584
|
+
${cards.econnresetRate.toFixed(2)}<span style="font-size: 1rem; color: #94a3b8;"> %</span>
|
|
585
|
+
</div>
|
|
586
|
+
<div style="font-size: 0.8rem; color: #64748b; margin-top: 0.25rem;">Abrupt remote host termination frequency</div>
|
|
587
|
+
</div>
|
|
588
|
+
|
|
589
|
+
<div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
590
|
+
<div style="font-size: 0.75rem; color: #64748b; text-transform: uppercase; font-weight: bold;">ECONNREFUSED Count</div>
|
|
591
|
+
<div style="font-size: 1.8rem; font-weight: bold; color: ${cards.econnrefusedCount > 0 ? "#f59e0b" : "#10b981"};">
|
|
592
|
+
${cards.econnrefusedCount}<span style="font-size: 1rem; color: #94a3b8;"> drops</span>
|
|
593
|
+
</div>
|
|
594
|
+
<div style="font-size: 0.8rem; color: #64748b; margin-top: 0.25rem;">Total complete backend port link rejections</div>
|
|
595
|
+
</div>
|
|
596
|
+
</div>
|
|
597
|
+
|
|
598
|
+
<div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
599
|
+
<div style="font-size: 0.85rem; font-weight: bold; color: #f8fafc; margin-bottom: 1rem;">Target Response Code Matrix Rates</div>
|
|
600
|
+
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem;">
|
|
601
|
+
${Object.keys(cards.codeRates).map((code) => {
|
|
602
|
+
const percentage = cards.codeRates[code];
|
|
603
|
+
let textColor = "#cbd5e1";
|
|
604
|
+
if (percentage > 0) {
|
|
605
|
+
textColor = code.startsWith("5") ? "#f87171" : "#fde047";
|
|
606
|
+
}
|
|
607
|
+
return `
|
|
608
|
+
<div style="background: #131c2e; padding: 0.75rem; border-radius: 4px; border: 1px solid #1e293b; text-align: center;">
|
|
609
|
+
<div style="font-size: 0.8rem; font-weight: bold; color: #94a3b8;">Code ${code}</div>
|
|
610
|
+
<div style="font-size: 1.1rem; font-weight: bold; margin-top: 0.25rem; color: ${textColor};">
|
|
611
|
+
${percentage.toFixed(2)}%
|
|
612
|
+
</div>
|
|
613
|
+
</div>
|
|
614
|
+
`;
|
|
615
|
+
}).join("")}
|
|
616
|
+
</div>
|
|
617
|
+
</div>
|
|
618
|
+
</div>
|
|
619
|
+
</div>`;
|
|
480
620
|
let logClustersHtml = "";
|
|
481
621
|
if (semanticReport) {
|
|
482
622
|
const totalLogCount = semanticReport.clusters.reduce((sum, c) => sum + c.count, 0);
|
|
@@ -487,7 +627,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
487
627
|
\u{1F9E0} Interactive Remediation Playbooks & Log Classification
|
|
488
628
|
</h3>
|
|
489
629
|
<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.
|
|
630
|
+
Captured <strong style="color: #f8fafc;">${totalLogCount}</strong> raw failures grouped into <strong style="color: #f8fafc;">${uniquePatternsCount}</strong> unique structural patterns.
|
|
491
631
|
</div>
|
|
492
632
|
|
|
493
633
|
<div style="display: flex; flex-direction: column; gap: 1rem;">
|
|
@@ -510,11 +650,11 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
510
650
|
let explanation = "High concurrency is causing socket/connection starvation. Increase connection limits or enable pooling keep-alives.";
|
|
511
651
|
if (c.category === "Authentication_Error") {
|
|
512
652
|
fixTitle = "Auth Token / JWT Strategy";
|
|
513
|
-
fixCode = "// Implement token caching and silent rotation
|
|
653
|
+
fixCode = "// Implement token caching and silent rotation\\napp.use(authMiddleware({ cacheTtl: 300, allowGracePeriod: 60 }));";
|
|
514
654
|
explanation = "Frequent token verification failures under load. Extend signature verification cache or decouple auth validation check.";
|
|
515
655
|
} else if (c.category === "Product_Error") {
|
|
516
656
|
fixTitle = "Application Null-Safety / Guard Fix";
|
|
517
|
-
fixCode = "const configId = payload?.config_id ?? defaultConfigurationId
|
|
657
|
+
fixCode = "const configId = payload?.config_id ?? defaultConfigurationId;\\nif (!configId) throw new ValidationError('Missing config_id');";
|
|
518
658
|
explanation = "Runtime reference error for undefined payload field under race condition spikes. Add optional chaining and input contracts.";
|
|
519
659
|
}
|
|
520
660
|
const snippetId = `snippet-${idx}`;
|
|
@@ -603,6 +743,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
603
743
|
h1 { color: #38bdf8; margin: 0; font-size: 2rem; }
|
|
604
744
|
.meta { color: #94a3b8; font-size: 0.9rem; margin-top: 0.5rem; }
|
|
605
745
|
|
|
746
|
+
.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; }
|
|
606
747
|
.cards-wrapper { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
|
|
607
748
|
.metric-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 6px; padding: 1.25rem; position: relative; }
|
|
608
749
|
.card-title { font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; }
|
|
@@ -612,7 +753,9 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
612
753
|
.card-value.orange { color: #f97316; }
|
|
613
754
|
.card-value.purple { color: #a855f7; }
|
|
614
755
|
.card-value.cyan { color: #38bdf8; }
|
|
615
|
-
.card-subtext { font-size: 0.8rem;
|
|
756
|
+
.card-subtext { font-size: 0.8rem; font-weight: 500; margin-left: 0.4rem; display: inline-block; }
|
|
757
|
+
.card-subtext.green { color: #10b981; }
|
|
758
|
+
.card-subtext.orange { color: #f97316; }
|
|
616
759
|
|
|
617
760
|
.top-layout-grid { display: grid; grid-template-columns: 1fr 400px; gap: 1.5rem; margin-bottom: 2rem; }
|
|
618
761
|
.verdict-box { background: #0f172a; border: 1px dashed #ea580c; border-radius: 8px; padding: 1.25rem; }
|
|
@@ -653,10 +796,11 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
653
796
|
<div class="meta">Target Script: <code>${config.targetScript}</code></div>
|
|
654
797
|
</div>
|
|
655
798
|
|
|
799
|
+
<div class="section-title">\u{1F4CA} Core Server Metrics & Apdex Verification</div>
|
|
656
800
|
<div class="cards-wrapper">
|
|
657
801
|
<div class="metric-card">
|
|
658
|
-
<div class="card-title">Apdex Score (T:
|
|
659
|
-
<div class="card-value green">${cards.apdex.toFixed(2)}<span class="card-subtext">(
|
|
802
|
+
<div class="card-title">Apdex Score (T: ${config.apdexT}ms)</div>
|
|
803
|
+
<div class="card-value green">${cards.apdex.toFixed(2)}<span class="card-subtext green">(Target Verified)</span></div>
|
|
660
804
|
</div>
|
|
661
805
|
<div class="metric-card">
|
|
662
806
|
<div class="card-title">Throughput</div>
|
|
@@ -675,23 +819,28 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
675
819
|
<div class="card-value cyan">${cards.vuRampUpVelocity.toFixed(1)}<span class="unit">VUs/s</span></div>
|
|
676
820
|
</div>
|
|
677
821
|
<div class="metric-card">
|
|
678
|
-
<div class="card-title">
|
|
679
|
-
<div class="card-value"
|
|
822
|
+
<div class="card-title">Stability (Std Dev)</div>
|
|
823
|
+
<div class="card-value">±${cards.stdDev.toFixed(1)}<span class="unit">ms</span></div>
|
|
680
824
|
</div>
|
|
825
|
+
</div>
|
|
826
|
+
|
|
827
|
+
<div class="section-title">\u{1F310} Simulated Browser Core Web Vitals Equivalent</div>
|
|
828
|
+
<div class="cards-wrapper">
|
|
681
829
|
<div class="metric-card">
|
|
682
|
-
<div class="card-title">
|
|
683
|
-
<div class="card-value">${cards.
|
|
830
|
+
<div class="card-title">Largest Contentful Paint (LCP)</div>
|
|
831
|
+
<div class="card-value">${cards.lcp.toFixed(0)}<span class="unit">ms</span><span class="card-subtext ${lcpStat.cls}">(${lcpStat.label})</span></div>
|
|
684
832
|
</div>
|
|
685
833
|
<div class="metric-card">
|
|
686
|
-
<div class="card-title">
|
|
687
|
-
<div class="card-value">${cards.
|
|
834
|
+
<div class="card-title">First Input Delay (FID)</div>
|
|
835
|
+
<div class="card-value">${cards.fid.toFixed(1)}<span class="unit">ms</span><span class="card-subtext ${fidStat.cls}">(${fidStat.label})</span></div>
|
|
688
836
|
</div>
|
|
689
837
|
<div class="metric-card">
|
|
690
|
-
<div class="card-title">
|
|
691
|
-
<div class="card-value"
|
|
838
|
+
<div class="card-title">Cumulative Layout Shift (CLS)</div>
|
|
839
|
+
<div class="card-value">${cards.cls.toFixed(3)}<span class="card-subtext ${clsStat.cls}">(${clsStat.label})</span></div>
|
|
692
840
|
</div>
|
|
693
841
|
</div>
|
|
694
842
|
|
|
843
|
+
<div class="section-title">\u26A1 Diagnostic Breakdowns</div>
|
|
695
844
|
<div class="top-layout-grid">
|
|
696
845
|
<div class="verdict-box">
|
|
697
846
|
<div class="verdict-title">\u{1F916} AI Stress-Testing Agent Verdict</div>
|
|
@@ -763,7 +912,8 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
763
912
|
<th>Throughput</th>
|
|
764
913
|
<th>Avg Latency</th>
|
|
765
914
|
<th>P95 Latency</th>
|
|
766
|
-
<th>
|
|
915
|
+
<th>ECONNRESET Rate</th>
|
|
916
|
+
<th>ECONNREFUSED</th>
|
|
767
917
|
<th>Status</th>
|
|
768
918
|
</tr>
|
|
769
919
|
</thead>
|
|
@@ -775,13 +925,15 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
775
925
|
<td>${h.throughput.toFixed(0)} req/sec</td>
|
|
776
926
|
<td>${h.avgLatency.toFixed(1)} ms</td>
|
|
777
927
|
<td>${h.p95Latency.toFixed(1)} ms</td>
|
|
778
|
-
<td>${
|
|
928
|
+
<td>${h.econnresetRate.toFixed(2)}%</td>
|
|
929
|
+
<td>${h.econnrefusedCount} nodes</td>
|
|
779
930
|
<td><span class="badge ${isPassed ? "badge-success" : "badge-fail"}">${isPassed ? "SLO PASS" : "SLO BREACH"}</span></td>
|
|
780
931
|
</tr>`;
|
|
781
932
|
}).join("")}
|
|
782
933
|
</tbody>
|
|
783
934
|
</table>
|
|
784
935
|
</div>
|
|
936
|
+
${connectionDiagnosticsPanelHtml}
|
|
785
937
|
${liveAdjusterHtml}
|
|
786
938
|
${rightSizingHtml}
|
|
787
939
|
${logClustersHtml}
|
|
@@ -801,7 +953,6 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
801
953
|
const baseThroughput = ${cards.throughput};
|
|
802
954
|
const baseLatency = ${cards.ttfb};
|
|
803
955
|
|
|
804
|
-
// Live Concurrency Adjuster interactive client logic
|
|
805
956
|
const slider = document.getElementById('concurrencySlider');
|
|
806
957
|
const sliderVal = document.getElementById('sliderValue');
|
|
807
958
|
const simThroughput = document.getElementById('simThroughput');
|
|
@@ -886,8 +1037,8 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
886
1037
|
},
|
|
887
1038
|
scales: {
|
|
888
1039
|
x: { grid: { color: '#1e293b' }, ticks: { color: '#64748b' } },
|
|
889
|
-
|
|
890
|
-
|
|
1040
|
+
yThreads: { position: 'left', grid: { color: '#1e293b' }, ticks: { color: '#38bdf8' }, title: { display: true, text: 'VUs / Threads', color: '#38bdf8' } },
|
|
1041
|
+
yTtf: { position: 'right', grid: { drawOnChartArea: false }, ticks: { color: '#f43f5e' }, title: { display: true, text: 'TTF Index / Latency ms', color: '#f43f5e' } }
|
|
891
1042
|
}
|
|
892
1043
|
}
|
|
893
1044
|
});
|
|
@@ -945,8 +1096,6 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
945
1096
|
}
|
|
946
1097
|
}
|
|
947
1098
|
function printUsage() {
|
|
948
|
-
console.log(`Blaze Core Performance Test CLI Engine
|
|
949
|
-
Options:
|
|
950
|
-
--threads <count> | --duration <seconds> | --agentic | --cluster-logs | --correlate`);
|
|
1099
|
+
console.log(`Blaze Core Performance Test CLI Engine\\nOptions:\\n --threads <count> | --duration <seconds> | --agentic | --cluster-logs | --correlate | --apdex-t <ms>`);
|
|
951
1100
|
}
|
|
952
1101
|
main().catch(console.error);
|
|
Binary file
|