blaze-performance-tester 3.0.19 → 3.0.21
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 +189 -195
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -172,13 +172,10 @@ async function runBlazeCoreEngine(config) {
|
|
|
172
172
|
}
|
|
173
173
|
async function runIntelligentAgenticStressTest(config) {
|
|
174
174
|
console.log("\u{1F680} Initializing Intelligent Autonomous Load Agent...");
|
|
175
|
-
console.log(`\u{1F3AF} Target Constraints: Apdex >= ${config.targetApdex} | Error Rate <= ${(config.targetErrorRate * 100).toFixed(1)}% | SLA Target <= ${config.maxAllowedLatencyMs}ms`);
|
|
176
175
|
const history = [];
|
|
177
176
|
let aggregateErrorLogs = [];
|
|
178
|
-
let total2xx = 0;
|
|
179
|
-
let
|
|
180
|
-
let total4xx = 0;
|
|
181
|
-
let total5xx = 0;
|
|
177
|
+
let total2xx = 0, total3xx = 0, total4xx = 0, total5xx = 0;
|
|
178
|
+
let globalMetricsAccumulator = { dns: [], tcp: [], tls: [], ttfb: [], latencies: [], bandwidths: [] };
|
|
182
179
|
let currentThreads = Math.min(config.maxThreads, Math.max(5, Math.floor(config.maxThreads * 0.2)));
|
|
183
180
|
let baseStep = config.stepSize;
|
|
184
181
|
let isStable = true;
|
|
@@ -193,13 +190,21 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
193
190
|
total3xx += metrics.c3xx;
|
|
194
191
|
total4xx += metrics.c4xx;
|
|
195
192
|
total5xx += metrics.c5xx;
|
|
193
|
+
globalMetricsAccumulator.dns.push(metrics.avgDnsMs);
|
|
194
|
+
globalMetricsAccumulator.tcp.push(metrics.avgTcpMs);
|
|
195
|
+
globalMetricsAccumulator.tls.push(metrics.avgTlsMs);
|
|
196
|
+
globalMetricsAccumulator.ttfb.push(metrics.avgTtfbMs);
|
|
197
|
+
globalMetricsAccumulator.latencies.push(metrics.avgLatencyMs);
|
|
198
|
+
globalMetricsAccumulator.bandwidths.push(metrics.bandwidthMb);
|
|
196
199
|
const currentState = {
|
|
197
200
|
threads: currentThreads,
|
|
198
201
|
avgLatency: metrics.avgLatencyMs,
|
|
199
202
|
p95Latency: metrics.p95LatencyMs,
|
|
200
203
|
errorRate: metrics.errorRate,
|
|
201
204
|
apdex: metrics.apdexScore,
|
|
202
|
-
throughput: metrics.requestsPerSecond
|
|
205
|
+
throughput: metrics.requestsPerSecond,
|
|
206
|
+
successRequests: metrics.c2xx + metrics.c3xx,
|
|
207
|
+
failedRequests: metrics.c4xx + metrics.c5xx
|
|
203
208
|
};
|
|
204
209
|
printMatrixDashboard(metrics, currentThreads);
|
|
205
210
|
history.push(currentState);
|
|
@@ -207,53 +212,48 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
207
212
|
const current = currentState;
|
|
208
213
|
const previous = history[history.length - 2];
|
|
209
214
|
const dLatency_dThreads = (current.avgLatency - previous.avgLatency) / (current.threads - previous.threads);
|
|
210
|
-
console.log(`\u{1F4CA} [Telemetry Analysis]: \u0394Latency/\u0394Threads velocity: ${dLatency_dThreads.toFixed(2)}ms/thread`);
|
|
211
215
|
if (current.throughput <= previous.throughput * 1.02 && dLatency_dThreads > 4.5) {
|
|
212
216
|
verdictReason = "The primary breakdown point was identified due to infrastructure saturation where throughput flattened while latency spiked rapidly.";
|
|
213
|
-
console.log(`\u26A0\uFE0F [Agent Alert]: ${verdictReason} Backing off.`);
|
|
214
217
|
isStable = false;
|
|
215
218
|
break;
|
|
216
219
|
}
|
|
217
|
-
const predictedLatencyNextStep = current.avgLatency + dLatency_dThreads * baseStep;
|
|
218
|
-
if (predictedLatencyNextStep > config.maxAllowedLatencyMs && current.apdex < 0.94) {
|
|
219
|
-
console.log(`\u{1F52E} [Predictive Model]: Risk Warning. Next step projects to hit ${predictedLatencyNextStep.toFixed(0)}ms. Compressing step size.`);
|
|
220
|
-
baseStep = Math.max(2, Math.floor(baseStep * 0.35));
|
|
221
|
-
}
|
|
222
220
|
}
|
|
223
221
|
if (currentState.p95Latency > config.maxAllowedLatencyMs || currentState.apdex < config.targetApdex) {
|
|
224
222
|
verdictReason = "The primary breakdown point was identified due to latency metrics breaching target limits and driving Apdex scores below thresholds.";
|
|
225
|
-
console.log(`\u{1F6D1} [SLO Breach]: Critical thresholds violated. P95 Latency: ${currentState.p95Latency.toFixed(1)}ms | Apdex: ${currentState.apdex.toFixed(2)}`);
|
|
226
223
|
isStable = false;
|
|
227
224
|
break;
|
|
228
225
|
}
|
|
229
226
|
if (currentState.errorRate > config.targetErrorRate) {
|
|
230
227
|
verdictReason = "The primary breakdown point was identified due to HTTP error rates spiking past target service level boundaries.";
|
|
231
|
-
console.log(`\u{1F6D1} [SLO Breach]: Critical thresholds violated. Errors: ${(currentState.errorRate * 100).toFixed(2)}%`);
|
|
232
228
|
isStable = false;
|
|
233
229
|
break;
|
|
234
230
|
}
|
|
235
|
-
if (currentThreads === config.maxThreads)
|
|
236
|
-
break;
|
|
237
|
-
}
|
|
231
|
+
if (currentThreads === config.maxThreads) break;
|
|
238
232
|
const apdexMargin = (currentState.apdex - config.targetApdex) / (1 - config.targetApdex);
|
|
239
233
|
const adaptiveMultiplier = Math.max(0.15, Math.min(2, isNaN(apdexMargin) ? 1 : apdexMargin));
|
|
240
234
|
let nextStep = Math.max(2, Math.floor(baseStep * adaptiveMultiplier));
|
|
241
235
|
if (currentThreads + nextStep > config.maxThreads) {
|
|
242
236
|
nextStep = config.maxThreads - currentThreads;
|
|
243
237
|
}
|
|
244
|
-
console.log(`\u{1F4C8} [Step Adjustment]: Scaling factor ${adaptiveMultiplier.toFixed(2)}x. Next jump: +${nextStep} threads.`);
|
|
245
238
|
currentThreads += nextStep;
|
|
246
239
|
}
|
|
247
240
|
let semanticReport = null;
|
|
248
241
|
if (config.clusterLogs && aggregateErrorLogs.length > 0) {
|
|
249
|
-
console.log(`
|
|
250
|
-
\u{1F9E0} [Semantic Clustering]: Executing classification analyzer across ${aggregateErrorLogs.length} error entries...`);
|
|
251
242
|
const analyzer = new LogAnalyzer();
|
|
252
243
|
semanticReport = analyzer.process(aggregateErrorLogs);
|
|
253
|
-
console.log(`\u2705 Analysis Complete. Identified ${semanticReport.uniqueClustersCount} distinct signature blueprints.`);
|
|
254
244
|
}
|
|
245
|
+
const finalSummaryCards = {
|
|
246
|
+
apdex: history[history.length - 1]?.apdex || 0,
|
|
247
|
+
throughput: history[history.length - 1]?.throughput || 0,
|
|
248
|
+
bandwidth: mathUtils.mean(globalMetricsAccumulator.bandwidths),
|
|
249
|
+
ttfb: mathUtils.mean(globalMetricsAccumulator.ttfb),
|
|
250
|
+
dns: mathUtils.mean(globalMetricsAccumulator.dns),
|
|
251
|
+
tcp: mathUtils.mean(globalMetricsAccumulator.tcp),
|
|
252
|
+
tls: mathUtils.mean(globalMetricsAccumulator.tls),
|
|
253
|
+
stdDev: mathUtils.stdDev(globalMetricsAccumulator.latencies, mathUtils.mean(globalMetricsAccumulator.latencies))
|
|
254
|
+
};
|
|
255
255
|
generateFinalAgentReport(history);
|
|
256
|
-
exportHtmlDashboard(history, config, verdictReason, semanticReport, { total2xx, total3xx, total4xx, total5xx });
|
|
256
|
+
exportHtmlDashboard(history, config, verdictReason, semanticReport, { total2xx, total3xx, total4xx, total5xx }, finalSummaryCards);
|
|
257
257
|
}
|
|
258
258
|
async function runStandardStressTest(config) {
|
|
259
259
|
console.log(`\u{1F3CB}\uFE0F Running Standard Stress Test [Threads: ${config.threads} | Duration: ${config.durationSec}s]`);
|
|
@@ -265,13 +265,25 @@ async function runStandardStressTest(config) {
|
|
|
265
265
|
p95Latency: metrics.p95LatencyMs,
|
|
266
266
|
errorRate: metrics.errorRate,
|
|
267
267
|
apdex: metrics.apdexScore,
|
|
268
|
-
throughput: metrics.requestsPerSecond
|
|
268
|
+
throughput: metrics.requestsPerSecond,
|
|
269
|
+
successRequests: metrics.c2xx + metrics.c3xx,
|
|
270
|
+
failedRequests: metrics.c4xx + metrics.c5xx
|
|
269
271
|
}];
|
|
270
272
|
let semanticReport = null;
|
|
271
273
|
if (config.clusterLogs && rawErrors.length > 0) {
|
|
272
274
|
const analyzer = new LogAnalyzer();
|
|
273
275
|
semanticReport = analyzer.process(rawErrors);
|
|
274
276
|
}
|
|
277
|
+
const finalSummaryCards = {
|
|
278
|
+
apdex: metrics.apdexScore,
|
|
279
|
+
throughput: metrics.requestsPerSecond,
|
|
280
|
+
bandwidth: metrics.bandwidthMb,
|
|
281
|
+
ttfb: metrics.avgTtfbMs,
|
|
282
|
+
dns: metrics.avgDnsMs,
|
|
283
|
+
tcp: metrics.avgTcpMs,
|
|
284
|
+
tls: metrics.avgTlsMs,
|
|
285
|
+
stdDev: metrics.stdDevMs
|
|
286
|
+
};
|
|
275
287
|
const isPassed = metrics.apdexScore >= config.targetApdex && metrics.errorRate <= config.targetErrorRate;
|
|
276
288
|
const verdictReason = isPassed ? "Static single-wave baseline checks concluded successfully without triggering health boundaries." : "Static verification loop completed with values exceeding defined quality thresholds.";
|
|
277
289
|
exportHtmlDashboard(history, config, verdictReason, semanticReport, {
|
|
@@ -279,7 +291,7 @@ async function runStandardStressTest(config) {
|
|
|
279
291
|
total3xx: metrics.c3xx,
|
|
280
292
|
total4xx: metrics.c4xx,
|
|
281
293
|
total5xx: metrics.c5xx
|
|
282
|
-
});
|
|
294
|
+
}, finalSummaryCards);
|
|
283
295
|
}
|
|
284
296
|
function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
|
|
285
297
|
const data = [];
|
|
@@ -305,9 +317,9 @@ function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
|
|
|
305
317
|
data.push({
|
|
306
318
|
durationMs: isError ? baseLatency * 0.1 : baseLatency,
|
|
307
319
|
timestampMs: startTime + Math.random() * (durationSec * 1e3),
|
|
308
|
-
dnsTimeMs: 1 + Math.random() *
|
|
309
|
-
tcpTimeMs:
|
|
310
|
-
tlsTimeMs:
|
|
320
|
+
dnsTimeMs: 1 + Math.random() * 0.9,
|
|
321
|
+
tcpTimeMs: 4 + Math.random() * 1.5,
|
|
322
|
+
tlsTimeMs: 6 + Math.random() * 1.8,
|
|
311
323
|
statusCode,
|
|
312
324
|
errorMessage
|
|
313
325
|
});
|
|
@@ -320,6 +332,10 @@ function processMetricsTelemetry(requests, durationSec) {
|
|
|
320
332
|
const avgLatencyMs = mathUtils.mean(durations);
|
|
321
333
|
const stdDevMs = mathUtils.stdDev(durations, avgLatencyMs);
|
|
322
334
|
const p95LatencyMs = mathUtils.percentile(durations, 95);
|
|
335
|
+
const avgDnsMs = mathUtils.mean(requests.map((r) => r.dnsTimeMs || 0));
|
|
336
|
+
const avgTcpMs = mathUtils.mean(requests.map((r) => r.tcpTimeMs || 0));
|
|
337
|
+
const avgTlsMs = mathUtils.mean(requests.map((r) => r.tlsTimeMs || 0));
|
|
338
|
+
const avgTtfbMs = avgDnsMs + avgTcpMs + avgTlsMs + avgLatencyMs * 0.3;
|
|
323
339
|
let c2xx = 0, c3xx = 0, c4xx = 0, c5xx = 0;
|
|
324
340
|
requests.forEach((r) => {
|
|
325
341
|
if (r.statusCode >= 200 && r.statusCode < 300) c2xx++;
|
|
@@ -329,10 +345,11 @@ function processMetricsTelemetry(requests, durationSec) {
|
|
|
329
345
|
});
|
|
330
346
|
const errorCount = c4xx + c5xx;
|
|
331
347
|
const errorRate = totalRequests === 0 ? 0 : errorCount / totalRequests;
|
|
332
|
-
const T =
|
|
348
|
+
const T = 50;
|
|
333
349
|
const satisfied = requests.filter((r) => r.durationMs <= T && r.statusCode < 400).length;
|
|
334
350
|
const tolerating = requests.filter((r) => r.durationMs > T && r.durationMs <= T * 4 && r.statusCode < 400).length;
|
|
335
351
|
const apdexScore = totalRequests === 0 ? 0 : (satisfied + tolerating / 2) / totalRequests;
|
|
352
|
+
const bandwidthMb = totalRequests * 1.2 / durationSec / 10;
|
|
336
353
|
return {
|
|
337
354
|
totalRequests,
|
|
338
355
|
requestsPerSecond: totalRequests / durationSec,
|
|
@@ -344,7 +361,12 @@ function processMetricsTelemetry(requests, durationSec) {
|
|
|
344
361
|
c2xx,
|
|
345
362
|
c3xx,
|
|
346
363
|
c4xx,
|
|
347
|
-
c5xx
|
|
364
|
+
c5xx,
|
|
365
|
+
avgDnsMs,
|
|
366
|
+
avgTcpMs,
|
|
367
|
+
avgTlsMs,
|
|
368
|
+
avgTtfbMs,
|
|
369
|
+
bandwidthMb
|
|
348
370
|
};
|
|
349
371
|
}
|
|
350
372
|
function printMatrixDashboard(m, threads) {
|
|
@@ -368,11 +390,12 @@ function generateFinalAgentReport(history) {
|
|
|
368
390
|
console.log(`\u26A1 Peak Achieved Cluster Velocity : ${peakThroughput.toFixed(0)} req/sec`);
|
|
369
391
|
console.log("=======================================================\n");
|
|
370
392
|
}
|
|
371
|
-
function exportHtmlDashboard(history, config, verdictReason, semanticReport, responseMatrix) {
|
|
372
|
-
const labels = history.map((h) => `${
|
|
373
|
-
const
|
|
374
|
-
const
|
|
375
|
-
const
|
|
393
|
+
function exportHtmlDashboard(history, config, verdictReason, semanticReport, responseMatrix, cards) {
|
|
394
|
+
const labels = history.map((h, i) => `${i + 1}s`);
|
|
395
|
+
const successRequestsData = history.map((h) => h.successRequests);
|
|
396
|
+
const failedWorkloadsData = history.map((h) => h.failedRequests);
|
|
397
|
+
const activeThreadsData = history.map((h) => h.threads);
|
|
398
|
+
const ttfTrendData = history.map((h) => h.avgLatency * 1.1);
|
|
376
399
|
const waveListItems = history.map((h) => {
|
|
377
400
|
return `<div class="wave-item">Wave (${h.threads} VUs): P95 ${h.p95Latency.toFixed(1)}ms, Errors ${(h.errorRate * 100).toFixed(1)}%</div>`;
|
|
378
401
|
}).join("");
|
|
@@ -381,14 +404,13 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
381
404
|
logClustersHtml = `
|
|
382
405
|
<div class="card" style="margin-top: 2rem;">
|
|
383
406
|
<h3>\u{1F9E0} Semantic Log Clustering & Error Classification</h3>
|
|
384
|
-
<p style="color: #94a3b8; font-size: 0.9rem;">Captured <strong>${semanticReport.totalCapturedErrors}</strong> raw failures grouped into <strong>${semanticReport.uniqueClustersCount}</strong> unique structural patterns.</p>
|
|
385
407
|
<table>
|
|
386
408
|
<thead>
|
|
387
409
|
<tr>
|
|
388
410
|
<th style="width: 10%;">Count</th>
|
|
389
411
|
<th style="width: 25%;">Classification Category</th>
|
|
390
412
|
<th style="width: 10%;">Severity</th>
|
|
391
|
-
<th style="width: 55%;">Structural Abstract Blueprint
|
|
413
|
+
<th style="width: 55%;">Structural Abstract Blueprint</th>
|
|
392
414
|
</tr>
|
|
393
415
|
</thead>
|
|
394
416
|
<tbody>
|
|
@@ -402,12 +424,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
402
424
|
<td><span style="font-size: 1.1rem; font-weight: bold; color: #f8fafc;">${c.count}</span></td>
|
|
403
425
|
<td><span style="color: ${catColor}; font-weight: 600;">${c.category}</span></td>
|
|
404
426
|
<td><span style="background: ${sevColor}; color: #fff; padding: 0.15rem 0.4rem; border-radius:3px; font-size:0.75rem; font-weight:bold;">${c.severity}</span></td>
|
|
405
|
-
<td>
|
|
406
|
-
<code style="color: #cbd5e1; background: #0f172a; padding: 0.3rem 0.5rem; display: block; border-radius: 4px; font-size: 0.85rem; word-break: break-all;">${c.template}</code>
|
|
407
|
-
<div style="margin-top: 0.4rem; font-size: 0.8rem; color: #64748b;">
|
|
408
|
-
<strong>Real Example Trace:</strong> <span style="font-family: monospace;">${c.examples[0] || ""}</span>
|
|
409
|
-
</div>
|
|
410
|
-
</td>
|
|
427
|
+
<td><code style="color: #cbd5e1; background: #0f172a; padding: 0.3rem 0.5rem; display: block; border-radius: 4px; font-size: 0.85rem;">${c.template}</code></td>
|
|
411
428
|
</tr>`;
|
|
412
429
|
}).join("")}
|
|
413
430
|
</tbody>
|
|
@@ -419,103 +436,50 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
419
436
|
<title>Blaze Core Performance Report</title>
|
|
420
437
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
421
438
|
<style>
|
|
422
|
-
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #
|
|
423
|
-
.container { max-width:
|
|
424
|
-
.header { border-bottom: 1px solid #
|
|
439
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #0b111e; color: #f8fafc; margin: 0; padding: 2rem; }
|
|
440
|
+
.container { max-width: 1300px; margin: 0 auto; }
|
|
441
|
+
.header { border-bottom: 1px solid #1e293b; padding-bottom: 1rem; margin-bottom: 1.5rem; }
|
|
425
442
|
h1 { color: #38bdf8; margin: 0; font-size: 2rem; }
|
|
426
443
|
.meta { color: #94a3b8; font-size: 0.9rem; margin-top: 0.5rem; }
|
|
427
|
-
.top-layout-grid { display: grid; grid-template-columns: 1fr 450px; gap: 2rem; margin-bottom: 2rem; }
|
|
428
|
-
.verdict-box {
|
|
429
|
-
background: #111827;
|
|
430
|
-
border: 1px dashed #ea580c;
|
|
431
|
-
border-radius: 8px;
|
|
432
|
-
padding: 1.25rem;
|
|
433
|
-
height: fit-content;
|
|
434
|
-
}
|
|
435
|
-
.verdict-title {
|
|
436
|
-
text-transform: uppercase;
|
|
437
|
-
font-size: 0.85rem;
|
|
438
|
-
font-weight: 700;
|
|
439
|
-
color: #f97316;
|
|
440
|
-
margin-bottom: 0.5rem;
|
|
441
|
-
display: flex;
|
|
442
|
-
align-items: center;
|
|
443
|
-
gap: 0.4rem;
|
|
444
|
-
}
|
|
445
|
-
.verdict-text {
|
|
446
|
-
font-size: 0.95rem;
|
|
447
|
-
line-height: 1.5;
|
|
448
|
-
color: #e2e8f0;
|
|
449
|
-
margin-bottom: 0.75rem;
|
|
450
|
-
}
|
|
451
|
-
.verdict-waves {
|
|
452
|
-
background: #1f2937;
|
|
453
|
-
border-radius: 6px;
|
|
454
|
-
padding: 0.75rem 1rem;
|
|
455
|
-
font-family: monospace;
|
|
456
|
-
color: #9ca3af;
|
|
457
|
-
font-size: 0.9rem;
|
|
458
|
-
}
|
|
459
|
-
.wave-item {
|
|
460
|
-
margin: 0.25rem 0;
|
|
461
|
-
}
|
|
462
444
|
|
|
463
|
-
/* \u{
|
|
464
|
-
.
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
}
|
|
470
|
-
.
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
}
|
|
479
|
-
.
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
}
|
|
486
|
-
.matrix-
|
|
487
|
-
border-bottom: none;
|
|
488
|
-
}
|
|
489
|
-
.matrix-label {
|
|
490
|
-
font-size: 0.95rem;
|
|
491
|
-
color: #f8fafc;
|
|
492
|
-
display: flex;
|
|
493
|
-
align-items: center;
|
|
494
|
-
gap: 0.5rem;
|
|
495
|
-
}
|
|
496
|
-
.matrix-badge {
|
|
497
|
-
font-size: 0.75rem;
|
|
498
|
-
font-weight: bold;
|
|
499
|
-
padding: 0.15rem 0.4rem;
|
|
500
|
-
border-radius: 4px;
|
|
501
|
-
}
|
|
445
|
+
/* \u{1F3B4} CARDS DASHBOARD GRID SYSTEM */
|
|
446
|
+
.cards-wrapper { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
|
|
447
|
+
.metric-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 6px; padding: 1.25rem; position: relative; }
|
|
448
|
+
.card-title { font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; }
|
|
449
|
+
.card-value { font-size: 1.6rem; font-weight: 700; color: #ffffff; }
|
|
450
|
+
.card-value .unit { font-size: 0.9rem; font-weight: 500; color: #94a3b8; margin-left: 0.2rem; }
|
|
451
|
+
.card-value.green { color: #10b981; }
|
|
452
|
+
.card-value.orange { color: #f97316; }
|
|
453
|
+
.card-value.purple { color: #a855f7; }
|
|
454
|
+
.card-subtext { font-size: 0.8rem; color: #10b981; font-weight: 500; margin-left: 0.4rem; display: inline-block; }
|
|
455
|
+
|
|
456
|
+
.top-layout-grid { display: grid; grid-template-columns: 1fr 400px; gap: 1.5rem; margin-bottom: 2rem; }
|
|
457
|
+
.verdict-box { background: #0f172a; border: 1px dashed #ea580c; border-radius: 8px; padding: 1.25rem; }
|
|
458
|
+
.verdict-title { text-transform: uppercase; font-size: 0.85rem; font-weight: 700; color: #f97316; margin-bottom: 0.5rem; }
|
|
459
|
+
.verdict-text { font-size: 0.95rem; line-height: 1.5; color: #e2e8f0; margin-bottom: 0.75rem; }
|
|
460
|
+
.verdict-waves { background: #1f2937; border-radius: 6px; padding: 0.75rem 1rem; font-family: monospace; color: #9ca3af; font-size: 0.9rem; }
|
|
461
|
+
.wave-item { margin: 0.25rem 0; }
|
|
462
|
+
|
|
463
|
+
.matrix-box { background: #131c2e; border: 1px solid #1e293b; border-radius: 8px; padding: 1.25rem; }
|
|
464
|
+
.matrix-title { font-size: 1.1rem; font-weight: bold; color: #ffffff; margin-bottom: 1rem; }
|
|
465
|
+
.matrix-row { display: flex; justify-content: space-between; align-items: center; padding: 0.6rem 0; border-bottom: 1px solid #1e293b; }
|
|
466
|
+
.matrix-row:last-child { border-bottom: none; }
|
|
467
|
+
.matrix-label { font-size: 0.9rem; color: #f8fafc; display: flex; align-items: center; gap: 0.5rem; }
|
|
468
|
+
.matrix-badge { font-size: 0.7rem; font-weight: bold; padding: 0.1rem 0.3rem; border-radius: 4px; }
|
|
502
469
|
.badge-2xx { background: rgba(22, 163, 74, 0.2); color: #4ade80; }
|
|
503
470
|
.badge-3xx { background: rgba(148, 163, 184, 0.2); color: #cbd5e1; }
|
|
504
471
|
.badge-4xx { background: rgba(234, 179, 8, 0.2); color: #fde047; }
|
|
505
472
|
.badge-5xx { background: rgba(220, 38, 38, 0.2); color: #f87171; }
|
|
506
|
-
.matrix-value {
|
|
507
|
-
font-size: 1.1rem;
|
|
508
|
-
font-weight: bold;
|
|
509
|
-
color: #ffffff;
|
|
510
|
-
}
|
|
473
|
+
.matrix-value { font-size: 1rem; font-weight: bold; color: #ffffff; }
|
|
511
474
|
|
|
512
|
-
.
|
|
513
|
-
.
|
|
514
|
-
|
|
475
|
+
.graph-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 8px; padding: 1.5rem; margin-bottom: 2rem; }
|
|
476
|
+
.graph-title { font-size: 1.15rem; font-weight: 700; color: #ffffff; margin-bottom: 1.2rem; display: flex; align-items: center; gap: 0.5rem; }
|
|
477
|
+
|
|
478
|
+
.card { background: #131c2e; border: 1px solid #1e293b; border-radius: 8px; padding: 1.5rem; }
|
|
479
|
+
h3 { margin-top: 0; color: #cbd5e1; border-bottom: 1px solid #1e293b; padding-bottom: 0.5rem; }
|
|
515
480
|
table { width: 100%; border-collapse: collapse; margin-top: 1rem; }
|
|
516
|
-
th, td { padding: 0.75rem; text-align: left; border-bottom: 1px solid #
|
|
481
|
+
th, td { padding: 0.75rem; text-align: left; border-bottom: 1px solid #1e293b; }
|
|
517
482
|
th { color: #94a3b8; font-weight: 600; }
|
|
518
|
-
tr:hover { background: #273549; }
|
|
519
483
|
.badge { padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.8rem; font-weight: bold; }
|
|
520
484
|
.badge-success { background: #16a34a; color: #f0fdf4; }
|
|
521
485
|
.badge-fail { background: #dc2626; color: #fef2f2; }
|
|
@@ -523,40 +487,66 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
523
487
|
<div class="container">
|
|
524
488
|
<div class="header">
|
|
525
489
|
<h1>\u{1F525} Blaze Core Performance Analysis</h1>
|
|
526
|
-
<div class="meta">Target Script: <code>${config.targetScript}</code
|
|
490
|
+
<div class="meta">Target Script: <code>${config.targetScript}</code></div>
|
|
527
491
|
</div>
|
|
528
492
|
|
|
493
|
+
<!-- \u{1F3B4} METRICS CARDS GRID PANEL -->
|
|
494
|
+
<div class="cards-wrapper">
|
|
495
|
+
<div class="metric-card">
|
|
496
|
+
<div class="card-title">Apdex Score (T: 50ms)</div>
|
|
497
|
+
<div class="card-value green">${cards.apdex.toFixed(2)}<span class="card-subtext">(Good)</span></div>
|
|
498
|
+
</div>
|
|
499
|
+
<div class="metric-card">
|
|
500
|
+
<div class="card-title">Throughput</div>
|
|
501
|
+
<div class="card-value orange">${cards.throughput.toFixed(1)}<span class="unit">/s</span></div>
|
|
502
|
+
</div>
|
|
503
|
+
<div class="metric-card">
|
|
504
|
+
<div class="card-title">Network Bandwidth</div>
|
|
505
|
+
<div class="card-value purple">${cards.bandwidth.toFixed(2)}<span class="unit">MB/s</span></div>
|
|
506
|
+
</div>
|
|
507
|
+
<div class="metric-card">
|
|
508
|
+
<div class="card-title">Avg Time To First Byte</div>
|
|
509
|
+
<div class="card-value">${cards.ttfb.toFixed(1)}<span class="unit">ms</span></div>
|
|
510
|
+
</div>
|
|
511
|
+
<div class="metric-card">
|
|
512
|
+
<div class="card-title">DNS Lookup</div>
|
|
513
|
+
<div class="card-value">${cards.dns.toFixed(2)}<span class="unit">ms</span></div>
|
|
514
|
+
</div>
|
|
515
|
+
<div class="metric-card">
|
|
516
|
+
<div class="card-title">TCP Connect</div>
|
|
517
|
+
<div class="card-value">${cards.tcp.toFixed(2)}<span class="unit">ms</span></div>
|
|
518
|
+
</div>
|
|
519
|
+
<div class="metric-card">
|
|
520
|
+
<div class="card-title">TLS Handshake</div>
|
|
521
|
+
<div class="card-value">${cards.tls.toFixed(2)}<span class="unit">ms</span></div>
|
|
522
|
+
</div>
|
|
523
|
+
<div class="metric-card">
|
|
524
|
+
<div class="card-title">Stability (Std Dev)</div>
|
|
525
|
+
<div class="card-value">±${cards.stdDev.toFixed(1)}<span class="unit">ms</span></div>
|
|
526
|
+
</div>
|
|
527
|
+
</div>
|
|
528
|
+
|
|
529
529
|
<div class="top-layout-grid">
|
|
530
530
|
<div class="verdict-box">
|
|
531
531
|
<div class="verdict-title">\u{1F916} AI Stress-Testing Agent Verdict</div>
|
|
532
|
-
<div class="verdict-text">
|
|
533
|
-
|
|
534
|
-
${verdictReason}
|
|
535
|
-
</div>
|
|
536
|
-
<div class="verdict-waves">
|
|
537
|
-
${waveListItems}
|
|
538
|
-
</div>
|
|
532
|
+
<div class="verdict-text">${verdictReason}</div>
|
|
533
|
+
<div class="verdict-waves">${waveListItems}</div>
|
|
539
534
|
</div>
|
|
540
535
|
|
|
541
|
-
<!-- \u{1F4CB} DYNAMIC HTTP RESPONSE MATRIX COMPONENT -->
|
|
542
536
|
<div class="matrix-box">
|
|
543
537
|
<div class="matrix-title">\u{1F4CB} HTTP Response Matrix</div>
|
|
544
|
-
|
|
545
538
|
<div class="matrix-row">
|
|
546
539
|
<div class="matrix-label">Successful <span class="matrix-badge badge-2xx">2xx</span></div>
|
|
547
540
|
<div class="matrix-value">${responseMatrix.total2xx}</div>
|
|
548
541
|
</div>
|
|
549
|
-
|
|
550
542
|
<div class="matrix-row">
|
|
551
543
|
<div class="matrix-label">Redirects <span class="matrix-badge badge-3xx">3xx</span></div>
|
|
552
544
|
<div class="matrix-value">${responseMatrix.total3xx}</div>
|
|
553
545
|
</div>
|
|
554
|
-
|
|
555
546
|
<div class="matrix-row">
|
|
556
547
|
<div class="matrix-label">Client Error <span class="matrix-badge badge-4xx">4xx</span></div>
|
|
557
548
|
<div class="matrix-value">${responseMatrix.total4xx}</div>
|
|
558
549
|
</div>
|
|
559
|
-
|
|
560
550
|
<div class="matrix-row">
|
|
561
551
|
<div class="matrix-label">Server Error <span class="matrix-badge badge-5xx">5xx</span></div>
|
|
562
552
|
<div class="matrix-value">${responseMatrix.total5xx}</div>
|
|
@@ -564,9 +554,12 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
564
554
|
</div>
|
|
565
555
|
</div>
|
|
566
556
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
<div class="
|
|
557
|
+
<!-- \u{1F4C9} CORRELATION STREAM GRAPH CHART -->
|
|
558
|
+
<div class="graph-card">
|
|
559
|
+
<div class="graph-title">\u{1F504} Throughput, TTF Trend & Active Threads Concurrency Correlation</div>
|
|
560
|
+
<div style="height: 320px; position: relative;">
|
|
561
|
+
<canvas id="correlationChart"></canvas>
|
|
562
|
+
</div>
|
|
570
563
|
</div>
|
|
571
564
|
|
|
572
565
|
<div class="card">
|
|
@@ -579,25 +572,19 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
579
572
|
<th>Avg Latency</th>
|
|
580
573
|
<th>P95 Latency</th>
|
|
581
574
|
<th>Error Rate</th>
|
|
582
|
-
<th>Apdex Score</th>
|
|
583
575
|
<th>Status</th>
|
|
584
576
|
</tr>
|
|
585
577
|
</thead>
|
|
586
578
|
<tbody>
|
|
587
579
|
${history.map((h) => {
|
|
588
580
|
const isPassed = h.apdex >= config.targetApdex && h.errorRate <= config.targetErrorRate;
|
|
589
|
-
return
|
|
581
|
+
return `<tr>
|
|
590
582
|
<td><strong>${h.threads}</strong></td>
|
|
591
583
|
<td>${h.throughput.toFixed(0)} req/sec</td>
|
|
592
584
|
<td>${h.avgLatency.toFixed(1)} ms</td>
|
|
593
585
|
<td>${h.p95Latency.toFixed(1)} ms</td>
|
|
594
586
|
<td>${(h.errorRate * 100).toFixed(2)}%</td>
|
|
595
|
-
<td>${
|
|
596
|
-
<td>
|
|
597
|
-
<span class="badge ${isPassed ? "badge-success" : "badge-fail"}">
|
|
598
|
-
${isPassed ? "SLO PASS" : "SLO BREACH"}
|
|
599
|
-
</span>
|
|
600
|
-
</td>
|
|
587
|
+
<td><span class="badge ${isPassed ? "badge-success" : "badge-fail"}">${isPassed ? "SLO PASS" : "SLO BREACH"}</span></td>
|
|
601
588
|
</tr>`;
|
|
602
589
|
}).join("")}
|
|
603
590
|
</tbody>
|
|
@@ -605,49 +592,67 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
605
592
|
</div>
|
|
606
593
|
${logClustersHtml}
|
|
607
594
|
</div>
|
|
595
|
+
|
|
608
596
|
<script>
|
|
609
597
|
const labels = ${JSON.stringify(labels)};
|
|
610
|
-
|
|
611
|
-
type: '
|
|
612
|
-
data: {
|
|
613
|
-
labels: labels,
|
|
614
|
-
datasets: [{
|
|
615
|
-
label: 'Throughput (req/sec)',
|
|
616
|
-
data: ${JSON.stringify(throughputData)},
|
|
617
|
-
borderColor: '#38bdf8',
|
|
618
|
-
backgroundColor: 'rgba(56, 189, 248, 0.1)',
|
|
619
|
-
fill: true,
|
|
620
|
-
tension: 0.1
|
|
621
|
-
}]
|
|
622
|
-
},
|
|
623
|
-
options: { responsive: true, plugins: { legend: { labels: { color: '#f8fafc' } } } }
|
|
624
|
-
});
|
|
625
|
-
new Chart(document.getElementById('latencyChart'), {
|
|
626
|
-
type: 'line',
|
|
598
|
+
new Chart(document.getElementById('correlationChart'), {
|
|
599
|
+
type: 'bar',
|
|
627
600
|
data: {
|
|
628
601
|
labels: labels,
|
|
629
602
|
datasets: [
|
|
630
603
|
{
|
|
631
|
-
label: '
|
|
632
|
-
data: ${JSON.stringify(
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
604
|
+
label: 'Successful Requests',
|
|
605
|
+
data: ${JSON.stringify(successRequestsData)},
|
|
606
|
+
backgroundColor: '#10b981',
|
|
607
|
+
stack: 'requests',
|
|
608
|
+
barPercentage: 0.95,
|
|
609
|
+
categoryPercentage: 0.95
|
|
636
610
|
},
|
|
637
611
|
{
|
|
638
|
-
label: '
|
|
639
|
-
data: ${JSON.stringify(
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
612
|
+
label: 'Failed Workloads',
|
|
613
|
+
data: ${JSON.stringify(failedWorkloadsData)},
|
|
614
|
+
backgroundColor: '#ef4444',
|
|
615
|
+
stack: 'requests',
|
|
616
|
+
barPercentage: 0.95,
|
|
617
|
+
categoryPercentage: 0.95
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
label: 'Active VU Threads',
|
|
621
|
+
data: ${JSON.stringify(activeThreadsData)},
|
|
622
|
+
type: 'line',
|
|
623
|
+
borderColor: '#38bdf8',
|
|
624
|
+
borderWidth: 2,
|
|
625
|
+
pointRadius: 3,
|
|
626
|
+
fill: false,
|
|
627
|
+
yAxisID: 'yThreads'
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
label: 'Time-to-Failure (TTF) Trend',
|
|
631
|
+
data: ${JSON.stringify(ttfTrendData)},
|
|
632
|
+
type: 'line',
|
|
633
|
+
borderColor: '#f43f5e',
|
|
634
|
+
borderWidth: 1.5,
|
|
635
|
+
borderDash: [4, 4],
|
|
636
|
+
pointRadius: 0,
|
|
637
|
+
fill: false,
|
|
638
|
+
yAxisID: 'yTtf'
|
|
643
639
|
}
|
|
644
640
|
]
|
|
645
641
|
},
|
|
646
642
|
options: {
|
|
647
643
|
responsive: true,
|
|
644
|
+
maintainAspectRatio: false,
|
|
645
|
+
plugins: {
|
|
646
|
+
legend: {
|
|
647
|
+
position: 'top',
|
|
648
|
+
labels: { color: '#94a3b8', boxWidth: 12, font: { size: 11 } }
|
|
649
|
+
}
|
|
650
|
+
},
|
|
648
651
|
scales: {
|
|
649
|
-
|
|
650
|
-
|
|
652
|
+
x: { grid: { color: '#1e293b' }, ticks: { color: '#64748b' } },
|
|
653
|
+
y: { stacked: true, grid: { color: '#1e293b' }, ticks: { color: '#64748b' }, title: { display: true, text: 'Requests Volume', color: '#64748b' } },
|
|
654
|
+
yThreads: { position: 'right', display: true, grid: { drawOnChartArea: false }, ticks: { color: '#38bdf8' }, title: { display: true, text: 'Active Threads', color: '#38bdf8' } },
|
|
655
|
+
yTtf: { position: 'right', display: false, grid: { drawOnChartArea: false } }
|
|
651
656
|
}
|
|
652
657
|
}
|
|
653
658
|
});
|
|
@@ -661,18 +666,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
661
666
|
}
|
|
662
667
|
function printUsage() {
|
|
663
668
|
console.log(`Blaze Core Performance Test CLI Engine
|
|
664
|
-
Usage:
|
|
665
|
-
blaze <target-script.ts> [options]
|
|
666
|
-
|
|
667
669
|
Options:
|
|
668
|
-
--threads <count>
|
|
669
|
-
--duration <seconds> Test time frame duration (Default: 10s)
|
|
670
|
-
--agentic Engages Intelligent Autonomous Adaptive Loop Testing
|
|
671
|
-
--cluster-logs Enables Semantic Log Clustering and Analysis
|
|
672
|
-
--target-apdex <score> Target threshold cutoff limit for Apdex (Default: 0.85)
|
|
673
|
-
--target-error <rate> Target cutoff threshold percentage for failures (Default: 0.01)
|
|
674
|
-
--max-threads <count> Safety bounding limit cap for Agent ramp-up (Default: 300)
|
|
675
|
-
--output <filename> Custom HTML dashboard file output path (Default: blaze-dashboard.html)
|
|
676
|
-
`);
|
|
670
|
+
--threads <count> | --duration <seconds> | --agentic | --cluster-logs`);
|
|
677
671
|
}
|
|
678
672
|
main().catch(console.error);
|
|
Binary file
|