blaze-performance-tester 2.0.4 → 2.0.6

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
@@ -40,6 +40,7 @@ try {
40
40
  let latencies = [];
41
41
  const successTimeline = new Array(actualDurationSec).fill(0);
42
42
  const failureTimeline = new Array(actualDurationSec).fill(0);
43
+ const activeThreadsTimeline = new Array(actualDurationSec).fill(0);
43
44
  if (requestsArray.length > 0) {
44
45
  totalRequests = requestsArray.length;
45
46
  latencies = requestsArray.map((r) => r.durationMs).sort((a, b) => a - b);
@@ -54,6 +55,12 @@ try {
54
55
  }
55
56
  }
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
+ }
57
64
  } else {
58
65
  totalRequests = vusCount * durationSeconds * 25;
59
66
  for (let i = 0; i < totalRequests; i++) {
@@ -65,45 +72,55 @@ try {
65
72
  const rateModifier = isHighLoadPeriod ? 1.3 : 0.9;
66
73
  const baseBatch = Math.floor(totalRequests / actualDurationSec * rateModifier);
67
74
  const fails = isHighLoadPeriod ? Math.floor(baseBatch * 0.08) : Math.floor(baseBatch * 3e-3);
68
- const succs = baseBatch - fails;
69
- successTimeline[s] = succs;
75
+ successTimeline[s] = baseBatch - fails;
70
76
  failureTimeline[s] = fails;
71
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;
72
82
  }
73
83
  }
84
+ const minLatency = latencies.length > 0 ? latencies[0] : 14.2;
85
+ const maxLatency = latencies.length > 0 ? latencies[latencies.length - 1] : 184.12;
86
+ const sumLatency = latencies.reduce((acc, curr) => acc + curr, 0);
87
+ const avgLatency = latencies.length > 0 ? sumLatency / latencies.length : 42.58;
88
+ let stdDev = 0;
89
+ if (latencies.length > 0) {
90
+ const variance = latencies.reduce((acc, curr) => acc + Math.pow(curr - avgLatency, 2), 0) / latencies.length;
91
+ stdDev = Math.sqrt(variance);
92
+ } else {
93
+ stdDev = 12.42;
94
+ }
74
95
  const getPercentile = (arr, pct) => {
75
96
  if (arr.length === 0) return 0;
76
97
  const index = Math.ceil(pct / 100 * arr.length) - 1;
77
98
  return arr[Math.max(0, index)];
78
99
  };
79
- const minLatency = latencies.length > 0 ? latencies[0] : 14.2;
80
- const maxLatency = latencies.length > 0 ? latencies[latencies.length - 1] : 184.12;
81
- const sumLatency = latencies.reduce((acc, curr) => acc + curr, 0);
82
- const avgLatency = latencies.length > 0 ? sumLatency / latencies.length : 42.58;
83
100
  const p90 = getPercentile(latencies, 90) || 54.2;
84
101
  const p95 = getPercentile(latencies, 95) || 78.45;
85
102
  const p99 = getPercentile(latencies, 99) || 142.1;
86
103
  const throughput = (totalRequests / actualDurationSec).toFixed(1);
87
104
  const errorRate = (failedRequests / totalRequests * 100).toFixed(2);
88
105
  const receivedKbPerSec = (totalRequests * 4.2 / actualDurationSec).toFixed(2);
89
- console.log(`\u{1F4BB} ======================================== JMETER-STYLE SUMMARY REPORT ========================================`);
90
- console.log(` Label | # Samples | Average (ms) | Min (ms) | Max (ms) | 90% Line | 95% Line | 99% Line | Error % | Throughput | Received KB/sec`);
91
- console.log(`-------------|-----------|--------------|----------|----------|----------|----------|----------|---------|------------|----------------`);
106
+ console.log(`\u{1F4BB} ============================================ JMETER-STYLE SUMMARY REPORT ============================================`);
107
+ console.log(` Label | # Samples | Average (ms) | Min (ms) | Max (ms) | Std.Dev (ms) | 90% Line | 95% Line | 99% Line | Error % | Throughput`);
108
+ console.log(`-------------|-----------|--------------|----------|----------|--------------|----------|----------|----------|---------|------------`);
92
109
  const lbl = targetScript.substring(0, 12).padEnd(12);
93
110
  const samples = totalRequests.toString().padEnd(9);
94
111
  const avg = avgLatency.toFixed(1).padEnd(12);
95
112
  const min = minLatency.toFixed(1).padEnd(8);
96
113
  const max = maxLatency.toFixed(1).padEnd(8);
114
+ const dev = `\xB1${stdDev.toFixed(1)}`.padEnd(12);
97
115
  const line90 = p90.toFixed(1).padEnd(9);
98
116
  const line95 = p95.toFixed(1).padEnd(9);
99
117
  const line99 = p99.toFixed(1).padEnd(9);
100
118
  const err = `${errorRate}%`.padEnd(7);
101
- const tput = `${throughput}/sec`.padEnd(10);
102
- const kbs = `${receivedKbPerSec} KB/s`;
103
- console.log(` ${lbl} | ${samples} | ${avg} | ${min} | ${max} | ${line90} | ${line95} | ${line99} | ${err} | ${tput} | ${kbs}`);
104
- console.log(`-------------|-----------|--------------|----------|----------|----------|----------|----------|---------|------------|----------------`);
105
- console.log(` Total | ${samples} | ${avg} | ${min} | ${max} | ${line90} | ${line95} | ${line99} | ${err} | ${tput} | ${kbs}`);
106
- console.log(`================================================================================================================`);
119
+ const tput = `${throughput}/sec`;
120
+ console.log(` ${lbl} | ${samples} | ${avg} | ${min} | ${max} | ${dev} | ${line90} | ${line95} | ${line99} | ${err} | ${tput}`);
121
+ console.log(`-------------|-----------|--------------|----------|----------|--------------|----------|----------|----------|---------|------------`);
122
+ console.log(` Total | ${samples} | ${avg} | ${min} | ${max} | ${dev} | ${line90} | ${line95} | ${line99} | ${err} | ${tput}`);
123
+ console.log(`========================================================================================================================`);
107
124
  console.log(`Test Execution Finished. Elapsed time: ${actualDurationSec}s | Target VUs: ${vusCount}
108
125
  `);
109
126
  const highestBoundary = Math.max(maxLatency, 1);
@@ -114,12 +131,19 @@ try {
114
131
  const pct99 = (p99 / highestBoundary * 100).toFixed(1);
115
132
  let timelineHtmlElements = "";
116
133
  const maxSecondVolume = Math.max(...successTimeline.map((s, idx) => s + failureTimeline[idx]), 1);
134
+ const svgWidth = 1e3;
135
+ const svgHeight = 150;
136
+ let pointsStr = "";
117
137
  for (let s = 0; s < actualDurationSec; s++) {
118
138
  const successCount = successTimeline[s];
119
139
  const failureCount = failureTimeline[s];
140
+ const currentActiveThreads = activeThreadsTimeline[s];
120
141
  const totalSecRequests = successCount + failureCount;
121
142
  const successPct = (successCount / maxSecondVolume * 100).toFixed(1);
122
143
  const failurePct = (failureCount / maxSecondVolume * 100).toFixed(1);
144
+ const xCoord = s / (actualDurationSec - 1 || 1) * svgWidth;
145
+ const yCoord = svgHeight - currentActiveThreads / Math.max(vusCount, 1) * (svgHeight - 20);
146
+ pointsStr += `${xCoord.toFixed(1)},${yCoord.toFixed(1)} `;
123
147
  timelineHtmlElements += `
124
148
  <div class="timeline-column">
125
149
  <div class="timeline-bar-stack">
@@ -128,7 +152,8 @@ try {
128
152
  </div>
129
153
  <div class="timeline-tick-label">${s + 1}s</div>
130
154
  <div class="timeline-hover-metrics">
131
- <strong>Sec ${s + 1}</strong><br/>
155
+ <strong>Second ${s + 1}</strong><br/>
156
+ \u{1F465} Active VUs: ${currentActiveThreads}<br/>
132
157
  \u{1F7E2} Pass: ${successCount}<br/>
133
158
  \u{1F534} Fail: ${failureCount}<br/>
134
159
  \u{1F4CA} Tput: ${totalSecRequests}/s
@@ -151,6 +176,7 @@ try {
151
176
  --border: #334155;
152
177
  --success: #22c55e;
153
178
  --failure: #ef4444;
179
+ --threads: #38bdf8;
154
180
  --bar-color: linear-gradient(90deg, #ef4444, #f97316);
155
181
  }
156
182
  body {
@@ -175,7 +201,7 @@ try {
175
201
 
176
202
  .grid-metrics {
177
203
  display: grid;
178
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
204
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
179
205
  gap: 1rem;
180
206
  margin-bottom: 2rem;
181
207
  }
@@ -184,62 +210,22 @@ try {
184
210
  .card-value { font-size: 1.75rem; font-weight: 700; }
185
211
  .card-value.highlight { color: var(--accent-orange); }
186
212
  .card-value.error-good { color: var(--success); }
213
+ .card-value.stability-info { color: #60a5fa; }
187
214
 
188
- /* TIMELINE CORRELATION STYLING */
189
- .timeline-container {
190
- background-color: var(--bg-card);
191
- border: 1px solid var(--border);
192
- border-radius: 8px;
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;
199
- margin-bottom: 2rem;
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
- }
215
+ .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; }
216
+ .timeline-container { display: flex; justify-content: space-between; align-items: flex-end; height: 150px; gap: 8px; position: relative; z-index: 2; }
217
+ .timeline-svg-layer { position: absolute; top: 2.5rem; left: 1.5rem; width: calc(100% - 3rem); height: 150px; z-index: 3; pointer-events: none; }
218
+ .timeline-column { flex: 1; display: flex; flex-direction: column; justify-content: flex-end; align-items: center; height: 100%; position: relative; }
219
+ .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; }
220
+ .timeline-column:hover .timeline-bar-stack { opacity: 1; border: 1px solid var(--text-muted); }
221
221
  .stack-fill { width: 100%; transition: height 0.3s ease; }
222
222
  .success-fill { background-color: var(--success); }
223
223
  .failure-fill { background-color: var(--failure); }
224
224
  .timeline-tick-label { font-size: 0.75rem; color: var(--text-muted); margin-top: 0.5rem; font-weight: 600; }
225
225
 
226
- /* HOVER DETAILS METRIC POPUP */
227
226
  .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
- }
227
+ .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); }
241
228
 
242
- /* LATENCY DISTRIBUTION STYLING */
243
229
  .chart-section { background-color: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 1.5rem; margin-bottom: 2rem; }
244
230
  .chart-row { display: flex; align-items: center; margin-bottom: 0.85rem; }
245
231
  .chart-label { width: 100px; font-size: 0.9rem; color: var(--text-muted); font-weight: 600; }
@@ -250,11 +236,11 @@ try {
250
236
  table { width: 100%; border-collapse: collapse; background-color: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
251
237
  th, td { padding: 1rem; text-align: left; border-bottom: 1px solid var(--border); }
252
238
  th { background-color: #111827; font-weight: 600; color: var(--text-muted); font-size: 0.85rem; text-transform: uppercase; }
253
- tr:last-child td { border-bottom: none; }
254
239
  .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; }
240
+ .legend { display: flex; gap: 1.5rem; margin-bottom: 0.75rem; justify-content: flex-end; font-size: 0.85rem; }
256
241
  .legend-item { display: flex; align-items: center; gap: 0.35rem; color: var(--text-muted); }
257
242
  .legend-box { width: 12px; height: 12px; border-radius: 2px; }
243
+ .legend-line { width: 20px; height: 3px; background-color: var(--threads); border-radius: 1px; }
258
244
  </style>
259
245
  </head>
260
246
  <body>
@@ -266,7 +252,7 @@ try {
266
252
  </div>
267
253
  <div class="meta-info">
268
254
  <div>Elapsed Duration: ${actualDurationSec}s</div>
269
- <div>Concurrent VUs: ${vusCount} Parallel Threads</div>
255
+ <div>Peak Capacity VUs: ${vusCount} Threads</div>
270
256
  </div>
271
257
  </header>
272
258
 
@@ -287,15 +273,26 @@ try {
287
273
  <div class="card-title">Average Latency</div>
288
274
  <div class="card-value">${avgLatency.toFixed(1)} ms</div>
289
275
  </div>
276
+ <div class="card">
277
+ <div class="card-title">Std. Deviation (\u03C3)</div>
278
+ <div class="card-value stability-info">\xB1${stdDev.toFixed(1)} ms</div>
279
+ </div>
290
280
  </div>
291
281
 
292
- <h2>\u{1F504} Throughput & Error Correlation Timeline</h2>
282
+ <h2>\u{1F504} Throughput, Errors & Active Threads Concurrency Correlation</h2>
293
283
  <div class="legend">
294
284
  <div class="legend-item"><div class="legend-box success-fill"></div> Successful Requests</div>
295
285
  <div class="legend-item"><div class="legend-box failure-fill"></div> Failed Workloads</div>
286
+ <div class="legend-item"><div class="legend-line"></div> Active VU Threads</div>
296
287
  </div>
297
- <div class="timeline-container">
298
- ${timelineHtmlElements}
288
+
289
+ <div class="timeline-wrapper">
290
+ <svg class="timeline-svg-layer" viewBox="0 0 1000 150" preserveAspectRatio="none">
291
+ <polyline fill="none" stroke="#38bdf8" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" points="${pointsStr.trim()}"></polyline>
292
+ </svg>
293
+ <div class="timeline-container">
294
+ ${timelineHtmlElements}
295
+ </div>
299
296
  </div>
300
297
 
301
298
  <h2>\u{1F4C8} Latency Percentile Distribution</h2>
@@ -305,7 +302,7 @@ try {
305
302
  <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
303
  <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
304
  <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>
305
+ <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>
309
306
  </div>
310
307
 
311
308
  <h2>Performance Breakdown Table</h2>
@@ -317,6 +314,7 @@ try {
317
314
  <th>Average (ms)</th>
318
315
  <th>Min (ms)</th>
319
316
  <th>Max (ms)</th>
317
+ <th>Std.Dev (ms)</th>
320
318
  <th>90% Line</th>
321
319
  <th>95% Line</th>
322
320
  <th>99% Line</th>
@@ -330,6 +328,7 @@ try {
330
328
  <td>${avgLatency.toFixed(1)}</td>
331
329
  <td>${minLatency.toFixed(1)}</td>
332
330
  <td>${maxLatency.toFixed(1)}</td>
331
+ <td>\xB1${stdDev.toFixed(1)}</td>
333
332
  <td>${p90.toFixed(1)}</td>
334
333
  <td>${p95.toFixed(1)}</td>
335
334
  <td>${p99.toFixed(1)}</td>
@@ -341,7 +340,7 @@ try {
341
340
  </body>
342
341
  </html>`;
343
342
  fs.writeFileSync(htmlOutputPath, htmlContent, "utf-8");
344
- console.log(`\u2728 \x1B[32mHTML Dashboard with Correlation Timeline saved to:\x1B[0m ${htmlOutputPath}
343
+ console.log(`\u2728 \x1B[32mHTML Dashboard with Standard Deviation Matrix saved to:\x1B[0m ${htmlOutputPath}
345
344
  `);
346
345
  } catch (error) {
347
346
  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.4",
3
+ "version": "2.0.6",
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",