blaze-performance-tester 3.1.54 → 3.1.56
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 +119 -17
- package/dist/index.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -151,6 +151,31 @@ var mathUtils = {
|
|
|
151
151
|
return sorted[Math.max(0, index)];
|
|
152
152
|
}
|
|
153
153
|
};
|
|
154
|
+
function getLoadMultiplier(elapsedMs, rampUpMs, steadyMs, rampDownMs) {
|
|
155
|
+
const totalDurationMs = rampUpMs + steadyMs + rampDownMs;
|
|
156
|
+
if (elapsedMs < rampUpMs && rampUpMs > 0) {
|
|
157
|
+
return elapsedMs / rampUpMs;
|
|
158
|
+
} else if (elapsedMs < rampUpMs + steadyMs) {
|
|
159
|
+
return 1;
|
|
160
|
+
} else if (elapsedMs < totalDurationMs && rampDownMs > 0) {
|
|
161
|
+
const timeIntoRampDown = elapsedMs - (rampUpMs + steadyMs);
|
|
162
|
+
return 1 - timeIntoRampDown / rampDownMs;
|
|
163
|
+
}
|
|
164
|
+
return elapsedMs >= totalDurationMs ? 0 : 1;
|
|
165
|
+
}
|
|
166
|
+
function loadBaselineData(filePath) {
|
|
167
|
+
if (!filePath) return null;
|
|
168
|
+
try {
|
|
169
|
+
const resolved = path.resolve(filePath);
|
|
170
|
+
if (fs.existsSync(resolved)) {
|
|
171
|
+
console.log(`\u{1F4C1} Loaded baseline comparison telemetry from: ${resolved}`);
|
|
172
|
+
return JSON.parse(fs.readFileSync(resolved, "utf-8"));
|
|
173
|
+
}
|
|
174
|
+
} catch (e) {
|
|
175
|
+
console.error(`\u274C Failed to parse baseline telemetry file: ${e}`);
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
154
179
|
async function generateGeminiRCA(history, finalSummary, errorLogs) {
|
|
155
180
|
const apiKey = process.env.GEMINI_API_KEY;
|
|
156
181
|
if (!apiKey) {
|
|
@@ -272,23 +297,32 @@ function parseArguments(args) {
|
|
|
272
297
|
const clusterLogs = !args.includes("--no-cluster-logs");
|
|
273
298
|
let threads = 10;
|
|
274
299
|
let durationSec = 10;
|
|
300
|
+
let rampUpSec = 0;
|
|
301
|
+
let rampDownSec = 0;
|
|
275
302
|
let targetApdex = 0.85;
|
|
276
303
|
let targetErrorRate = 0.01;
|
|
277
304
|
let maxThreads = 300;
|
|
278
305
|
let maxAllowedLatencyMs = 800;
|
|
279
306
|
let outputHtml = "blaze-dashboard.html";
|
|
307
|
+
let baselinePath = null;
|
|
280
308
|
const threadsIdx = args.indexOf("--threads");
|
|
281
309
|
if (threadsIdx !== -1) threads = parseInt(args[threadsIdx + 1], 10);
|
|
282
310
|
const durationIdx = args.indexOf("--duration");
|
|
283
311
|
if (durationIdx !== -1) durationSec = parseInt(args[durationIdx + 1], 10);
|
|
312
|
+
const rampUpIdx = args.indexOf("--ramp-up");
|
|
313
|
+
if (rampUpIdx !== -1) rampUpSec = parseInt(args[rampUpIdx + 1], 10);
|
|
314
|
+
const rampDownIdx = args.indexOf("--ramp-down");
|
|
315
|
+
if (rampDownIdx !== -1) rampDownSec = parseInt(args[rampDownIdx + 1], 10);
|
|
284
316
|
const apdexIdx = args.indexOf("--target-apdex");
|
|
285
|
-
if (apdexIdx !== -1) targetApdex = parseFloat(
|
|
317
|
+
if (apdexIdx !== -1) targetApdex = parseFloat(apdexIdx + 1);
|
|
286
318
|
const errorIdx = args.indexOf("--target-error");
|
|
287
319
|
if (errorIdx !== -1) targetErrorRate = parseFloat(args[errorIdx + 1]);
|
|
288
320
|
const maxThreadsIdx = args.indexOf("--max-threads");
|
|
289
321
|
if (maxThreadsIdx !== -1) maxThreads = parseInt(args[maxThreadsIdx + 1], 10);
|
|
290
322
|
const outputIdx = args.indexOf("--output");
|
|
291
323
|
if (outputIdx !== -1) outputHtml = args[outputIdx + 1];
|
|
324
|
+
const baselineIdx = args.indexOf("--baseline");
|
|
325
|
+
if (baselineIdx !== -1) baselinePath = args[baselineIdx + 1];
|
|
292
326
|
const positionalNums = args.slice(1).filter((arg) => !arg.startsWith("-")).map(Number).filter((n) => !isNaN(n));
|
|
293
327
|
if (positionalNums.length > 0 && threadsIdx === -1 && !isAgentic) threads = positionalNums[0];
|
|
294
328
|
if (positionalNums.length > 1 && durationIdx === -1 && !isAgentic) durationSec = positionalNums[1];
|
|
@@ -315,12 +349,15 @@ function parseArguments(args) {
|
|
|
315
349
|
clusterLogs,
|
|
316
350
|
threads,
|
|
317
351
|
durationSec,
|
|
352
|
+
rampUpSec,
|
|
353
|
+
rampDownSec,
|
|
318
354
|
targetApdex,
|
|
319
355
|
targetErrorRate,
|
|
320
356
|
maxThreads,
|
|
321
357
|
stepSize: 15,
|
|
322
358
|
maxAllowedLatencyMs,
|
|
323
|
-
outputHtml
|
|
359
|
+
outputHtml,
|
|
360
|
+
baselinePath
|
|
324
361
|
};
|
|
325
362
|
}
|
|
326
363
|
async function runBlazeCoreEngine(config) {
|
|
@@ -334,7 +371,7 @@ async function runBlazeCoreEngine(config) {
|
|
|
334
371
|
} catch (err) {
|
|
335
372
|
}
|
|
336
373
|
if (!rawRequests || rawRequests.length === 0) {
|
|
337
|
-
rawRequests = generateSimulationData(config.threads, config.durationSec, config.maxAllowedLatencyMs);
|
|
374
|
+
rawRequests = generateSimulationData(config.threads, config.durationSec, config.maxAllowedLatencyMs, config.rampUpSec, config.rampDownSec);
|
|
338
375
|
}
|
|
339
376
|
const warmUpCutoff = Math.floor(rawRequests.length * 0.1);
|
|
340
377
|
const steadyStateRequests = rawRequests.slice(warmUpCutoff);
|
|
@@ -475,7 +512,9 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
475
512
|
tls: mathUtils.mean(globalMetricsAccumulator.tls),
|
|
476
513
|
stdDev: mathUtils.stdDev(globalMetricsAccumulator.latencies, mathUtils.mean(globalMetricsAccumulator.latencies)),
|
|
477
514
|
saturationKneePoint: saturationKneePoint || finalState.threads || 50,
|
|
478
|
-
healthScore
|
|
515
|
+
healthScore,
|
|
516
|
+
errorRate: finalState.errorRate,
|
|
517
|
+
p95Latency: finalState.p95Latency
|
|
479
518
|
};
|
|
480
519
|
generateFinalAgentReport(history, healthScore);
|
|
481
520
|
const geminiAnalysisHtml = await generateGeminiRCA(history, finalSummaryCards, aggregateErrorLogs);
|
|
@@ -489,10 +528,11 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
489
528
|
const code = Number(codeStr);
|
|
490
529
|
breakdownRates[code] = totalRequestsAccumulator === 0 ? 0 : globalCodeCounts[code] / totalRequestsAccumulator * 100;
|
|
491
530
|
});
|
|
492
|
-
|
|
531
|
+
const baselineData = loadBaselineData(config.baselinePath);
|
|
532
|
+
exportHtmlDashboard(history, config, verdictReason, semanticReport, { total1xx, total2xx, total3xx, total4xx, total5xx, breakdownRates }, finalSummaryCards, lastRawRequests, geminiAnalysisHtml, baselineData);
|
|
493
533
|
}
|
|
494
534
|
async function runStandardStressTest(config) {
|
|
495
|
-
console.log(`\u{1F3CB}\uFE0F Running Standard Stress Test [Threads: ${config.threads} | Duration: ${config.durationSec}s]`);
|
|
535
|
+
console.log(`\u{1F3CB}\uFE0F Running Standard Stress Test [Threads: ${config.threads} | Duration: ${config.durationSec}s | RampUp: ${config.rampUpSec}s | RampDown: ${config.rampDownSec}s]`);
|
|
496
536
|
const { metrics, rawErrors, rawRequests } = await runBlazeCoreEngine(config);
|
|
497
537
|
printMatrixDashboard(metrics, config.threads);
|
|
498
538
|
const healthScore = computeHealthScoreValue(metrics.apdexScore, metrics.errorRate, metrics.p95LatencyMs, config.maxAllowedLatencyMs);
|
|
@@ -524,7 +564,9 @@ async function runStandardStressTest(config) {
|
|
|
524
564
|
tls: metrics.avgTlsMs,
|
|
525
565
|
stdDev: metrics.stdDevMs,
|
|
526
566
|
saturationKneePoint: config.threads,
|
|
527
|
-
healthScore
|
|
567
|
+
healthScore,
|
|
568
|
+
errorRate: metrics.errorRate,
|
|
569
|
+
p95Latency: metrics.p95LatencyMs
|
|
528
570
|
};
|
|
529
571
|
const isPassed = metrics.apdexScore >= config.targetApdex && metrics.errorRate <= config.targetErrorRate;
|
|
530
572
|
const verdictReason = isPassed ? "Static single-wave baseline checks concluded successfully without triggering health boundaries." : "Static verification loop completed with values exceeding defined quality thresholds.";
|
|
@@ -538,6 +580,7 @@ async function runStandardStressTest(config) {
|
|
|
538
580
|
const code = Number(codeStr);
|
|
539
581
|
breakdownRates[code] = metrics.totalRequests === 0 ? 0 : metrics.codeCounts[code] / metrics.totalRequests * 100;
|
|
540
582
|
});
|
|
583
|
+
const baselineData = loadBaselineData(config.baselinePath);
|
|
541
584
|
exportHtmlDashboard(history, config, verdictReason, semanticReport, {
|
|
542
585
|
total1xx: metrics.c1xx,
|
|
543
586
|
total2xx: metrics.c2xx,
|
|
@@ -545,7 +588,7 @@ async function runStandardStressTest(config) {
|
|
|
545
588
|
total4xx: metrics.c4xx,
|
|
546
589
|
total5xx: metrics.c5xx,
|
|
547
590
|
breakdownRates
|
|
548
|
-
}, finalSummaryCards, rawRequests, geminiAnalysisHtml);
|
|
591
|
+
}, finalSummaryCards, rawRequests, geminiAnalysisHtml, baselineData);
|
|
549
592
|
}
|
|
550
593
|
function getFallbackClusterLogs() {
|
|
551
594
|
const logs = [];
|
|
@@ -559,15 +602,23 @@ function getFallbackClusterLogs() {
|
|
|
559
602
|
add("HTTP/1.1 504 Gateway Timeout on POST /api/v1/checkout - upstream socket drop after 30000ms", 19);
|
|
560
603
|
return logs;
|
|
561
604
|
}
|
|
562
|
-
function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
|
|
605
|
+
function generateSimulationData(threads, durationSec, maxAllowedLatencyMs, rampUpSec = 0, rampDownSec = 0) {
|
|
563
606
|
const data = [];
|
|
564
607
|
const totalRequests = Math.max(15, threads * durationSec * 30);
|
|
565
608
|
const startTime = Date.now();
|
|
609
|
+
const rampUpMs = rampUpSec * 1e3;
|
|
610
|
+
const steadyMs = durationSec * 1e3;
|
|
611
|
+
const rampDownMs = rampDownSec * 1e3;
|
|
612
|
+
const totalTestMs = rampUpMs + steadyMs + rampDownMs;
|
|
566
613
|
const targetThresholdBreak = Math.max(5, Math.floor(maxAllowedLatencyMs * 0.05));
|
|
567
|
-
const isBreached = threads > targetThresholdBreak;
|
|
568
|
-
const stressFactor = isBreached ? Math.pow(threads / targetThresholdBreak, 1.5) : 1;
|
|
569
614
|
const errorPool = getFallbackClusterLogs();
|
|
570
615
|
for (let i = 0; i < totalRequests; i++) {
|
|
616
|
+
const offsetMs = Math.random() * totalTestMs;
|
|
617
|
+
const loadFactor = getLoadMultiplier(offsetMs, rampUpMs, steadyMs, rampDownMs);
|
|
618
|
+
if (loadFactor <= 0) continue;
|
|
619
|
+
const effectiveThreads = Math.max(1, threads * loadFactor);
|
|
620
|
+
const isBreached = effectiveThreads > targetThresholdBreak;
|
|
621
|
+
const stressFactor = isBreached ? Math.pow(effectiveThreads / targetThresholdBreak, 1.5) : 1;
|
|
571
622
|
const isError = Math.random() < (isBreached ? 0.05 : 1e-3);
|
|
572
623
|
const baseLatency = (25 + Math.random() * 35) * stressFactor;
|
|
573
624
|
let errorMessage;
|
|
@@ -579,7 +630,7 @@ function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
|
|
|
579
630
|
}
|
|
580
631
|
data.push({
|
|
581
632
|
durationMs: isError ? baseLatency * 0.1 : baseLatency,
|
|
582
|
-
timestampMs: startTime +
|
|
633
|
+
timestampMs: startTime + offsetMs,
|
|
583
634
|
dnsTimeMs: 1 + Math.random() * 0.9,
|
|
584
635
|
tcpTimeMs: 4 + Math.random() * 1.5,
|
|
585
636
|
tlsTimeMs: 6 + Math.random() * 1.8,
|
|
@@ -662,7 +713,12 @@ function generateFinalAgentReport(history, score) {
|
|
|
662
713
|
console.log(`\u26A1 Peak Achieved Cluster Velocity : ${peakThroughput.toFixed(0)} req/sec`);
|
|
663
714
|
console.log("=======================================================\n");
|
|
664
715
|
}
|
|
665
|
-
function exportHtmlDashboard(history, config, verdictReason, semanticReport, responseMatrix, cards, rawRequests = [], geminiAnalysisHtml = "") {
|
|
716
|
+
function exportHtmlDashboard(history, config, verdictReason, semanticReport, responseMatrix, cards, rawRequests = [], geminiAnalysisHtml = "", baselineData = null) {
|
|
717
|
+
const summaryArtifactPath = config.outputHtml.replace(/\.html$/, "-summary.json");
|
|
718
|
+
try {
|
|
719
|
+
fs.writeFileSync(summaryArtifactPath, JSON.stringify(cards, null, 2), "utf-8");
|
|
720
|
+
} catch (e) {
|
|
721
|
+
}
|
|
666
722
|
const labels = history.map((h, i) => `${i + 1}s`);
|
|
667
723
|
const successRequestsData = history.map((h) => h.successRequests);
|
|
668
724
|
const failedWorkloadsData = history.map((h) => h.failedRequests);
|
|
@@ -679,6 +735,50 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
679
735
|
x: Number(((r.timestampMs - firstTimestamp) / 1e3).toFixed(2)),
|
|
680
736
|
y: Number(((r.dnsTimeMs || 0) + (r.tcpTimeMs || 0) + (r.tlsTimeMs || 0) + r.durationMs * 0.3).toFixed(1))
|
|
681
737
|
}));
|
|
738
|
+
let baselineComparisonHtml = "";
|
|
739
|
+
if (baselineData) {
|
|
740
|
+
const diffApdex = cards.apdex - baselineData.apdex;
|
|
741
|
+
const diffThroughput = (cards.throughput - baselineData.throughput) / (baselineData.throughput || 1) * 100;
|
|
742
|
+
const diffTtfb = (cards.ttfb - baselineData.ttfb) / (baselineData.ttfb || 1) * 100;
|
|
743
|
+
const diffHealth = cards.healthScore - baselineData.healthScore;
|
|
744
|
+
const formatDelta = (val, isInverse = false) => {
|
|
745
|
+
const good = isInverse ? val <= 0 : val >= 0;
|
|
746
|
+
const color = good ? "#10b981" : "#ef4444";
|
|
747
|
+
const sign = val > 0 ? "+" : "";
|
|
748
|
+
return `<span style="color: ${color}; font-weight: bold;">${sign}${val.toFixed(1)}%</span>`;
|
|
749
|
+
};
|
|
750
|
+
baselineComparisonHtml = `
|
|
751
|
+
<div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #7c3aed;">
|
|
752
|
+
<h3 style="color: #a78bfa; display: flex; align-items: center; gap: 0.6rem; font-size: 1.2rem; border-bottom: 1px solid #1e293b; padding-bottom: 0.5rem;">
|
|
753
|
+
\u{1F4CA} Baseline Regression Comparison (Delta Engine)
|
|
754
|
+
</h3>
|
|
755
|
+
<p style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 1.5rem;">
|
|
756
|
+
Comparing current execution metrics directly against reference payload from baseline source.
|
|
757
|
+
</p>
|
|
758
|
+
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem;">
|
|
759
|
+
<div style="background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
760
|
+
<div class="card-title">Apdex Shift</div>
|
|
761
|
+
<div style="font-size: 1.4rem; font-weight: bold; color: #ffffff;">${cards.apdex.toFixed(2)}</div>
|
|
762
|
+
<div style="font-size: 0.8rem; margin-top: 0.2rem;">Delta: <span style="color: ${diffApdex >= 0 ? "#10b981" : "#ef4444"}; font-weight: bold;">${diffApdex >= 0 ? "+" : ""}${diffApdex.toFixed(2)}</span></div>
|
|
763
|
+
</div>
|
|
764
|
+
<div style="background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
765
|
+
<div class="card-title">Throughput Delta</div>
|
|
766
|
+
<div style="font-size: 1.4rem; font-weight: bold; color: #ffffff;">${cards.throughput.toFixed(0)}/s</div>
|
|
767
|
+
<div style="font-size: 0.8rem; margin-top: 0.2rem;">Delta: ${formatDelta(diffThroughput, false)}</div>
|
|
768
|
+
</div>
|
|
769
|
+
<div style="background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
770
|
+
<div class="card-title">TTFB Latency Shift</div>
|
|
771
|
+
<div style="font-size: 1.4rem; font-weight: bold; color: #ffffff;">${cards.ttfb.toFixed(1)}ms</div>
|
|
772
|
+
<div style="font-size: 0.8rem; margin-top: 0.2rem;">Delta: ${formatDelta(diffTtfb, true)}</div>
|
|
773
|
+
</div>
|
|
774
|
+
<div style="background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
775
|
+
<div class="card-title">Health Score Diff</div>
|
|
776
|
+
<div style="font-size: 1.4rem; font-weight: bold; color: #ffffff;">${cards.healthScore}/100</div>
|
|
777
|
+
<div style="font-size: 0.8rem; margin-top: 0.2rem;">Delta: <span style="color: ${diffHealth >= 0 ? "#10b981" : "#ef4444"}; font-weight: bold;">${diffHealth >= 0 ? "+" : ""}${diffHealth} pts</span></div>
|
|
778
|
+
</div>
|
|
779
|
+
</div>
|
|
780
|
+
</div>`;
|
|
781
|
+
}
|
|
682
782
|
let logClustersHtml = "";
|
|
683
783
|
if (semanticReport) {
|
|
684
784
|
const totalLogCount = semanticReport.clusters.reduce((sum, c) => sum + c.count, 0);
|
|
@@ -903,7 +1003,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
903
1003
|
<div class="container">
|
|
904
1004
|
<div class="header">
|
|
905
1005
|
<h1>\u{1F525} Blaze Core Performance Analysis</h1>
|
|
906
|
-
<div class="meta">Target Script: <code>${config.targetScript}</code
|
|
1006
|
+
<div class="meta">Target Script: <code>${config.targetScript}</code> | Ramp-Up: ${config.rampUpSec}s | Ramp-Down: ${config.rampDownSec}s</div>
|
|
907
1007
|
</div>
|
|
908
1008
|
|
|
909
1009
|
<div class="cards-wrapper">
|
|
@@ -941,6 +1041,8 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
941
1041
|
</div>
|
|
942
1042
|
</div>
|
|
943
1043
|
|
|
1044
|
+
${baselineComparisonHtml}
|
|
1045
|
+
|
|
944
1046
|
<div class="top-layout-grid">
|
|
945
1047
|
<div class="verdict-box">
|
|
946
1048
|
<div class="verdict-title">\u{1F916} AI Stress-Testing Agent Verdict</div>
|
|
@@ -1095,7 +1197,6 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
1095
1197
|
});
|
|
1096
1198
|
}
|
|
1097
1199
|
|
|
1098
|
-
// Client-side NLP Parser Engine Execution Implementation
|
|
1099
1200
|
function executeNlpQuery(queryString) {
|
|
1100
1201
|
const q = queryString.toLowerCase().trim();
|
|
1101
1202
|
const rows = document.querySelectorAll('#telemetryTableBody tr');
|
|
@@ -1140,7 +1241,8 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
1140
1241
|
}
|
|
1141
1242
|
});
|
|
1142
1243
|
|
|
1143
|
-
feedback.innerHTML = q === '' ? '' : '\u{1F50D} Match verification resolved <strong>' + matchCount + '</strong> parameters inside telemetry array.';
|
|
1244
|
+
feedback.innerHTML = q === '' ? '' : '\u{1F50D} Match verification resolved <strong>' + matchCount + '</strong> parameters inside telemetry array.';
|
|
1245
|
+
}
|
|
1144
1246
|
|
|
1145
1247
|
function setNlpQuery(str) {
|
|
1146
1248
|
const input = document.getElementById('nlpQueryInput');
|
|
@@ -1337,6 +1439,6 @@ feedback.innerHTML = q === '' ? '' : '\u{1F50D} Match verification resolved <str
|
|
|
1337
1439
|
function printUsage() {
|
|
1338
1440
|
console.log(`Blaze Core Performance Test CLI Engine
|
|
1339
1441
|
Options:
|
|
1340
|
-
--threads <count> | --duration <seconds> | --
|
|
1442
|
+
--threads <count> | --duration <seconds> | --baseline <json_file> | --output <html_file>`);
|
|
1341
1443
|
}
|
|
1342
1444
|
main().catch(console.error);
|
|
Binary file
|