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 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 T = 50;
418
- const satisfied = requests.filter((r) => r.durationMs <= T && r.statusCode < 400).length;
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)}%</div>`;
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; color: #10b981; font-weight: 500; margin-left: 0.4rem; display: inline-block; }
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: 50ms)</div>
659
- <div class="card-value green">${cards.apdex.toFixed(2)}<span class="card-subtext">(Good)</span></div>
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">DNS Lookup</div>
679
- <div class="card-value">${cards.dns.toFixed(2)}<span class="unit">ms</span></div>
714
+ <div class="card-title">Stability (Std Dev)</div>
715
+ <div class="card-value">&plusmn;${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">TCP Connect</div>
683
- <div class="card-value">${cards.tcp.toFixed(2)}<span class="unit">ms</span></div>
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">TLS Handshake</div>
687
- <div class="card-value">${cards.tls.toFixed(2)}<span class="unit">ms</span></div>
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">Stability (Std Dev)</div>
691
- <div class="card-value">&plusmn;${cards.stdDev.toFixed(1)}<span class="unit">ms</span></div>
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blaze-performance-tester",
3
- "version": "3.1.6",
3
+ "version": "3.1.7",
4
4
  "description": "A high-performance, multi-threaded load testing engine built with Rust and QuickJS.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",