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 +72 -73
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
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
|
-
|
|
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}
|
|
90
|
-
console.log(` Label | # Samples | Average (ms) | Min (ms) | Max (ms) | 90% Line | 95% Line | 99% Line | Error % | Throughput
|
|
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
|
|
102
|
-
|
|
103
|
-
console.log(
|
|
104
|
-
console.log(
|
|
105
|
-
console.log(
|
|
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>
|
|
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(
|
|
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
|
-
|
|
189
|
-
.timeline-container {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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.
|
|
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>
|
|
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 &
|
|
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
|
-
|
|
298
|
-
|
|
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="
|
|
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
|
|
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
|