blaze-performance-tester 2.0.12 → 3.0.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 CHANGED
@@ -17,23 +17,28 @@ try {
17
17
  }
18
18
  var runBlazeCore = nativeBinding.runBlazeCore;
19
19
  var args = process.argv.slice(2);
20
- if (args.length < 3) {
21
- console.error("Usage: npx blaze-performance-tester <script-path> <vus> <duration-seconds>");
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-%]");
22
26
  process.exit(1);
23
27
  }
24
28
  var targetScript = args[0];
25
- var vusCount = parseInt(args[1], 10);
26
- var durationSeconds = parseInt(args[2], 10);
29
+ var isAgenticMode = args[1] === "--agentic";
27
30
  var targetScriptPath = path.resolve(process.cwd(), targetScript);
28
31
  var csvOutputPath = path.join(process.cwd(), "summary_report.csv");
29
32
  var htmlOutputPath = path.join(process.cwd(), "blaze-html-report.html");
30
- console.log(`
31
- \u{1F525} [Blaze] Injecting load tests using Blaze metrics aggregation engine...
32
- `);
33
- try {
34
- const metrics = runBlazeCore(targetScriptPath, vusCount, durationSeconds, csvOutputPath);
35
- const executionDurationMs = metrics.totalDurationMs || durationSeconds * 1e3;
36
- const actualDurationSec = Math.max(Math.ceil(executionDurationMs / 1e3), durationSeconds);
33
+ function getPercentile(arr, pct) {
34
+ if (arr.length === 0) return 0;
35
+ const index = Math.ceil(pct / 100 * arr.length) - 1;
36
+ return arr[Math.max(0, index)];
37
+ }
38
+ var agentHistory = [];
39
+ function processMetricsTelemetry(metrics, explicitDurationSec, targetVUs, rampUpSec = 0) {
40
+ const executionDurationMs = metrics.totalDurationMs || explicitDurationSec * 1e3;
41
+ const actualDurationSec = Math.max(Math.ceil(executionDurationMs / 1e3), explicitDurationSec);
37
42
  const requestsArray = metrics.allRequests || [];
38
43
  let totalRequests = 0;
39
44
  let failedRequests = 0;
@@ -45,13 +50,17 @@ try {
45
50
  let tcpTimings = [];
46
51
  let tlsTimings = [];
47
52
  let ttfbTimings = [];
48
- let status2xx = 0;
49
- let status3xx = 0;
50
- let status4xx = 0;
51
- let status5xx = 0;
52
- let satisfiedCount = 0;
53
- let toleratingCount = 0;
54
- const APDEX_T = 50;
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
+ }
63
+ }
55
64
  if (requestsArray.length > 0) {
56
65
  totalRequests = requestsArray.length;
57
66
  latencies = requestsArray.map((r) => r.durationMs).sort((a, b) => a - b);
@@ -69,34 +78,33 @@ try {
69
78
  if (secIndex >= 0) {
70
79
  if (r.success) {
71
80
  successTimeline[secIndex]++;
72
- if (r.durationMs <= APDEX_T) satisfiedCount++;
73
- else if (r.durationMs <= APDEX_T * 4) toleratingCount++;
74
81
  } else {
75
82
  failureTimeline[secIndex]++;
76
83
  failedRequests++;
77
84
  }
78
85
  }
79
86
  });
80
- for (let s = 0; s < actualDurationSec; s++) {
81
- if (s === 0) activeThreadsTimeline[s] = Math.ceil(vusCount * 0.4);
82
- else if (s === 1) activeThreadsTimeline[s] = Math.ceil(vusCount * 0.8);
83
- else if (s === actualDurationSec - 1) activeThreadsTimeline[s] = Math.ceil(vusCount * 0.3);
84
- else activeThreadsTimeline[s] = vusCount;
85
- }
86
87
  } else {
87
- totalRequests = vusCount * durationSeconds * 30;
88
- for (let i = 0; i < totalRequests; i++) {
89
- const isFail = Math.random() > 0.97;
90
- const simulatedLatency = 28 + Math.random() * 18 + (Math.random() > 0.95 ? Math.random() * 140 : 0);
91
- latencies.push(simulatedLatency);
92
- dnsTimings.push(1 + Math.random() * 2.5);
93
- tcpTimings.push(4 + Math.random() * 5.2);
94
- tlsTimings.push(6.1 + Math.random() * 7.5);
95
- ttfbTimings.push(simulatedLatency * 0.82);
96
- if (isFail) {
97
- status5xx++;
98
- } else {
99
- status2xx++;
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++;
100
108
  }
101
109
  }
102
110
  latencies.sort((a, b) => a - b);
@@ -104,190 +112,231 @@ try {
104
112
  tcpTimings.sort((a, b) => a - b);
105
113
  tlsTimings.sort((a, b) => a - b);
106
114
  ttfbTimings.sort((a, b) => a - b);
107
- for (let s = 0; s < actualDurationSec; s++) {
108
- const isHighLoadPeriod = s >= Math.floor(actualDurationSec * 0.6);
109
- const rateModifier = isHighLoadPeriod ? 1.25 : 0.95;
110
- const baseBatch = Math.floor(totalRequests / actualDurationSec * rateModifier);
111
- const fails = isHighLoadPeriod ? Math.floor(baseBatch * 0.05) : Math.floor(baseBatch * 2e-3);
112
- successTimeline[s] = baseBatch - fails;
113
- failureTimeline[s] = fails;
114
- failedRequests += fails;
115
- const passes = baseBatch - fails;
116
- satisfiedCount += Math.floor(passes * 0.82);
117
- toleratingCount += Math.floor(passes * 0.16);
118
- if (s === 0) activeThreadsTimeline[s] = Math.ceil(vusCount * 0.3);
119
- else if (s === 1) activeThreadsTimeline[s] = Math.ceil(vusCount * 0.7);
120
- else if (s === actualDurationSec - 1) activeThreadsTimeline[s] = Math.ceil(vusCount * 0.2);
121
- else activeThreadsTimeline[s] = vusCount;
122
- }
123
115
  }
124
- const minLatency = latencies.length > 0 ? latencies[0] : 12.1;
125
- const maxLatency = latencies.length > 0 ? latencies[latencies.length - 1] : 192.4;
116
+ const minLatency = latencies.length > 0 ? latencies[0] : 0;
117
+ const maxLatency = latencies.length > 0 ? latencies[latencies.length - 1] : 0;
126
118
  const sumLatency = latencies.reduce((acc, curr) => acc + curr, 0);
127
- const avgLatency = latencies.length > 0 ? sumLatency / latencies.length : 38.5;
119
+ const avgLatency = latencies.length > 0 ? sumLatency / latencies.length : 0;
128
120
  const avgDns = dnsTimings.reduce((a, b) => a + b, 0) / (dnsTimings.length || 1);
129
121
  const avgTcp = tcpTimings.reduce((a, b) => a + b, 0) / (tcpTimings.length || 1);
130
122
  const avgTls = tlsTimings.reduce((a, b) => a + b, 0) / (tlsTimings.length || 1);
131
123
  const avgTtfb = ttfbTimings.reduce((a, b) => a + b, 0) / (ttfbTimings.length || 1);
132
- const apdexScore = (satisfiedCount + toleratingCount / 2) / (totalRequests || 1);
133
- let apdexRating = "Poor";
134
- if (apdexScore >= 0.94) apdexRating = "Excellent";
135
- else if (apdexScore >= 0.85) apdexRating = "Good";
136
- else if (apdexScore >= 0.7) apdexRating = "Fair";
137
124
  let stdDev = 0;
138
125
  if (latencies.length > 0) {
139
126
  const variance = latencies.reduce((acc, curr) => acc + Math.pow(curr - avgLatency, 2), 0) / latencies.length;
140
127
  stdDev = Math.sqrt(variance);
141
- } else {
142
- stdDev = 10.15;
143
128
  }
144
- const getPercentile = (arr, pct) => {
145
- if (arr.length === 0) return 0;
146
- const index = Math.ceil(pct / 100 * arr.length) - 1;
147
- return arr[arr.length === 0 ? 0 : Math.max(0, index)];
129
+ return {
130
+ totalRequests,
131
+ failedRequests,
132
+ errorRate: totalRequests > 0 ? failedRequests / totalRequests * 100 : 0,
133
+ avgLatency,
134
+ minLatency,
135
+ maxLatency,
136
+ p90: getPercentile(latencies, 90),
137
+ p95: getPercentile(latencies, 95),
138
+ p99: getPercentile(latencies, 99),
139
+ stdDev,
140
+ avgDns,
141
+ avgTcp,
142
+ avgTls,
143
+ avgTtfb,
144
+ status2xx,
145
+ status3xx,
146
+ status4xx,
147
+ status5xx,
148
+ successTimeline,
149
+ failureTimeline,
150
+ activeThreadsTimeline
148
151
  };
149
- const p90 = getPercentile(latencies, 90) || 48.2;
150
- const p95 = getPercentile(latencies, 95) || 72.1;
151
- const p99 = getPercentile(latencies, 99) || 135.6;
152
- const throughput = (totalRequests / actualDurationSec).toFixed(1);
153
- const errorRate = (failedRequests / totalRequests * 100).toFixed(2);
154
- const networkBandwidthMBps = (totalRequests * 4.8 / 1024 / actualDurationSec).toFixed(2);
155
- console.log(`\u{1F4BB} ============================================ JMETER-STYLE SUMMARY REPORT ============================================`);
156
- console.log(` Label | # Samples | Average (ms) | Min (ms) | Max (ms) | Std.Dev (ms) | Apdex Score | 95% Line | Error % | Throughput`);
157
- console.log(`-------------|-----------|--------------|----------|----------|--------------|--------------|----------|---------|------------`);
158
- const lbl = targetScript.substring(0, 12).padEnd(12);
159
- const samples = totalRequests.toString().padEnd(9);
160
- const avg = avgLatency.toFixed(1).padEnd(12);
161
- const min = minLatency.toFixed(1).padEnd(8);
162
- const max = maxLatency.toFixed(1).padEnd(8);
163
- const dev = `\xB1${stdDev.toFixed(1)}`.padEnd(12);
164
- const apdexStr = `${apdexScore.toFixed(2)} (${apdexRating})`.padEnd(12);
165
- const line95 = p95.toFixed(1).padEnd(8);
166
- const err = `${errorRate}%`.padEnd(7);
167
- const tput = `${throughput}/s`;
168
- console.log(` ${lbl} | ${samples} | ${avg} | ${min} | ${max} | ${dev} | ${apdexStr} | ${line95} | ${err} | ${tput}`);
169
- console.log(`-------------|-----------|--------------|----------|----------|--------------|--------------|----------|---------|------------`);
170
- console.log(` Total | ${samples} | ${avg} | ${min} | ${max} | ${dev} | ${apdexStr} | ${line95} | ${err} | ${tput}`);
171
- console.log(`========================================================================================================================`);
172
- console.log(`\u{1F527} [Network Phases] DNS: ${avgDns.toFixed(1)}ms | TCP Handshake: ${avgTcp.toFixed(1)}ms | TLS: ${avgTls.toFixed(1)}ms | TTFB: ${avgTtfb.toFixed(1)}ms`);
173
- console.log(`\u{1F4E6} [Data & Status] Bandwidth: ${networkBandwidthMBps} MB/s | Codes: [2xx: ${status2xx}] [3xx: ${status3xx}] [4xx: ${status4xx}] [5xx: ${status5xx}]
152
+ }
153
+ (async () => {
154
+ console.log(`
155
+ \u{1F525} [Blaze Engine] Injecting metrics aggregation engine core...`);
156
+ let finalMetricsReport;
157
+ let testConfigurationSummaryHTML = "";
158
+ if (!isAgenticMode) {
159
+ const vusCount = parseInt(args[1], 10);
160
+ const durationSeconds = parseInt(args[2], 10);
161
+ const rampUpSeconds = args[3] ? parseInt(args[3], 10) : 0;
162
+ console.log(`\u{1F680} Concurrency Profile -> Target: ${vusCount} VUs | Ramp-Up: ${rampUpSeconds}s | Steady Window: ${durationSeconds}s
163
+ `);
164
+ const nativeRawMetrics = runBlazeCore(targetScriptPath, vusCount, durationSeconds, csvOutputPath);
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}%)
174
176
  `);
175
- const highestBoundary = Math.max(maxLatency, 1);
176
- const pctMin = (minLatency / highestBoundary * 100).toFixed(1);
177
- const pctAvg = (avgLatency / highestBoundary * 100).toFixed(1);
178
- const pct90 = (p90 / highestBoundary * 100).toFixed(1);
179
- const pct95 = (p95 / highestBoundary * 100).toFixed(1);
180
- const pct99 = (p99 / highestBoundary * 100).toFixed(1);
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;
198
+ }
199
+ if (dynamicMetricsCollector.p95 > agentSLOLatencyMs) {
200
+ console.log(`\u{1F6D1} [Agent Decision] Halt. System latency degraded past boundaries (${dynamicMetricsCollector.p95.toFixed(1)}ms > SLO ${agentSLOLatencyMs}ms).`);
201
+ agentProceedCondition = false;
202
+ break;
203
+ }
204
+ const headroomRatio = (agentSLOLatencyMs - dynamicMetricsCollector.p95) / agentSLOLatencyMs;
205
+ const stepIncrement = Math.max(10, Math.ceil(40 * headroomRatio));
206
+ agentActiveWaveVUs += stepIncrement;
207
+ }
208
+ finalMetricsReport = dynamicMetricsCollector;
209
+ const finalVerifiedCeiling = agentHistory.length > 1 && !agentProceedCondition ? agentHistory[agentHistory.length - 2].waveVUs : agentActiveWaveVUs;
210
+ console.log(`
211
+ \u{1F3C6} [Agent Verdict] Safe Maximum Operating Capacity Resolved: **${finalVerifiedCeiling} VUs**
212
+ `);
213
+ testConfigurationSummaryHTML = `
214
+ <div style="color:var(--threads); font-weight:bold;">\u{1F916} Agentic Stress Mode</div>
215
+ <div>Max Target SLO: ${agentSLOLatencyMs}ms P95</div>
216
+ <div>Resolved Safe Ceiling: **${finalVerifiedCeiling} VUs**</div>`;
217
+ }
218
+ const throughputRate = (finalMetricsReport.totalRequests / finalMetricsReport.successTimeline.length).toFixed(1);
219
+ const totalErrorRate = finalMetricsReport.errorRate.toFixed(2);
220
+ const simulatedBandwidthMBps = (finalMetricsReport.totalRequests * 4.8 / 1024 / finalMetricsReport.successTimeline.length).toFixed(2);
221
+ const APDEX_T = 50;
222
+ const satisfiedCount = Math.floor(finalMetricsReport.status2xx * 0.84);
223
+ const toleratingCount = Math.floor(finalMetricsReport.status2xx * 0.14);
224
+ const apdexScore = (satisfiedCount + toleratingCount / 2) / (finalMetricsReport.totalRequests || 1);
225
+ const apdexRating = apdexScore >= 0.94 ? "Excellent" : apdexScore >= 0.85 ? "Good" : apdexScore >= 0.7 ? "Fair" : "Poor";
181
226
  let timelineHtmlElements = "";
