blaze-performance-tester 3.1.11 → 3.1.12
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 +115 -13
- package/dist/index.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
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") {
|
|
@@ -262,6 +264,8 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
262
264
|
threads: currentThreads,
|
|
263
265
|
avgLatency: metrics.avgLatencyMs,
|
|
264
266
|
p95Latency: metrics.p95LatencyMs,
|
|
267
|
+
minLatency: metrics.minLatencyMs,
|
|
268
|
+
maxLatency: metrics.maxLatencyMs,
|
|
265
269
|
errorRate: metrics.errorRate,
|
|
266
270
|
apdex: metrics.apdexScore,
|
|
267
271
|
throughput: metrics.requestsPerSecond,
|
|
@@ -337,7 +341,9 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
337
341
|
cls: mathUtils.mean(globalMetricsAccumulator.cls),
|
|
338
342
|
econnresetRate: totalGlobalRequests === 0 ? 0 : totalEconnreset / totalGlobalRequests * 100,
|
|
339
343
|
econnrefusedCount: totalEconnrefused,
|
|
340
|
-
codeRates: aggregatedCodeRates
|
|
344
|
+
codeRates: aggregatedCodeRates,
|
|
345
|
+
minLatency: history.length === 0 ? 0 : Math.min(...history.map((h) => h.minLatency)),
|
|
346
|
+
maxLatency: history.length === 0 ? 0 : Math.max(...history.map((h) => h.maxLatency))
|
|
341
347
|
};
|
|
342
348
|
generateFinalAgentReport(history);
|
|
343
349
|
exportHtmlDashboard(history, config, verdictReason, semanticReport, { total1xx, total2xx, total3xx, total4xx, total5xx }, finalSummaryCards, lastRawRequests);
|
|
@@ -350,6 +356,8 @@ async function runStandardStressTest(config) {
|
|
|
350
356
|
threads: config.threads,
|
|
351
357
|
avgLatency: metrics.avgLatencyMs,
|
|
352
358
|
p95Latency: metrics.p95LatencyMs,
|
|
359
|
+
minLatency: metrics.minLatencyMs,
|
|
360
|
+
maxLatency: metrics.maxLatencyMs,
|
|
353
361
|
errorRate: metrics.errorRate,
|
|
354
362
|
apdex: metrics.apdexScore,
|
|
355
363
|
throughput: metrics.requestsPerSecond,
|
|
@@ -385,7 +393,9 @@ async function runStandardStressTest(config) {
|
|
|
385
393
|
cls: metrics.clsScore,
|
|
386
394
|
econnresetRate: metrics.econnresetRate,
|
|
387
395
|
econnrefusedCount: metrics.econnrefusedCount,
|
|
388
|
-
codeRates: metrics.codeRates
|
|
396
|
+
codeRates: metrics.codeRates,
|
|
397
|
+
minLatency: metrics.minLatencyMs,
|
|
398
|
+
maxLatency: metrics.maxLatencyMs
|
|
389
399
|
};
|
|
390
400
|
const isPassed = metrics.apdexScore >= config.targetApdex && metrics.errorRate <= config.targetErrorRate;
|
|
391
401
|
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 +449,7 @@ function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
|
|
|
439
449
|
if (Math.random() < 0.03) statusCode = 302;
|
|
440
450
|
}
|
|
441
451
|
data.push({
|
|
442
|
-
durationMs: isError ? baseLatency * 0.1 : baseLatency,
|
|
452
|
+
durationMs: isError ? baseLatency * 0.1 + 2 : baseLatency + Math.random() * 20,
|
|
443
453
|
timestampMs: startTime + Math.random() * (durationSec * 1e3),
|
|
444
454
|
dnsTimeMs: 1 + Math.random() * 0.9,
|
|
445
455
|
tcpTimeMs: 4 + Math.random() * 1.5,
|
|
@@ -456,6 +466,8 @@ function processMetricsTelemetry(requests, durationSec, apdexT) {
|
|
|
456
466
|
const avgLatencyMs = mathUtils.mean(durations);
|
|
457
467
|
const stdDevMs = mathUtils.stdDev(durations, avgLatencyMs);
|
|
458
468
|
const p95LatencyMs = mathUtils.percentile(durations, 95);
|
|
469
|
+
const minLatencyMs = mathUtils.min(durations);
|
|
470
|
+
const maxLatencyMs = mathUtils.max(durations);
|
|
459
471
|
const avgDnsMs = mathUtils.mean(requests.map((r) => r.dnsTimeMs || 0));
|
|
460
472
|
const avgTcpMs = mathUtils.mean(requests.map((r) => r.tcpTimeMs || 0));
|
|
461
473
|
const avgTlsMs = mathUtils.mean(requests.map((r) => r.tlsTimeMs || 0));
|
|
@@ -463,7 +475,6 @@ function processMetricsTelemetry(requests, durationSec, apdexT) {
|
|
|
463
475
|
let c1xx = 0, c2xx = 0, c3xx = 0, c4xx = 0, c5xx = 0;
|
|
464
476
|
let econnresetCount = 0;
|
|
465
477
|
let econnrefusedCount = 0;
|
|
466
|
-
const targetCodes = [400, 401, 403, 404, 429, 500, 502, 503, 504];
|
|
467
478
|
const codeCounts = { 400: 0, 401: 0, 403: 0, 404: 0, 429: 0, 500: 0, 502: 0, 503: 0, 504: 0 };
|
|
468
479
|
requests.forEach((r) => {
|
|
469
480
|
if (r.statusCode >= 100 && r.statusCode < 200) c1xx++;
|
|
@@ -480,7 +491,7 @@ function processMetricsTelemetry(requests, durationSec, apdexT) {
|
|
|
480
491
|
}
|
|
481
492
|
});
|
|
482
493
|
const codeRates = {};
|
|
483
|
-
|
|
494
|
+
Object.keys(codeCounts).forEach((code) => {
|
|
484
495
|
codeRates[code] = totalRequests === 0 ? 0 : codeCounts[code] / totalRequests * 100;
|
|
485
496
|
});
|
|
486
497
|
const econnresetRate = totalRequests === 0 ? 0 : econnresetCount / totalRequests * 100;
|
|
@@ -499,6 +510,8 @@ function processMetricsTelemetry(requests, durationSec, apdexT) {
|
|
|
499
510
|
avgLatencyMs,
|
|
500
511
|
stdDevMs,
|
|
501
512
|
p95LatencyMs,
|
|
513
|
+
minLatencyMs,
|
|
514
|
+
maxLatencyMs,
|
|
502
515
|
errorRate,
|
|
503
516
|
apdexScore,
|
|
504
517
|
c1xx,
|
|
@@ -528,7 +541,7 @@ function printMatrixDashboard(m, threads) {
|
|
|
528
541
|
console.log(`-----------------------------------------------------------------------------------------`);
|
|
529
542
|
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
543
|
console.log(`-----------------------------------------------------------------------------------------`);
|
|
531
|
-
console.log(`| Network
|
|
544
|
+
console.log(`| Network & Floor -> RESET: ${m.econnresetRate.toFixed(1)}% | Min/Max Boundary: ${m.minLatencyMs.toFixed(1)}ms / ${m.maxLatencyMs.toFixed(1)}ms`);
|
|
532
545
|
console.log(`-----------------------------------------------------------------------------------------`);
|
|
533
546
|
}
|
|
534
547
|
function generateFinalAgentReport(history) {
|
|
@@ -551,7 +564,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
551
564
|
const activeThreadsData = history.map((h) => h.threads);
|
|
552
565
|
const ttfTrendData = history.map((h) => h.avgLatency * 1.1);
|
|
553
566
|
const waveListItems = history.map((h) => {
|
|
554
|
-
return `<div class="wave-item">Wave (${h.threads} VUs):
|
|
567
|
+
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
568
|
}).join("");
|
|
556
569
|
const estimatedMonthlyCost = (cards.throughput * 0.045 * 24 * 30).toFixed(2);
|
|
557
570
|
const recommendedCpuCores = Math.max(2, Math.ceil(cards.saturationKneePoint / 75));
|
|
@@ -567,6 +580,84 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
567
580
|
const lcpStat = getLcpStatus(cards.lcp);
|
|
568
581
|
const fidStat = getFidStatus(cards.fid);
|
|
569
582
|
const clsStat = getClsStatus(cards.cls);
|
|
583
|
+
const lcp = cards.lcp;
|
|
584
|
+
let seoScore = 100;
|
|
585
|
+
let visibilityLossPct = 0;
|
|
586
|
+
let trafficDropPct = 0;
|
|
587
|
+
let rankDropPositions = 0;
|
|
588
|
+
if (lcp > 2500) {
|
|
589
|
+
if (lcp <= 4e3) {
|
|
590
|
+
const ratio = (lcp - 2500) / 1500;
|
|
591
|
+
seoScore = 100 - ratio * 30;
|
|
592
|
+
visibilityLossPct = ratio * 14.5;
|
|
593
|
+
trafficDropPct = ratio * 18.2;
|
|
594
|
+
rankDropPositions = Number((ratio * 1.2).toFixed(1));
|
|
595
|
+
} else {
|
|
596
|
+
const ratio = Math.min(1, (lcp - 4e3) / 4e3);
|
|
597
|
+
seoScore = Math.max(8, 70 - ratio * 62);
|
|
598
|
+
visibilityLossPct = 14.5 + ratio * 52;
|
|
599
|
+
trafficDropPct = 18.2 + ratio * 58.5;
|
|
600
|
+
rankDropPositions = Number((1.2 + ratio * 4.6).toFixed(1));
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
let seoColor = "#10b981";
|
|
604
|
+
let seoStatus = "EXCELLENT";
|
|
605
|
+
if (seoScore < 90) {
|
|
606
|
+
seoColor = "#f59e0b";
|
|
607
|
+
seoStatus = "NEEDS IMPROVEMENT";
|
|
608
|
+
}
|
|
609
|
+
if (seoScore < 60) {
|
|
610
|
+
seoColor = "#ef4444";
|
|
611
|
+
seoStatus = "CRITICAL RISK / PENALIZED";
|
|
612
|
+
}
|
|
613
|
+
const seoPredictorPanelHtml = `
|
|
614
|
+
<div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
|
|
615
|
+
<h3 style="color: #38bdf8; font-size: 1.2rem; border-bottom: none; margin-bottom: 0.25rem;">
|
|
616
|
+
\u{1F50D} Google SEO Rank & Search Visibility Impact Predictor
|
|
617
|
+
</h3>
|
|
618
|
+
<div style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 1.5rem;">
|
|
619
|
+
Predictive algorithmic simulation modeling response degradation metrics directly against Google Core Web Vitals signal rules.
|
|
620
|
+
</div>
|
|
621
|
+
|
|
622
|
+
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; flex-wrap: wrap;">
|
|
623
|
+
<div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b; text-align: center;">
|
|
624
|
+
<div style="font-size: 0.75rem; color: #64748b; text-transform: uppercase; font-weight: bold;">Core Web Vitals SEO Score</div>
|
|
625
|
+
<div style="font-size: 1.8rem; font-weight: bold; color: ${seoColor}; margin-top: 0.25rem;">
|
|
626
|
+
${seoScore.toFixed(0)}<span style="font-size: 1rem; color: #94a3b8;">/100</span>
|
|
627
|
+
</div>
|
|
628
|
+
<div style="font-size: 0.75rem; font-weight: bold; margin-top: 0.4rem; color: ${seoColor};">${seoStatus}</div>
|
|
629
|
+
</div>
|
|
630
|
+
|
|
631
|
+
<div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b; text-align: center;">
|
|
632
|
+
<div style="font-size: 0.75rem; color: #64748b; text-transform: uppercase; font-weight: bold;">Search Visibility Loss</div>
|
|
633
|
+
<div style="font-size: 1.8rem; font-weight: bold; color: ${visibilityLossPct > 0 ? "#ef4444" : "#10b981"}; margin-top: 0.25rem;">
|
|
634
|
+
-${visibilityLossPct.toFixed(1)}%
|
|
635
|
+
</div>
|
|
636
|
+
<div style="font-size: 0.75rem; color: #64748b; margin-top: 0.4rem;">Projected SERP Impression Drop</div>
|
|
637
|
+
</div>
|
|
638
|
+
|
|
639
|
+
<div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b; text-align: center;">
|
|
640
|
+
<div style="font-size: 0.75rem; color: #64748b; text-transform: uppercase; font-weight: bold;">Est. Organic Traffic Loss</div>
|
|
641
|
+
<div style="font-size: 1.8rem; font-weight: bold; color: ${trafficDropPct > 0 ? "#ef4444" : "#10b981"}; margin-top: 0.25rem;">
|
|
642
|
+
-${trafficDropPct.toFixed(1)}%
|
|
643
|
+
</div>
|
|
644
|
+
<div style="font-size: 0.75rem; color: #64748b; margin-top: 0.4rem;">Expected funnel volume drop</div>
|
|
645
|
+
</div>
|
|
646
|
+
|
|
647
|
+
<div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b; text-align: center;">
|
|
648
|
+
<div style="font-size: 0.75rem; color: #64748b; text-transform: uppercase; font-weight: bold;">Avg SERP Position Shift</div>
|
|
649
|
+
<div style="font-size: 1.8rem; font-weight: bold; color: ${rankDropPositions > 0 ? "#f59e0b" : "#10b981"}; margin-top: 0.25rem;">
|
|
650
|
+
${rankDropPositions > 0 ? `+${rankDropPositions}` : "0.0"}
|
|
651
|
+
</div>
|
|
652
|
+
<div style="font-size: 0.75rem; color: #64748b; margin-top: 0.4rem;">Rank places dropped down standard index</div>
|
|
653
|
+
</div>
|
|
654
|
+
</div>
|
|
655
|
+
|
|
656
|
+
<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;">
|
|
657
|
+
<strong>\u{1F52E} SEO Engine Intelligence Breakdown:</strong>
|
|
658
|
+
${lcp <= 2500 ? "Your simulated Largest Contentful Paint equivalent falls safely within Google's 'Good' range (≤ 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 (> 4000ms). At this level, ranking algorithms actively de-weight domains. Expect structural search visibility erosion up to ${visibilityLossPct.toFixed(0)}% and immediate organic traffic traffic redirection.`}
|
|
659
|
+
</div>
|
|
660
|
+
</div>`;
|
|
570
661
|
const connectionDiagnosticsPanelHtml = `
|
|
571
662
|
<div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
|
|
572
663
|
<h3 style="color: #38bdf8; font-size: 1.2rem; border-bottom: none; margin-bottom: 0.25rem;">
|
|
@@ -744,7 +835,8 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
744
835
|
.meta { color: #94a3b8; font-size: 0.9rem; margin-top: 0.5rem; }
|
|
745
836
|
|
|
746
837
|
.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(
|
|
838
|
+
.cards-wrapper { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
|
|
839
|
+
@media (max-width: 1024px) { .cards-wrapper { grid-template-columns: repeat(2, 1fr); } }
|
|
748
840
|
.metric-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 6px; padding: 1.25rem; position: relative; }
|
|
749
841
|
.card-title { font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; }
|
|
750
842
|
.card-value { font-size: 1.6rem; font-weight: 700; color: #ffffff; }
|
|
@@ -753,6 +845,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
753
845
|
.card-value.orange { color: #f97316; }
|
|
754
846
|
.card-value.purple { color: #a855f7; }
|
|
755
847
|
.card-value.cyan { color: #38bdf8; }
|
|
848
|
+
.card-value.red { color: #f87171; }
|
|
756
849
|
.card-subtext { font-size: 0.8rem; font-weight: 500; margin-left: 0.4rem; display: inline-block; }
|
|
757
850
|
.card-subtext.green { color: #10b981; }
|
|
758
851
|
.card-subtext.orange { color: #f97316; }
|
|
@@ -800,12 +893,20 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
800
893
|
<div class="cards-wrapper">
|
|
801
894
|
<div class="metric-card">
|
|
802
895
|
<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">(
|
|
896
|
+
<div class="card-value green">${cards.apdex.toFixed(2)}<span class="card-subtext green">(Verified)</span></div>
|
|
804
897
|
</div>
|
|
805
898
|
<div class="metric-card">
|
|
806
899
|
<div class="card-title">Throughput</div>
|
|
807
900
|
<div class="card-value orange">${cards.throughput.toFixed(1)}<span class="unit">/s</span></div>
|
|
808
901
|
</div>
|
|
902
|
+
<div class="metric-card">
|
|
903
|
+
<div class="card-title">Minimum Response Time</div>
|
|
904
|
+
<div class="card-value green">${cards.minLatency.toFixed(1)}<span class="unit">ms</span></div>
|
|
905
|
+
</div>
|
|
906
|
+
<div class="metric-card">
|
|
907
|
+
<div class="card-title">Maximum Response Time</div>
|
|
908
|
+
<div class="card-value red">${cards.maxLatency.toFixed(1)}<span class="unit">ms</span></div>
|
|
909
|
+
</div>
|
|
809
910
|
<div class="metric-card">
|
|
810
911
|
<div class="card-title">Network Bandwidth</div>
|
|
811
912
|
<div class="card-value purple">${cards.bandwidth.toFixed(2)}<span class="unit">MB/s</span></div>
|
|
@@ -912,8 +1013,8 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
912
1013
|
<th>Throughput</th>
|
|
913
1014
|
<th>Avg Latency</th>
|
|
914
1015
|
<th>P95 Latency</th>
|
|
915
|
-
<th>
|
|
916
|
-
<th>
|
|
1016
|
+
<th>Min / Max Floor</th>
|
|
1017
|
+
<th>RESET Rate</th>
|
|
917
1018
|
<th>Status</th>
|
|
918
1019
|
</tr>
|
|
919
1020
|
</thead>
|
|
@@ -925,14 +1026,15 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
925
1026
|
<td>${h.throughput.toFixed(0)} req/sec</td>
|
|
926
1027
|
<td>${h.avgLatency.toFixed(1)} ms</td>
|
|
927
1028
|
<td>${h.p95Latency.toFixed(1)} ms</td>
|
|
1029
|
+
<td><small>${h.minLatency.toFixed(1)}ms / ${h.maxLatency.toFixed(1)}ms</small></td>
|
|
928
1030
|
<td>${h.econnresetRate.toFixed(2)}%</td>
|
|
929
|
-
<td>${h.econnrefusedCount} nodes</td>
|
|
930
1031
|
<td><span class="badge ${isPassed ? "badge-success" : "badge-fail"}">${isPassed ? "SLO PASS" : "SLO BREACH"}</span></td>
|
|
931
1032
|
</tr>`;
|
|
932
1033
|
}).join("")}
|
|
933
1034
|
</tbody>
|
|
934
1035
|
</table>
|
|
935
1036
|
</div>
|
|
1037
|
+
${seoPredictorPanelHtml}
|
|
936
1038
|
${connectionDiagnosticsPanelHtml}
|
|
937
1039
|
${liveAdjusterHtml}
|
|
938
1040
|
${rightSizingHtml}
|
|
Binary file
|