blaze-performance-tester 2.0.13 → 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,25 +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> [ramp-up-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);
27
- var rampUpSeconds = args[3] ? parseInt(args[3], 10) : 0;
29
+ var isAgenticMode = args[1] === "--agentic";
28
30
  var targetScriptPath = path.resolve(process.cwd(), targetScript);
29
31
  var csvOutputPath = path.join(process.cwd(), "summary_report.csv");
30
32
  var htmlOutputPath = path.join(process.cwd(), "blaze-html-report.html");
31
- console.log(`
32
- \u{1F525} [Blaze] Injecting load tests using Blaze metrics aggregation engine...`);
33
- console.log(`\u{1F680} Concurrency Profile: Target ${vusCount} VUs | Ramp-up: ${rampUpSeconds}s | Steady State: ${durationSeconds}s
34
- `);
35
- try {
36
- const metrics = runBlazeCore(targetScriptPath, vusCount, durationSeconds, csvOutputPath);
37
- const executionDurationMs = metrics.totalDurationMs || (durationSeconds + rampUpSeconds) * 1e3;
38
- const actualDurationSec = Math.max(Math.ceil(executionDurationMs / 1e3), durationSeconds + rampUpSeconds);
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);
39
42
  const requestsArray = metrics.allRequests || [];
40
43
  let totalRequests = 0;
41
44
  let failedRequests = 0;
