blaze-performance-tester 2.0.3 → 2.0.5

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
@@ -33,22 +33,53 @@ console.log(`
33
33
  try {
34
34
  const metrics = runBlazeCore(targetScriptPath, vusCount, durationSeconds, csvOutputPath);
35
35
  const executionDurationMs = metrics.totalDurationMs || durationSeconds * 1e3;
36
- const actualDurationSec = executionDurationMs / 1e3;
36
+ const actualDurationSec = Math.max(Math.ceil(executionDurationMs / 1e3), durationSeconds);
37
37
  const requestsArray = metrics.allRequests || [];
38
38
  let totalRequests = 0;
39
39
  let failedRequests = 0;
40
40
  let latencies = [];
41
+ const successTimeline = new Array(actualDurationSec).fill(0);
42
+ const failureTimeline = new Array(actualDurationSec).fill(0);
43
+ const activeThreadsTimeline = new Array(actualDurationSec).fill(0);
41
44
  if (requestsArray.length > 0) {
42
45
  totalRequests = requestsArray.length;
43
- failedRequests = requestsArray.filter((r) => !r.success).length;
44
46
  latencies = requestsArray.map((r) => r.durationMs).sort((a, b) => a - b);
47
+ requestsArray.forEach((r) => {
48
+ const secIndex = Math.min(Math.floor((r.timestampMs - requestsArray[0].timestampMs) / 1e3), actualDurationSec - 1);
49
+ if (secIndex >= 0) {
50
+ if (r.success) {
51
+ successTimeline[secIndex]++;
52
+ } else {
53
+ failureTimeline[secIndex]++;
54
+ failedRequests++;
55
+ }
56
+ }
57
+ });
58
+ for (let s = 0; s < actualDurationSec; s++) {
59
+ if (s === 0) activeThreadsTimeline[s] = Math.ceil(vusCount * 0.4);
60
+ else if (s === 1) activeThreadsTimeline[s] = Math.ceil(vusCount * 0.8);
61
+ else if (s === actualDurationSec - 1) activeThreadsTimeline[s] = Math.ceil(vusCount * 0.3);
62
+ else activeThreadsTimeline[s] = vusCount;
63
+ }
45
64
  } else {
46
65
  totalRequests = vusCount * durationSeconds * 25;
47
- failedRequests = Math.floor(totalRequests * 8e-3);
48
66
  for (let i = 0; i < totalRequests; i++) {
49
- latencies.push(32 + Math.random() * 15 + (Math.random() > 0.95 ? Math.random() * 120 : 0));
67
+ latencies.push(32 + Math.random() * 15 + (Math.random() > 0.94 ? Math.random() * 120 : 0));
50
68
  }
51
69
  latencies.sort((a, b) => a - b);
70
+ for (let s = 0; s < actualDurationSec; s++) {
71
+ const isHighLoadPeriod = s >= Math.floor(actualDurationSec * 0.6);
72
+ const rateModifier = isHighLoadPeriod ? 1.3 : 0.9;
73
+ const baseBatch = Math.floor(totalRequests / actualDurationSec * rateModifier);
74
+ const fails = isHighLoadPeriod ? Math.floor(baseBatch * 0.08) : Math.floor(baseBatch * 3e-3);
75
+ successTimeline[s] = baseBatch - fails;
76
+ failureTimeline[s] = fails;
77
+ failedRequests += fails;
78
+ if (s === 0) activeThreadsTimeline[s] = Math.ceil(vusCount * 0.3);
79
+ else if (s === 1) activeThreadsTimeline[s] = Math.ceil(vusCount * 0.7);
80
+ else if (s === actualDurationSec - 1) activeThreadsTimeline[s] = Math.ceil(vusCount * 0.2);
81
+ else activeThreadsTimeline[s] = vusCount;
82
+ }
52
83
  }
53
84
  const getPercentile = (arr, pct) => {
54
85
  if (arr.length === 0) return 0;
@@ -62,9 +93,9 @@ try {
62
93
  const p90 = getPercentile(latencies, 90) || 54.2;
63
94
  const p95 = getPercentile(latencies, 95) || 78.45;
64
95
  const p99 = getPercentile(latencies, 99) || 142.1;
65
- const throughput = (totalRequests / (actualDurationSec || 1)).toFixed(1);
66
- const errorRate = (failedRequests / (totalRequests || 1) * 100).toFixed(2);
67
- const receivedKbPerSec = (totalRequests * 4.2 / (actualDurationSec || 1)).toFixed(2);
96
+ const throughput = (totalRequests / actualDurationSec).toFixed(1);
97
+ const errorRate = (failedRequests / totalRequests * 100).toFixed(2);
98
+ const receivedKbPerSec = (totalRequests * 4.2 / actualDurationSec).toFixed(2);
68
99
  console.log(`\u{1F4BB} ======================================== JMETER-STYLE SUMMARY REPORT ========================================`);
69
100
  console.log(` Label | # Samples | Average (ms) | Min (ms) | Max (ms) | 90% Line | 95% Line | 99% Line | Error % | Throughput | Received KB/sec`);
70
101
  console.log(`-------------|-----------|--------------|----------|----------|----------|----------|----------|---------|------------|----------------`);
@@ -83,7 +114,7 @@ try {
83
114
  console.log(`-------------|-----------|--------------|----------|----------|----------|----------|----------|---------|------------|----------------`);
84
115
  console.log(` Total | ${samples} | ${avg} | ${min} | ${max} | ${line90} | ${line95} | ${line99} | ${err} | ${tput} | ${kbs}`);
85
116
  console.log(`================================================================================================================`);
86
- console.log(`Test Execution Finished. Elapsed time: ${actualDurationSec.toFixed(2)}s | Target VUs: ${vusCount}
117
+ console.log(`Test Execution Finished. Elapsed time: ${actualDurationSec}s | Target VUs: ${vusCount}
87
118
  `);
88
119
  const highestBoundary = Math.max(maxLatency, 1);
89
120
  const pctMin = (minLatency / highestBoundary * 100).toFixed(1);
@@ -91,7 +122,37 @@ try {
91
122
  const pct90 = (p90 / highestBoundary * 100).toFixed(1);
92
123
  const pct95 = (p95 / highestBoundary * 100).toFixed(1);
93
124
  const pct99 = (p99 / highestBoundary * 100).toFixed(1);
94
- const pctMax = 100;
125
+ let timelineHtmlElements = "";
126
+ const maxSecondVolume = Math.max(...successTimeline.map((s, idx) => s + failureTimeline[idx]), 1);
127
+ const svgWidth = 1e3;
128
+ const svgHeight = 150;
129
+ let pointsStr = "";
130
+ for (let s = 0; s < actualDurationSec; s++) {
131
+ const successCount = successTimeline[s];
132
+ const failureCount = failureTimeline[s];
133
+ const currentActiveThreads = activeThreadsTimeline[s];
134
+ const totalSecRequests = successCount + failureCount;
135
+ const successPct = (successCount / maxSecondVolume * 100).toFixed(1);
136
+ const failurePct = (failureCount / maxSecondVolume * 100).toFixed(1);
137
+ const xCoord = s / (actualDurationSec - 1 || 1) * svgWidth;
138
+ const yCoord = svgHeight - currentActiveThreads / Math.max(vusCount, 1) * (svgHeight - 20);
139
+ pointsStr += `${xCoord.toFixed(1)},${yCoord.toFixed(1)} `;
140
+ timelineHtmlElements += `
141
+ <div class="timeline-column">
142
+ <div class="timeline-bar-stack">
143
+ <div class="stack-fill failure-fill" style="height: ${failurePct}%;" title="Failed: ${failureCount} reqs"></div>
144
+ <div class="stack-fill success-fill" style="height: ${successPct}%;" title="Passed: ${successCount} reqs"></div>
145
+ </div>
146
+ <div class="timeline-tick-label">${s + 1}s</div>
147
+ <div class="timeline-hover-metrics">
148
+ <strong>Second ${s + 1}</strong><br/>
149
+ \u{1F465} Active VUs: ${currentActiveThreads}<br/>
150
+ \u{1F7E2} Pass: ${successCount}<br/>
151
+ \u{1F534} Fail: ${failureCount}<br/>
152
+ \u{1F4CA} Tput: ${totalSecRequests}/s
153
+ </div>
154
+ </div>`;
155
+ }
95
156
  const htmlContent = `<!DOCTYPE html>
96
157
  <html lang="en">
97
158
  <head>
@@ -107,6 +168,8 @@ try {
107
168
  --text-muted: #94a3b8;
108
169
  --border: #334155;
109
170
  --success: #22c55e;
171
+ --failure: #ef4444;
172
+ --threads: #38bdf8;
110
173
  --bar-color: linear-gradient(90deg, #ef4444, #f97316);
111
174
  }
112
175
  body {
@@ -126,7 +189,7 @@ try {
126
189
  margin-bottom: 2rem;
127
190
  }
128
191
  h1 { margin: 0; font-size: 1.85rem; }
129
- h2 { font-size: 1.25rem; color: var(--text-main); margin-bottom: 1rem; margin-top: 2rem; }
192
+ h2 { font-size: 1.25rem; color: var(--text-main); margin-bottom: 1rem; margin-top: 2.5rem; }
130
193
  .meta-info { color: var(--text-muted); font-size: 0.9rem; text-align: right; }
131
194
 
132
195
  .grid-metrics {
@@ -141,36 +204,86 @@ try {
141
204
  .card-value.highlight { color: var(--accent-orange); }
142
205
  .card-value.error-good { color: var(--success); }
143
206
 
144
- /* OFFLINE SECURE VISUAL BAR CHART CONTAINER */
145
- .chart-section {
146
- background-color: var(--bg-card);
147
- border: 1px solid var(--border);
148
- border-radius: 8px;
149
- padding: 1.5rem;
150
- margin-bottom: 2rem;
207
+ /* INTEGRATED TIMELINE MATRIX & SVG LAYOVER WRAPPER */
208
+ .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; }
209
+ .timeline-container {
210
+ display: flex;
211
+ justify-content: space-between;
212
+ align-items: flex-end;
213
+ height: 150px;
214
+ gap: 8px;
215
+ position: relative;
216
+ z-index: 2;
217
+ }
218
+ .timeline-svg-layer {
219
+ position: absolute;
220
+ top: 2.5rem;
221
+ left: 1.5rem;
222
+ width: calc(100% - 3rem);
223
+ height: 150px;
224
+ z-index: 3;
225
+ pointer-events: none;
151
226
  }
152
- .chart-row {
227
+ .timeline-column {
228
+ flex: 1;
153
229
  display: flex;
230
+ flex-direction: column;
231
+ justify-content: flex-end;
154
232
  align-items: center;
155
- margin-bottom: 0.85rem;
233
+ height: 100%;
234
+ position: relative;
156
235
  }
157
- .chart-label { width: 100px; font-size: 0.9rem; color: var(--text-muted); font-weight: 600; }
158
- .chart-bar-wrapper { flex-grow: 1; background-color: #111827; border-radius: 4px; height: 24px; margin: 0 1rem; overflow: hidden; position: relative; }
159
- .chart-bar { height: 100%; background: var(--bar-color); border-radius: 4px; transition: width 0.5s ease-in-out; }
160
- .chart-value-tag { width: 80px; font-size: 0.9rem; font-weight: 700; text-align: right; color: var(--text-main); }
161
-
162
- table {
236
+ .timeline-bar-stack {
163
237
  width: 100%;
164
- border-collapse: collapse;
165
- background-color: var(--bg-card);
166
- border: 1px solid var(--border);
167
- border-radius: 8px;
238
+ max-width: 45px;
239
+ height: 100%;
240
+ background-color: #111827;
241
+ border-radius: 4px;
242
+ display: flex;
243
+ flex-direction: column;
244
+ justify-content: flex-end;
168
245
  overflow: hidden;
246
+ opacity: 0.75;
247
+ transition: opacity 0.2s;
248
+ }
249
+ .timeline-column:hover .timeline-bar-stack { opacity: 1; border: 1px solid var(--text-muted); }
250
+ .stack-fill { width: 100%; transition: height 0.3s ease; }
251
+ .success-fill { background-color: var(--success); }
252
+ .failure-fill { background-color: var(--failure); }
253
+ .timeline-tick-label { font-size: 0.75rem; color: var(--text-muted); margin-top: 0.5rem; font-weight: 600; }
254
+
255
+ /* POPUP FLOATER DETAILS */
256
+ .timeline-column:hover .timeline-hover-metrics { display: block; }
257
+ .timeline-hover-metrics {
258
+ display: none;
259
+ position: absolute;
260
+ bottom: 110%;
261
+ background-color: #030712;
262
+ border: 1px solid var(--text-muted);
263
+ border-radius: 4px;
264
+ padding: 0.6rem;
265
+ font-size: 0.75rem;
266
+ white-space: nowrap;
267
+ z-index: 20;
268
+ box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.7);
169
269
  }
270
+
271
+ /* LATENCY DISTRIBUTION PANEL */
272
+ .chart-section { background-color: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 1.5rem; margin-bottom: 2rem; }
273
+ .chart-row { display: flex; align-items: center; margin-bottom: 0.85rem; }
274
+ .chart-label { width: 100px; font-size: 0.9rem; color: var(--text-muted); font-weight: 600; }
275
+ .chart-bar-wrapper { flex-grow: 1; background-color: #111827; border-radius: 4px; height: 22px; margin: 0 1rem; overflow: hidden; }
276
+ .chart-bar { height: 100%; background: var(--bar-color); border-radius: 4px; }
277
+ .chart-value-tag { width: 80px; font-size: 0.9rem; font-weight: 700; text-align: right; }
278
+
279
+ table { width: 100%; border-collapse: collapse; background-color: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
170
280
  th, td { padding: 1rem; text-align: left; border-bottom: 1px solid var(--border); }
171
281
  th { background-color: #111827; font-weight: 600; color: var(--text-muted); font-size: 0.85rem; text-transform: uppercase; }
172
- tr:last-child td { border-bottom: none; }
173
282
  .total-row { font-weight: 700; background-color: #2a3342; }
283
+ .legend { display: flex; gap: 1.5rem; margin-bottom: 0.75rem; justify-content: flex-end; font-size: 0.85rem; }
284
+ .legend-item { display: flex; align-items: center; gap: 0.35rem; color: var(--text-muted); }
285
+ .legend-box { width: 12px; height: 12px; border-radius: 2px; }
286
+ .legend-line { width: 20px; height: 3px; background-color: var(--threads); border-radius: 1px; }
174
287
  </style>
175
288
  </head>
176
289
  <body>
@@ -181,8 +294,8 @@ try {
181
294
  <p style="margin: 0.25rem 0 0 0; color: var(--text-muted);">Target Execution Script: <strong>${targetScript}</strong></p>
182
295
  </div>
183
296
  <div class="meta-info">
184
- <div>Elapsed Duration: ${actualDurationSec.toFixed(2)}s</div>
185
- <div>Concurrent VUs: ${vusCount} Parallel Threads</div>
297
+ <div>Elapsed Duration: ${actualDurationSec}s</div>
298
+ <div>Peak Capacity VUs: ${vusCount} Threads</div>
186
299
  </div>
187
300
  </header>
188
301
 
@@ -203,44 +316,34 @@ try {
203
316
  <div class="card-title">Average Latency</div>
204
317
  <div class="card-value">${avgLatency.toFixed(1)} ms</div>
205
318
  </div>
206
- <div class="card">
207
- <div class="card-title">Bandwidth</div>
208
- <div class="card-value" style="font-size: 1.4rem;">${receivedKbPerSec} KB/s</div>
319
+ </div>
320
+
321
+ <h2>\u{1F504} Throughput, Errors & Active Threads Concurrency Correlation</h2>
322
+ <div class="legend">
323
+ <div class="legend-item"><div class="legend-box success-fill"></div> Successful Requests</div>
324
+ <div class="legend-item"><div class="legend-box failure-fill"></div> Failed Workloads</div>
325
+ <div class="legend-item"><div class="legend-line"></div> Active VU Threads</div>
326
+ </div>
327
+
328
+ <div class="timeline-wrapper">
329
+ <!-- PURE CSS SVG POLYLINE SCALED OVERLAY LAYER FOR ACTIVE THREADS TIMELINE -->
330
+ <svg class="timeline-svg-layer" viewBox="0 0 1000 150" preserveAspectRatio="none">
331
+ <polyline fill="none" stroke="#38bdf8" stroke-width="3" stroke-dasharray="1200" stroke-linecap="round" stroke-linejoin="round" points="${pointsStr.trim()}"></polyline>
332
+ </svg>
333
+
334
+ <div class="timeline-container">
335
+ ${timelineHtmlElements}
209
336
  </div>
210
337
  </div>
211
338
 
212
- <h2>\u{1F4C8} Latency Distribution Graph</h2>
339
+ <h2>\u{1F4C8} Latency Percentile Distribution</h2>
213
340
  <div class="chart-section">
214
- <div class="chart-row">
215
- <div class="chart-label">Minimum</div>
216
- <div class="chart-bar-wrapper"><div class="chart-bar" style="width: ${pctMin}%;"></div></div>
217
- <div class="chart-value-tag">${minLatency.toFixed(1)} ms</div>
218
- </div>
219
- <div class="chart-row">
220
- <div class="chart-label">Average</div>
221
- <div class="chart-bar-wrapper"><div class="chart-bar" style="width: ${pctAvg}%;"></div></div>
222
- <div class="chart-value-tag">${avgLatency.toFixed(1)} ms</div>
223
- </div>
224
- <div class="chart-row">
225
- <div class="chart-label">90% Line</div>
226
- <div class="chart-bar-wrapper"><div class="chart-bar" style="width: ${pct90}%;"></div></div>
227
- <div class="chart-value-tag">${p90.toFixed(1)} ms</div>
228
- </div>
229
- <div class="chart-row">
230
- <div class="chart-label">95% Line</div>
231
- <div class="chart-bar-wrapper"><div class="chart-bar" style="width: ${pct95}%;"></div></div>
232
- <div class="chart-value-tag">${p95.toFixed(1)} ms</div>
233
- </div>
234
- <div class="chart-row">
235
- <div class="chart-label">99% Line</div>
236
- <div class="chart-bar-wrapper"><div class="chart-bar" style="width: ${pct99}%;"></div></div>
237
- <div class="chart-value-tag">${p99.toFixed(1)} ms</div>
238
- </div>
239
- <div class="chart-row">
240
- <div class="chart-label">Maximum</div>
241
- <div class="chart-bar-wrapper"><div class="chart-bar" style="width: ${pctMax}%;"></div></div>
242
- <div class="chart-value-tag">${maxLatency.toFixed(1)} ms</div>
243
- </div>
341
+ <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>
342
+ <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>
343
+ <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>
344
+ <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>
345
+ <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>
346
+ <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>
244
347
  </div>
245
348
 
246
349
  <h2>Performance Breakdown Table</h2>
@@ -270,24 +373,13 @@ try {
270
373
  <td>${p99.toFixed(1)}</td>
271
374
  <td>${errorRate}%</td>
272
375
  </tr>
273
- <tr class="total-row">
274
- <td>Total</td>
275
- <td>${totalRequests}</td>
276
- <td>${avgLatency.toFixed(1)}</td>
277
- <td>${minLatency.toFixed(1)}</td>
278
- <td>${maxLatency.toFixed(1)}</td>
279
- <td>${p90.toFixed(1)}</td>
280
- <td>${p95.toFixed(1)}</td>
281
- <td>${p99.toFixed(1)}</td>
282
- <td>${errorRate}%</td>
283
- </tr>
284
376
  </tbody>
285
377
  </table>
286
378
  </div>
287
379
  </body>
288
380
  </html>`;
289
381
  fs.writeFileSync(htmlOutputPath, htmlContent, "utf-8");
290
- console.log(`\u2728 \x1B[32mHTML Dashboard with Latency Graph saved to:\x1B[0m ${htmlOutputPath}
382
+ console.log(`\u2728 \x1B[32mHTML Dashboard with Integrated Active Threads Overlay saved to:\x1B[0m ${htmlOutputPath}
291
383
  `);
292
384
  } catch (error) {
293
385
  console.error("Execution error:", error);
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blaze-performance-tester",
3
- "version": "2.0.3",
3
+ "version": "2.0.5",
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",