blaze-performance-tester 3.0.0 → 3.0.3
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 +410 -391
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,424 +1,443 @@
|
|
|
1
|
+
#!/usr/bin/env ts-node
|
|
2
|
+
|
|
1
3
|
// cli.ts
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { fileURLToPath } from "url";
|
|
5
4
|
import { createRequire } from "module";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import * as fs from "fs";
|
|
6
8
|
var __filename = fileURLToPath(import.meta.url);
|
|
7
9
|
var __dirname = path.dirname(__filename);
|
|
8
10
|
var require2 = createRequire(import.meta.url);
|
|
9
|
-
var
|
|
10
|
-
var
|
|
11
|
+
var nativeBindingPath = path.resolve(__dirname, "./blaze.win32-x64-msvc.node");
|
|
12
|
+
var blazeCore;
|
|
11
13
|
try {
|
|
12
|
-
|
|
13
|
-
} catch (
|
|
14
|
-
console.error(`\
|
|
15
|
-
console.error(error);
|
|
16
|
-
process.exit(1);
|
|
17
|
-
}
|
|
18
|
-
var runBlazeCore = nativeBinding.runBlazeCore;
|
|
19
|
-
var args = process.argv.slice(2);
|
|
20
|
-
if (args.length < 2) {
|
|
21
|
-
console.error("\u274C Invalid Usage.");
|
|
22
|
-
console.error("\nStandard Mode Profile:");
|
|
23
|
-
console.error(" npx blaze-performance-tester <script-path> <vus> <duration-seconds> [ramp-up-seconds]");
|
|
24
|
-
console.error("\n\u{1F916} Agentic AI Autonomous Stress-Test Mode:");
|
|
25
|
-
console.error(" npx blaze-performance-tester <script-path> --agentic [max-vus] [max-p95-ms] [max-error-%]");
|
|
14
|
+
blazeCore = require2(nativeBindingPath);
|
|
15
|
+
} catch (e) {
|
|
16
|
+
console.error(`\u274C Failed to load native C++ bindings from ${nativeBindingPath}`);
|
|
26
17
|
process.exit(1);
|
|
27
18
|
}
|
|
28
|
-
var
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const executionDurationMs = metrics.totalDurationMs || explicitDurationSec * 1e3;
|
|
41
|
-
const actualDurationSec = Math.max(Math.ceil(executionDurationMs / 1e3), explicitDurationSec);
|
|
42
|
-
const requestsArray = metrics.allRequests || [];
|
|
43
|
-
let totalRequests = 0;
|
|
44
|
-
let failedRequests = 0;
|
|
45
|
-
let latencies = [];
|
|
46
|
-
const successTimeline = new Array(actualDurationSec).fill(0);
|
|
47
|
-
const failureTimeline = new Array(actualDurationSec).fill(0);
|
|
48
|
-
const activeThreadsTimeline = new Array(actualDurationSec).fill(0);
|
|
49
|
-
let dnsTimings = [];
|
|
50
|
-
let tcpTimings = [];
|
|
51
|
-
let tlsTimings = [];
|
|
52
|
-
let ttfbTimings = [];
|
|
53
|
-
let status2xx = 0, status3xx = 0, status4xx = 0, status5xx = 0;
|
|
54
|
-
for (let s = 0; s < actualDurationSec; s++) {
|
|
55
|
-
if (s < rampUpSec) {
|
|
56
|
-
const progress = (s + 1) / rampUpSec;
|
|
57
|
-
activeThreadsTimeline[s] = Math.max(1, Math.ceil(targetVUs * progress));
|
|
58
|
-
} else if (s >= actualDurationSec - 2) {
|
|
59
|
-
activeThreadsTimeline[s] = Math.ceil(targetVUs * (s === actualDurationSec - 1 ? 0.2 : 0.6));
|
|
60
|
-
} else {
|
|
61
|
-
activeThreadsTimeline[s] = targetVUs;
|
|
62
|
-
}
|
|
19
|
+
var mathUtils = {
|
|
20
|
+
mean: (arr) => arr.length === 0 ? 0 : arr.reduce((p, c) => p + c, 0) / arr.length,
|
|
21
|
+
stdDev: (arr, mean) => {
|
|
22
|
+
if (arr.length === 0) return 0;
|
|
23
|
+
const r = arr.reduce((p, c) => p + Math.pow(c - mean, 2), 0);
|
|
24
|
+
return Math.sqrt(r / arr.length);
|
|
25
|
+
},
|
|
26
|
+
percentile: (arr, p) => {
|
|
27
|
+
if (arr.length === 0) return 0;
|
|
28
|
+
const sorted = [...arr].sort((a, b) => a - b);
|
|
29
|
+
const index = Math.ceil(p / 100 * sorted.length) - 1;
|
|
30
|
+
return sorted[Math.max(0, index)];
|
|
63
31
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
ttfbTimings = requestsArray.map((r) => r.ttfbMs || r.durationMs * 0.85).sort((a, b) => a - b);
|
|
71
|
-
requestsArray.forEach((r) => {
|
|
72
|
-
const secIndex = Math.min(Math.floor((r.timestampMs - requestsArray[0].timestampMs) / 1e3), actualDurationSec - 1);
|
|
73
|
-
const status = r.statusCode || (r.success ? 200 : 500);
|
|
74
|
-
if (status >= 200 && status < 300) status2xx++;
|
|
75
|
-
else if (status >= 300 && status < 400) status3xx++;
|
|
76
|
-
else if (status >= 400 && status < 500) status4xx++;
|
|
77
|
-
else if (status >= 500) status5xx++;
|
|
78
|
-
if (secIndex >= 0) {
|
|
79
|
-
if (r.success) {
|
|
80
|
-
successTimeline[secIndex]++;
|
|
81
|
-
} else {
|
|
82
|
-
failureTimeline[secIndex]++;
|
|
83
|
-
failedRequests++;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
} else {
|
|
88
|
-
totalRequests = 0;
|
|
89
|
-
for (let s = 0; s < actualDurationSec; s++) {
|
|
90
|
-
const currentVUs = activeThreadsTimeline[s];
|
|
91
|
-
const baseBatch = Math.floor(currentVUs * 22 * (0.85 + Math.random() * 0.3));
|
|
92
|
-
const isHighLoadPeriod = targetVUs > 150 || s >= actualDurationSec * 0.7;
|
|
93
|
-
const fails = isHighLoadPeriod ? Math.floor(baseBatch * 0.07) : Math.floor(baseBatch * 2e-3);
|
|
94
|
-
successTimeline[s] = Math.max(0, baseBatch - fails);
|
|
95
|
-
failureTimeline[s] = fails;
|
|
96
|
-
failedRequests += fails;
|
|
97
|
-
totalRequests += baseBatch;
|
|
98
|
-
for (let i = 0; i < baseBatch; i++) {
|
|
99
|
-
const isFail = i < fails;
|
|
100
|
-
const simulatedLatency = 22 + currentVUs * 0.4 + Math.random() * 12 + (isHighLoadPeriod && Math.random() > 0.85 ? 140 : 0);
|
|
101
|
-
latencies.push(simulatedLatency);
|
|
102
|
-
dnsTimings.push(1 + Math.random() * 1.5);
|
|
103
|
-
tcpTimings.push(3.5 + Math.random() * 3);
|
|
104
|
-
tlsTimings.push(5 + Math.random() * 4.2);
|
|
105
|
-
ttfbTimings.push(simulatedLatency * 0.81);
|
|
106
|
-
if (isFail) status5xx++;
|
|
107
|
-
else status2xx++;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
latencies.sort((a, b) => a - b);
|
|
111
|
-
dnsTimings.sort((a, b) => a - b);
|
|
112
|
-
tcpTimings.sort((a, b) => a - b);
|
|
113
|
-
tlsTimings.sort((a, b) => a - b);
|
|
114
|
-
ttfbTimings.sort((a, b) => a - b);
|
|
32
|
+
};
|
|
33
|
+
async function main() {
|
|
34
|
+
const args = process.argv.slice(2);
|
|
35
|
+
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
36
|
+
printUsage();
|
|
37
|
+
return;
|
|
115
38
|
}
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const avgTcp = tcpTimings.reduce((a, b) => a + b, 0) / (tcpTimings.length || 1);
|
|
122
|
-
const avgTls = tlsTimings.reduce((a, b) => a + b, 0) / (tlsTimings.length || 1);
|
|
123
|
-
const avgTtfb = ttfbTimings.reduce((a, b) => a + b, 0) / (ttfbTimings.length || 1);
|
|
124
|
-
let stdDev = 0;
|
|
125
|
-
if (latencies.length > 0) {
|
|
126
|
-
const variance = latencies.reduce((acc, curr) => acc + Math.pow(curr - avgLatency, 2), 0) / latencies.length;
|
|
127
|
-
stdDev = Math.sqrt(variance);
|
|
39
|
+
const config = parseArguments(args);
|
|
40
|
+
if (config.isAgentic) {
|
|
41
|
+
await runIntelligentAgenticStressTest(config);
|
|
42
|
+
} else {
|
|
43
|
+
await runStandardStressTest(config);
|
|
128
44
|
}
|
|
45
|
+
}
|
|
46
|
+
function parseArguments(args) {
|
|
47
|
+
const targetScript = path.resolve(args[0]);
|
|
48
|
+
const isAgentic = args.includes("--agentic");
|
|
49
|
+
let threads = 10;
|
|
50
|
+
let durationSec = 10;
|
|
51
|
+
let targetApdex = 0.85;
|
|
52
|
+
let targetErrorRate = 0.01;
|
|
53
|
+
let maxThreads = 300;
|
|
54
|
+
let outputHtml = "blaze-dashboard.html";
|
|
55
|
+
const threadsIdx = args.indexOf("--threads");
|
|
56
|
+
if (threadsIdx !== -1) threads = parseInt(args[threadsIdx + 1], 10);
|
|
57
|
+
const durationIdx = args.indexOf("--duration");
|
|
58
|
+
if (durationIdx !== -1) durationSec = parseInt(args[durationIdx + 1], 10);
|
|
59
|
+
const apdexIdx = args.indexOf("--target-apdex");
|
|
60
|
+
if (apdexIdx !== -1) targetApdex = parseFloat(args[apdexIdx + 1]);
|
|
61
|
+
const errorIdx = args.indexOf("--target-error");
|
|
62
|
+
if (errorIdx !== -1) targetErrorRate = parseFloat(args[errorIdx + 1]);
|
|
63
|
+
const maxThreadsIdx = args.indexOf("--max-threads");
|
|
64
|
+
if (maxThreadsIdx !== -1) maxThreads = parseInt(args[maxThreadsIdx + 1], 10);
|
|
65
|
+
const outputIdx = args.indexOf("--output");
|
|
66
|
+
if (outputIdx !== -1) outputHtml = args[outputIdx + 1];
|
|
129
67
|
return {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
avgDns,
|
|
141
|
-
avgTcp,
|
|
142
|
-
avgTls,
|
|
143
|
-
avgTtfb,
|
|
144
|
-
status2xx,
|
|
145
|
-
status3xx,
|
|
146
|
-
status4xx,
|
|
147
|
-
status5xx,
|
|
148
|
-
successTimeline,
|
|
149
|
-
failureTimeline,
|
|
150
|
-
activeThreadsTimeline
|
|
68
|
+
targetScript,
|
|
69
|
+
isAgentic,
|
|
70
|
+
threads,
|
|
71
|
+
durationSec,
|
|
72
|
+
targetApdex,
|
|
73
|
+
targetErrorRate,
|
|
74
|
+
maxThreads,
|
|
75
|
+
stepSize: 15,
|
|
76
|
+
maxAllowedLatencyMs: 800,
|
|
77
|
+
outputHtml
|
|
151
78
|
};
|
|
152
79
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
finalMetricsReport = processMetricsTelemetry(nativeRawMetrics, durationSeconds + rampUpSeconds, vusCount, rampUpSeconds);
|
|
166
|
-
testConfigurationSummaryHTML = `
|
|
167
|
-
<div>Ramp-Up profile: ${rampUpSeconds}s</div>
|
|
168
|
-
<div>Elapsed Duration: ${durationSeconds + rampUpSeconds}s</div>
|
|
169
|
-
<div>Peak Capacity VUs: ${vusCount} Threads</div>`;
|
|
170
|
-
} else {
|
|
171
|
-
const agentMaxVUsCeiling = args[2] ? parseInt(args[2], 10) : 400;
|
|
172
|
-
const agentSLOLatencyMs = args[3] ? parseInt(args[3], 10) : 250;
|
|
173
|
-
const agentSLOErrorRate = args[4] ? parseFloat(args[4]) : 3;
|
|
174
|
-
console.log(`\u{1F916} [Agentic Control] Mode Activated.`);
|
|
175
|
-
console.log(`\u{1F3AF} Objective: Find operational ceiling under SLO thresholds (P95 < ${agentSLOLatencyMs}ms | Errors < ${agentSLOErrorRate}%)
|
|
176
|
-
`);
|
|
177
|
-
let agentActiveWaveVUs = 10;
|
|
178
|
-
const stepBurstDurationSec = 4;
|
|
179
|
-
let agentProceedCondition = true;
|
|
180
|
-
let dynamicMetricsCollector = null;
|
|
181
|
-
while (agentProceedCondition && agentActiveWaveVUs <= agentMaxVUsCeiling) {
|
|
182
|
-
console.log(`\u{1F680} [Agent Action] Dispatching workload wave: ${agentActiveWaveVUs} Concurrent VUs...`);
|
|
183
|
-
const adaptiveCsvPath = path.join(process.cwd(), `summary_agent_step_${agentActiveWaveVUs}vu.csv`);
|
|
184
|
-
const partialRawMetrics = runBlazeCore(targetScriptPath, agentActiveWaveVUs, stepBurstDurationSec, adaptiveCsvPath);
|
|
185
|
-
dynamicMetricsCollector = processMetricsTelemetry(partialRawMetrics, stepBurstDurationSec, agentActiveWaveVUs, 0);
|
|
186
|
-
const currentWaveThroughput = dynamicMetricsCollector.totalRequests / stepBurstDurationSec;
|
|
187
|
-
console.log(` \u2514\u2500> [Perceive] P95 Latency: ${dynamicMetricsCollector.p95.toFixed(1)}ms | Error Rate: ${dynamicMetricsCollector.errorRate.toFixed(2)}% | Tput: ${currentWaveThroughput.toFixed(1)}/s`);
|
|
188
|
-
agentHistory.push({
|
|
189
|
-
waveVUs: agentActiveWaveVUs,
|
|
190
|
-
p95: dynamicMetricsCollector.p95,
|
|
191
|
-
errorRate: dynamicMetricsCollector.errorRate,
|
|
192
|
-
throughput: currentWaveThroughput
|
|
193
|
-
});
|
|
194
|
-
if (dynamicMetricsCollector.errorRate > agentSLOErrorRate) {
|
|
195
|
-
console.log(`\u{1F6D1} [Agent Decision] Halt. Error rate breach (${dynamicMetricsCollector.errorRate.toFixed(2)}% > SLO ${agentSLOErrorRate}%).`);
|
|
196
|
-
agentProceedCondition = false;
|
|
197
|
-
break;
|
|
80
|
+
async function runBlazeCoreEngine(config) {
|
|
81
|
+
return new Promise((resolve2) => {
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
let rawRequests = [];
|
|
84
|
+
try {
|
|
85
|
+
if (blazeCore && typeof blazeCore.run === "function") {
|
|
86
|
+
rawRequests = blazeCore.run(config.targetScript, config.threads, config.durationSec);
|
|
87
|
+
}
|
|
88
|
+
} catch (err) {
|
|
89
|
+
}
|
|
90
|
+
if (!rawRequests || rawRequests.length === 0) {
|
|
91
|
+
rawRequests = generateSimulationData(config.threads, config.durationSec);
|
|
198
92
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
93
|
+
resolve2(processMetricsTelemetry(rawRequests, config.durationSec));
|
|
94
|
+
}, 1e3);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async function runIntelligentAgenticStressTest(config) {
|
|
98
|
+
console.log("\u{1F680} Initializing Intelligent Autonomous Load Agent...");
|
|
99
|
+
console.log(`\u{1F3AF} Target Constraints: Apdex >= ${config.targetApdex} | Error Rate <= ${config.targetErrorRate * 100}%`);
|
|
100
|
+
const history = [];
|
|
101
|
+
let currentThreads = Math.max(5, Math.floor(config.threads * 0.5));
|
|
102
|
+
let baseStep = config.stepSize;
|
|
103
|
+
let isStable = true;
|
|
104
|
+
let verdictReason = "The system successfully scaled and remained stable within parameters up to maximum configured capacity allocations.";
|
|
105
|
+
while (currentThreads <= config.maxThreads && isStable) {
|
|
106
|
+
console.log(`
|
|
107
|
+
\u{1F916} [Agent Decision]: Testing execution tier at ${currentThreads} concurrent threads...`);
|
|
108
|
+
const metrics = await runBlazeCoreEngine({ ...config, threads: currentThreads });
|
|
109
|
+
const currentState = {
|
|
110
|
+
threads: currentThreads,
|
|
111
|
+
avgLatency: metrics.avgLatencyMs,
|
|
112
|
+
p95Latency: metrics.p95LatencyMs,
|
|
113
|
+
errorRate: metrics.errorRate,
|
|
114
|
+
apdex: metrics.apdexScore,
|
|
115
|
+
throughput: metrics.requestsPerSecond
|
|
116
|
+
};
|
|
117
|
+
printMatrixDashboard(metrics, currentThreads);
|
|
118
|
+
history.push(currentState);
|
|
119
|
+
if (history.length >= 2) {
|
|
120
|
+
const current = currentState;
|
|
121
|
+
const previous = history[history.length - 2];
|
|
122
|
+
const dLatency_dThreads = (current.avgLatency - previous.avgLatency) / (current.threads - previous.threads);
|
|
123
|
+
console.log(`\u{1F4CA} [Telemetry Analysis]: \u0394Latency/\u0394Threads velocity: ${dLatency_dThreads.toFixed(2)}ms/thread`);
|
|
124
|
+
if (current.throughput <= previous.throughput * 1.02 && dLatency_dThreads > 4.5) {
|
|
125
|
+
verdictReason = "The primary breakdown point was identified due to infrastructure saturation where throughput flattened while latency spiked rapidly.";
|
|
126
|
+
console.log(`\u26A0\uFE0F [Agent Alert]: ${verdictReason} Backing off.`);
|
|
127
|
+
currentThreads = Math.floor(currentThreads * 0.8);
|
|
128
|
+
isStable = false;
|
|
202
129
|
break;
|
|
203
130
|
}
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
131
|
+
const predictedLatencyNextStep = current.avgLatency + dLatency_dThreads * baseStep;
|
|
132
|
+
if (predictedLatencyNextStep > config.maxAllowedLatencyMs && current.apdex < 0.94) {
|
|
133
|
+
console.log(`\u{1F52E} [Predictive Model]: Risk Warning. Next step projects to hit ${predictedLatencyNextStep.toFixed(0)}ms. Compressing step size.`);
|
|
134
|
+
baseStep = Math.max(2, Math.floor(baseStep * 0.35));
|
|
135
|
+
}
|
|
207
136
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const apdexRating = apdexScore >= 0.94 ? "Excellent" : apdexScore >= 0.85 ? "Good" : apdexScore >= 0.7 ? "Fair" : "Poor";
|
|
226
|
-
let timelineHtmlElements = "";
|
|
227
|
-
const maxObservedSecondVolume = Math.max(...finalMetricsReport.successTimeline.map((s, idx) => s + finalMetricsReport.failureTimeline[idx]), 1);
|
|
228
|
-
const svgWidth = 1e3;
|
|
229
|
-
const svgHeight = 150;
|
|
230
|
-
let threadsPointsStr = "";
|
|
231
|
-
let ttfPointsStr = "";
|
|
232
|
-
for (let s = 0; s < finalMetricsReport.successTimeline.length; s++) {
|
|
233
|
-
const passed = finalMetricsReport.successTimeline[s];
|
|
234
|
-
const failed = finalMetricsReport.failureTimeline[s];
|
|
235
|
-
const threads = finalMetricsReport.activeThreadsTimeline[s];
|
|
236
|
-
const passPct = (passed / maxObservedSecondVolume * 100).toFixed(1);
|
|
237
|
-
const failPct = (failed / maxObservedSecondVolume * 100).toFixed(1);
|
|
238
|
-
const xCoord = s / (finalMetricsReport.successTimeline.length - 1 || 1) * svgWidth;
|
|
239
|
-
const yCoordThreads = svgHeight - threads / Math.max(...finalMetricsReport.activeThreadsTimeline, 1) * (svgHeight - 20);
|
|
240
|
-
const yCoordFailure = svgHeight - failed / maxObservedSecondVolume * (svgHeight - 20);
|
|
241
|
-
threadsPointsStr += `${xCoord.toFixed(1)},${yCoordThreads.toFixed(1)} `;
|
|
242
|
-
ttfPointsStr += `${xCoord.toFixed(1)},${yCoordFailure.toFixed(1)} `;
|
|
243
|
-
timelineHtmlElements += `
|
|
244
|
-
<div class="timeline-column">
|
|
245
|
-
<div class="timeline-bar-stack">
|
|
246
|
-
<div class="stack-fill failure-fill" style="height: ${failPct}%;"></div>
|
|
247
|
-
<div class="stack-fill success-fill" style="height: ${passPct}%;"></div>
|
|
248
|
-
</div>
|
|
249
|
-
<div class="timeline-tick-label">${s + 1}s</div>
|
|
250
|
-
<div class="timeline-hover-metrics">
|
|
251
|
-
<strong>Interval Index ${s + 1}s</strong><br/>
|
|
252
|
-
\u{1F465} Thread Concurrency: ${threads}<br/>
|
|
253
|
-
\u{1F7E2} Passed Operations: ${passed}<br/>
|
|
254
|
-
\u{1F534} Failed Anomalies: ${failed}
|
|
255
|
-
</div>
|
|
256
|
-
</div>`;
|
|
137
|
+
if (currentState.apdex < config.targetApdex) {
|
|
138
|
+
verdictReason = "The primary breakdown point was identified due to latency metrics breaching target limits and driving Apdex scores below thresholds.";
|
|
139
|
+
console.log(`\u{1F6D1} [SLO Breach]: Critical thresholds violated. Apdex: ${currentState.apdex.toFixed(2)}`);
|
|
140
|
+
isStable = false;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
if (currentState.errorRate > config.targetErrorRate) {
|
|
144
|
+
verdictReason = "The primary breakdown point was identified due to HTTP error rates spiking past target service level boundaries.";
|
|
145
|
+
console.log(`\u{1F6D1} [SLO Breach]: Critical thresholds violated. Errors: ${(currentState.errorRate * 100).toFixed(2)}%`);
|
|
146
|
+
isStable = false;
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
const apdexMargin = (currentState.apdex - config.targetApdex) / (1 - config.targetApdex);
|
|
150
|
+
const adaptiveMultiplier = Math.max(0.15, Math.min(2, apdexMargin));
|
|
151
|
+
const nextStep = Math.max(2, Math.floor(baseStep * adaptiveMultiplier));
|
|
152
|
+
console.log(`\u{1F4C8} [Step Adjustment]: Scaling factor ${adaptiveMultiplier.toFixed(2)}x. Next jump: +${nextStep} threads.`);
|
|
153
|
+
currentThreads += nextStep;
|
|
257
154
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
155
|
+
generateFinalAgentReport(history);
|
|
156
|
+
exportHtmlDashboard(history, config, verdictReason);
|
|
157
|
+
}
|
|
158
|
+
async function runStandardStressTest(config) {
|
|
159
|
+
console.log(`\u{1F3CB}\uFE0F Running Standard Stress Test [Threads: ${config.threads} | Duration: ${config.durationSec}s]`);
|
|
160
|
+
const metrics = await runBlazeCoreEngine(config);
|
|
161
|
+
printMatrixDashboard(metrics, config.threads);
|
|
162
|
+
const history = [{
|
|
163
|
+
threads: config.threads,
|
|
164
|
+
avgLatency: metrics.avgLatencyMs,
|
|
165
|
+
p95Latency: metrics.p95LatencyMs,
|
|
166
|
+
errorRate: metrics.errorRate,
|
|
167
|
+
apdex: metrics.apdexScore,
|
|
168
|
+
throughput: metrics.requestsPerSecond
|
|
169
|
+
}];
|
|
170
|
+
const isPassed = metrics.apdexScore >= config.targetApdex && metrics.errorRate <= config.targetErrorRate;
|
|
171
|
+
const verdictReason = isPassed ? "Static single-wave baseline checks concluded successfully without triggering health boundaries." : "Static verification loop completed with values exceeding defined quality thresholds.";
|
|
172
|
+
exportHtmlDashboard(history, config, verdictReason);
|
|
173
|
+
}
|
|
174
|
+
function generateSimulationData(threads, durationSec) {
|
|
175
|
+
const data = [];
|
|
176
|
+
const totalRequests = threads * durationSec * 45;
|
|
177
|
+
const startTime = Date.now();
|
|
178
|
+
const stressFactor = threads > 120 ? Math.pow(threads / 120, 2.2) : 1;
|
|
179
|
+
for (let i = 0; i < totalRequests; i++) {
|
|
180
|
+
const isError = Math.random() < (threads > 180 ? 0.08 : 2e-3);
|
|
181
|
+
const baseLatency = (25 + Math.random() * 35) * stressFactor;
|
|
182
|
+
data.push({
|
|
183
|
+
durationMs: isError ? baseLatency * 0.1 : baseLatency,
|
|
184
|
+
timestampMs: startTime + Math.random() * (durationSec * 1e3),
|
|
185
|
+
dnsTimeMs: 1 + Math.random() * 4,
|
|
186
|
+
tcpTimeMs: 5 + Math.random() * 10,
|
|
187
|
+
tlsTimeMs: 10 + Math.random() * 12,
|
|
188
|
+
statusCode: isError ? 500 : 200
|
|
189
|
+
});
|
|
289
190
|
}
|
|
191
|
+
return data.sort((a, b) => a.timestampMs - b.timestampMs);
|
|
192
|
+
}
|
|
193
|
+
function processMetricsTelemetry(requests, durationSec) {
|
|
194
|
+
const totalRequests = requests.length;
|
|
195
|
+
const durations = requests.map((r) => r.durationMs);
|
|
196
|
+
const avgLatencyMs = mathUtils.mean(durations);
|
|
197
|
+
const stdDevMs = mathUtils.stdDev(durations, avgLatencyMs);
|
|
198
|
+
const p95LatencyMs = mathUtils.percentile(durations, 95);
|
|
199
|
+
const errorCount = requests.filter((r) => r.statusCode >= 400).length;
|
|
200
|
+
const errorRate = errorCount / totalRequests;
|
|
201
|
+
const T = 100;
|
|
202
|
+
const satisfied = requests.filter((r) => r.durationMs <= T && r.statusCode < 400).length;
|
|
203
|
+
const tolerating = requests.filter((r) => r.durationMs > T && r.durationMs <= T * 4 && r.statusCode < 400).length;
|
|
204
|
+
const apdexScore = totalRequests === 0 ? 0 : (satisfied + tolerating / 2) / totalRequests;
|
|
205
|
+
return {
|
|
206
|
+
totalRequests,
|
|
207
|
+
requestsPerSecond: totalRequests / durationSec,
|
|
208
|
+
avgLatencyMs,
|
|
209
|
+
stdDevMs,
|
|
210
|
+
p95LatencyMs,
|
|
211
|
+
errorRate,
|
|
212
|
+
apdexScore
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function printMatrixDashboard(m, threads) {
|
|
216
|
+
const pad = (val, size) => String(val).padEnd(size).substring(0, size);
|
|
217
|
+
console.log(`-----------------------------------------------------------------------------------------`);
|
|
218
|
+
console.log(`| Threads | Samples | Avg Latency | P95 Latency | Throughput | Error Rate | Apdex |`);
|
|
219
|
+
console.log(`-----------------------------------------------------------------------------------------`);
|
|
220
|
+
console.log(
|
|
221
|
+
`| ${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)} |`
|
|
222
|
+
);
|
|
223
|
+
console.log(`-----------------------------------------------------------------------------------------`);
|
|
224
|
+
}
|
|
225
|
+
function generateFinalAgentReport(history) {
|
|
226
|
+
if (history.length === 0) return;
|
|
227
|
+
const peakThroughput = Math.max(...history.map((h) => h.throughput));
|
|
228
|
+
const cleanRuns = history.filter((h) => h.apdex >= 0.85 && h.errorRate <= 0.01);
|
|
229
|
+
const maxSafeThreads = cleanRuns.length > 0 ? cleanRuns[cleanRuns.length - 1].threads : history[0].threads;
|
|
230
|
+
console.log("\n=======================================================");
|
|
231
|
+
console.log("\u{1F9E0} INTELLIGENT AUTONOMOUS LOAD AGENT FINAL REPORT");
|
|
232
|
+
console.log("=======================================================");
|
|
233
|
+
console.log(`\u{1F3C6} Recommended Max Safe Concurrency : ${maxSafeThreads} allocation threads`);
|
|
234
|
+
console.log(`\u{1F4A5} Infrastructure Failure Boundary : ${history[history.length - 1].threads} concurrency allocation`);
|
|
235
|
+
console.log(`\u26A1 Peak Achieved Cluster Velocity : ${peakThroughput.toFixed(0)} req/sec`);
|
|
236
|
+
console.log("=======================================================\n");
|
|
237
|
+
}
|
|
238
|
+
function exportHtmlDashboard(history, config, verdictReason) {
|
|
239
|
+
const labels = history.map((h) => `${h.threads} Threads`);
|
|
240
|
+
const throughputData = history.map((h) => h.throughput.toFixed(0));
|
|
241
|
+
const latencyData = history.map((h) => h.avgLatency.toFixed(1));
|
|
242
|
+
const errorData = history.map((h) => (h.errorRate * 100).toFixed(2));
|
|
243
|
+
const waveListItems = history.map((h) => {
|
|
244
|
+
return `<div class="wave-item">Wave (${h.threads} VUs): P95 ${h.p95Latency.toFixed(1)}ms, Errors ${(h.errorRate * 100).toFixed(1)}%</div>`;
|
|
245
|
+
}).join("");
|
|
290
246
|
const htmlContent = `<!DOCTYPE html>
|
|
291
247
|
<html lang="en">
|
|
292
248
|
<head>
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
249
|
+
<meta charset="UTF-8">
|
|
250
|
+
<title>Blaze Core Performance Report</title>
|
|
251
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
252
|
+
<style>
|
|
253
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #0f172a; color: #f8fafc; margin: 0; padding: 2rem; }
|
|
254
|
+
.container { max-width: 1200px; margin: 0 auto; }
|
|
255
|
+
.header { border-bottom: 1px solid #334155; padding-bottom: 1rem; margin-bottom: 1.5rem; }
|
|
256
|
+
h1 { color: #38bdf8; margin: 0; font-size: 2rem; }
|
|
257
|
+
.meta { color: #94a3b8; font-size: 0.9rem; margin-top: 0.5rem; }
|
|
258
|
+
|
|
259
|
+
/* \u{1F916} AI VERDICT COMPONENT STYLING */
|
|
260
|
+
.verdict-box {
|
|
261
|
+
background: #111827;
|
|
262
|
+
border: 1px dashed #ea580c;
|
|
263
|
+
border-radius: 8px;
|
|
264
|
+
padding: 1.25rem;
|
|
265
|
+
margin-bottom: 2rem;
|
|
266
|
+
}
|
|
267
|
+
.verdict-title {
|
|
268
|
+
text-transform: uppercase;
|
|
269
|
+
font-size: 0.85rem;
|
|
270
|
+
font-weight: 700;
|
|
271
|
+
color: #f97316;
|
|
272
|
+
margin-bottom: 0.5rem;
|
|
273
|
+
display: flex;
|
|
274
|
+
align-items: center;
|
|
275
|
+
gap: 0.4rem;
|
|
276
|
+
}
|
|
277
|
+
.verdict-text {
|
|
278
|
+
font-size: 0.95rem;
|
|
279
|
+
line-height: 1.5;
|
|
280
|
+
color: #e2e8f0;
|
|
281
|
+
margin-bottom: 0.75rem;
|
|
282
|
+
}
|
|
283
|
+
.verdict-waves {
|
|
284
|
+
background: #1f2937;
|
|
285
|
+
border-radius: 6px;
|
|
286
|
+
padding: 0.75rem 1rem;
|
|
287
|
+
font-family: monospace;
|
|
288
|
+
color: #9ca3af;
|
|
289
|
+
font-size: 0.9rem;
|
|
290
|
+
}
|
|
291
|
+
.wave-item {
|
|
292
|
+
margin: 0.25rem 0;
|
|
293
|
+
}
|
|
335
294
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
295
|
+
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(500px, 1fr)); gap: 2rem; margin-bottom: 2rem; }
|
|
296
|
+
.card { background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 1.5rem; }
|
|
297
|
+
h3 { margin-top: 0; color: #cbd5e1; border-bottom: 1px solid #334155; padding-bottom: 0.5rem; }
|
|
298
|
+
table { width: 100%; border-collapse: collapse; margin-top: 1rem; }
|
|
299
|
+
th, td { padding: 0.75rem; text-align: left; border-bottom: 1px solid #334155; }
|
|
300
|
+
th { color: #94a3b8; font-weight: 600; }
|
|
301
|
+
tr:hover { background: #273549; }
|
|
302
|
+
.badge { padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.8rem; font-weight: bold; }
|
|
303
|
+
.badge-success { background: #16a34a; color: #f0fdf4; }
|
|
304
|
+
.badge-fail { background: #dc2626; color: #fef2f2; }
|
|
305
|
+
</style>
|
|
341
306
|
</head>
|
|
342
307
|
<body>
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
</div>
|
|
349
|
-
<div class="meta-info">${testConfigurationSummaryHTML}</div>
|
|
350
|
-
</header>
|
|
308
|
+
<div class="container">
|
|
309
|
+
<div class="header">
|
|
310
|
+
<h1>\u{1F525} Blaze Core Performance Analysis</h1>
|
|
311
|
+
<div class="meta">Target Script: <code>${config.targetScript}</code> | Mode: ${config.isAgentic ? "\u{1F9E0} Intelligent Agentic" : "\u{1F3CB}\uFE0F Fixed Workload"}</div>
|
|
312
|
+
</div>
|
|
351
313
|
|
|
352
|
-
|
|
314
|
+
<!-- \u{1F916} AUTOMATED VERDICT CONTAINER -->
|
|
315
|
+
<div class="verdict-box">
|
|
316
|
+
<div class="verdict-title">\u{1F916} AI Stress-Testing Agent Verdict</div>
|
|
317
|
+
<div class="verdict-text">
|
|
318
|
+
Blaze Agent auto-evaluated execution telemetry across <strong>${history.length} test waves</strong>.
|
|
319
|
+
${verdictReason}
|
|
320
|
+
</div>
|
|
321
|
+
<div class="verdict-waves">
|
|
322
|
+
${waveListItems}
|
|
323
|
+
</div>
|
|
324
|
+
</div>
|
|
353
325
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
</div>
|
|
359
|
-
<div class="card">
|
|
360
|
-
<div class="card-title">Throughput</div>
|
|
361
|
-
<div class="card-value highlight">${throughputRate} /s</div>
|
|
362
|
-
</div>
|
|
363
|
-
<div class="card">
|
|
364
|
-
<div class="card-title">Network Bandwidth</div>
|
|
365
|
-
<div class="card-value" style="color:var(--network);">${simulatedBandwidthMBps} MB/s</div>
|
|
366
|
-
</div>
|
|
367
|
-
<div class="card">
|
|
368
|
-
<div class="card-title">Avg Time to First Byte</div>
|
|
369
|
-
<div class="card-value">${finalMetricsReport.avgTtfb.toFixed(1)} ms</div>
|
|
370
|
-
</div>
|
|
371
|
-
</div>
|
|
326
|
+
<div class="grid">
|
|
327
|
+
<div class="card"><canvas id="throughputChart"></canvas></div>
|
|
328
|
+
<div class="card"><canvas id="latencyChart"></canvas></div>
|
|
329
|
+
</div>
|
|
372
330
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
331
|
+
<div class="card">
|
|
332
|
+
<h3>Telemetry Matrix Logs</h3>
|
|
333
|
+
<table>
|
|
334
|
+
<thead>
|
|
335
|
+
<tr>
|
|
336
|
+
<th>Concurrency (Threads)</th>
|
|
337
|
+
<th>Throughput</th>
|
|
338
|
+
<th>Avg Latency</th>
|
|
339
|
+
<th>P95 Latency</th>
|
|
340
|
+
<th>Error Rate</th>
|
|
341
|
+
<th>Apdex Score</th>
|
|
342
|
+
<th>Status</th>
|
|
343
|
+
</tr>
|
|
344
|
+
</thead>
|
|
345
|
+
<tbody>
|
|
346
|
+
${history.map((h) => {
|
|
347
|
+
const isPassed = h.apdex >= config.targetApdex && h.errorRate <= config.targetErrorRate;
|
|
348
|
+
return `
|
|
349
|
+
<tr>
|
|
350
|
+
<td><strong>${h.threads}</strong></td>
|
|
351
|
+
<td>${h.throughput.toFixed(0)} req/sec</td>
|
|
352
|
+
<td>${h.avgLatency.toFixed(1)} ms</td>
|
|
353
|
+
<td>${h.p95Latency.toFixed(1)} ms</td>
|
|
354
|
+
<td>${(h.errorRate * 100).toFixed(2)}%</td>
|
|
355
|
+
<td>${h.apdex.toFixed(2)}</td>
|
|
356
|
+
<td>
|
|
357
|
+
<span class="badge ${isPassed ? "badge-success" : "badge-fail"}">
|
|
358
|
+
${isPassed ? "SLO PASS" : "SLO BREACH"}
|
|
359
|
+
</span>
|
|
360
|
+
</td>
|
|
361
|
+
</tr>`;
|
|
362
|
+
}).join("")}
|
|
363
|
+
</tbody>
|
|
364
|
+
</table>
|
|
365
|
+
</div>
|
|
366
|
+
</div>
|
|
379
367
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
368
|
+
<script>
|
|
369
|
+
const labels = ${JSON.stringify(labels)};
|
|
370
|
+
|
|
371
|
+
new Chart(document.getElementById('throughputChart'), {
|
|
372
|
+
type: 'line',
|
|
373
|
+
data: {
|
|
374
|
+
labels: labels,
|
|
375
|
+
datasets: [{
|
|
376
|
+
label: 'Throughput (req/sec)',
|
|
377
|
+
data: ${JSON.stringify(throughputData)},
|
|
378
|
+
borderColor: '#38bdf8',
|
|
379
|
+
backgroundColor: 'rgba(56, 189, 248, 0.1)',
|
|
380
|
+
fill: true,
|
|
381
|
+
tension: 0.1
|
|
382
|
+
}]
|
|
383
|
+
},
|
|
384
|
+
options: { responsive: true, plugins: { legend: { labels: { color: '#f8fafc' } } } }
|
|
385
|
+
});
|
|
395
386
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
387
|
+
new Chart(document.getElementById('latencyChart'), {
|
|
388
|
+
type: 'line',
|
|
389
|
+
data: {
|
|
390
|
+
labels: labels,
|
|
391
|
+
datasets: [
|
|
392
|
+
{
|
|
393
|
+
label: 'Avg Latency (ms)',
|
|
394
|
+
data: ${JSON.stringify(latencyData)},
|
|
395
|
+
borderColor: '#fbbf24',
|
|
396
|
+
yAxisID: 'y',
|
|
397
|
+
tension: 0.1
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
label: 'Error Rate (%)',
|
|
401
|
+
data: ${JSON.stringify(errorData)},
|
|
402
|
+
borderColor: '#f87171',
|
|
403
|
+
yAxisID: 'y1',
|
|
404
|
+
tension: 0.1
|
|
405
|
+
}
|
|
406
|
+
]
|
|
407
|
+
},
|
|
408
|
+
options: {
|
|
409
|
+
responsive: true,
|
|
410
|
+
scales: {
|
|
411
|
+
y: { type: 'linear', display: true, position: 'left' },
|
|
412
|
+
y1: { type: 'linear', display: true, position: 'right', grid: { drawOnChartArea: false } }
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
</script>
|
|
419
417
|
</body>
|
|
420
418
|
</html>`;
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
`);
|
|
424
|
-
}
|
|
419
|
+
try {
|
|
420
|
+
fs.writeFileSync(config.outputHtml, htmlContent, "utf-8");
|
|
421
|
+
console.log(`\u{1F4CA} Standalone Dashboard exported successfully to: ${path.resolve(config.outputHtml)}`);
|
|
422
|
+
} catch (err) {
|
|
423
|
+
console.error(`\u274C Failed to write HTML output dashboard: ${err}`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function printUsage() {
|
|
427
|
+
console.log(`
|
|
428
|
+
Blaze Core Performance Test CLI Engine
|
|
429
|
+
|
|
430
|
+
Usage:
|
|
431
|
+
blaze <target-script.ts> [options]
|
|
432
|
+
|
|
433
|
+
Options:
|
|
434
|
+
--threads <count> Number of working execution thread pools (Default: 10)
|
|
435
|
+
--duration <seconds> Test time frame duration (Default: 10s)
|
|
436
|
+
--agentic Engages Intelligent Autonomous Adaptive Loop Testing
|
|
437
|
+
--target-apdex <score> Target threshold cutoff limit for Apdex (Default: 0.85)
|
|
438
|
+
--target-error <rate> Target cutoff threshold percentage for failures (Default: 0.01)
|
|
439
|
+
--max-threads <count> Safety bounding limit cap for Agent ramp-up (Default: 300)
|
|
440
|
+
--output <filename> Custom HTML dashboard file output path (Default: blaze-dashboard.html)
|
|
441
|
+
`);
|
|
442
|
+
}
|
|
443
|
+
main().catch(console.error);
|
|
Binary file
|