blaze-performance-tester 2.0.3 → 2.0.4

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,43 @@ 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);
41
43
  if (requestsArray.length > 0) {
42
44
  totalRequests = requestsArray.length;
43
- failedRequests = requestsArray.filter((r) => !r.success).length;
44
45
  latencies = requestsArray.map((r) => r.durationMs).sort((a, b) => a - b);
46
+ requestsArray.forEach((r) => {
47
+ const secIndex = Math.min(Math.floor((r.timestampMs - requestsArray[0].timestampMs) / 1e3), actualDurationSec - 1);
48
+ if (secIndex >= 0) {
49
+ if (r.success) {
50
+ successTimeline[secIndex]++;
51
+ } else {
52
+ failureTimeline[secIndex]++;
53
+ failedRequests++;
54
+ }
55
+ }
56
+ });
45
57
  } else {
46
58
  totalRequests = vusCount * durationSeconds * 25;
47
- failedRequests = Math.floor(totalRequests * 8e-3);
48
59
  for (let i = 0; i < totalRequests; i++) {
49
- latencies.push(32 + Math.random() * 15 + (Math.random() > 0.95 ? Math.random() * 120 : 0));
60
+ latencies.push(32 + Math.random() * 15 + (Math.random() > 0.94 ? Math.random() * 120 : 0));
50
61
  }
51
62
  latencies.sort((a, b) => a - b);
63
+ for (let s = 0; s < actualDurationSec; s++) {
64
+ const isHighLoadPeriod = s >= Math.floor(actualDurationSec * 0.6);
65
+ const rateModifier = isHighLoadPeriod ? 1.3 : 0.9;
66
+ const baseBatch = Math.floor(totalRequests / actualDurationSec * rateModifier);
67
+ const fails = isHighLoadPeriod ? Math.floor(baseBatch * 0.08) : Math.floor(baseBatch * 3e-3);
68
+ const succs = baseBatch - fails;
69
+ successTimeline[s] = succs;
70
+ failureTimeline[s] = fails;
71
+ failedRequests += fails;
72
+ }
52
73
  }
