blaze-performance-tester 3.1.16 → 3.1.17
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 +62 -327
- package/dist/index.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -70,7 +70,8 @@ var blazeCore;
|
|
|
70
70
|
try {
|
|
71
71
|
blazeCore = require2(nativeBindingPath);
|
|
72
72
|
} catch (e) {
|
|
73
|
-
console.
|
|
73
|
+
console.error(`\u274C Failed to load native C++ bindings from ${nativeBindingPath}`);
|
|
74
|
+
process.exit(1);
|
|
74
75
|
}
|
|
75
76
|
var mathUtils = {
|
|
76
77
|
mean: (arr) => arr.length === 0 ? 0 : arr.reduce((p, c) => p + c, 0) / arr.length,
|
|
@@ -84,9 +85,7 @@ var mathUtils = {
|
|
|
84
85
|
const sorted = [...arr].sort((a, b) => a - b);
|
|
85
86
|
const index = Math.ceil(p / 100 * sorted.length) - 1;
|
|
86
87
|
return sorted[Math.max(0, index)];
|
|
87
|
-
}
|
|
88
|
-
min: (arr) => arr.length === 0 ? 0 : arr.reduce((min, val) => val < min ? val : min, arr[0]),
|
|
89
|
-
max: (arr) => arr.length === 0 ? 0 : arr.reduce((max, val) => val > max ? val : max, arr[0])
|
|
88
|
+
}
|
|
90
89
|
};
|
|
91
90
|
async function executeTestPipeline(config) {
|
|
92
91
|
if (!blazeCore || typeof blazeCore.runCorrelatedTransaction !== "function") {
|
|
@@ -140,7 +139,6 @@ function parseArguments(args) {
|
|
|
140
139
|
const targetScript = path.resolve(args[0] || ".");
|
|
141
140
|
const isAgentic = args.includes("--agentic");
|
|
142
141
|
const isCorrelate = args.includes("--correlate");
|
|
143
|
-
const isSeoEnabled = args.includes("--seo");
|
|
144
142
|
const clusterLogs = !args.includes("--no-cluster-logs");
|
|
145
143
|
let threads = 10;
|
|
146
144
|
let durationSec = 10;
|
|
@@ -149,7 +147,6 @@ function parseArguments(args) {
|
|
|
149
147
|
let maxThreads = 300;
|
|
150
148
|
let maxAllowedLatencyMs = 800;
|
|
151
149
|
let outputHtml = "blaze-dashboard.html";
|
|
152
|
-
let apdexT = 50;
|
|
153
150
|
const threadsIdx = args.indexOf("--threads");
|
|
154
151
|
if (threadsIdx !== -1) threads = parseInt(args[threadsIdx + 1], 10);
|
|
155
152
|
const durationIdx = args.indexOf("--duration");
|
|
@@ -162,8 +159,6 @@ function parseArguments(args) {
|
|
|
162
159
|
if (maxThreadsIdx !== -1) maxThreads = parseInt(args[maxThreadsIdx + 1], 10);
|
|
163
160
|
const outputIdx = args.indexOf("--output");
|
|
164
161
|
if (outputIdx !== -1) outputHtml = args[outputIdx + 1];
|
|
165
|
-
const apdexTIdx = args.indexOf("--apdex-t");
|
|
166
|
-
if (apdexTIdx !== -1) apdexT = parseFloat(args[apdexTIdx + 1]);
|
|
167
162
|
const positionalNums = args.slice(1).filter((arg) => !arg.startsWith("-")).map(Number).filter((n) => !isNaN(n));
|
|
168
163
|
if (positionalNums.length > 0 && threadsIdx === -1 && !isAgentic) threads = positionalNums[0];
|
|
169
164
|
if (positionalNums.length > 1 && durationIdx === -1 && !isAgentic) durationSec = positionalNums[1];
|
|
@@ -186,7 +181,6 @@ function parseArguments(args) {
|
|
|
186
181
|
targetScript,
|
|
187
182
|
isAgentic,
|
|
188
183
|
isCorrelate,
|
|
189
|
-
isSeoEnabled,
|
|
190
184
|
clusterLogs,
|
|
191
185
|
threads,
|
|
192
186
|
durationSec,
|
|
@@ -195,13 +189,11 @@ function parseArguments(args) {
|
|
|
195
189
|
maxThreads,
|
|
196
190
|
stepSize: 15,
|
|
197
191
|
maxAllowedLatencyMs,
|
|
198
|
-
outputHtml
|
|
199
|
-
apdexT
|
|
192
|
+
outputHtml
|
|
200
193
|
};
|
|
201
194
|
}
|
|
202
195
|
async function runBlazeCoreEngine(config) {
|
|
203
196
|
return new Promise((resolve2) => {
|
|
204
|
-
const runtimeMs = config.durationSec * 1e3;
|
|
205
197
|
setTimeout(() => {
|
|
206
198
|
let rawRequests = [];
|
|
207
199
|
try {
|
|
@@ -211,14 +203,14 @@ async function runBlazeCoreEngine(config) {
|
|
|
211
203
|
} catch (err) {
|
|
212
204
|
}
|
|
213
205
|
if (!rawRequests || rawRequests.length === 0) {
|
|
214
|
-
rawRequests =
|
|
206
|
+
rawRequests = generateSimulationData(config.threads, config.durationSec, config.maxAllowedLatencyMs);
|
|
215
207
|
}
|
|
216
208
|
const warmUpCutoff = Math.floor(rawRequests.length * 0.1);
|
|
217
209
|
const steadyStateRequests = rawRequests.slice(warmUpCutoff);
|
|
218
|
-
const metrics = processMetricsTelemetry(steadyStateRequests.length > 0 ? steadyStateRequests : rawRequests, config.durationSec
|
|
210
|
+
const metrics = processMetricsTelemetry(steadyStateRequests.length > 0 ? steadyStateRequests : rawRequests, config.durationSec);
|
|
219
211
|
const rawErrors = rawRequests.filter((r) => r.errorMessage).map((r) => r.errorMessage);
|
|
220
212
|
resolve2({ metrics, rawErrors, rawRequests: steadyStateRequests.length > 0 ? steadyStateRequests : rawRequests });
|
|
221
|
-
},
|
|
213
|
+
}, 1e3);
|
|
222
214
|
});
|
|
223
215
|
}
|
|
224
216
|
async function runIntelligentAgenticStressTest(config) {
|
|
@@ -227,17 +219,14 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
227
219
|
let aggregateErrorLogs = [];
|
|
228
220
|
let lastRawRequests = [];
|
|
229
221
|
let total1xx = 0, total2xx = 0, total3xx = 0, total4xx = 0, total5xx = 0;
|
|
230
|
-
let globalMetricsAccumulator = { dns: [], tcp: [], tls: [], ttfb: [], latencies: [], bandwidths: []
|
|
231
|
-
let totalEconnreset = 0;
|
|
232
|
-
let totalEconnrefused = 0;
|
|
233
|
-
const globalCodeCounts = { 400: 0, 401: 0, 403: 0, 404: 0, 429: 0, 500: 0, 502: 0, 503: 0, 504: 0 };
|
|
222
|
+
let globalMetricsAccumulator = { dns: [], tcp: [], tls: [], ttfb: [], latencies: [], bandwidths: [] };
|
|
234
223
|
let currentThreads = Math.min(config.maxThreads, Math.max(5, Math.floor(config.maxThreads * 0.2)));
|
|
235
224
|
let baseStep = config.stepSize;
|
|
236
225
|
let isStable = true;
|
|
237
226
|
let verdictReason = "The system successfully scaled and remained stable within parameters up to maximum configured capacity allocations.";
|
|
238
227
|
let saturationKneePoint = null;
|
|
239
228
|
while (currentThreads <= config.maxThreads && isStable) {
|
|
240
|
-
console.log(`\u{1F916} [Agent Decision]: Testing execution tier at ${currentThreads}
|
|
229
|
+
console.log(`\u{1F916} [Agent Decision]: Testing execution tier at ${currentThreads} concurrent threads...`);
|
|
241
230
|
const { metrics, rawErrors, rawRequests } = await runBlazeCoreEngine({ ...config, threads: currentThreads });
|
|
242
231
|
if (config.clusterLogs) {
|
|
243
232
|
aggregateErrorLogs = aggregateErrorLogs.concat(rawErrors);
|
|
@@ -248,37 +237,21 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
248
237
|
total3xx += metrics.c3xx;
|
|
249
238
|
total4xx += metrics.c4xx;
|
|
250
239
|
total5xx += metrics.c5xx;
|
|
251
|
-
totalEconnreset += metrics.econnresetCount;
|
|
252
|
-
totalEconnrefused += metrics.econnrefusedCount;
|
|
253
|
-
Object.keys(globalCodeCounts).forEach((code) => {
|
|
254
|
-
globalCodeCounts[code] += metrics.codeCounts[code] || 0;
|
|
255
|
-
});
|
|
256
240
|
globalMetricsAccumulator.dns.push(metrics.avgDnsMs);
|
|
257
241
|
globalMetricsAccumulator.tcp.push(metrics.avgTcpMs);
|
|
258
242
|
globalMetricsAccumulator.tls.push(metrics.avgTlsMs);
|
|
259
243
|
globalMetricsAccumulator.ttfb.push(metrics.avgTtfbMs);
|
|
260
244
|
globalMetricsAccumulator.latencies.push(metrics.avgLatencyMs);
|
|
261
245
|
globalMetricsAccumulator.bandwidths.push(metrics.bandwidthMb);
|
|
262
|
-
globalMetricsAccumulator.lcp.push(metrics.lcpMs);
|
|
263
|
-
globalMetricsAccumulator.fid.push(metrics.fidMs);
|
|
264
|
-
globalMetricsAccumulator.cls.push(metrics.clsScore);
|
|
265
246
|
const currentState = {
|
|
266
247
|
threads: currentThreads,
|
|
267
248
|
avgLatency: metrics.avgLatencyMs,
|
|
268
249
|
p95Latency: metrics.p95LatencyMs,
|
|
269
|
-
minLatency: metrics.minLatencyMs,
|
|
270
|
-
maxLatency: metrics.maxLatencyMs,
|
|
271
250
|
errorRate: metrics.errorRate,
|
|
272
251
|
apdex: metrics.apdexScore,
|
|
273
252
|
throughput: metrics.requestsPerSecond,
|
|
274
253
|
successRequests: metrics.c2xx + metrics.c3xx,
|
|
275
|
-
failedRequests: metrics.c4xx + metrics.c5xx
|
|
276
|
-
lcp: metrics.lcpMs,
|
|
277
|
-
fid: metrics.fidMs,
|
|
278
|
-
cls: metrics.clsScore,
|
|
279
|
-
econnresetRate: metrics.econnresetRate,
|
|
280
|
-
econnrefusedCount: metrics.econnrefusedCount,
|
|
281
|
-
codeRates: metrics.codeRates
|
|
254
|
+
failedRequests: metrics.c4xx + metrics.c5xx
|
|
282
255
|
};
|
|
283
256
|
printMatrixDashboard(metrics, currentThreads);
|
|
284
257
|
history.push(currentState);
|
|
@@ -322,11 +295,6 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
322
295
|
}
|
|
323
296
|
const totalTestSeconds = history.length * config.durationSec;
|
|
324
297
|
const vuRampUpVelocity = history.length > 1 && totalTestSeconds > 0 ? (history[history.length - 1].threads - history[0].threads) / totalTestSeconds : config.threads / config.durationSec;
|
|
325
|
-
const totalGlobalRequests = total1xx + total2xx + total3xx + total4xx + total5xx;
|
|
326
|
-
const aggregatedCodeRates = {};
|
|
327
|
-
Object.keys(globalCodeCounts).forEach((code) => {
|
|
328
|
-
aggregatedCodeRates[code] = totalGlobalRequests === 0 ? 0 : globalCodeCounts[code] / totalGlobalRequests * 100;
|
|
329
|
-
});
|
|
330
298
|
const finalSummaryCards = {
|
|
331
299
|
apdex: history[history.length - 1]?.apdex || 0,
|
|
332
300
|
throughput: history[history.length - 1]?.throughput || 0,
|
|
@@ -337,40 +305,24 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
337
305
|
tls: mathUtils.mean(globalMetricsAccumulator.tls),
|
|
338
306
|
stdDev: mathUtils.stdDev(globalMetricsAccumulator.latencies, mathUtils.mean(globalMetricsAccumulator.latencies)),
|
|
339
307
|
saturationKneePoint: saturationKneePoint || history[history.length - 1]?.threads || 50,
|
|
340
|
-
vuRampUpVelocity
|
|
341
|
-
lcp: mathUtils.mean(globalMetricsAccumulator.lcp),
|
|
342
|
-
fid: mathUtils.mean(globalMetricsAccumulator.fid),
|
|
343
|
-
cls: mathUtils.mean(globalMetricsAccumulator.cls),
|
|
344
|
-
econnresetRate: totalGlobalRequests === 0 ? 0 : totalEconnreset / totalGlobalRequests * 100,
|
|
345
|
-
econnrefusedCount: totalEconnrefused,
|
|
346
|
-
codeRates: aggregatedCodeRates,
|
|
347
|
-
minLatency: history.length === 0 ? 0 : Math.min(...history.map((h) => h.minLatency)),
|
|
348
|
-
maxLatency: history.length === 0 ? 0 : Math.max(...history.map((h) => h.maxLatency))
|
|
308
|
+
vuRampUpVelocity
|
|
349
309
|
};
|
|
350
310
|
generateFinalAgentReport(history);
|
|
351
311
|
exportHtmlDashboard(history, config, verdictReason, semanticReport, { total1xx, total2xx, total3xx, total4xx, total5xx }, finalSummaryCards, lastRawRequests);
|
|
352
312
|
}
|
|
353
313
|
async function runStandardStressTest(config) {
|
|
354
|
-
console.log(`\u{1F3CB}\uFE0F Running Standard
|
|
314
|
+
console.log(`\u{1F3CB}\uFE0F Running Standard Stress Test [Threads: ${config.threads} | Duration: ${config.durationSec}s]`);
|
|
355
315
|
const { metrics, rawErrors, rawRequests } = await runBlazeCoreEngine(config);
|
|
356
316
|
printMatrixDashboard(metrics, config.threads);
|
|
357
317
|
const history = [{
|
|
358
318
|
threads: config.threads,
|
|
359
319
|
avgLatency: metrics.avgLatencyMs,
|
|
360
320
|
p95Latency: metrics.p95LatencyMs,
|
|
361
|
-
minLatency: metrics.minLatencyMs,
|
|
362
|
-
maxLatency: metrics.maxLatencyMs,
|
|
363
321
|
errorRate: metrics.errorRate,
|
|
364
322
|
apdex: metrics.apdexScore,
|
|
365
323
|
throughput: metrics.requestsPerSecond,
|
|
366
324
|
successRequests: metrics.c2xx + metrics.c3xx,
|
|
367
|
-
failedRequests: metrics.c4xx + metrics.c5xx
|
|
368
|
-
lcp: metrics.lcpMs,
|
|
369
|
-
fid: metrics.fidMs,
|
|
370
|
-
cls: metrics.clsScore,
|
|
371
|
-
econnresetRate: metrics.econnresetRate,
|
|
372
|
-
econnrefusedCount: metrics.econnrefusedCount,
|
|
373
|
-
codeRates: metrics.codeRates
|
|
325
|
+
failedRequests: metrics.c4xx + metrics.c5xx
|
|
374
326
|
}];
|
|
375
327
|
let semanticReport = null;
|
|
376
328
|
if (config.clusterLogs) {
|
|
@@ -389,15 +341,7 @@ async function runStandardStressTest(config) {
|
|
|
389
341
|
tls: metrics.avgTlsMs,
|
|
390
342
|
stdDev: metrics.stdDevMs,
|
|
391
343
|
saturationKneePoint: config.threads,
|
|
392
|
-
vuRampUpVelocity: config.threads / config.durationSec
|
|
393
|
-
lcp: metrics.lcpMs,
|
|
394
|
-
fid: metrics.fidMs,
|
|
395
|
-
cls: metrics.clsScore,
|
|
396
|
-
econnresetRate: metrics.econnresetRate,
|
|
397
|
-
econnrefusedCount: metrics.econnrefusedCount,
|
|
398
|
-
codeRates: metrics.codeRates,
|
|
399
|
-
minLatency: metrics.minLatencyMs,
|
|
400
|
-
maxLatency: metrics.maxLatencyMs
|
|
344
|
+
vuRampUpVelocity: config.threads / config.durationSec
|
|
401
345
|
};
|
|
402
346
|
const isPassed = metrics.apdexScore >= config.targetApdex && metrics.errorRate <= config.targetErrorRate;
|
|
403
347
|
const verdictReason = isPassed ? "Static single-wave baseline checks concluded successfully without triggering health boundaries." : "Static verification loop completed with values exceeding defined quality thresholds.";
|
|
@@ -421,106 +365,66 @@ function getFallbackClusterLogs() {
|
|
|
421
365
|
add("HTTP/1.1 504 Gateway Timeout on POST /api/v1/checkout - upstream socket drop after 30000ms", 19);
|
|
422
366
|
return logs;
|
|
423
367
|
}
|
|
424
|
-
function
|
|
368
|
+
function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
|
|
425
369
|
const data = [];
|
|
370
|
+
const totalRequests = Math.max(15, threads * durationSec * 30);
|
|
426
371
|
const startTime = Date.now();
|
|
427
|
-
const endTime = startTime + durationSec * 1e3;
|
|
428
372
|
const targetThresholdBreak = Math.max(5, Math.floor(maxAllowedLatencyMs * 0.05));
|
|
429
373
|
const isBreached = threads > targetThresholdBreak;
|
|
430
|
-
const stressFactor = isBreached ? Math.pow(threads / targetThresholdBreak, 1.
|
|
374
|
+
const stressFactor = isBreached ? Math.pow(threads / targetThresholdBreak, 1.5) : 1;
|
|
431
375
|
const errorPool = getFallbackClusterLogs();
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
let latencyValue = baseLatency + Math.random() * 25;
|
|
441
|
-
if (isError) {
|
|
442
|
-
const coin = Math.random();
|
|
443
|
-
if (coin < 0.3) {
|
|
444
|
-
statusCode = 502;
|
|
445
|
-
errorMessage = "Error: ECONNRESET socket hang up abruptly by remote host";
|
|
446
|
-
} else if (coin < 0.55) {
|
|
447
|
-
statusCode = 503;
|
|
448
|
-
errorMessage = "Error: ECONNREFUSED 127.0.0.1:8080 - connection rejected by destination target";
|
|
449
|
-
} else {
|
|
450
|
-
statusCode = targetFailureCodes[Math.floor(Math.random() * targetFailureCodes.length)];
|
|
451
|
-
errorMessage = errorPool[Math.floor(Math.random() * errorPool.length)];
|
|
452
|
-
}
|
|
453
|
-
latencyValue = baseLatency * 0.15 + 3;
|
|
454
|
-
} else {
|
|
455
|
-
if (Math.random() < 0.02) statusCode = 302;
|
|
456
|
-
}
|
|
457
|
-
data.push({
|
|
458
|
-
durationMs: latencyValue,
|
|
459
|
-
timestampMs: currentTimelineOffsetMs,
|
|
460
|
-
dnsTimeMs: 0.8 + Math.random() * 1.2,
|
|
461
|
-
tcpTimeMs: 3.5 + Math.random() * 2.1,
|
|
462
|
-
tlsTimeMs: 5 + Math.random() * 2.5,
|
|
463
|
-
statusCode,
|
|
464
|
-
errorMessage
|
|
465
|
-
});
|
|
466
|
-
const thinkTime = 10 + Math.random() * 20;
|
|
467
|
-
currentTimelineOffsetMs += latencyValue + thinkTime;
|
|
376
|
+
for (let i = 0; i < totalRequests; i++) {
|
|
377
|
+
const isError = Math.random() < (isBreached ? 0.05 : 1e-3);
|
|
378
|
+
const baseLatency = (25 + Math.random() * 35) * stressFactor;
|
|
379
|
+
let errorMessage;
|
|
380
|
+
let statusCode = 200;
|
|
381
|
+
if (isError) {
|
|
382
|
+
statusCode = Math.random() > 0.3 ? 500 : 404;
|
|
383
|
+
errorMessage = errorPool[Math.floor(Math.random() * errorPool.length)];
|
|
468
384
|
}
|
|
385
|
+
data.push({
|
|
386
|
+
durationMs: isError ? baseLatency * 0.1 : baseLatency,
|
|
387
|
+
timestampMs: startTime + Math.random() * (durationSec * 1e3),
|
|
388
|
+
dnsTimeMs: 1 + Math.random() * 0.9,
|
|
389
|
+
tcpTimeMs: 4 + Math.random() * 1.5,
|
|
390
|
+
tlsTimeMs: 6 + Math.random() * 1.8,
|
|
391
|
+
statusCode,
|
|
392
|
+
errorMessage
|
|
393
|
+
});
|
|
469
394
|
}
|
|
470
395
|
return data.sort((a, b) => a.timestampMs - b.timestampMs);
|
|
471
396
|
}
|
|
472
|
-
function processMetricsTelemetry(requests, durationSec
|
|
397
|
+
function processMetricsTelemetry(requests, durationSec) {
|
|
473
398
|
const totalRequests = requests.length;
|
|
474
399
|
const durations = requests.map((r) => r.durationMs);
|
|
475
400
|
const avgLatencyMs = mathUtils.mean(durations);
|
|
476
401
|
const stdDevMs = mathUtils.stdDev(durations, avgLatencyMs);
|
|
477
402
|
const p95LatencyMs = mathUtils.percentile(durations, 95);
|
|
478
|
-
const minLatencyMs = mathUtils.min(durations);
|
|
479
|
-
const maxLatencyMs = mathUtils.max(durations);
|
|
480
403
|
const avgDnsMs = mathUtils.mean(requests.map((r) => r.dnsTimeMs || 0));
|
|
481
404
|
const avgTcpMs = mathUtils.mean(requests.map((r) => r.tcpTimeMs || 0));
|
|
482
405
|
const avgTlsMs = mathUtils.mean(requests.map((r) => r.tlsTimeMs || 0));
|
|
483
406
|
const avgTtfbMs = avgDnsMs + avgTcpMs + avgTlsMs + avgLatencyMs * 0.3;
|
|
484
407
|
let c1xx = 0, c2xx = 0, c3xx = 0, c4xx = 0, c5xx = 0;
|
|
485
|
-
let econnresetCount = 0;
|
|
486
|
-
let econnrefusedCount = 0;
|
|
487
|
-
const codeCounts = { 400: 0, 401: 0, 403: 0, 404: 0, 429: 0, 500: 0, 502: 0, 503: 0, 504: 0 };
|
|
488
408
|
requests.forEach((r) => {
|
|
489
409
|
if (r.statusCode >= 100 && r.statusCode < 200) c1xx++;
|
|
490
410
|
else if (r.statusCode >= 200 && r.statusCode < 300) c2xx++;
|
|
491
411
|
else if (r.statusCode >= 300 && r.statusCode < 400) c3xx++;
|
|
492
412
|
else if (r.statusCode >= 400 && r.statusCode < 500) c4xx++;
|
|
493
413
|
else if (r.statusCode >= 500) c5xx++;
|
|
494
|
-
if (r.statusCode in codeCounts) {
|
|
495
|
-
codeCounts[r.statusCode]++;
|
|
496
|
-
}
|
|
497
|
-
if (r.errorMessage) {
|
|
498
|
-
if (r.errorMessage.includes("ECONNRESET")) econnresetCount++;
|
|
499
|
-
if (r.errorMessage.includes("ECONNREFUSED")) econnrefusedCount++;
|
|
500
|
-
}
|
|
501
|
-
});
|
|
502
|
-
const codeRates = {};
|
|
503
|
-
Object.keys(codeCounts).forEach((code) => {
|
|
504
|
-
codeRates[code] = totalRequests === 0 ? 0 : codeCounts[code] / totalRequests * 100;
|
|
505
414
|
});
|
|
506
|
-
const econnresetRate = totalRequests === 0 ? 0 : econnresetCount / totalRequests * 100;
|
|
507
415
|
const errorCount = c4xx + c5xx;
|
|
508
416
|
const errorRate = totalRequests === 0 ? 0 : errorCount / totalRequests;
|
|
509
|
-
const
|
|
510
|
-
const
|
|
417
|
+
const T = 50;
|
|
418
|
+
const satisfied = requests.filter((r) => r.durationMs <= T && r.statusCode < 400).length;
|
|
419
|
+
const tolerating = requests.filter((r) => r.durationMs > T && r.durationMs <= T * 4 && r.statusCode < 400).length;
|
|
511
420
|
const apdexScore = totalRequests === 0 ? 0 : (satisfied + tolerating / 2) / totalRequests;
|
|
512
|
-
const bandwidthMb = totalRequests * 1.
|
|
513
|
-
const lcpMs = avgTtfbMs * 1.6 + (p95LatencyMs - avgLatencyMs) * 0.4;
|
|
514
|
-
const fidMs = Math.max(1.5, stdDevMs * 0.18 + errorRate * 60);
|
|
515
|
-
const clsScore = Math.min(0.5, errorRate * 0.25 + (avgLatencyMs > 400 ? 0.08 : 0.01));
|
|
421
|
+
const bandwidthMb = totalRequests * 1.2 / durationSec / 10;
|
|
516
422
|
return {
|
|
517
423
|
totalRequests,
|
|
518
424
|
requestsPerSecond: totalRequests / durationSec,
|
|
519
425
|
avgLatencyMs,
|
|
520
426
|
stdDevMs,
|
|
521
427
|
p95LatencyMs,
|
|
522
|
-
minLatencyMs,
|
|
523
|
-
maxLatencyMs,
|
|
524
428
|
errorRate,
|
|
525
429
|
apdexScore,
|
|
526
430
|
c1xx,
|
|
@@ -532,15 +436,7 @@ function processMetricsTelemetry(requests, durationSec, apdexT) {
|
|
|
532
436
|
avgTcpMs,
|
|
533
437
|
avgTlsMs,
|
|
534
438
|
avgTtfbMs,
|
|
535
|
-
bandwidthMb
|
|
536
|
-
lcpMs,
|
|
537
|
-
fidMs,
|
|
538
|
-
clsScore,
|
|
539
|
-
econnresetCount,
|
|
540
|
-
econnresetRate,
|
|
541
|
-
econnrefusedCount,
|
|
542
|
-
codeCounts,
|
|
543
|
-
codeRates
|
|
439
|
+
bandwidthMb
|
|
544
440
|
};
|
|
545
441
|
}
|
|
546
442
|
function printMatrixDashboard(m, threads) {
|
|
@@ -550,8 +446,6 @@ function printMatrixDashboard(m, threads) {
|
|
|
550
446
|
console.log(`-----------------------------------------------------------------------------------------`);
|
|
551
447
|
console.log(`| ${pad(threads, 7)} | ${pad(m.totalRequests, 9)} | ${pad(m.avgLatencyMs.toFixed(1) + "ms", 11)} | ${pad(m.p95LatencyMs.toFixed(1) + "ms", 11)} | ${pad(m.requestsPerSecond.toFixed(0) + "/s", 10)} | ${pad((m.errorRate * 100).toFixed(2) + "%", 10)} | ${pad(m.apdexScore.toFixed(2), 9)} |`);
|
|
552
448
|
console.log(`-----------------------------------------------------------------------------------------`);
|
|
553
|
-
console.log(`| Network & Floor -> RESET: ${m.econnresetRate.toFixed(1)}% | Min/Max Boundary: ${m.minLatencyMs.toFixed(1)}ms / ${m.maxLatencyMs.toFixed(1)}ms`);
|
|
554
|
-
console.log(`-----------------------------------------------------------------------------------------`);
|
|
555
449
|
}
|
|
556
450
|
function generateFinalAgentReport(history) {
|
|
557
451
|
if (history.length === 0) return;
|
|
@@ -573,7 +467,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
573
467
|
const activeThreadsData = history.map((h) => h.threads);
|
|
574
468
|
const ttfTrendData = history.map((h) => h.avgLatency * 1.1);
|
|
575
469
|
const waveListItems = history.map((h) => {
|
|
576
|
-
return `<div class="wave-item">Wave (${h.threads} VUs):
|
|
470
|
+
return `<div class="wave-item">Wave (${h.threads} VUs): P95 ${h.p95Latency.toFixed(1)}ms, Errors ${(h.errorRate * 100).toFixed(1)}%</div>`;
|
|
577
471
|
}).join("");
|
|
578
472
|
const estimatedMonthlyCost = (cards.throughput * 0.045 * 24 * 30).toFixed(2);
|
|
579
473
|
const recommendedCpuCores = Math.max(2, Math.ceil(cards.saturationKneePoint / 75));
|
|
@@ -583,143 +477,6 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
583
477
|
x: Number(((r.timestampMs - firstTimestamp) / 1e3).toFixed(2)),
|
|
584
478
|
y: Number(((r.dnsTimeMs || 0) + (r.tcpTimeMs || 0) + (r.tlsTimeMs || 0) + r.durationMs * 0.3).toFixed(1))
|
|
585
479
|
}));
|
|
586
|
-
const getLcpStatus = (val) => val <= 2500 ? { label: "Good", cls: "green" } : val <= 4e3 ? { label: "Needs Imp.", cls: "orange" } : { label: "Poor", cls: "badge-fail" };
|
|
587
|
-
const getFidStatus = (val) => val <= 100 ? { label: "Good", cls: "green" } : val <= 300 ? { label: "Needs Imp.", cls: "orange" } : { label: "Poor", cls: "badge-fail" };
|
|
588
|
-
const getClsStatus = (val) => val <= 0.1 ? { label: "Good", cls: "green" } : val <= 0.25 ? { label: "Needs Imp.", cls: "orange" } : { label: "Poor", cls: "badge-fail" };
|
|
589
|
-
const lcpStat = getLcpStatus(cards.lcp);
|
|
590
|
-
const fidStat = getFidStatus(cards.fid);
|
|
591
|
-
const clsStat = getClsStatus(cards.cls);
|
|
592
|
-
let seoPredictorPanelHtml = "";
|
|
593
|
-
if (config.isSeoEnabled) {
|
|
594
|
-
const lcp = cards.lcp;
|
|
595
|
-
let seoScore = 100;
|
|
596
|
-
let visibilityLossPct = 0;
|
|
597
|
-
let trafficDropPct = 0;
|
|
598
|
-
let rankDropPositions = 0;
|
|
599
|
-
if (lcp > 2500) {
|
|
600
|
-
if (lcp <= 4e3) {
|
|
601
|
-
const ratio = (lcp - 2500) / 1500;
|
|
602
|
-
seoScore = 100 - ratio * 30;
|
|
603
|
-
visibilityLossPct = ratio * 14.5;
|
|
604
|
-
trafficDropPct = ratio * 18.2;
|
|
605
|
-
rankDropPositions = Number((ratio * 1.2).toFixed(1));
|
|
606
|
-
} else {
|
|
607
|
-
const ratio = Math.min(1, (lcp - 4e3) / 4e3);
|
|
608
|
-
seoScore = Math.max(8, 70 - ratio * 62);
|
|
609
|
-
visibilityLossPct = 14.5 + ratio * 52;
|
|
610
|
-
trafficDropPct = 18.2 + ratio * 58.5;
|
|
611
|
-
rankDropPositions = Number((1.2 + ratio * 4.6).toFixed(1));
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
let seoColor = "#10b981";
|
|
615
|
-
let seoStatus = "EXCELLENT";
|
|
616
|
-
if (seoScore < 90) {
|
|
617
|
-
seoColor = "#f59e0b";
|
|
618
|
-
seoStatus = "NEEDS IMPROVEMENT";
|
|
619
|
-
}
|
|
620
|
-
if (seoScore < 60) {
|
|
621
|
-
seoColor = "#ef4444";
|
|
622
|
-
seoStatus = "CRITICAL RISK / PENALIZED";
|
|
623
|
-
}
|
|
624
|
-
seoPredictorPanelHtml = `
|
|
625
|
-
<div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
|
|
626
|
-
<h3 style="color: #38bdf8; font-size: 1.2rem; border-bottom: none; margin-bottom: 0.25rem;">
|
|
627
|
-
\u{1F50D} Google SEO Rank & Search Visibility Impact Predictor
|
|
628
|
-
</h3>
|
|
629
|
-
<div style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 1.5rem;">
|
|
630
|
-
Predictive algorithmic simulation modeling response degradation metrics directly against Google Core Web Vitals signal rules.
|
|
631
|
-
</div>
|
|
632
|
-
|
|
633
|
-
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; flex-wrap: wrap;">
|
|
634
|
-
<div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b; text-align: center;">
|
|
635
|
-
<div style="font-size: 0.75rem; color: #64748b; text-transform: uppercase; font-weight: bold;">Core Web Vitals SEO Score</div>
|
|
636
|
-
<div style="font-size: 1.8rem; font-weight: bold; color: ${seoColor}; margin-top: 0.25rem;">
|
|
637
|
-
${seoScore.toFixed(0)}<span style="font-size: 1rem; color: #94a3b8;">/100</span>
|
|
638
|
-
</div>
|
|
639
|
-
<div style="font-size: 0.75rem; font-weight: bold; margin-top: 0.4rem; color: ${seoColor};">${seoStatus}</div>
|
|
640
|
-
</div>
|
|
641
|
-
|
|
642
|
-
<div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b; text-align: center;">
|
|
643
|
-
<div style="font-size: 0.75rem; color: #64748b; text-transform: uppercase; font-weight: bold;">Search Visibility Loss</div>
|
|
644
|
-
<div style="font-size: 1.8rem; font-weight: bold; color: ${visibilityLossPct > 0 ? "#ef4444" : "#10b981"}; margin-top: 0.25rem;">
|
|
645
|
-
-${visibilityLossPct.toFixed(1)}%
|
|
646
|
-
</div>
|
|
647
|
-
<div style="font-size: 0.75rem; color: #64748b; margin-top: 0.4rem;">Projected SERP Impression Drop</div>
|
|
648
|
-
</div>
|
|
649
|
-
|
|
650
|
-
<div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b; text-align: center;">
|
|
651
|
-
<div style="font-size: 0.75rem; color: #64748b; text-transform: uppercase; font-weight: bold;">Funnel Conversion Loss</div>
|
|
652
|
-
<div style="font-size: 1.8rem; font-weight: bold; color: ${trafficDropPct > 0 ? "#ef4444" : "#10b981"}; margin-top: 0.25rem;">
|
|
653
|
-
-${trafficDropPct.toFixed(1)}%
|
|
654
|
-
</div>
|
|
655
|
-
<div style="font-size: 0.75rem; color: #64748b; margin-top: 0.4rem;">Expected funnel volume drop</div>
|
|
656
|
-
</div>
|
|
657
|
-
|
|
658
|
-
<div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b; text-align: center;">
|
|
659
|
-
<div style="font-size: 0.75rem; color: #64748b; text-transform: uppercase; font-weight: bold;">Avg SERP Position Shift</div>
|
|
660
|
-
<div style="font-size: 1.8rem; font-weight: bold; color: ${rankDropPositions > 0 ? "#f59e0b" : "#10b981"}; margin-top: 0.25rem;">
|
|
661
|
-
${rankDropPositions > 0 ? `+${rankDropPositions}` : "0.0"}
|
|
662
|
-
</div>
|
|
663
|
-
<div style="font-size: 0.75rem; color: #64748b; margin-top: 0.4rem;">Rank places dropped down standard index</div>
|
|
664
|
-
</div>
|
|
665
|
-
</div>
|
|
666
|
-
|
|
667
|
-
<div style="margin-top: 1rem; background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b; font-size: 0.85rem; line-height: 1.5; color: #cbd5e1;">
|
|
668
|
-
<strong>\u{1F52E} SEO Engine Intelligence Breakdown:</strong>
|
|
669
|
-
${lcp <= 2500 ? "Your simulated Largest Contentful Paint equivalent falls safely within Google's 'Good' range (≤ 2500ms). The parsing layer calculates zero performance-based rank penalties under current system load profiles." : lcp <= 4e3 ? `Warning: Current concurrency load has pushed LCP to ${lcp.toFixed(0)}ms, landing in Google's 'Needs Improvement' window. This response drag risks triggering index rank adjustments, potentially demoting listings down by ~${rankDropPositions} positions and sacrificing around ${trafficDropPct.toFixed(0)}% of organic top-of-funnel users.` : `Critical Signal Breach: Load performance has forced LCP to ${lcp.toFixed(0)}ms, severely violating Google's 'Poor' performance ceiling (> 4000ms). At this level, ranking algorithms actively de-weight domains. Expect structural search visibility erosion up to ${visibilityLossPct.toFixed(0)}% and immediate organic traffic redirection.`}
|
|
670
|
-
</div>
|
|
671
|
-
</div>`;
|
|
672
|
-
}
|
|
673
|
-
const connectionDiagnosticsPanelHtml = `
|
|
674
|
-
<div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
|
|
675
|
-
<h3 style="color: #38bdf8; font-size: 1.2rem; border-bottom: none; margin-bottom: 0.25rem;">
|
|
676
|
-
\u{1F50C} Network Connection & Detailed HTTP Status Diagnostics
|
|
677
|
-
</h3>
|
|
678
|
-
<div style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 1.5rem;">
|
|
679
|
-
Real-time breakdown tracking transport anomalies and precise layer-7 distribution indicators.
|
|
680
|
-
</div>
|
|
681
|
-
|
|
682
|
-
<div style="display: grid; grid-template-columns: 1fr 2fr; gap: 2rem; flex-wrap: wrap;">
|
|
683
|
-
<div style="display: flex; flex-direction: column; gap: 1rem;">
|
|
684
|
-
<div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
685
|
-
<div style="font-size: 0.75rem; color: #64748b; text-transform: uppercase; font-weight: bold;">ECONNRESET Rate</div>
|
|
686
|
-
<div style="font-size: 1.8rem; font-weight: bold; color: ${cards.econnresetRate > 1 ? "#ef4444" : "#10b981"};">
|
|
687
|
-
${cards.econnresetRate.toFixed(2)}<span style="font-size: 1rem; color: #94a3b8;"> %</span>
|
|
688
|
-
</div>
|
|
689
|
-
<div style="font-size: 0.8rem; color: #64748b; margin-top: 0.25rem;">Abrupt remote host termination frequency</div>
|
|
690
|
-
</div>
|
|
691
|
-
|
|
692
|
-
<div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
693
|
-
<div style="font-size: 0.75rem; color: #64748b; text-transform: uppercase; font-weight: bold;">ECONNREFUSED Count</div>
|
|
694
|
-
<div style="font-size: 1.8rem; font-weight: bold; color: ${cards.econnrefusedCount > 0 ? "#f59e0b" : "#10b981"};">
|
|
695
|
-
${cards.econnrefusedCount}<span style="font-size: 1rem; color: #94a3b8;"> drops</span>
|
|
696
|
-
</div>
|
|
697
|
-
<div style="font-size: 0.8rem; color: #64748b; margin-top: 0.25rem;">Total complete backend port link rejections</div>
|
|
698
|
-
</div>
|
|
699
|
-
</div>
|
|
700
|
-
|
|
701
|
-
<div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
702
|
-
<div style="font-size: 0.85rem; font-weight: bold; color: #f8fafc; margin-bottom: 1rem;">Target Response Code Matrix Rates</div>
|
|
703
|
-
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem;">
|
|
704
|
-
${Object.keys(cards.codeRates).map((code) => {
|
|
705
|
-
const percentage = cards.codeRates[code];
|
|
706
|
-
let textColor = "#cbd5e1";
|
|
707
|
-
if (percentage > 0) {
|
|
708
|
-
textColor = code.startsWith("5") ? "#f87171" : "#fde047";
|
|
709
|
-
}
|
|
710
|
-
return `
|
|
711
|
-
<div style="background: #131c2e; padding: 0.75rem; border-radius: 4px; border: 1px solid #1e293b; text-align: center;">
|
|
712
|
-
<div style="font-size: 0.8rem; font-weight: bold; color: #94a3b8;">Code ${code}</div>
|
|
713
|
-
<div style="font-size: 1.1rem; font-weight: bold; margin-top: 0.25rem; color: ${textColor};">
|
|
714
|
-
${percentage.toFixed(2)}%
|
|
715
|
-
</div>
|
|
716
|
-
</div>
|
|
717
|
-
`;
|
|
718
|
-
}).join("")}
|
|
719
|
-
</div>
|
|
720
|
-
</div>
|
|
721
|
-
</div>
|
|
722
|
-
</div>`;
|
|
723
480
|
let logClustersHtml = "";
|
|
724
481
|
if (semanticReport) {
|
|
725
482
|
const totalLogCount = semanticReport.clusters.reduce((sum, c) => sum + c.count, 0);
|
|
@@ -730,7 +487,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
730
487
|
\u{1F9E0} Interactive Remediation Playbooks & Log Classification
|
|
731
488
|
</h3>
|
|
732
489
|
<div style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 1.5rem;">
|
|
733
|
-
Captured <strong style="color: #f8fafc;">${totalLogCount}</strong> raw failures grouped into <strong style="color: #f8fafc;">${uniquePatternsCount}</strong> unique structural patterns.
|
|
490
|
+
Captured <strong style="color: #f8fafc;">${totalLogCount}</strong> raw failures grouped into <strong style="color: #f8fafc;">${uniquePatternsCount}</strong> unique structural patterns. Expand a playbook to inspect automated remediation strategies and diagnostics confidence ratings.
|
|
734
491
|
</div>
|
|
735
492
|
|
|
736
493
|
<div style="display: flex; flex-direction: column; gap: 1rem;">
|
|
@@ -753,11 +510,11 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
753
510
|
let explanation = "High concurrency is causing socket/connection starvation. Increase connection limits or enable pooling keep-alives.";
|
|
754
511
|
if (c.category === "Authentication_Error") {
|
|
755
512
|
fixTitle = "Auth Token / JWT Strategy";
|
|
756
|
-
fixCode = "// Implement token caching and silent rotation
|
|
513
|
+
fixCode = "// Implement token caching and silent rotation\napp.use(authMiddleware({ cacheTtl: 300, allowGracePeriod: 60 }));";
|
|
757
514
|
explanation = "Frequent token verification failures under load. Extend signature verification cache or decouple auth validation check.";
|
|
758
515
|
} else if (c.category === "Product_Error") {
|
|
759
516
|
fixTitle = "Application Null-Safety / Guard Fix";
|
|
760
|
-
fixCode = "const configId = payload?.config_id ?? defaultConfigurationId
|
|
517
|
+
fixCode = "const configId = payload?.config_id ?? defaultConfigurationId;\nif (!configId) throw new ValidationError('Missing config_id');";
|
|
761
518
|
explanation = "Runtime reference error for undefined payload field under race condition spikes. Add optional chaining and input contracts.";
|
|
762
519
|
}
|
|
763
520
|
const snippetId = `snippet-${idx}`;
|
|
@@ -846,9 +603,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
846
603
|
h1 { color: #38bdf8; margin: 0; font-size: 2rem; }
|
|
847
604
|
.meta { color: #94a3b8; font-size: 0.9rem; margin-top: 0.5rem; }
|
|
848
605
|
|
|
849
|
-
.
|
|
850
|
-
.cards-wrapper { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
|
|
851
|
-
@media (max-width: 1024px) { .cards-wrapper { grid-template-columns: repeat(2, 1fr); } }
|
|
606
|
+
.cards-wrapper { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
|
|
852
607
|
.metric-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 6px; padding: 1.25rem; position: relative; }
|
|
853
608
|
.card-title { font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; }
|
|
854
609
|
.card-value { font-size: 1.6rem; font-weight: 700; color: #ffffff; }
|
|
@@ -857,10 +612,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
857
612
|
.card-value.orange { color: #f97316; }
|
|
858
613
|
.card-value.purple { color: #a855f7; }
|
|
859
614
|
.card-value.cyan { color: #38bdf8; }
|
|
860
|
-
.card-
|
|
861
|
-
.card-subtext { font-size: 0.8rem; font-weight: 500; margin-left: 0.4rem; display: inline-block; }
|
|
862
|
-
.card-subtext.green { color: #10b981; }
|
|
863
|
-
.card-subtext.orange { color: #f97316; }
|
|
615
|
+
.card-subtext { font-size: 0.8rem; color: #10b981; font-weight: 500; margin-left: 0.4rem; display: inline-block; }
|
|
864
616
|
|
|
865
617
|
.top-layout-grid { display: grid; grid-template-columns: 1fr 400px; gap: 1.5rem; margin-bottom: 2rem; }
|
|
866
618
|
.verdict-box { background: #0f172a; border: 1px dashed #ea580c; border-radius: 8px; padding: 1.25rem; }
|
|
@@ -901,24 +653,15 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
901
653
|
<div class="meta">Target Script: <code>${config.targetScript}</code></div>
|
|
902
654
|
</div>
|
|
903
655
|
|
|
904
|
-
<div class="section-title">\u{1F4CA} Core Server Metrics & Apdex Verification</div>
|
|
905
656
|
<div class="cards-wrapper">
|
|
906
657
|
<div class="metric-card">
|
|
907
|
-
<div class="card-title">Apdex Score (T:
|
|
908
|
-
<div class="card-value green">${cards.apdex.toFixed(2)}<span class="card-subtext
|
|
658
|
+
<div class="card-title">Apdex Score (T: 50ms)</div>
|
|
659
|
+
<div class="card-value green">${cards.apdex.toFixed(2)}<span class="card-subtext">(Good)</span></div>
|
|
909
660
|
</div>
|
|
910
661
|
<div class="metric-card">
|
|
911
662
|
<div class="card-title">Throughput</div>
|
|
912
663
|
<div class="card-value orange">${cards.throughput.toFixed(1)}<span class="unit">/s</span></div>
|
|
913
664
|
</div>
|
|
914
|
-
<div class="metric-card">
|
|
915
|
-
<div class="card-title">Minimum Response Time</div>
|
|
916
|
-
<div class="card-value green">${cards.minLatency.toFixed(1)}<span class="unit">ms</span></div>
|
|
917
|
-
</div>
|
|
918
|
-
<div class="metric-card">
|
|
919
|
-
<div class="card-title">Maximum Response Time</div>
|
|
920
|
-
<div class="card-value red">${cards.maxLatency.toFixed(1)}<span class="unit">ms</span></div>
|
|
921
|
-
</div>
|
|
922
665
|
<div class="metric-card">
|
|
923
666
|
<div class="card-title">Network Bandwidth</div>
|
|
924
667
|
<div class="card-value purple">${cards.bandwidth.toFixed(2)}<span class="unit">MB/s</span></div>
|
|
@@ -932,28 +675,23 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
932
675
|
<div class="card-value cyan">${cards.vuRampUpVelocity.toFixed(1)}<span class="unit">VUs/s</span></div>
|
|
933
676
|
</div>
|
|
934
677
|
<div class="metric-card">
|
|
935
|
-
<div class="card-title">
|
|
936
|
-
<div class="card-value"
|
|
678
|
+
<div class="card-title">DNS Lookup</div>
|
|
679
|
+
<div class="card-value">${cards.dns.toFixed(2)}<span class="unit">ms</span></div>
|
|
937
680
|
</div>
|
|
938
|
-
</div>
|
|
939
|
-
|
|
940
|
-
<div class="section-title">\u{1F310} Simulated Browser Core Web Vitals Equivalent</div>
|
|
941
|
-
<div class="cards-wrapper">
|
|
942
681
|
<div class="metric-card">
|
|
943
|
-
<div class="card-title">
|
|
944
|
-
<div class="card-value">${cards.
|
|
682
|
+
<div class="card-title">TCP Connect</div>
|
|
683
|
+
<div class="card-value">${cards.tcp.toFixed(2)}<span class="unit">ms</span></div>
|
|
945
684
|
</div>
|
|
946
685
|
<div class="metric-card">
|
|
947
|
-
<div class="card-title">
|
|
948
|
-
<div class="card-value">${cards.
|
|
686
|
+
<div class="card-title">TLS Handshake</div>
|
|
687
|
+
<div class="card-value">${cards.tls.toFixed(2)}<span class="unit">ms</span></div>
|
|
949
688
|
</div>
|
|
950
689
|
<div class="metric-card">
|
|
951
|
-
<div class="card-title">
|
|
952
|
-
<div class="card-value"
|
|
690
|
+
<div class="card-title">Stability (Std Dev)</div>
|
|
691
|
+
<div class="card-value">±${cards.stdDev.toFixed(1)}<span class="unit">ms</span></div>
|
|
953
692
|
</div>
|
|
954
693
|
</div>
|
|
955
694
|
|
|
956
|
-
<div class="section-title">\u26A1 Diagnostic Breakdowns</div>
|
|
957
695
|
<div class="top-layout-grid">
|
|
958
696
|
<div class="verdict-box">
|
|
959
697
|
<div class="verdict-title">\u{1F916} AI Stress-Testing Agent Verdict</div>
|
|
@@ -1025,8 +763,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
1025
763
|
<th>Throughput</th>
|
|
1026
764
|
<th>Avg Latency</th>
|
|
1027
765
|
<th>P95 Latency</th>
|
|
1028
|
-
<th>
|
|
1029
|
-
<th>RESET Rate</th>
|
|
766
|
+
<th>Error Rate</th>
|
|
1030
767
|
<th>Status</th>
|
|
1031
768
|
</tr>
|
|
1032
769
|
</thead>
|
|
@@ -1038,16 +775,13 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
1038
775
|
<td>${h.throughput.toFixed(0)} req/sec</td>
|
|
1039
776
|
<td>${h.avgLatency.toFixed(1)} ms</td>
|
|
1040
777
|
<td>${h.p95Latency.toFixed(1)} ms</td>
|
|
1041
|
-
<td
|
|
1042
|
-
<td>${h.econnresetRate.toFixed(2)}%</td>
|
|
778
|
+
<td>${(h.errorRate * 100).toFixed(2)}%</td>
|
|
1043
779
|
<td><span class="badge ${isPassed ? "badge-success" : "badge-fail"}">${isPassed ? "SLO PASS" : "SLO BREACH"}</span></td>
|
|
1044
780
|
</tr>`;
|
|
1045
781
|
}).join("")}
|
|
1046
782
|
</tbody>
|
|
1047
783
|
</table>
|
|
1048
784
|
</div>
|
|
1049
|
-
${seoPredictorPanelHtml}
|
|
1050
|
-
${connectionDiagnosticsPanelHtml}
|
|
1051
785
|
${liveAdjusterHtml}
|
|
1052
786
|
${rightSizingHtml}
|
|
1053
787
|
${logClustersHtml}
|
|
@@ -1067,6 +801,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
1067
801
|
const baseThroughput = ${cards.throughput};
|
|
1068
802
|
const baseLatency = ${cards.ttfb};
|
|
1069
803
|
|
|
804
|
+
// Live Concurrency Adjuster interactive client logic
|
|
1070
805
|
const slider = document.getElementById('concurrencySlider');
|
|
1071
806
|
const sliderVal = document.getElementById('sliderValue');
|
|
1072
807
|
const simThroughput = document.getElementById('simThroughput');
|
|
@@ -1151,8 +886,8 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
1151
886
|
},
|
|
1152
887
|
scales: {
|
|
1153
888
|
x: { grid: { color: '#1e293b' }, ticks: { color: '#64748b' } },
|
|
1154
|
-
|
|
1155
|
-
|
|
889
|
+
y_threads: { position: 'left', grid: { color: '#1e293b' }, ticks: { color: '#38bdf8' }, title: { display: true, text: 'VUs / Threads', color: '#38bdf8' } },
|
|
890
|
+
y_ttf: { position: 'right', grid: { drawOnChartArea: false }, ticks: { color: '#f43f5e' }, title: { display: true, text: 'TTF Index / Latency ms', color: '#f43f5e' } }
|
|
1156
891
|
}
|
|
1157
892
|
}
|
|
1158
893
|
});
|
|
@@ -1212,6 +947,6 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
1212
947
|
function printUsage() {
|
|
1213
948
|
console.log(`Blaze Core Performance Test CLI Engine
|
|
1214
949
|
Options:
|
|
1215
|
-
--threads <count> | --duration <seconds> | --agentic | --cluster-logs | --correlate
|
|
950
|
+
--threads <count> | --duration <seconds> | --agentic | --cluster-logs | --correlate`);
|
|
1216
951
|
}
|
|
1217
952
|
main().catch(console.error);
|
|
Binary file
|