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 +170 -78
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
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.
|
|
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 /
|
|
66
|
-
const errorRate = (failedRequests /
|
|
67
|
-
const receivedKbPerSec = (totalRequests * 4.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
|
|
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
|
-
|
|
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:
|
|
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
|
-
/*
|
|
145
|
-
.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
.
|
|
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
|
-
|
|
233
|
+
height: 100%;
|
|
234
|
+
position: relative;
|
|
156
235
|
}
|
|
157
|
-
.
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
border-radius:
|
|
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
|
|
185
|
-
<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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
|
339
|
+
<h2>\u{1F4C8} Latency Percentile Distribution</h2>
|
|
213
340
|
<div class="chart-section">
|
|
214
|
-
<div class="chart-row">
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
|
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
|