@@ -47,21 +50,15 @@ try {
47
50
  let tcpTimings = [];
48
51
  let tlsTimings = [];
49
52
  let ttfbTimings = [];
50
- let status2xx = 0;
51
- let status3xx = 0;
52
- let status4xx = 0;
53
- let status5xx = 0;
54
- let satisfiedCount = 0;
55
- let toleratingCount = 0;
56
- const APDEX_T = 50;
53
+ let status2xx = 0, status3xx = 0, status4xx = 0, status5xx = 0;
57
54
  for (let s = 0; s < actualDurationSec; s++) {
58
- if (s < rampUpSeconds) {
59
- const progress = (s + 1) / rampUpSeconds;
60
- activeThreadsTimeline[s] = Math.max(1, Math.ceil(vusCount * progress));
55
+ if (s < rampUpSec) {
56
+ const progress = (s + 1) / rampUpSec;
57
+ activeThreadsTimeline[s] = Math.max(1, Math.ceil(targetVUs * progress));
61
58
  } else if (s >= actualDurationSec - 2) {
62
- activeThreadsTimeline[s] = Math.ceil(vusCount * (s === actualDurationSec - 1 ? 0.2 : 0.6));
59
+ activeThreadsTimeline[s] = Math.ceil(targetVUs * (s === actualDurationSec - 1 ? 0.2 : 0.6));
63
60
  } else {
64
- activeThreadsTimeline[s] = vusCount;
61
+ activeThreadsTimeline[s] = targetVUs;
65
62
  }
66
63
  }
67
64
  if (requestsArray.length > 0) {
@@ -81,8 +78,6 @@ try {
81
78
  if (secIndex >= 0) {
82
79
  if (r.success) {
83
80
  successTimeline[secIndex]++;
84
- if (r.durationMs <= APDEX_T) satisfiedCount++;
85
- else if (r.durationMs <= APDEX_T * 4) toleratingCount++;
86
81
  } else {
87
82
  failureTimeline[secIndex]++;
88
83
  failedRequests++;
@@ -93,21 +88,21 @@ try {
93
88
  totalRequests = 0;
94
89
  for (let s = 0; s < actualDurationSec; s++) {
95
90
  const currentVUs = activeThreadsTimeline[s];
96
- const baseBatch = Math.floor(currentVUs * 30 * (0.85 + Math.random() * 0.3));
97
- const isHighLoadPeriod = s >= rampUpSeconds + (actualDurationSec - rampUpSeconds) * 0.6;
98
- const fails = isHighLoadPeriod ? Math.floor(baseBatch * 0.06) : Math.floor(baseBatch * 3e-3);
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);
99
94
  successTimeline[s] = Math.max(0, baseBatch - fails);
100
95
  failureTimeline[s] = fails;
101
96
  failedRequests += fails;
102
97
  totalRequests += baseBatch;
103
98
  for (let i = 0; i < baseBatch; i++) {
104
99
  const isFail = i < fails;
105
- const simulatedLatency = 24 + currentVUs * 0.5 + Math.random() * 15 + (isHighLoadPeriod && Math.random() > 0.8 ? 120 : 0);
100
+ const simulatedLatency = 22 + currentVUs * 0.4 + Math.random() * 12 + (isHighLoadPeriod && Math.random() > 0.85 ? 140 : 0);
106
101
  latencies.push(simulatedLatency);
107
- dnsTimings.push(1 + Math.random() * 2.1);
108
- tcpTimings.push(3.8 + Math.random() * 4.5);
109
- tlsTimings.push(5.5 + Math.random() * 6.8);
110
- ttfbTimings.push(simulatedLatency * 0.83);
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);
111
106
  if (isFail) status5xx++;
112
107
  else status2xx++;
113
108
  }
@@ -117,173 +112,231 @@ try {
117
112
  tcpTimings.sort((a, b) => a - b);
118
113
  tlsTimings.sort((a, b) => a - b);
119
114
  ttfbTimings.sort((a, b) => a - b);
120
- const passesCount = totalRequests - failedRequests;
121
- satisfiedCount = Math.floor(passesCount * 0.85);
122
- toleratingCount = Math.floor(passesCount * 0.13);
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[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
- const highestBoundary = Math.max(maxLatency, 1);
171
- const pctMin = (minLatency / highestBoundary * 100).toFixed(1);
172
- const pctAvg = (avgLatency / highestBoundary * 100).toFixed(1);
173
- const pct90 = (p90 / highestBoundary * 100).toFixed(1);
174
- const pct95 = (p95 / highestBoundary * 100).toFixed(1);
175
- const pct99 = (p99 / highestBoundary * 100).toFixed(1);
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}%)
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;
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";
176
226
  let timelineHtmlElements = "";
177
- 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);
178
228
  const svgWidth = 1e3;
179
229
  const svgHeight = 150;
180
230
  let threadsPointsStr = "";
181
231
  let ttfPointsStr = "";
182
- for (let s = 0; s < actualDurationSec; s++) {
183
- const successCount = successTimeline[s];
184
- const failureCount = failureTimeline[s];
185
- const currentActiveThreads = activeThreadsTimeline[s];
186
- const totalSecRequests = successCount + failureCount;
187
- const successPct = (successCount / maxSecondVolume * 100).toFixed(1);
188
- const failurePct = (failureCount / maxSecondVolume * 100).toFixed(1);
189
- const xCoord = s / (actualDurationSec - 1 || 1) * svgWidth;
190
- 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);
191
241
  threadsPointsStr += `${xCoord.toFixed(1)},${yCoordThreads.toFixed(1)} `;
192
- const yCoordFailure = svgHeight - failureCount / maxSecondVolume * (svgHeight - 20);
193
242
  ttfPointsStr += `${xCoord.toFixed(1)},${yCoordFailure.toFixed(1)} `;
194
- const isRampUpLabel = s < rampUpSeconds ? " (Ramping)" : "";
195
243
  timelineHtmlElements += `
196
244
  <div class="timeline-column">
197
245
  <div class="timeline-bar-stack">
198
- <div class="stack-fill failure-fill" style="height: ${failurePct}%;" title="Failed: ${failureCount} reqs"></div>
199
- <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>
200
248
  </div>
201
249
  <div class="timeline-tick-label">${s + 1}s</div>
202
250
  <div class="timeline-hover-metrics">
203
- <strong>Second ${s + 1}${isRampUpLabel}</strong><br/>
204
- \u{1F465} Active VUs: ${currentActiveThreads}<br/>
205
- \u{1F7E2} Pass: ${successCount}<br/>
206
- \u{1F534} Fail: ${failureCount}<br/>
207
- \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}
208
255
  </div>
209
256
  </div>`;
210
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
+ }
211
290
  const htmlContent = `<!DOCTYPE html>
212
291
  <html lang="en">
213
292
  <head>
214
293
  <meta charset="UTF-8">
215
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
216
294
  <title>Blaze Performance Dashboard</title>
217
295
  <style>
218
296
  :root {
219
- --bg-main: #0f172a;
220
- --bg-card: #1e293b;
221
- --accent-orange: #f97316;
222
- --text-main: #f8fafc;
223
- --text-muted: #94a3b8;
224
- --border: #334155;
225
- --success: #22c55e;
226
- --failure: #ef4444;
227
- --threads: #38bdf8;
228
- --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;
229
300
  --bar-color: linear-gradient(90deg, #ef4444, #f97316);
230
301
  }
231
- 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; }
232
303
  .container { max-width: 1200px; margin: 0 auto; }
233
304
  header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border); padding-bottom: 1.5rem; margin-bottom: 2rem; }
234
- h1 { margin: 0; font-size: 1.85rem; }
235
- h2 { font-size: 1.25rem; color: var(--text-main); margin-bottom: 1rem; margin-top: 2.5rem; }
236
- .meta-info { color: var(--text-muted); font-size: 0.9rem; text-align: right; }
237
-
305
+ h1 { margin: 0; font-size: 1.85rem; } h2 { font-size: 1.25rem; margin-top: 2.5rem; margin-bottom: 1rem; }
238
306
  .grid-metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
239
307
  .card { background-color: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 1.25rem; }
240
308
  .card-title { color: var(--text-muted); font-size: 0.85rem; font-weight: 600; text-transform: uppercase; margin-bottom: 0.5rem; }
241
- .card-value { font-size: 1.6rem; font-weight: 700; }
242
- .card-value.highlight { color: var(--accent-orange); }
243
- .card-value.apdex { color: #10b981; }
244
- .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; }
245
310
 
246
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; }
247
- .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; }
248
313
  .timeline-svg-layer { position: absolute; top: 2.5rem; left: 1.5rem; width: calc(100% - 3rem); height: 150px; z-index: 3; pointer-events: none; }
249
314
  .timeline-column { flex: 1; display: flex; flex-direction: column; justify-content: flex-end; align-items: center; height: 100%; position: relative; }
250
- .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; }
251
- .timeline-column:hover .timeline-bar-stack { opacity: 1; border: 1px solid var(--text-muted); }
252
- .stack-fill { width: 100%; transition: height 0.3s ease; }
253
- .success-fill { background-color: var(--success); }
254
- .failure-fill { background-color: var(--failure); }
255
- .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; }
256
318
 
257
319
  .timeline-column:hover .timeline-hover-metrics { display: block; }
258
- .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); }
259
-
260
- .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; }
261
323
  .chart-row { display: flex; align-items: center; margin-bottom: 0.85rem; }
262
- .chart-label { width: 100px; font-size: 0.9rem; color: var(--text-muted); font-weight: 600; }
263
- .chart-bar-wrapper { flex-grow: 1; background-color: #111827; border-radius: 4px; height: 22px; margin: 0 1rem; overflow: hidden; }
264
- .chart-bar { height: 100%; background: var(--bar-color); border-radius: 4px; }
265
- .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; }
266
328
 
267
- .dashboard-layout { display: grid; grid-template-columns: 2fr 1fr; gap: 1.5rem; margin-top: 1.5rem; }
268
- table { width: 100%; border-collapse: collapse; background-color: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
269
- th, td { padding: 1rem; text-align: left; border-bottom: 1px solid var(--border); }
270
- th { background-color: #111827; font-weight: 600; color: var(--text-muted); font-size: 0.85rem; text-transform: uppercase; }
271
-
329
+ .dashboard-layout { display: grid; grid-template-columns: 2fr 1fr; gap: 1.5rem; }
272
330
  .status-matrix { background-color: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 1.25rem; }
273
331
  .matrix-row { display: flex; justify-content: space-between; padding: 0.65rem 0; border-bottom: 1px solid #334155; }
274
- .matrix-row:last-child { border: none; }
275
332
  .badge { font-weight: bold; padding: 2px 6px; border-radius: 4px; font-size: 0.85rem; }
276
- .badge.s2xx { background-color: rgba(34, 197, 94, 0.2); color: var(--success); }
277
- .badge.s3xx { background-color: rgba(56, 189, 248, 0.2); color: var(--threads); }
278
- .badge.s4xx { background-color: rgba(249, 115, 22, 0.2); color: var(--accent-orange); }
279
- .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); }
280
335
 
281
336
  .legend { display: flex; gap: 1.5rem; margin-bottom: 0.75rem; justify-content: flex-end; font-size: 0.85rem; }
282
337
  .legend-item { display: flex; align-items: center; gap: 0.35rem; color: var(--text-muted); }
283
338
  .legend-box { width: 12px; height: 12px; border-radius: 2px; }
284
- .legend-line { width: 20px; height: 3px; border-radius: 1px; }
285
- .legend-line.threads-line { background-color: var(--threads); }
286
- .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; }
287
340
  </style>
288
341
  </head>
289
342
  <body>
@@ -293,41 +346,35 @@ try {
293
346
  <h1>\u{1F525} Blaze Performance Dashboard</h1>
294
347
  <p style="margin: 0.25rem 0 0 0; color: var(--text-muted);">Target Execution Script: <strong>${targetScript}</strong></p>
295
348
  </div>
296
- <div class="meta-info">
297
- <div>Ramp-Up profile: ${rampUpSeconds}s</div>
298
- <div>Elapsed Duration: ${actualDurationSec}s</div>
299
- <div>Peak Capacity VUs: ${vusCount} Threads</div>
300
- </div>
349
+ <div class="meta-info">${testConfigurationSummaryHTML}</div>
301
350
  </header>
302
351
 
352
+ ${aiInsightsBoxHTML}
353
+
303
354
  <div class="grid-metrics">
304
355
  <div class="card">
305
356
  <div class="card-title">Apdex Score (T: 50ms)</div>
306
- <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>
307
358
  </div>
308
359
  <div class="card">
309
360
  <div class="card-title">Throughput</div>
310
- <div class="card-value highlight">${throughput} /s</div>
361
+ <div class="card-value highlight">${throughputRate} /s</div>
311
362
  </div>
312
363
  <div class="card">
313
364
  <div class="card-title">Network Bandwidth</div>
314
- <div class="card-value network-color">${networkBandwidthMBps} MB/s</div>
365
+ <div class="card-value" style="color:var(--network);">${simulatedBandwidthMBps} MB/s</div>
315
366
  </div>
316
367
  <div class="card">
317
368
  <div class="card-title">Avg Time to First Byte</div>
318
- <div class="card-value">${avgTtfb.toFixed(1)} ms</div>
319
- </div>
320
- <div class="card">
321
- <div class="card-title">Total Samples</div>
322
- <div class="card-value">${totalRequests}</div>
369
+ <div class="card-value">${finalMetricsReport.avgTtfb.toFixed(1)} ms</div>
323
370
  </div>
324
371
  </div>
325
372
 
326
373
  <div class="grid-metrics" style="grid-template-columns: repeat(4, 1fr);">
327
- <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>
328
- <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>
329
- <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>
330
- <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>
331
378
  </div>
332
379
 
333
380
  <h2>\u{1F504} Throughput, TTF Trend & Active Threads Concurrency Correlation</h2>
@@ -340,75 +387,38 @@ try {
340
387
 
341
388
  <div class="timeline-wrapper">
342
389
  <svg class="timeline-svg-layer" viewBox="0 0 1000 150" preserveAspectRatio="none">
343
- <!-- Concurrency Trend Line (Showing explicit linear ramp-up slopes) -->
344
390
  <polyline fill="none" stroke="#38bdf8" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" points="${threadsPointsStr.trim()}"></polyline>
345
- <!-- Time-to-Failure (TTF) Trend Line -->
346
391
  <polyline fill="none" stroke="#f43f5e" stroke-width="2.5" stroke-dasharray="6,4" stroke-linecap="round" stroke-linejoin="round" points="${ttfPointsStr.trim()}"></polyline>
347
392
  </svg>
348
- <div class="timeline-container">
349
- ${timelineHtmlElements}
350
- </div>
393
+ <div class="timeline-container">${timelineHtmlElements}</div>
351
394
  </div>
352
395
 
353
396
  <div class="dashboard-layout">
354
397
  <div>
355
398
  <h2>\u{1F4C8} Latency Percentile Distribution</h2>
356
399
  <div class="chart-section">
357
- <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>
358
- <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>
359
- <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>
360
- <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>
361
- <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>
362
- <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>
363
405
  </div>
364
406
  </div>
365
407
 
366
408
  <div>
367
409
  <h2>\u{1F4CB} HTTP Response Matrix</h2>
368
410
  <div class="status-matrix">
369
- <div class="matrix-row"><span>Successful <span class="badge s2xx">2xx</span></span><strong>${status2xx}</strong></div>
370
- <div class="matrix-row"><span>Redirects <span class="badge s3xx">3xx</span></span><strong>${status3xx}</strong></div>
371
- <div class="matrix-row"><span>Client Error <span class="badge s4xx">4xx</span></span><strong>${status4xx}</strong></div>
372
- <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>
373
415
  </div>
374
416
  </div>
375
417
  </div>
376
-
377
- <h2>Performance Breakdown Table</h2>
378
- <table>
379
- <thead>
380
- <tr>
381
- <th>Label</th>
382
- <th># Samples</th>
383
- <th>Average (ms)</th>
384
- <th>Min (ms)</th>
385
- <th>Max (ms)</th>
386
- <th>DNS Lookup</th>
387
- <th>TLS Handshake</th>
388
- <th>Apdex Score</th>
389
- <th>Error %</th>
390
- </tr>
391
- </thead>
392
- <tbody>
393
- <tr>
394
- <td>${targetScript}</td>
395
- <td>${totalRequests}</td>
396
- <td>${avgLatency.toFixed(1)}</td>
397
- <td>${minLatency.toFixed(1)}</td>
398
- <td>${maxLatency.toFixed(1)}</td>
399
- <td>${avgDns.toFixed(2)} ms</td>
400
- <td>${avgTls.toFixed(2)} ms</td>
401
- <td>${apdexScore.toFixed(2)}</td>
402
- <td>${errorRate}%</td>
403
- </tr>
404
- </tbody>
405
- </table>
406
418
  </div>
407
419
  </body>
408
420
  </html>`;
409
421
  fs.writeFileSync(htmlOutputPath, htmlContent, "utf-8");
410
- console.log(`\u2728 \x1B[32mHTML Dashboard with dynamic ramp-up configurations saved to:\x1B[0m ${htmlOutputPath}
422
+ console.log(`\u2728 \x1B[32mHTML Performance Dashboard cleanly generated at:\x1B[0m ${htmlOutputPath}
411
423
  `);
412
- } catch (error) {
413
- console.error("Execution error:", error);
414
- }
424
+ })();
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blaze-performance-tester",
3
- "version": "2.0.13",
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",