blaze-performance-tester 3.1.11 → 3.1.13

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
@@ -85,7 +85,9 @@ var mathUtils = {
85
85
  const sorted = [...arr].sort((a, b) => a - b);
86
86
  const index = Math.ceil(p / 100 * sorted.length) - 1;
87
87
  return sorted[Math.max(0, index)];
88
- }
88
+ },
89
+ min: (arr) => arr.length === 0 ? 0 : arr.reduce((min, val) => val < min ? val : min, arr[0]),
90
+ max: (arr) => arr.length === 0 ? 0 : arr.reduce((max, val) => val > max ? val : max, arr[0])
89
91
  };
90
92
  async function executeTestPipeline(config) {
91
93
  if (!blazeCore || typeof blazeCore.runCorrelatedTransaction !== "function") {
@@ -139,6 +141,7 @@ function parseArguments(args) {
139
141
  const targetScript = path.resolve(args[0] || ".");
140
142
  const isAgentic = args.includes("--agentic");
141
143
  const isCorrelate = args.includes("--correlate");
144
+ const isSeoEnabled = args.includes("--seo");
142
145
  const clusterLogs = !args.includes("--no-cluster-logs");
143
146
  let threads = 10;
144
147
  let durationSec = 10;
@@ -184,6 +187,7 @@ function parseArguments(args) {
184
187
  targetScript,
185
188
  isAgentic,
186
189
  isCorrelate,
190
+ isSeoEnabled,
187
191
  clusterLogs,
188
192
  threads,
189
193
  durationSec,
@@ -262,6 +266,8 @@ async function runIntelligentAgenticStressTest(config) {
262
266
  threads: currentThreads,
263
267
  avgLatency: metrics.avgLatencyMs,
264
268
  p95Latency: metrics.p95LatencyMs,
269
+ minLatency: metrics.minLatencyMs,
270
+ maxLatency: metrics.maxLatencyMs,
265
271
  errorRate: metrics.errorRate,
266
272
  apdex: metrics.apdexScore,
267
273
  throughput: metrics.requestsPerSecond,
@@ -337,7 +343,9 @@ async function runIntelligentAgenticStressTest(config) {
337
343
  cls: mathUtils.mean(globalMetricsAccumulator.cls),
338
344
  econnresetRate: totalGlobalRequests === 0 ? 0 : totalEconnreset / totalGlobalRequests * 100,
339
345
  econnrefusedCount: totalEconnrefused,
340
- codeRates: aggregatedCodeRates
346
+ codeRates: aggregatedCodeRates,
347
+ minLatency: history.length === 0 ? 0 : Math.min(...history.map((h) => h.minLatency)),
348
+ maxLatency: history.length === 0 ? 0 : Math.max(...history.map((h) => h.maxLatency))
341
349
  };
342
350
  generateFinalAgentReport(history);
343
351
  exportHtmlDashboard(history, config, verdictReason, semanticReport, { total1xx, total2xx, total3xx, total4xx, total5xx }, finalSummaryCards, lastRawRequests);
@@ -350,6 +358,8 @@ async function runStandardStressTest(config) {
350
358
  threads: config.threads,
351
359
  avgLatency: metrics.avgLatencyMs,
352
360
  p95Latency: metrics.p95LatencyMs,
361
+ minLatency: metrics.minLatencyMs,
362
+ maxLatency: metrics.maxLatencyMs,
353
363
  errorRate: metrics.errorRate,
354
364
  apdex: metrics.apdexScore,
355
365
  throughput: metrics.requestsPerSecond,
@@ -385,7 +395,9 @@ async function runStandardStressTest(config) {
385
395
  cls: metrics.clsScore,
386
396
  econnresetRate: metrics.econnresetRate,
387
397
  econnrefusedCount: metrics.econnrefusedCount,
388
- codeRates: metrics.codeRates
398
+ codeRates: metrics.codeRates,
399
+ minLatency: metrics.minLatencyMs,
400
+ maxLatency: metrics.maxLatencyMs
389
401
  };
390
402
  const isPassed = metrics.apdexScore >= config.targetApdex && metrics.errorRate <= config.targetErrorRate;
391
403
  const verdictReason = isPassed ? "Static single-wave baseline checks concluded successfully without triggering health boundaries." : "Static verification loop completed with values exceeding defined quality thresholds.";
@@ -439,7 +451,7 @@ function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
439
451
  if (Math.random() < 0.03) statusCode = 302;
440
452
  }
441
453
  data.push({
442
- durationMs: isError ? baseLatency * 0.1 : baseLatency,
454
+ durationMs: isError ? baseLatency * 0.1 + 2 : baseLatency + Math.random() * 20,
443
455
  timestampMs: startTime + Math.random() * (durationSec * 1e3),
444
456
  dnsTimeMs: 1 + Math.random() * 0.9,
445
457
  tcpTimeMs: 4 + Math.random() * 1.5,
@@ -456,6 +468,8 @@ function processMetricsTelemetry(requests, durationSec, apdexT) {
456
468
  const avgLatencyMs = mathUtils.mean(durations);
457
469
  const stdDevMs = mathUtils.stdDev(durations, avgLatencyMs);
458
470
  const p95LatencyMs = mathUtils.percentile(durations, 95);
471
+ const minLatencyMs = mathUtils.min(durations);
472
+ const maxLatencyMs = mathUtils.max(durations);
459
473
  const avgDnsMs = mathUtils.mean(requests.map((r) => r.dnsTimeMs || 0));
460
474
  const avgTcpMs = mathUtils.mean(requests.map((r) => r.tcpTimeMs || 0));
461
475
  const avgTlsMs = mathUtils.mean(requests.map((r) => r.tlsTimeMs || 0));
@@ -463,7 +477,6 @@ function processMetricsTelemetry(requests, durationSec, apdexT) {
463
477
  let c1xx = 0, c2xx = 0, c3xx = 0, c4xx = 0, c5xx = 0;
464
478
  let econnresetCount = 0;
465
479
  let econnrefusedCount = 0;
466
- const targetCodes = [400, 401, 403, 404, 429, 500, 502, 503, 504];
467
480
  const codeCounts = { 400: 0, 401: 0, 403: 0, 404: 0, 429: 0, 500: 0, 502: 0, 503: 0, 504: 0 };
468
481
  requests.forEach((r) => {
469
482
  if (r.statusCode >= 100 && r.statusCode < 200) c1xx++;
@@ -480,7 +493,7 @@ function processMetricsTelemetry(requests, durationSec, apdexT) {
480
493
  }
481
494
  });
482
495
  const codeRates = {};
483
- targetCodes.forEach((code) => {
496
+ Object.keys(codeCounts).forEach((code) => {
484
497
  codeRates[code] = totalRequests === 0 ? 0 : codeCounts[code] / totalRequests * 100;
485
498
  });
486
499
  const econnresetRate = totalRequests === 0 ? 0 : econnresetCount / totalRequests * 100;
@@ -499,6 +512,8 @@ function processMetricsTelemetry(requests, durationSec, apdexT) {
499
512
  avgLatencyMs,
500
513
  stdDevMs,
501
514
  p95LatencyMs,
515
+ minLatencyMs,
516
+ maxLatencyMs,
502
517
  errorRate,
503
518
  apdexScore,
504
519
  c1xx,
@@ -528,7 +543,7 @@ function printMatrixDashboard(m, threads) {
528
543
  console.log(`-----------------------------------------------------------------------------------------`);
529
544
  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)} |`);
530
545
  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 |`);
546
+ console.log(`| Network & Floor -> RESET: ${m.econnresetRate.toFixed(1)}% | Min/Max Boundary: ${m.minLatencyMs.toFixed(1)}ms / ${m.maxLatencyMs.toFixed(1)}ms`);
532
547
  console.log(`-----------------------------------------------------------------------------------------`);
533
548
  }
534
549
  function generateFinalAgentReport(history) {
@@ -551,7 +566,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
551
566
  const activeThreadsData = history.map((h) => h.threads);
552
567
  const ttfTrendData = history.map((h) => h.avgLatency * 1.1);
553
568
  const waveListItems = history.map((h) => {
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>`;
569
+ return `<div class="wave-item">Wave (${h.threads} VUs): Floor ${h.minLatency.toFixed(1)}ms | Ceil ${h.maxLatency.toFixed(1)}ms | P95 ${h.p95Latency.toFixed(1)}ms</div>`;
555
570
  }).join("");
556
571
  const estimatedMonthlyCost = (cards.throughput * 0.045 * 24 * 30).toFixed(2);
557
572
  const recommendedCpuCores = Math.max(2, Math.ceil(cards.saturationKneePoint / 75));
@@ -567,6 +582,86 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
567
582
  const lcpStat = getLcpStatus(cards.lcp);
568
583
  const fidStat = getFidStatus(cards.fid);
569
584
  const clsStat = getClsStatus(cards.cls);
585
+ let seoPredictorPanelHtml = "";
586
+ if (config.isSeoEnabled) {
587
+ const lcp = cards.lcp;
588
+ let seoScore = 100;
589
+ let visibilityLossPct = 0;
590
+ let trafficDropPct = 0;
591
+ let rankDropPositions = 0;
592
+ if (lcp > 2500) {
593
+ if (lcp <= 4e3) {
594
+ const ratio = (lcp - 2500) / 1500;
595
+ seoScore = 100 - ratio * 30;
596
+ visibilityLossPct = ratio * 14.5;
597
+ trafficDropPct = ratio * 18.2;
598
+ rankDropPositions = Number((ratio * 1.2).toFixed(1));
599
+ } else {
600
+ const ratio = Math.min(1, (lcp - 4e3) / 4e3);
601
+ seoScore = Math.max(8, 70 - ratio * 62);
602
+ visibilityLossPct = 14.5 + ratio * 52;
603
+ trafficDropPct = 18.2 + ratio * 58.5;
604
+ rankDropPositions = Number((1.2 + ratio * 4.6).toFixed(1));
605
+ }
606
+ }
607
+ let seoColor = "#10b981";
608
+ let seoStatus = "EXCELLENT";
609
+ if (seoScore < 90) {
610
+ seoColor = "#f59e0b";
611
+ seoStatus = "NEEDS IMPROVEMENT";
612
+ }
613
+ if (seoScore < 60) {
614
+ seoColor = "#ef4444";
615
+ seoStatus = "CRITICAL RISK / PENALIZED";
616
+ }
617
+ seoPredictorPanelHtml = `
618
+ <div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
619
+ <h3 style="color: #38bdf8; font-size: 1.2rem; border-bottom: none; margin-bottom: 0.25rem;">
620
+ \u{1F50D} Google SEO Rank & Search Visibility Impact Predictor
621
+ </h3>
622
+ <div style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 1.5rem;">
623
+ Predictive algorithmic simulation modeling response degradation metrics directly against Google Core Web Vitals signal rules.
624
+ </div>
625
+
626
+ <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; flex-wrap: wrap;">
627
+ <div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b; text-align: center;">
628
+ <div style="font-size: 0.75rem; color: #64748b; text-transform: uppercase; font-weight: bold;">Core Web Vitals SEO Score</div>
629
+ <div style="font-size: 1.8rem; font-weight: bold; color: ${seoColor}; margin-top: 0.25rem;">
630
+ ${seoScore.toFixed(0)}<span style="font-size: 1rem; color: #94a3b8;">/100</span>
631
+ </div>
632
+ <div style="font-size: 0.75rem; font-weight: bold; margin-top: 0.4rem; color: ${seoColor};">${seoStatus}</div>
633
+ </div>
634
+
635
+ <div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b; text-align: center;">
636
+ <div style="font-size: 0.75rem; color: #64748b; text-transform: uppercase; font-weight: bold;">Search Visibility Loss</div>
637
+ <div style="font-size: 1.8rem; font-weight: bold; color: ${visibilityLossPct > 0 ? "#ef4444" : "#10b981"}; margin-top: 0.25rem;">
638
+ -${visibilityLossPct.toFixed(1)}%
639
+ </div>
640
+ <div style="font-size: 0.75rem; color: #64748b; margin-top: 0.4rem;">Projected SERP Impression Drop</div>
641
+ </div>
642
+
643
+ <div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b; text-align: center;">
644
+ <div style="font-size: 1.8rem; font-weight: bold; color: ${trafficDropPct > 0 ? "#ef4444" : "#10b981"}; margin-top: 0.25rem;">
645
+ -${trafficDropPct.toFixed(1)}%
646
+ </div>
647
+ <div style="font-size: 0.75rem; color: #64748b; margin-top: 0.4rem;">Expected funnel volume drop</div>
648
+ </div>
649
+
650
+ <div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b; text-align: center;">
651
+ <div style="font-size: 0.75rem; color: #64748b; text-transform: uppercase; font-weight: bold;">Avg SERP Position Shift</div>
652
+ <div style="font-size: 1.8rem; font-weight: bold; color: ${rankDropPositions > 0 ? "#f59e0b" : "#10b981"}; margin-top: 0.25rem;">
653
+ ${rankDropPositions > 0 ? `+${rankDropPositions}` : "0.0"}
654
+ </div>
655
+ <div style="font-size: 0.75rem; color: #64748b; margin-top: 0.4rem;">Rank places dropped down standard index</div>
656
+ </div>
657
+ </div>
658
+
659
+ <div style="margin-top: 1rem; background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b; font-size: 0.85rem; line-height: 1.5; color: #cbd5e1;">
660
+ <strong>\u{1F52E} SEO Engine Intelligence Breakdown:</strong>
661
+ ${lcp <= 2500 ? "Your simulated Largest Contentful Paint equivalent falls safely within Google's 'Good' range (&le; 2500ms). The parsing layer calculates zero performance-based rank penalties under current system load profiles." : lcp <= 4e3 ? `Warning: Current concurrency load has pushed LCP to ${lcp.toFixed(0)}ms, landing in Google's 'Needs Improvement' window. This response drag risks triggering index rank adjustments, potentially demoting listings down by ~${rankDropPositions} positions and sacrificing around ${trafficDropPct.toFixed(0)}% of organic top-of-funnel users.` : `Critical Signal Breach: Load performance has forced LCP to ${lcp.toFixed(0)}ms, severely violating Google's 'Poor' performance ceiling (&gt; 4000ms). At this level, ranking algorithms actively de-weight domains. Expect structural search visibility erosion up to ${visibilityLossPct.toFixed(0)}% and immediate organic traffic redirection.`}
662
+ </div>
663
+ </div>`;
664
+ }
570
665
  const connectionDiagnosticsPanelHtml = `
571
666
  <div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
572
667
  <h3 style="color: #38bdf8; font-size: 1.2rem; border-bottom: none; margin-bottom: 0.25rem;">
@@ -744,7 +839,8 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
744
839
  .meta { color: #94a3b8; font-size: 0.9rem; margin-top: 0.5rem; }
745
840
 
746
841
  .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; }
747
- .cards-wrapper { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
842
+ .cards-wrapper { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
843
+ @media (max-width: 1024px) { .cards-wrapper { grid-template-columns: repeat(2, 1fr); } }
748
844
  .metric-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 6px; padding: 1.25rem; position: relative; }
749
845
  .card-title { font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; }
750
846
  .card-value { font-size: 1.6rem; font-weight: 700; color: #ffffff; }
@@ -753,6 +849,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
753
849
  .card-value.orange { color: #f97316; }
754
850
  .card-value.purple { color: #a855f7; }
755
851
  .card-value.cyan { color: #38bdf8; }
852
+ .card-value.red { color: #f87171; }
756
853
  .card-subtext { font-size: 0.8rem; font-weight: 500; margin-left: 0.4rem; display: inline-block; }
757
854
  .card-subtext.green { color: #10b981; }
758
855
  .card-subtext.orange { color: #f97316; }
@@ -800,12 +897,20 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
800
897
  <div class="cards-wrapper">
801
898
  <div class="metric-card">
802
899
  <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>
900
+ <div class="card-value green">${cards.apdex.toFixed(2)}<span class="card-subtext green">(Verified)</span></div>
804
901
  </div>
805
902
  <div class="metric-card">
806
903
  <div class="card-title">Throughput</div>
807
904
  <div class="card-value orange">${cards.throughput.toFixed(1)}<span class="unit">/s</span></div>
808
905
  </div>
906
+ <div class="metric-card">
907
+ <div class="card-title">Minimum Response Time</div>
908
+ <div class="card-value green">${cards.minLatency.toFixed(1)}<span class="unit">ms</span></div>
909
+ </div>
910
+ <div class="metric-card">
911
+ <div class="card-title">Maximum Response Time</div>
912
+ <div class="card-value red">${cards.maxLatency.toFixed(1)}<span class="unit">ms</span></div>
913
+ </div>
809
914
  <div class="metric-card">
810
915
  <div class="card-title">Network Bandwidth</div>
811
916
  <div class="card-value purple">${cards.bandwidth.toFixed(2)}<span class="unit">MB/s</span></div>
@@ -912,8 +1017,8 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
912
1017
  <th>Throughput</th>
913
1018
  <th>Avg Latency</th>
914
1019
  <th>P95 Latency</th>
915
- <th>ECONNRESET Rate</th>
916
- <th>ECONNREFUSED</th>
1020
+ <th>Min / Max Floor</th>
1021
+ <th>RESET Rate</th>
917
1022
  <th>Status</th>
918
1023
  </tr>
919
1024
  </thead>
@@ -925,14 +1030,15 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
925
1030
  <td>${h.throughput.toFixed(0)} req/sec</td>
926
1031
  <td>${h.avgLatency.toFixed(1)} ms</td>
927
1032
  <td>${h.p95Latency.toFixed(1)} ms</td>
1033
+ <td><small>${h.minLatency.toFixed(1)}ms / ${h.maxLatency.toFixed(1)}ms</small></td>
928
1034
  <td>${h.econnresetRate.toFixed(2)}%</td>
929
- <td>${h.econnrefusedCount} nodes</td>
930
1035
  <td><span class="badge ${isPassed ? "badge-success" : "badge-fail"}">${isPassed ? "SLO PASS" : "SLO BREACH"}</span></td>
931
1036
  </tr>`;
932
1037
  }).join("")}
933
1038
  </tbody>
934
1039
  </table>
935
1040
  </div>
1041
+ ${seoPredictorPanelHtml}
936
1042
  ${connectionDiagnosticsPanelHtml}
937
1043
  ${liveAdjusterHtml}
938
1044
  ${rightSizingHtml}
@@ -1096,6 +1202,8 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
1096
1202
  }
1097
1203
  }
1098
1204
  function printUsage() {
1099
- console.log(`Blaze Core Performance Test CLI Engine\\nOptions:\\n --threads <count> | --duration <seconds> | --agentic | --cluster-logs | --correlate | --apdex-t <ms>`);
1205
+ console.log(`Blaze Core Performance Test CLI Engine
1206
+ Options:
1207
+ --threads <count> | --duration <seconds> | --agentic | --cluster-logs | --correlate | --apdex-t <ms> | --seo`);
1100
1208
  }
1101
1209
  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.11",
3
+ "version": "3.1.13",
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",