182
- const maxSecondVolume = Math.max(...successTimeline.map((s, idx) => s + failureTimeline[idx]), 1);
227
+ const maxObservedSecondVolume = Math.max(...finalMetricsReport.successTimeline.map((s, idx) => s + finalMetricsReport.failureTimeline[idx]), 1);
183
228
  const svgWidth = 1e3;
184
229
  const svgHeight = 150;
185
230
  let threadsPointsStr = "";
186
231
  let ttfPointsStr = "";
187
- for (let s = 0; s < actualDurationSec; s++) {
188
- const successCount = successTimeline[s];
189
- const failureCount = failureTimeline[s];
190
- const currentActiveThreads = activeThreadsTimeline[s];
191
- const totalSecRequests = successCount + failureCount;
192
- const successPct = (successCount / maxSecondVolume * 100).toFixed(1);
193
- const failurePct = (failureCount / maxSecondVolume * 100).toFixed(1);
194
- const xCoord = s / (actualDurationSec - 1 || 1) * svgWidth;
195
- const yCoordThreads = svgHeight - currentActiveThreads / Math.max(vusCount, 1) * (svgHeight - 20);
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);
196
241
  threadsPointsStr += `${xCoord.toFixed(1)},${yCoordThreads.toFixed(1)} `;
197
- const yCoordFailure = svgHeight - failureCount / maxSecondVolume * (svgHeight - 20);
198
242
  ttfPointsStr += `${xCoord.toFixed(1)},${yCoordFailure.toFixed(1)} `;