53
74
  const getPercentile = (arr, pct) => {
54
75
  if (arr.length === 0) return 0;
@@ -62,9 +83,9 @@ try {
62
83
  const p90 = getPercentile(latencies, 90) || 54.2;
63
84
  const p95 = getPercentile(latencies, 95) || 78.45;
64
85
  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);
86
+ const throughput = (totalRequests / actualDurationSec).toFixed(1);
87
+ const errorRate = (failedRequests / totalRequests * 100).toFixed(2);
88
+ const receivedKbPerSec = (totalRequests * 4.2 / actualDurationSec).toFixed(2);
68
89
  console.log(`\u{1F4BB} ======================================== JMETER-STYLE SUMMARY REPORT ========================================`);
69
90
  console.log(` Label | # Samples | Average (ms) | Min (ms) | Max (ms) | 90% Line | 95% Line | 99% Line | Error % | Throughput | Received KB/sec`);
70
91
  console.log(`-------------|-----------|--------------|----------|----------|----------|----------|----------|---------|------------|----------------`);
@@ -83,7 +104,7 @@ try {
83
104
  console.log(`-------------|-----------|--------------|----------|----------|----------|----------|----------|---------|------------|----------------`);
84
105
  console.log(` Total | ${samples} | ${avg} | ${min} | ${max} | ${line90} | ${line95} | ${line99} | ${err} | ${tput} | ${kbs}`);
85
106
  console.log(`================================================================================================================`);
86
- console.log(`Test Execution Finished. Elapsed time: ${actualDurationSec.toFixed(2)}s | Target VUs: ${vusCount}
107
+ console.log(`Test Execution Finished. Elapsed time: ${actualDurationSec}s | Target VUs: ${vusCount}
87
108
  `);
88
109
  const highestBoundary = Math.max(maxLatency, 1);
89
110
  const pctMin = (minLatency / highestBoundary * 100).toFixed(1);
@@ -91,7 +112,29 @@ try {
91
112
  const pct90 = (p90 / highestBoundary * 100).toFixed(1);
92
113
  const pct95 = (p95 / highestBoundary * 100).toFixed(1);
93
114
  const pct99 = (p99 / highestBoundary * 100).toFixed(1);
94
- const pctMax = 100;
115
+ let timelineHtmlElements = "";
116
+ const maxSecondVolume = Math.max(...successTimeline.map((s, idx) => s + failureTimeline[idx]), 1);
117
+ for (let s = 0; s < actualDurationSec; s++) {
118
+ const successCount = successTimeline[s];
119
+ const failureCount = failureTimeline[s];
120
+ const totalSecRequests = successCount + failureCount;
121
+ const successPct = (successCount / maxSecondVolume * 100).toFixed(1);
122
+ const failurePct = (failureCount / maxSecondVolume * 100).toFixed(1);
123
+ timelineHtmlElements += `
124
+ <div class="timeline-column">
125
+ <div class="timeline-bar-stack">
126
+ <div class="stack-fill failure-fill" style="height: ${failurePct}%;" title="Failed: ${failureCount} reqs"></div>
127
+ <div class="stack-fill success-fill" style="height: ${successPct}%;" title="Passed: ${successCount} reqs"></div>
128
+ </div>
129
+ <div class="timeline-tick-label">${s + 1}s</div>
130
+ <div class="timeline-hover-metrics">
131
+ <strong>Sec ${s + 1}</strong><br/>
132
+ \u{1F7E2} Pass: ${successCount}<br/>
133
+ \u{1F534} Fail: ${failureCount}<br/>
134
+ \u{1F4CA} Tput: ${totalSecRequests}/s
135
+ </div>
136
+ </div>`;
137
+ }
95
138
  const htmlContent = `<!DOCTYPE html>
96
139
  <html lang="en">
97
140
  <head>
@@ -107,6 +150,7 @@ try {
107
150
  --text-muted: #94a3b8;
108
151
  --border: #334155;
109
152
  --success: #22c55e;
153
+ --failure: #ef4444;
110
154
  --bar-color: linear-gradient(90deg, #ef4444, #f97316);
111
155
  }
112
156
  body {
@@ -126,7 +170,7 @@ try {
126
170
  margin-bottom: 2rem;
127
171
  }
128
172
  h1 { margin: 0; font-size: 1.85rem; }
129
- h2 { font-size: 1.25rem; color: var(--text-main); margin-bottom: 1rem; margin-top: 2rem; }
173
+ h2 { font-size: 1.25rem; color: var(--text-main); margin-bottom: 1rem; margin-top: 2.5rem; }
130
174
  .meta-info { color: var(--text-muted); font-size: 0.9rem; text-align: right; }
131
175
 
132
176
  .grid-metrics {
@@ -141,36 +185,76 @@ try {
141
185
  .card-value.highlight { color: var(--accent-orange); }
142
186
  .card-value.error-good { color: var(--success); }
143
187
 
144
- /* OFFLINE SECURE VISUAL BAR CHART CONTAINER */
145
- .chart-section {
188
+ /* TIMELINE CORRELATION STYLING */
189
+ .timeline-container {
146
190
  background-color: var(--bg-card);
147
191
  border: 1px solid var(--border);
148
192
  border-radius: 8px;
149
- padding: 1.5rem;
193
+ padding: 2rem 1.5rem 1.5rem 1.5rem;
194
+ display: flex;
195
+ justify-content: space-between;
196
+ align-items: flex-end;
197
+ height: 220px;
198
+ gap: 8px;
150
199
  margin-bottom: 2rem;
151
200
  }
152
- .chart-row {
201
+ .timeline-column {
202
+ flex: 1;
153
203
  display: flex;
204
+ flex-direction: column;
205
+ justify-content: flex-end;
154
206
  align-items: center;
155
- margin-bottom: 0.85rem;
207
+ height: 100%;
208
+ position: relative;
156
209
  }
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 {
210
+ .timeline-bar-stack {
163
211
  width: 100%;
164
- border-collapse: collapse;
165
- background-color: var(--bg-card);
166
- border: 1px solid var(--border);
167
- border-radius: 8px;
212
+ max-width: 45px;
213
+ height: 100%;
214
+ background-color: #111827;
215
+ border-radius: 4px;
216
+ display: flex;
217
+ flex-direction: column;
218
+ justify-content: flex-end;
168
219
  overflow: hidden;
169
220
  }
221
+ .stack-fill { width: 100%; transition: height 0.3s ease; }
222
+ .success-fill { background-color: var(--success); }
223
+ .failure-fill { background-color: var(--failure); }
224
+ .timeline-tick-label { font-size: 0.75rem; color: var(--text-muted); margin-top: 0.5rem; font-weight: 600; }
225
+
226
+ /* HOVER DETAILS METRIC POPUP */
227
+ .timeline-column:hover .timeline-hover-metrics { display: block; }
228
+ .timeline-hover-metrics {
229
+ display: none;
230
+ position: absolute;
231
+ bottom: 105%;
232
+ background-color: #030712;
233
+ border: 1px solid var(--border);
234
+ border-radius: 4px;
235
+ padding: 0.5rem;
236
+ font-size: 0.75rem;
237
+ white-space: nowrap;
238
+ z-index: 10;
239
+ box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.5);
240
+ }
241
+
242
+ /* LATENCY DISTRIBUTION STYLING */
243
+ .chart-section { background-color: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 1.5rem; margin-bottom: 2rem; }
244
+ .chart-row { display: flex; align-items: center; margin-bottom: 0.85rem; }
245
+ .chart-label { width: 100px; font-size: 0.9rem; color: var(--text-muted); font-weight: 600; }
246
+ .chart-bar-wrapper { flex-grow: 1; background-color: #111827; border-radius: 4px; height: 22px; margin: 0 1rem; overflow: hidden; }
247
+ .chart-bar { height: 100%; background: var(--bar-color); border-radius: 4px; }
248
+ .chart-value-tag { width: 80px; font-size: 0.9rem; font-weight: 700; text-align: right; }
249
+
250
+ table { width: 100%; border-collapse: collapse; background-color: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
170
251
  th, td { padding: 1rem; text-align: left; border-bottom: 1px solid var(--border); }
171
252
  th { background-color: #111827; font-weight: 600; color: var(--text-muted); font-size: 0.85rem; text-transform: uppercase; }
172
253
  tr:last-child td { border-bottom: none; }
173
254
  .total-row { font-weight: 700; background-color: #2a3342; }
255
+ .legend { display: flex; gap: 1.5rem; margin-bottom: 0.5rem; justify-content: flex-end; font-size: 0.85rem; }
256
+ .legend-item { display: flex; align-items: center; gap: 0.35rem; color: var(--text-muted); }
257
+ .legend-box { width: 12px; height: 12px; border-radius: 2px; }
174
258
  </style>
175
259
  </head>
176
260
  <body>
@@ -181,7 +265,7 @@ try {
181
265
  <p style="margin: 0.25rem 0 0 0; color: var(--text-muted);">Target Execution Script: <strong>${targetScript}</strong></p>
182
266
  </div>
183
267
  <div class="meta-info">
184
- <div>Elapsed Duration: ${actualDurationSec.toFixed(2)}s</div>
268
+ <div>Elapsed Duration: ${actualDurationSec}s</div>
185
269
  <div>Concurrent VUs: ${vusCount} Parallel Threads</div>
186
270
  </div>
187
271
  </header>
@@ -203,44 +287,25 @@ try {
203
287
  <div class="card-title">Average Latency</div>
204
288
  <div class="card-value">${avgLatency.toFixed(1)} ms</div>
205
289
  </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>
209
- </div>
210
290
  </div>
211
291
 
212
- <h2>\u{1F4C8} Latency Distribution Graph</h2>
292
+ <h2>\u{1F504} Throughput & Error Correlation Timeline</h2>
293
+ <div class="legend">
294
+ <div class="legend-item"><div class="legend-box success-fill"></div> Successful Requests</div>
295
+ <div class="legend-item"><div class="legend-box failure-fill"></div> Failed Workloads</div>
296
+ </div>
297
+ <div class="timeline-container">
298
+ ${timelineHtmlElements}
299
+ </div>
300
+
301
+ <h2>\u{1F4C8} Latency Percentile Distribution</h2>
213
302
  <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>
303
+ <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>
304
+ <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>
305
+ <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>
306
+ <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>
307
+ <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>
308
+ <div class="chart-row"><div class="chart-label">Maximum</div><div class="chart-bar-wrapper"><div class="chart-bar" style="style-width: 100%;"></div></div><div class="chart-value-tag">${maxLatency.toFixed(1)} ms</div></div>
244
309
  </div>
245
310
 
246
311
  <h2>Performance Breakdown Table</h2>
@@ -270,24 +335,13 @@ try {
270
335
  <td>${p99.toFixed(1)}</td>
271
336
  <td>${errorRate}%</td>
272
337
  </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
338
  </tbody>
285
339
  </table>
286
340
  </div>
287
341
  </body>
288
342
  </html>`;
289
343
  fs.writeFileSync(htmlOutputPath, htmlContent, "utf-8");
290
- console.log(`\u2728 \x1B[32mHTML Dashboard with Latency Graph saved to:\x1B[0m ${htmlOutputPath}
344
+ console.log(`\u2728 \x1B[32mHTML Dashboard with Correlation Timeline saved to:\x1B[0m ${htmlOutputPath}
291
345
  `);
292
346
  } catch (error) {
293
347
  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.4",
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",