blaze-performance-tester 3.1.6 → 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 +68 -25
- 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,7 @@ 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;
|
|
@@ -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);
|
|
@@ -305,7 +315,10 @@ 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);
|
|
@@ -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.";
|
|
@@ -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,7 +457,10 @@ 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) {
|
|
@@ -446,6 +470,8 @@ function printMatrixDashboard(m, threads) {
|
|
|
446
470
|
console.log(`-----------------------------------------------------------------------------------------`);
|
|
447
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)} |`);
|
|
448
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;
|
|
@@ -467,7 +493,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
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 `<div class="wave-item">Wave (${h.threads} VUs): P95 ${h.p95Latency.toFixed(1)}ms, Errors ${(h.errorRate * 100).toFixed(1)}
|
|
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,6 +503,12 @@ 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
|
}));
|
|
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);
|
|
480
512
|
let logClustersHtml = "";
|
|
481
513
|
if (semanticReport) {
|
|
482
514
|
const totalLogCount = semanticReport.clusters.reduce((sum, c) => sum + c.count, 0);
|
|
@@ -603,6 +635,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
603
635
|
h1 { color: #38bdf8; margin: 0; font-size: 2rem; }
|
|
604
636
|
.meta { color: #94a3b8; font-size: 0.9rem; margin-top: 0.5rem; }
|
|
605
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; }
|
|
606
639
|
.cards-wrapper { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
|
|
607
640
|
.metric-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 6px; padding: 1.25rem; position: relative; }
|
|
608
641
|
.card-title { font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; }
|
|
@@ -612,7 +645,9 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
612
645
|
.card-value.orange { color: #f97316; }
|
|
613
646
|
.card-value.purple { color: #a855f7; }
|
|
614
647
|
.card-value.cyan { color: #38bdf8; }
|
|
615
|
-
.card-subtext { font-size: 0.8rem;
|
|
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; }
|
|
616
651
|
|
|
617
652
|
.top-layout-grid { display: grid; grid-template-columns: 1fr 400px; gap: 1.5rem; margin-bottom: 2rem; }
|
|
618
653
|
.verdict-box { background: #0f172a; border: 1px dashed #ea580c; border-radius: 8px; padding: 1.25rem; }
|
|
@@ -653,10 +688,11 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
653
688
|
<div class="meta">Target Script: <code>${config.targetScript}</code></div>
|
|
654
689
|
</div>
|
|
655
690
|
|
|
691
|
+
<div class="section-title">\u{1F4CA} Core Server Metrics & Apdex Verification</div>
|
|
656
692
|
<div class="cards-wrapper">
|
|
657
693
|
<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">(
|
|
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>
|
|
660
696
|
</div>
|
|
661
697
|
<div class="metric-card">
|
|
662
698
|
<div class="card-title">Throughput</div>
|
|
@@ -675,23 +711,28 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
675
711
|
<div class="card-value cyan">${cards.vuRampUpVelocity.toFixed(1)}<span class="unit">VUs/s</span></div>
|
|
676
712
|
</div>
|
|
677
713
|
<div class="metric-card">
|
|
678
|
-
<div class="card-title">
|
|
679
|
-
<div class="card-value"
|
|
714
|
+
<div class="card-title">Stability (Std Dev)</div>
|
|
715
|
+
<div class="card-value">±${cards.stdDev.toFixed(1)}<span class="unit">ms</span></div>
|
|
680
716
|
</div>
|
|
717
|
+
</div>
|
|
718
|
+
|
|
719
|
+
<div class="section-title">\u{1F310} Simulated Browser Core Web Vitals Equivalent</div>
|
|
720
|
+
<div class="cards-wrapper">
|
|
681
721
|
<div class="metric-card">
|
|
682
|
-
<div class="card-title">
|
|
683
|
-
<div class="card-value">${cards.
|
|
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>
|
|
684
724
|
</div>
|
|
685
725
|
<div class="metric-card">
|
|
686
|
-
<div class="card-title">
|
|
687
|
-
<div class="card-value">${cards.
|
|
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>
|
|
688
728
|
</div>
|
|
689
729
|
<div class="metric-card">
|
|
690
|
-
<div class="card-title">
|
|
691
|
-
<div class="card-value"
|
|
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>
|
|
692
732
|
</div>
|
|
693
733
|
</div>
|
|
694
734
|
|
|
735
|
+
<div class="section-title">\u26A1 Diagnostic Breakdowns</div>
|
|
695
736
|
<div class="top-layout-grid">
|
|
696
737
|
<div class="verdict-box">
|
|
697
738
|
<div class="verdict-title">\u{1F916} AI Stress-Testing Agent Verdict</div>
|
|
@@ -763,6 +804,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
763
804
|
<th>Throughput</th>
|
|
764
805
|
<th>Avg Latency</th>
|
|
765
806
|
<th>P95 Latency</th>
|
|
807
|
+
<th>Simulated LCP</th>
|
|
766
808
|
<th>Error Rate</th>
|
|
767
809
|
<th>Status</th>
|
|
768
810
|
</tr>
|
|
@@ -775,6 +817,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
775
817
|
<td>${h.throughput.toFixed(0)} req/sec</td>
|
|
776
818
|
<td>${h.avgLatency.toFixed(1)} ms</td>
|
|
777
819
|
<td>${h.p95Latency.toFixed(1)} ms</td>
|
|
820
|
+
<td>${h.lcp.toFixed(0)} ms</td>
|
|
778
821
|
<td>${(h.errorRate * 100).toFixed(2)}%</td>
|
|
779
822
|
<td><span class="badge ${isPassed ? "badge-success" : "badge-fail"}">${isPassed ? "SLO PASS" : "SLO BREACH"}</span></td>
|
|
780
823
|
</tr>`;
|
|
@@ -947,6 +990,6 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
947
990
|
function printUsage() {
|
|
948
991
|
console.log(`Blaze Core Performance Test CLI Engine
|
|
949
992
|
Options:
|
|
950
|
-
--threads <count> | --duration <seconds> | --agentic | --cluster-logs | --correlate
|
|
993
|
+
--threads <count> | --duration <seconds> | --agentic | --cluster-logs | --correlate | --apdex-t <ms>`);
|
|
951
994
|
}
|
|
952
995
|
main().catch(console.error);
|
|
Binary file
|