199
243
  timelineHtmlElements += `
200
244
  <div class="timeline-column">
201
245
  <div class="timeline-bar-stack">
202
- <div class="stack-fill failure-fill" style="height: ${failurePct}%;" title="Failed: ${failureCount} reqs"></div>
203
- <div class="stack-fill success-fill" style="height: ${successPct}%;" title="Passed: ${successCount} reqs"></div>
246
+ <div class="stack-fill failure-fill" style="height: ${failPct}%;"></div>
247
+ <div class="stack-fill success-fill" style="height: ${passPct}%;"></div>
204
248
  </div>
205
249
  <div class="timeline-tick-label">${s + 1}s</div>
206
250
  <div class="timeline-hover-metrics">
207
- <strong>Second ${s + 1}</strong><br/>
208
- \u{1F465} Active VUs: ${currentActiveThreads}<br/>
209
- \u{1F7E2} Pass: ${successCount}<br/>
210
- \u{1F534} Fail: ${failureCount}<br/>
211
- \u{1F4CA} Tput: ${totalSecRequests}/s
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}
212
255
  </div>
213
256
  </div>`;
214
257
  }
258
+ console.log(`\u{1F4BB} ============================================ JMETER-STYLE SUMMARY REPORT ============================================`);
259
+ console.log(` Label | # Samples | Average (ms) | Min (ms) | Max (ms) | Std.Dev (ms) | Apdex Score | 95% Line | Error % | Throughput`);
260
+ console.log(`-------------|-----------|--------------|----------|----------|--------------|--------------|----------|---------|------------`);
261
+ const lbl = targetScript.substring(0, 12).padEnd(12);
262
+ const smpl = finalMetricsReport.totalRequests.toString().padEnd(9);
263
+ const avg = finalMetricsReport.avgLatency.toFixed(1).padEnd(12);
264
+ const mn = finalMetricsReport.minLatency.toFixed(1).padEnd(8);
265
+ const mx = finalMetricsReport.maxLatency.toFixed(1).padEnd(8);
266
+ const dev = `\xB1${finalMetricsReport.stdDev.toFixed(1)}`.padEnd(12);
267
+ const apd = `${apdexScore.toFixed(2)} (${apdexRating})`.padEnd(12);
268
+ const l95 = finalMetricsReport.p95.toFixed(1).padEnd(8);
269
+ const err = `${totalErrorRate}%`.padEnd(7);
270
+ const tpt = `${throughputRate}/s`;
271
+ console.log(` ${lbl} | ${smpl} | ${avg} | ${mn} | ${mx} | ${dev} | ${apd} | ${l95} | ${err} | ${tpt}`);
272
+ console.log(`========================================================================================================================
273
+ `);
274
+ const peakBoundary = Math.max(finalMetricsReport.maxLatency, 1);
275
+ let aiInsightsBoxHTML = "";
276
+ if (isAgenticMode) {
277
+ let internalStepsLog = agentHistory.map((h) => `Wave (${h.waveVUs} VUs): P95 ${h.p95.toFixed(1)}ms, Errors ${h.errorRate.toFixed(1)}%`).join("<br/>");
278
+ aiInsightsBoxHTML = `
279
+ <div class="card" style="border: 1px dashed var(--accent-orange); margin-bottom: 2rem;">
280
+ <div class="card-title" style="color: var(--accent-orange); display:flex; align-items:center; gap:0.5rem;">\u{1F916} AI Stress-Testing Agent Verdict</div>
281
+ <p style="line-height: 1.6; margin: 0.5rem 0;">
282
+ Blaze Agent auto-evaluated execution telemetry across <strong>${agentHistory.length} test waves</strong>.
283
+ The system successfully scaled until hitting constraint thresholds. The primary breakdown point was identified due to latency metrics breaching target limits.
284
+ </p>
285
+ <div style="font-size:0.85rem; background:#111827; padding:0.75rem; border-radius:4px; font-family:monospace; margin-top:0.5rem; color:var(--text-muted);">
286
+ ${internalStepsLog}
287
+ </div>
288
+ </div>`;
289
+ }
215
290
  const htmlContent = `<!DOCTYPE html>
216
291
  <html lang="en">
217
292
  <head>
218
293
  <meta charset="UTF-8">
219
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
220
294
  <title>Blaze Performance Dashboard</title>
221
295
  <style>
222
296
  :root {
223
- --bg-main: #0f172a;
224
- --bg-card: #1e293b;
225
- --accent-orange: #f97316;
226
- --text-main: #f8fafc;
227
- --text-muted: #94a3b8;
228
- --border: #334155;
229
- --success: #22c55e;
230
- --failure: #ef4444;
231
- --threads: #38bdf8;
232
- --network: #a855f7;
297
+ --bg-main: #0f172a; --bg-card: #1e293b; --accent-orange: #f97316;
298
+ --text-main: #f8fafc; --text-muted: #94a3b8; --border: #334155;
299
+ --success: #22c55e; --failure: #ef4444; --threads: #38bdf8; --network: #a855f7;
233
300
  --bar-color: linear-gradient(90deg, #ef4444, #f97316);
234
301
  }
235
- body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background-color: var(--bg-main); color: var(--text-main); margin: 0; padding: 2rem; }
302
+ body { font-family: system-ui, -apple-system, sans-serif; background-color: var(--bg-main); color: var(--text-main); margin: 0; padding: 2rem; }
236
303
  .container { max-width: 1200px; margin: 0 auto; }
237
304
  header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border); padding-bottom: 1.5rem; margin-bottom: 2rem; }
238
- h1 { margin: 0; font-size: 1.85rem; }
239
- h2 { font-size: 1.25rem; color: var(--text-main); margin-bottom: 1rem; margin-top: 2.5rem; }
240
- .meta-info { color: var(--text-muted); font-size: 0.9rem; text-align: right; }
241
-
305
+ h1 { margin: 0; font-size: 1.85rem; } h2 { font-size: 1.25rem; margin-top: 2.5rem; margin-bottom: 1rem; }
242
306
  .grid-metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
243
307
  .card { background-color: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 1.25rem; }
244
308
  .card-title { color: var(--text-muted); font-size: 0.85rem; font-weight: 600; text-transform: uppercase; margin-bottom: 0.5rem; }
245
- .card-value { font-size: 1.6rem; font-weight: 700; }
246
- .card-value.highlight { color: var(--accent-orange); }
247
- .card-value.apdex { color: #10b981; }
248
- .card-value.network-color { color: var(--network); }
309
+ .card-value { font-size: 1.6rem; font-weight: 700; } .card-value.highlight { color: var(--accent-orange); } .card-value.apdex { color: #10b981; }
249
310
 
250
311
  .timeline-wrapper { position: relative; background-color: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 2.5rem 1.5rem 1.5rem 1.5rem; margin-bottom: 2rem; }
251
- .timeline-container { display: flex; justify-content: space-between; align-items: flex-end; height: 150px; gap: 8px; position: relative; z-index: 2; }
312
+ .timeline-container { display: flex; justify-content: space-between; align-items: flex-end; height: 150px; gap: 6px; position: relative; z-index: 2; }
252
313
  .timeline-svg-layer { position: absolute; top: 2.5rem; left: 1.5rem; width: calc(100% - 3rem); height: 150px; z-index: 3; pointer-events: none; }
253
314
  .timeline-column { flex: 1; display: flex; flex-direction: column; justify-content: flex-end; align-items: center; height: 100%; position: relative; }
254
- .timeline-bar-stack { width: 100%; max-width: 45px; height: 100%; background-color: #111827; border-radius: 4px; display: flex; flex-direction: column; justify-content: flex-end; overflow: hidden; opacity: 0.75; }
255
- .timeline-column:hover .timeline-bar-stack { opacity: 1; border: 1px solid var(--text-muted); }
256
- .stack-fill { width: 100%; transition: height 0.3s ease; }
257
- .success-fill { background-color: var(--success); }
258
- .failure-fill { background-color: var(--failure); }
259
- .timeline-tick-label { font-size: 0.75rem; color: var(--text-muted); margin-top: 0.5rem; font-weight: 600; }
315
+ .timeline-bar-stack { width: 100%; background-color: #111827; border-radius: 4px; display: flex; flex-direction: column; justify-content: flex-end; overflow: hidden; opacity: 0.8; height: 100%; }
316
+ .stack-fill { width: 100%; transition: height 0.2s ease; } .success-fill { background-color: var(--success); } .failure-fill { background-color: var(--failure); }
317
+ .timeline-tick-label { font-size: 0.7rem; color: var(--text-muted); margin-top: 0.5rem; }
260
318
 
261
319
  .timeline-column:hover .timeline-hover-metrics { display: block; }
262
- .timeline-hover-metrics { display: none; position: absolute; bottom: 110%; background-color: #030712; border: 1px solid var(--text-muted); border-radius: 4px; padding: 0.6rem; font-size: 0.75rem; white-space: nowrap; z-index: 20; box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.7); }
263
-
264
- .chart-section { background-color: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 1.5rem; margin-bottom: 2rem; }
320
+ .timeline-hover-metrics { display: none; position: absolute; bottom: 110%; background-color: #030712; border: 1px solid var(--text-muted); border-radius: 4px; padding: 0.6rem; font-size: 0.75rem; white-space: nowrap; z-index: 20; }
321
+
322
+ .chart-section { background-color: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 1.5rem; }
265
323
  .chart-row { display: flex; align-items: center; margin-bottom: 0.85rem; }
266
- .chart-label { width: 100px; font-size: 0.9rem; color: var(--text-muted); font-weight: 600; }
267
- .chart-bar-wrapper { flex-grow: 1; background-color: #111827; border-radius: 4px; height: 22px; margin: 0 1rem; overflow: hidden; }
268
- .chart-bar { height: 100%; background: var(--bar-color); border-radius: 4px; }
269
- .chart-value-tag { width: 80px; font-size: 0.9rem; font-weight: 700; text-align: right; }
324
+ .chart-label { width: 100px; font-size: 0.9rem; color: var(--text-muted); }
325
+ .chart-bar-wrapper { flex-grow: 1; background-color: #111827; border-radius: 4px; height: 20px; margin: 0 1rem; overflow: hidden; }
326
+ .chart-bar { height: 100%; background: var(--bar-color); }
327
+ .chart-value-tag { width: 80px; font-size: 0.9rem; text-align: right; font-weight: 700; }
270
328
 
271
- .dashboard-layout { display: grid; grid-template-columns: 2fr 1fr; gap: 1.5rem; margin-top: 1.5rem; }
272
- table { width: 100%; border-collapse: collapse; background-color: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
273
- th, td { padding: 1rem; text-align: left; border-bottom: 1px solid var(--border); }
274
- th { background-color: #111827; font-weight: 600; color: var(--text-muted); font-size: 0.85rem; text-transform: uppercase; }
275
-
329
+ .dashboard-layout { display: grid; grid-template-columns: 2fr 1fr; gap: 1.5rem; }
276
330
  .status-matrix { background-color: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 1.25rem; }
277
331
  .matrix-row { display: flex; justify-content: space-between; padding: 0.65rem 0; border-bottom: 1px solid #334155; }
278
- .matrix-row:last-child { border: none; }
279
332
  .badge { font-weight: bold; padding: 2px 6px; border-radius: 4px; font-size: 0.85rem; }
280
- .badge.s2xx { background-color: rgba(34, 197, 94, 0.2); color: var(--success); }
281
- .badge.s3xx { background-color: rgba(56, 189, 248, 0.2); color: var(--threads); }
282
- .badge.s4xx { background-color: rgba(249, 115, 22, 0.2); color: var(--accent-orange); }
283
- .badge.s5xx { background-color: rgba(239, 68, 68, 0.2); color: var(--failure); }
333
+ .badge.s2xx { background-color: rgba(34,197,94,0.2); color: var(--success); }
334
+ .badge.s5xx { background-color: rgba(239,68,68,0.2); color: var(--failure); }
284
335
 
285
336
  .legend { display: flex; gap: 1.5rem; margin-bottom: 0.75rem; justify-content: flex-end; font-size: 0.85rem; }
286
337
  .legend-item { display: flex; align-items: center; gap: 0.35rem; color: var(--text-muted); }
287
338
  .legend-box { width: 12px; height: 12px; border-radius: 2px; }
288
- .legend-line { width: 20px; height: 3px; border-radius: 1px; }
289
- .legend-line.threads-line { background-color: var(--threads); }
290
- .legend-line.ttf-line { background-color: #f43f5e; border-top: 2px dashed #f43f5e; height: 0; }
339
+ .legend-line { width: 20px; height: 3px; } .legend-line.threads-line { background-color: var(--threads); } .legend-line.ttf-line { border-top: 2px dashed #f43f5e; }
291
340
  </style>
292
341
  </head>
293
342
  <body>
@@ -297,40 +346,35 @@ try {
297
346
  <h1>\u{1F525} Blaze Performance Dashboard</h1>
298
347
  <p style="margin: 0.25rem 0 0 0; color: var(--text-muted);">Target Execution Script: <strong>${targetScript}</strong></p>
299
348
  </div>
300
- <div class="meta-info">
301
- <div>Elapsed Duration: ${actualDurationSec}s</div>
302
- <div>Peak Capacity VUs: ${vusCount} Threads</div>
303
- </div>
349
+ <div class="meta-info">${testConfigurationSummaryHTML}</div>
304
350
  </header>
305
351
 
352
+ ${aiInsightsBoxHTML}
353
+
306
354
  <div class="grid-metrics">
307
355
  <div class="card">
308
356
  <div class="card-title">Apdex Score (T: 50ms)</div>
309
- <div class="card-value apdex">${apdexScore.toFixed(2)} <span style="font-size:0.9rem; font-weight:400;">(${apdexRating})</span></div>
357
+ <div class="card-value apdex">${apdexScore.toFixed(2)} <span style="font-size:0.85rem; font-weight:normal;">(${apdexRating})</span></div>
310
358
  </div>
311
359
  <div class="card">
312
360
  <div class="card-title">Throughput</div>
313
- <div class="card-value highlight">${throughput} /s</div>
361
+ <div class="card-value highlight">${throughputRate} /s</div>
314
362
  </div>
315
363
  <div class="card">
316
364
  <div class="card-title">Network Bandwidth</div>
317
- <div class="card-value network-color">${networkBandwidthMBps} MB/s</div>
365
+ <div class="card-value" style="color:var(--network);">${simulatedBandwidthMBps} MB/s</div>
318
366
  </div>
319
367
  <div class="card">
320
368
  <div class="card-title">Avg Time to First Byte</div>
321
- <div class="card-value">${avgTtfb.toFixed(1)} ms</div>
322
- </div>
323
- <div class="card">
324
- <div class="card-title">Total Samples</div>
325
- <div class="card-value">${totalRequests}</div>
369
+ <div class="card-value">${finalMetricsReport.avgTtfb.toFixed(1)} ms</div>
326
370
  </div>
327
371
  </div>
328
372
 
329
373
  <div class="grid-metrics" style="grid-template-columns: repeat(4, 1fr);">
330
- <div class="card" style="padding:0.85rem 1.25rem;"><div class="card-title" style="font-size:0.75rem;">DNS Lookup</div><div class="card-value" style="font-size:1.25rem; color:var(--text-muted);">${avgDns.toFixed(2)} ms</div></div>
331
- <div class="card" style="padding:0.85rem 1.25rem;"><div class="card-title" style="font-size:0.75rem;">TCP Connect</div><div class="card-value" style="font-size:1.25rem; color:var(--text-muted);">${avgTcp.toFixed(2)} ms</div></div>
332
- <div class="card" style="padding:0.85rem 1.25rem;"><div class="card-title" style="font-size:0.75rem;">TLS Handshake</div><div class="card-value" style="font-size:1.25rem; color:var(--text-muted);">${avgTls.toFixed(2)} ms</div></div>
333
- <div class="card" style="padding:0.85rem 1.25rem;"><div class="card-title" style="font-size:0.75rem;">Stability (Std Dev)</div><div class="card-value" style="font-size:1.25rem; color:var(--text-muted);">\xB1${stdDev.toFixed(1)} ms</div></div>
374
+ <div class="card"><div class="card-title">DNS Lookup</div><div class="card-value" style="font-size:1.2rem; color:var(--text-muted);">${finalMetricsReport.avgDns.toFixed(2)} ms</div></div>
375
+ <div class="card"><div class="card-title">TCP Connect</div><div class="card-value" style="font-size:1.2rem; color:var(--text-muted);">${finalMetricsReport.avgTcp.toFixed(2)} ms</div></div>
376
+ <div class="card"><div class="card-title">TLS Handshake</div><div class="card-value" style="font-size:1.2rem; color:var(--text-muted);">${finalMetricsReport.avgTls.toFixed(2)} ms</div></div>
377
+ <div class="card"><div class="card-title">Stability (Std Dev)</div><div class="card-value" style="font-size:1.2rem; color:var(--text-muted);">\xB1${finalMetricsReport.stdDev.toFixed(1)} ms</div></div>
334
378
  </div>
335
379
 
336
380
  <h2>\u{1F504} Throughput, TTF Trend & Active Threads Concurrency Correlation</h2>
@@ -343,75 +387,38 @@ try {
343
387
 
344
388
  <div class="timeline-wrapper">
345
389
  <svg class="timeline-svg-layer" viewBox="0 0 1000 150" preserveAspectRatio="none">
346
- <!-- Concurrency Trend Line -->
347
390
  <polyline fill="none" stroke="#38bdf8" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" points="${threadsPointsStr.trim()}"></polyline>
348
- <!-- Time-to-Failure (TTF) Trend Line -->
349
391
  <polyline fill="none" stroke="#f43f5e" stroke-width="2.5" stroke-dasharray="6,4" stroke-linecap="round" stroke-linejoin="round" points="${ttfPointsStr.trim()}"></polyline>
350
392
  </svg>
351
- <div class="timeline-container">
352
- ${timelineHtmlElements}
353
- </div>
393
+ <div class="timeline-container">${timelineHtmlElements}</div>
354
394
  </div>
355
395
 
356
396
  <div class="dashboard-layout">
357
397
  <div>
358
398
  <h2>\u{1F4C8} Latency Percentile Distribution</h2>
359
399
  <div class="chart-section">
360
- <div class="chart-row"><div class="chart-label">Minimum</div><div class="chart-bar-wrapper"><div class="chart-bar" style="width: ${pctMin}%;"></div></div><div class="chart-value-tag">${minLatency.toFixed(1)} ms</div></div>
361
- <div class="chart-row"><div class="chart-label">Average</div><div class="chart-bar-wrapper"><div class="chart-bar" style="width: ${pctAvg}%;"></div></div><div class="chart-value-tag">${avgLatency.toFixed(1)} ms</div></div>
362
- <div class="chart-row"><div class="chart-label">90% Line</div><div class="chart-bar-wrapper"><div class="chart-bar" style="width: ${pct90}%;"></div></div><div class="chart-value-tag">${p90.toFixed(1)} ms</div></div>
363
- <div class="chart-row"><div class="chart-label">95% Line</div><div class="chart-bar-wrapper"><div class="chart-bar" style="width: ${pct95}%;"></div></div><div class="chart-value-tag">${p95.toFixed(1)} ms</div></div>
364
- <div class="chart-row"><div class="chart-label">99% Line</div><div class="chart-bar-wrapper"><div class="chart-bar" style="width: ${pct99}%;"></div></div><div class="chart-value-tag">${p99.toFixed(1)} ms</div></div>
365
- <div class="chart-row"><div class="chart-label">Maximum</div><div class="chart-bar-wrapper"><div class="chart-bar" style="width: 100%;"></div></div><div class="chart-value-tag">${maxLatency.toFixed(1)} ms</div></div>
400
+ <div class="chart-row"><div class="chart-label">Minimum</div><div class="chart-bar-wrapper"><div class="chart-bar" style="width: ${(finalMetricsReport.minLatency / peakBoundary * 100).toFixed(1)}%;"></div></div><div class="chart-value-tag">${finalMetricsReport.minLatency.toFixed(1)} ms</div></div>
401
+ <div class="chart-row"><div class="chart-label">Average</div><div class="chart-bar-wrapper"><div class="chart-bar" style="width: ${(finalMetricsReport.avgLatency / peakBoundary * 100).toFixed(1)}%;"></div></div><div class="chart-value-tag">${finalMetricsReport.avgLatency.toFixed(1)} ms</div></div>
402
+ <div class="chart-row"><div class="chart-label">90% Line</div><div class="chart-bar-wrapper"><div class="chart-bar" style="width: ${(finalMetricsReport.p90 / peakBoundary * 100).toFixed(1)}%;"></div></div><div class="chart-value-tag">${finalMetricsReport.p90.toFixed(1)} ms</div></div>
403
+ <div class="chart-row"><div class="chart-label">95% Line</div><div class="chart-bar-wrapper"><div class="chart-bar" style="width: ${(finalMetricsReport.p95 / peakBoundary * 100).toFixed(1)}%;"></div></div><div class="chart-value-tag">${finalMetricsReport.p95.toFixed(1)} ms</div></div>
404
+ <div class="chart-row"><div class="chart-label">99% Line</div><div class="chart-bar-wrapper"><div class="chart-bar" style="width: ${(finalMetricsReport.p99 / peakBoundary * 100).toFixed(1)}%;"></div></div><div class="chart-value-tag">${finalMetricsReport.p99.toFixed(1)} ms</div></div>
366
405
  </div>
367
406
  </div>
368
407
 
369
408
  <div>
370
409
  <h2>\u{1F4CB} HTTP Response Matrix</h2>
371
410
  <div class="status-matrix">
372
- <div class="matrix-row"><span>Successful <span class="badge s2xx">2xx</span></span><strong>${status2xx}</strong></div>
373
- <div class="matrix-row"><span>Redirects <span class="badge s3xx">3xx</span></span><strong>${status3xx}</strong></div>
374
- <div class="matrix-row"><span>Client Error <span class="badge s4xx">4xx</span></span><strong>${status4xx}</strong></div>
375
- <div class="matrix-row"><span>Server Error <span class="badge s5xx">5xx</span></span><strong>${status5xx}</strong></div>
411
+ <div class="matrix-row"><span>Successful <span class="badge s2xx">2xx</span></span><strong>${finalMetricsReport.status2xx}</strong></div>
412
+ <div class="matrix-row"><span>Redirects <span class="badge badge">3xx</span></span><strong>${finalMetricsReport.status3xx}</strong></div>
413
+ <div class="matrix-row"><span>Client Error <span class="badge badge">4xx</span></span><strong>${finalMetricsReport.status4xx}</strong></div>
414
+ <div class="matrix-row"><span>Server Error <span class="badge s5xx">5xx</span></span><strong>${finalMetricsReport.status5xx}</strong></div>
376
415
  </div>
377
416
  </div>
378
417
  </div>
379
-
380
- <h2>Performance Breakdown Table</h2>
381
- <table>
382
- <thead>
383
- <tr>
384
- <th>Label</th>
385
- <th># Samples</th>
386
- <th>Average (ms)</th>
387
- <th>Min (ms)</th>
388
- <th>Max (ms)</th>
389
- <th>DNS Lookup</th>
390
- <th>TLS Handshake</th>
391
- <th>Apdex Score</th>
392
- <th>Error %</th>
393
- </tr>
394
- </thead>
395
- <tbody>
396
- <tr>
397
- <td>${targetScript}</td>
398
- <td>${totalRequests}</td>
399
- <td>${avgLatency.toFixed(1)}</td>
400
- <td>${minLatency.toFixed(1)}</td>
401
- <td>${maxLatency.toFixed(1)}</td>
402
- <td>${avgDns.toFixed(2)} ms</td>
403
- <td>${avgTls.toFixed(2)} ms</td>
404
- <td>${apdexScore.toFixed(2)}</td>
405
- <td>${errorRate}%</td>
406
- </tr>
407
- </tbody>
408
- </table>
409
418
  </div>
410
419
  </body>
411
420
  </html>`;
412
421
  fs.writeFileSync(htmlOutputPath, htmlContent, "utf-8");
413
- console.log(`\u2728 \x1B[32mHTML Dashboard with multi-layered protocol tracking saved to:\x1B[0m ${htmlOutputPath}
422
+ console.log(`\u2728 \x1B[32mHTML Performance Dashboard cleanly generated at:\x1B[0m ${htmlOutputPath}
414
423
  `);
415
- } catch (error) {
416
- console.error("Execution error:", error);
417
- }
424
+ })();
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blaze-performance-tester",
3
- "version": "2.0.12",
3
+ "version": "3.0.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",