blaze-performance-tester 3.0.40 → 3.1.0
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 +148 -11
- package/dist/index.win32-x64-msvc.node +0 -0
- package/package.json +5 -5
package/dist/cli.js
CHANGED
|
@@ -147,12 +147,15 @@ 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");
|
|
153
154
|
if (durationIdx !== -1) durationSec = parseInt(args[durationIdx + 1], 10);
|
|
154
155
|
const apdexIdx = args.indexOf("--target-apdex");
|
|
155
156
|
if (apdexIdx !== -1) targetApdex = parseFloat(args[apdexIdx + 1]);
|
|
157
|
+
const apdexTIdx = args.indexOf("--apdex-t");
|
|
158
|
+
if (apdexTIdx !== -1) apdexT = parseInt(args[apdexTIdx + 1], 10);
|
|
156
159
|
const errorIdx = args.indexOf("--target-error");
|
|
157
160
|
if (errorIdx !== -1) targetErrorRate = parseFloat(args[errorIdx + 1]);
|
|
158
161
|
const maxThreadsIdx = args.indexOf("--max-threads");
|
|
@@ -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,11 @@ 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(
|
|
214
|
+
const metrics = processMetricsTelemetry(
|
|
215
|
+
steadyStateRequests.length > 0 ? steadyStateRequests : rawRequests,
|
|
216
|
+
config.durationSec,
|
|
217
|
+
config.apdexT
|
|
218
|
+
);
|
|
211
219
|
const rawErrors = rawRequests.filter((r) => r.errorMessage).map((r) => r.errorMessage);
|
|
212
220
|
resolve2({ metrics, rawErrors, rawRequests: steadyStateRequests.length > 0 ? steadyStateRequests : rawRequests });
|
|
213
221
|
}, 1e3);
|
|
@@ -394,7 +402,7 @@ function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
|
|
|
394
402
|
}
|
|
395
403
|
return data.sort((a, b) => a.timestampMs - b.timestampMs);
|
|
396
404
|
}
|
|
397
|
-
function processMetricsTelemetry(requests, durationSec) {
|
|
405
|
+
function processMetricsTelemetry(requests, durationSec, apdexT) {
|
|
398
406
|
const totalRequests = requests.length;
|
|
399
407
|
const durations = requests.map((r) => r.durationMs);
|
|
400
408
|
const avgLatencyMs = mathUtils.mean(durations);
|
|
@@ -414,7 +422,7 @@ function processMetricsTelemetry(requests, durationSec) {
|
|
|
414
422
|
});
|
|
415
423
|
const errorCount = c4xx + c5xx;
|
|
416
424
|
const errorRate = totalRequests === 0 ? 0 : errorCount / totalRequests;
|
|
417
|
-
const T =
|
|
425
|
+
const T = apdexT;
|
|
418
426
|
const satisfied = requests.filter((r) => r.durationMs <= T && r.statusCode < 400).length;
|
|
419
427
|
const tolerating = requests.filter((r) => r.durationMs > T && r.durationMs <= T * 4 && r.statusCode < 400).length;
|
|
420
428
|
const apdexScore = totalRequests === 0 ? 0 : (satisfied + tolerating / 2) / totalRequests;
|
|
@@ -477,6 +485,29 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
477
485
|
x: Number(((r.timestampMs - firstTimestamp) / 1e3).toFixed(2)),
|
|
478
486
|
y: Number(((r.dnsTimeMs || 0) + (r.tcpTimeMs || 0) + (r.tlsTimeMs || 0) + r.durationMs * 0.3).toFixed(1))
|
|
479
487
|
}));
|
|
488
|
+
const naturalLanguageAssistantHtml = `
|
|
489
|
+
<div class="card" style="margin-top: 2rem; background: linear-gradient(135deg, #1e1b4b 0%, #111827 100%); border: 1px solid #4338ca;">
|
|
490
|
+
<h3 style="color: #818cf8; display: flex; align-items: center; gap: 0.6rem; font-size: 1.25rem; margin-bottom: 0.5rem; border-bottom: none; padding-bottom: 0;">
|
|
491
|
+
\u{1F52E} Ask the Test \u2014 Embedded Natural Language Assistant
|
|
492
|
+
</h3>
|
|
493
|
+
<div style="color: #94a3b8; font-size: 0.85rem; margin-bottom: 1.25rem;">
|
|
494
|
+
Query metrics, log topologies, and SLA compliance profiles instantly. Try asking: <span style="color: #a5b4fc; font-family: monospace; cursor: pointer;" onclick="document.getElementById('assistantInput').value=this.innerText; runAssistantQuery();">"What was our peak throughput capacity?"</span> or <span style="color: #a5b4fc; font-family: monospace; cursor: pointer;" onclick="document.getElementById('assistantInput').value=this.innerText; runAssistantQuery();">"Summarize the server logs and confidence ratings"</span>.
|
|
495
|
+
</div>
|
|
496
|
+
<div style="display: flex; gap: 0.75rem; margin-bottom: 1rem;">
|
|
497
|
+
<input type="text" id="assistantInput" placeholder="Ask a question regarding test telemetry... (e.g., 'Did we break the target Apdex limit?')"
|
|
498
|
+
style="flex: 1; background: #030712; border: 1px solid #374151; border-radius: 6px; padding: 0.75rem 1rem; color: #f9fafb; font-size: 0.9rem; outline: none;"
|
|
499
|
+
onkeydown="if(event.key === 'Enter') runAssistantQuery();" />
|
|
500
|
+
<button onclick="runAssistantQuery()" style="background: #4f46e5; color: #ffffff; border: none; padding: 0 1.5rem; border-radius: 6px; font-weight: 600; font-size: 0.9rem; cursor: pointer; transition: background 0.2s;">
|
|
501
|
+
Query Engine
|
|
502
|
+
</button>
|
|
503
|
+
</div>
|
|
504
|
+
<div id="assistantResponseContainer" style="display: none; background: #030712; border: 1px solid #1f2937; border-radius: 6px; padding: 1.25rem;">
|
|
505
|
+
<div style="font-size: 0.7rem; font-weight: bold; color: #6366f1; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; display: flex; align-items: center; gap: 0.3rem;">
|
|
506
|
+
<span>\u{1F916} AI Assistant Response</span>
|
|
507
|
+
</div>
|
|
508
|
+
<div id="assistantResponseText" style="font-size: 0.9rem; line-height: 1.5; color: #e5e7eb; white-space: pre-wrap;"></div>
|
|
509
|
+
</div>
|
|
510
|
+
</div>`;
|
|
480
511
|
let logClustersHtml = "";
|
|
481
512
|
if (semanticReport) {
|
|
482
513
|
const totalLogCount = semanticReport.clusters.reduce((sum, c) => sum + c.count, 0);
|
|
@@ -517,7 +548,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
517
548
|
fixCode = "const configId = payload?.config_id ?? defaultConfigurationId;\nif (!configId) throw new ValidationError('Missing config_id');";
|
|
518
549
|
explanation = "Runtime reference error for undefined payload field under race condition spikes. Add optional chaining and input contracts.";
|
|
519
550
|
}
|
|
520
|
-
const snippetId =
|
|
551
|
+
const snippetId = "snippet-" + idx;
|
|
521
552
|
return `
|
|
522
553
|
<details style="background: #090d16; border: 1px solid #1e293b; border-radius: 6px; overflow: hidden;">
|
|
523
554
|
<summary style="padding: 1rem; cursor: pointer; display: flex; align-items: center; justify-content: space-between; user-select: none;">
|
|
@@ -546,7 +577,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
546
577
|
</details>`;
|
|
547
578
|
}).join("")}
|
|
548
579
|
</div>
|
|
549
|
-
|
|
580
|
+
`;
|
|
550
581
|
}
|
|
551
582
|
const liveAdjusterHtml = `
|
|
552
583
|
<div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
|
|
@@ -655,7 +686,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
655
686
|
|
|
656
687
|
<div class="cards-wrapper">
|
|
657
688
|
<div class="metric-card">
|
|
658
|
-
<div class="card-title">Apdex Score (T:
|
|
689
|
+
<div class="card-title">Apdex Score (T: ${config.apdexT}ms)</div>
|
|
659
690
|
<div class="card-value green">${cards.apdex.toFixed(2)}<span class="card-subtext">(Good)</span></div>
|
|
660
691
|
</div>
|
|
661
692
|
<div class="metric-card">
|
|
@@ -724,6 +755,8 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
724
755
|
</div>
|
|
725
756
|
</div>
|
|
726
757
|
|
|
758
|
+
${naturalLanguageAssistantHtml}
|
|
759
|
+
|
|
727
760
|
<div class="charts-grid">
|
|
728
761
|
<div class="graph-card">
|
|
729
762
|
<div class="graph-title">\u{1F4CA} Throughput Velocity & Workload Volume</div>
|
|
@@ -800,8 +833,111 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
800
833
|
const labels = ${JSON.stringify(labels)};
|
|
801
834
|
const baseThroughput = ${cards.throughput};
|
|
802
835
|
const baseLatency = ${cards.ttfb};
|
|
836
|
+
const telemetryHistory = ${JSON.stringify(history)};
|
|
837
|
+
const responseMatrixData = ${JSON.stringify(responseMatrix)};
|
|
838
|
+
const globalSummaryCards = ${JSON.stringify(cards)};
|
|
839
|
+
const semanticReportData = ${JSON.stringify(semanticReport)};
|
|
840
|
+
const agentVerdictText = ${JSON.stringify(verdictReason)};
|
|
841
|
+
|
|
842
|
+
function runAssistantQuery() {
|
|
843
|
+
const inputEl = document.getElementById('assistantInput');
|
|
844
|
+
const containerEl = document.getElementById('assistantResponseContainer');
|
|
845
|
+
const textEl = document.getElementById('assistantResponseText');
|
|
846
|
+
const query = inputEl.value.toLowerCase().trim();
|
|
847
|
+
|
|
848
|
+
if (!query) return;
|
|
849
|
+
|
|
850
|
+
let answer = "";
|
|
851
|
+
const match = (keywords) => keywords.some(k => query.includes(k));
|
|
852
|
+
|
|
853
|
+
if (match(['throughput', 'rps', 'requests per second', 'capacity', 'volume'])) {
|
|
854
|
+
const peakT = Math.max(...telemetryHistory.map(h => h.throughput)).toFixed(0);
|
|
855
|
+
const finalT = globalSummaryCards.throughput.toFixed(1);
|
|
856
|
+
answer = "\u{1F4C8} **Throughput Assessment:**
|
|
857
|
+
" +
|
|
858
|
+
"- The peak throughput velocity achieved during this test run was **" + peakT + " req/sec**.
|
|
859
|
+
" +
|
|
860
|
+
"- The normalized/final execution throughput tracking shows **" + finalT + " req/sec**.
|
|
861
|
+
" +
|
|
862
|
+
"If your infrastructure limits drop below these values, upstream buffers will choke, precipitating service level drops.";
|
|
863
|
+
}
|
|
864
|
+
else if (match(['latency', 'p95', 'average latency', 'slow', 'speed', 'ttfb', 'delay'])) {
|
|
865
|
+
const maxP95 = Math.max(...telemetryHistory.map(h => h.p95Latency)).toFixed(1);
|
|
866
|
+
answer = "\u23F1\uFE0F **Latency Profiling Telemetry:**
|
|
867
|
+
" +
|
|
868
|
+
"- Average Time-to-First-Byte (TTFB) registered across workloads: **" + globalSummaryCards.ttfb.toFixed(1) + " ms**.
|
|
869
|
+
" +
|
|
870
|
+
"- Peak Worst-Case P95 Latency boundary reached: **" + maxP95 + " ms**.
|
|
871
|
+
" +
|
|
872
|
+
"- Handshake Breakdown: DNS Lookup [" + globalSummaryCards.dns.toFixed(2) + "ms] | TCP Connect [" + globalSummaryCards.tcp.toFixed(2) + "ms] | TLS Handshake [" + globalSummaryCards.tls.toFixed(2) + "ms].
|
|
873
|
+
" +
|
|
874
|
+
"Spikes are heavily correlated to network layer pooling exhaustion rather than payload compute constraints.";
|
|
875
|
+
}
|
|
876
|
+
else if (match(['error', 'fail', 'crash', 'broken', '5xx', '4xx', 'econnrefused', 'timeout'])) {
|
|
877
|
+
const totalErrs = responseMatrixData.total4xx + responseMatrixData.total5xx;
|
|
878
|
+
answer = "\u{1F6A8} **Failure Breakdown & Topology Mapping:**
|
|
879
|
+
" +
|
|
880
|
+
"- A total of **" + totalErrs + " operations failed** during execution loops (" + responseMatrixData.total4xx + " Client 4xx Errors, " + responseMatrixData.total5xx + " Server 5xx Errors).
|
|
881
|
+
";
|
|
882
|
+
|
|
883
|
+
if (semanticReportData && semanticReportData.clusters && semanticReportData.clusters.length > 0) {
|
|
884
|
+
answer += "- **Primary Log Clusters Captured:**
|
|
885
|
+
";
|
|
886
|
+
semanticReportData.clusters.forEach((c) => {
|
|
887
|
+
const rawTemplate = c.template || '';
|
|
888
|
+
const idxClose = rawTemplate.indexOf(']');
|
|
889
|
+
const cleaned = idxClose !== -1 ? rawTemplate.substring(idxClose + 1).trim().substring(0, 80) + "..." : rawTemplate.substring(0, 80) + "...";
|
|
890
|
+
const freqScore = Math.min(25, c.count * 0.5);
|
|
891
|
+
const clarityBonus = c.severity === 'CRITICAL' ? 15 : 8;
|
|
892
|
+
const confidenceScore = Math.min(99, Math.round(65 + freqScore + clarityBonus));
|
|
893
|
+
answer += " * [" + c.count + "x] **" + c.category + "** (" + c.severity + ") -> Confidence Rating: *" + confidenceScore + "%*
|
|
894
|
+
\`" + cleaned + "\`
|
|
895
|
+
";
|
|
896
|
+
});
|
|
897
|
+
} else {
|
|
898
|
+
answer += "- No structural failure entries were recorded in stdout/stderr diagnostic streams.";
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
else if (match(['apdex', 'satisfaction', 'slo', 'breach', 'limit', 'pass', 'target'])) {
|
|
902
|
+
const passStatus = globalSummaryCards.apdex >= ${config.targetApdex};
|
|
903
|
+
answer = "\u{1F3AF} **SLO Verification Profile:**
|
|
904
|
+
" +
|
|
905
|
+
"- Target Configured Apdex Floor: **" + (${config.targetApdex}).toFixed(2) + "**
|
|
906
|
+
" +
|
|
907
|
+
"- Actual Achieved Runtime Apdex: **" + globalSummaryCards.apdex.toFixed(2) + "**
|
|
908
|
+
" +
|
|
909
|
+
"- **Status Status:** " + (passStatus ? "\u2705 **SLO PASS**" : "\u274C **SLO BREACHED**") + "
|
|
910
|
+
" +
|
|
911
|
+
"Verdict Context: " + agentVerdictText;
|
|
912
|
+
}
|
|
913
|
+
else if (match(['summary', 'verdict', 'recommendation', 'cost', 'cloud', 'sizing', 'what happened'])) {
|
|
914
|
+
answer = "\u{1F9E0} **AI Executive Summary Directive:**
|
|
915
|
+
" +
|
|
916
|
+
"- **System Verdict:** " + agentVerdictText + "
|
|
917
|
+
" +
|
|
918
|
+
"- **Recommended Max Safe Scaled Capacity:** " + globalSummaryCards.saturationKneePoint + " Virtual User Threads Allocation.
|
|
919
|
+
" +
|
|
920
|
+
"- **Infrastructure Target Allocation Strategy:** Right-size infrastructure to **" + Math.max(2, Math.ceil(globalSummaryCards.saturationKneePoint / 75)) + " vCores** and **" + (Math.max(2, Math.ceil(globalSummaryCards.saturationKneePoint / 75)) * 2) + " GB RAM** to minimize the projected cloud operating budget (~$" + (globalSummaryCards.throughput * 0.045 * 24 * 30).toFixed(2) + "/mo).";
|
|
921
|
+
}
|
|
922
|
+
else {
|
|
923
|
+
answer = "\u2753 **Intent Routing Fallback:**
|
|
924
|
+
" +
|
|
925
|
+
"I didn't quite catch that context parameter. Try asking specifically about:
|
|
926
|
+
" +
|
|
927
|
+
"\u2022 *'What was our peak throughput capacity?'*
|
|
928
|
+
" +
|
|
929
|
+
"\u2022 *'Show me the latency performance limits and handshake timings'*
|
|
930
|
+
" +
|
|
931
|
+
"\u2022 *'Summarize the server logs and confidence ratings'*
|
|
932
|
+
" +
|
|
933
|
+
"\u2022 *'Did we pass or breach our target SLO settings?'*";
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
textEl.innerHTML = answer.replace(/
|
|
937
|
+
/g, '<br>').replace(/**(.*?)**/g, '<strong>$1</strong>');
|
|
938
|
+
containerEl.style.display = 'block';
|
|
939
|
+
}
|
|
803
940
|
|
|
804
|
-
// Live Concurrency Adjuster interactive client logic
|
|
805
941
|
const slider = document.getElementById('concurrencySlider');
|
|
806
942
|
const sliderVal = document.getElementById('sliderValue');
|
|
807
943
|
const simThroughput = document.getElementById('simThroughput');
|
|
@@ -864,7 +1000,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
864
1000
|
borderWidth: 2.5,
|
|
865
1001
|
pointRadius: 4,
|
|
866
1002
|
fill: true,
|
|
867
|
-
yAxisID: '
|
|
1003
|
+
yAxisID: 'y_threads'
|
|
868
1004
|
},
|
|
869
1005
|
{
|
|
870
1006
|
label: 'Time-to-Failure (TTF) Trend',
|
|
@@ -874,7 +1010,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
874
1010
|
borderDash: [5, 5],
|
|
875
1011
|
pointRadius: 3,
|
|
876
1012
|
fill: false,
|
|
877
|
-
yAxisID: '
|
|
1013
|
+
yAxisID: 'y_ttf'
|
|
878
1014
|
}
|
|
879
1015
|
]
|
|
880
1016
|
},
|
|
@@ -947,6 +1083,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
947
1083
|
function printUsage() {
|
|
948
1084
|
console.log(`Blaze Core Performance Test CLI Engine
|
|
949
1085
|
Options:
|
|
950
|
-
--threads <count> | --duration <seconds> | --agentic | --cluster-logs | --correlate
|
|
1086
|
+
--threads <count> | --duration <seconds> | --agentic | --cluster-logs | --correlate
|
|
1087
|
+
--apdex-t <ms> Set customizable T-Threshold window (Default: 50ms)`);
|
|
951
1088
|
}
|
|
952
1089
|
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.0
|
|
3
|
+
"version": "3.1.0",
|
|
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",
|
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
"blaze.win32-x64-msvc.node"
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
|
-
"prebuild": "node -e \"if (!fs.existsSync('dist')) fs.mkdirSync('dist')\"",
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
"prebuild": "node -e \"if (!fs.existsSync('dist')) fs.mkdirSync('dist')\"",
|
|
22
|
+
"build:rust": "napi build --platform --release --output-dir dist --js index.js --dts index.d.ts",
|
|
23
|
+
"build:cli": "esbuild cli.ts --bundle --platform=node --format=esm --packages=external --outfile=dist/cli.js",
|
|
24
|
+
"build": "npm run prebuild && npm run build:rust && npm run build:cli"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"esbuild": "^0.20.0"
|