blaze-performance-tester 2.0.2 → 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,24 +104,54 @@ 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
  `);
109
+ const highestBoundary = Math.max(maxLatency, 1);
110
+ const pctMin = (minLatency / highestBoundary * 100).toFixed(1);
111
+ const pctAvg = (avgLatency / highestBoundary * 100).toFixed(1);
112
+ const pct90 = (p90 / highestBoundary * 100).toFixed(1);
113
+ const pct95 = (p95 / highestBoundary * 100).toFixed(1);
114
+ const pct99 = (p99 / highestBoundary * 100).toFixed(1);
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
+ }
88
138
  const htmlContent = `<!DOCTYPE html>
89
139
  <html lang="en">
90
140
  <head>
91
141
  <meta charset="UTF-8">
92
142
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
93
- <title>Blaze Performance Report</title>
143
+ <title>Blaze Performance Dashboard</title>
94
144
  <style>
95
145
  :root {
96
146
  --bg-main: #0f172a;
97
147
  --bg-card: #1e293b;
98
- --accent-fire: #ef4444;
99
148
  --accent-orange: #f97316;
100
149
  --text-main: #f8fafc;
101
150
  --text-muted: #94a3b8;
102
151
  --border: #334155;
103
152
  --success: #22c55e;
153
+ --failure: #ef4444;
154
+ --bar-color: linear-gradient(90deg, #ef4444, #f97316);
104
155
  }
105
156
  body {
106
157
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
@@ -109,10 +160,7 @@ try {
109
160
  margin: 0;
110
161
  padding: 2rem;
111
162
  }
112
- .container {
113
- max-width: 1200px;
114
- margin: 0 auto;
115
- }
163
+ .container { max-width: 1200px; margin: 0 auto; }
116
164
  header {
117
165
  display: flex;
118
166
  justify-content: space-between;
@@ -121,39 +169,92 @@ try {
121
169
  padding-bottom: 1.5rem;
122
170
  margin-bottom: 2rem;
123
171
  }
124
- h1 { margin: 0; font-size: 1.85rem; display: flex; align-items: center; gap: 0.5rem; }
172
+ h1 { margin: 0; font-size: 1.85rem; }
173
+ h2 { font-size: 1.25rem; color: var(--text-main); margin-bottom: 1rem; margin-top: 2.5rem; }
125
174
  .meta-info { color: var(--text-muted); font-size: 0.9rem; text-align: right; }
126
175
 
127
176
  .grid-metrics {
128
177
  display: grid;
129
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
178
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
130
179
  gap: 1rem;
131
- margin-bottom: 2.5rem;
132
- }
133
- .card {
134
- background-color: var(--bg-card);
135
- border: 1px solid var(--border);
136
- border-radius: 8px;
137
- padding: 1.25rem;
180
+ margin-bottom: 2rem;
138
181
  }
139
- .card-title { color: var(--text-muted); font-size: 0.85rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; }
140
- .card-value { font-size: 1.75rem; font-weight: 700; color: var(--text-main); }
182
+ .card { background-color: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 1.25rem; }
183
+ .card-title { color: var(--text-muted); font-size: 0.85rem; font-weight: 600; text-transform: uppercase; margin-bottom: 0.5rem; }
184
+ .card-value { font-size: 1.75rem; font-weight: 700; }
141
185
  .card-value.highlight { color: var(--accent-orange); }
142
186
  .card-value.error-good { color: var(--success); }
143
187
 
144
- table {
145
- width: 100%;
146
- border-collapse: collapse;
188
+ /* TIMELINE CORRELATION STYLING */
189
+ .timeline-container {
147
190
  background-color: var(--bg-card);
148
191
  border: 1px solid var(--border);
149
192
  border-radius: 8px;
150
- overflow: hidden;
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;
151
199
  margin-bottom: 2rem;
152
200
  }
201
+ .timeline-column {
202
+ flex: 1;
203
+ display: flex;
204
+ flex-direction: column;
205
+ justify-content: flex-end;
206
+ align-items: center;
207
+ height: 100%;
208
+ position: relative;
209
+ }
210
+ .timeline-bar-stack {
211
+ width: 100%;
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;
219
+ overflow: hidden;
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; }
153
251
  th, td { padding: 1rem; text-align: left; border-bottom: 1px solid var(--border); }
154
252
  th { background-color: #111827; font-weight: 600; color: var(--text-muted); font-size: 0.85rem; text-transform: uppercase; }
155
253
  tr:last-child td { border-bottom: none; }
156
- .total-row { font-weight: 700; background-color: #1e293b; }
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; }
157
258
  </style>
158
259
  </head>
159
260
  <body>
@@ -164,7 +265,7 @@ try {
164
265
  <p style="margin: 0.25rem 0 0 0; color: var(--text-muted);">Target Execution Script: <strong>${targetScript}</strong></p>
165
266
  </div>
166
267
  <div class="meta-info">
167
- <div>Elapsed Duration: ${actualDurationSec.toFixed(2)}s</div>
268
+ <div>Elapsed Duration: ${actualDurationSec}s</div>
168
269
  <div>Concurrent VUs: ${vusCount} Parallel Threads</div>
169
270
  </div>
170
271
  </header>
@@ -186,10 +287,25 @@ try {
186
287
  <div class="card-title">Average Latency</div>
187
288
  <div class="card-value">${avgLatency.toFixed(1)} ms</div>
188
289
  </div>
189
- <div class="card">
190
- <div class="card-title">Bandwidth</div>
191
- <div class="card-value" style="font-size: 1.4rem;">${receivedKbPerSec} KB/s</div>
192
- </div>
290
+ </div>
291
+
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>
302
+ <div class="chart-section">
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>
193
309
  </div>
194
310
 
195
311
  <h2>Performance Breakdown Table</h2>
@@ -219,24 +335,13 @@ try {
219
335
  <td>${p99.toFixed(1)}</td>
220
336
  <td>${errorRate}%</td>
221
337
  </tr>
222
- <tr class="total-row">
223
- <td>Total</td>
224
- <td>${totalRequests}</td>
225
- <td>${avgLatency.toFixed(1)}</td>
226
- <td>${minLatency.toFixed(1)}</td>
227
- <td>${maxLatency.toFixed(1)}</td>
228
- <td>${p90.toFixed(1)}</td>
229
- <td>${p95.toFixed(1)}</td>
230
- <td>${p99.toFixed(1)}</td>
231
- <td>${errorRate}%</td>
232
- </tr>
233
338
  </tbody>
234
339
  </table>
235
340
  </div>
236
341
  </body>
237
342
  </html>`;
238
343
  fs.writeFileSync(htmlOutputPath, htmlContent, "utf-8");
239
- console.log(`\u2728 \x1B[32mHTML Dashboard successfully saved to:\x1B[0m ${htmlOutputPath}
344
+ console.log(`\u2728 \x1B[32mHTML Dashboard with Correlation Timeline saved to:\x1B[0m ${htmlOutputPath}
240
345
  `);
241
346
  } catch (error) {
242
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.2",
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",