blaze-performance-tester 3.1.16 → 3.1.18
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 +86 -388
- 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);
|
|
@@ -320,13 +293,6 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
320
293
|
const analyzer = new LogAnalyzer();
|
|
321
294
|
semanticReport = analyzer.process(aggregateErrorLogs);
|
|
322
295
|
}
|
|
323
|
-
const totalTestSeconds = history.length * config.durationSec;
|
|
324
|
-
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
296
|
const finalSummaryCards = {
|
|
331
297
|
apdex: history[history.length - 1]?.apdex || 0,
|
|
332
298
|
throughput: history[history.length - 1]?.throughput || 0,
|
|
@@ -336,41 +302,24 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
336
302
|
tcp: mathUtils.mean(globalMetricsAccumulator.tcp),
|
|
337
303
|
tls: mathUtils.mean(globalMetricsAccumulator.tls),
|
|
338
304
|
stdDev: mathUtils.stdDev(globalMetricsAccumulator.latencies, mathUtils.mean(globalMetricsAccumulator.latencies)),
|
|
339
|
-
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))
|
|
305
|
+
saturationKneePoint: saturationKneePoint || history[history.length - 1]?.threads || 50
|
|
349
306
|
};
|
|
350
307
|
generateFinalAgentReport(history);
|
|
351
308
|
exportHtmlDashboard(history, config, verdictReason, semanticReport, { total1xx, total2xx, total3xx, total4xx, total5xx }, finalSummaryCards, lastRawRequests);
|
|
352
309
|
}
|
|
353
310
|
async function runStandardStressTest(config) {
|
|
354
|
-
console.log(`\u{1F3CB}\uFE0F Running Standard
|
|
311
|
+
console.log(`\u{1F3CB}\uFE0F Running Standard Stress Test [Threads: ${config.threads} | Duration: ${config.durationSec}s]`);
|
|
355
312
|
const { metrics, rawErrors, rawRequests } = await runBlazeCoreEngine(config);
|
|
356
313
|
printMatrixDashboard(metrics, config.threads);
|
|
357
314
|
const history = [{
|
|
358
315
|
threads: config.threads,
|
|
359
316
|
avgLatency: metrics.avgLatencyMs,
|
|
360
317
|
p95Latency: metrics.p95LatencyMs,
|
|
361
|
-
minLatency: metrics.minLatencyMs,
|
|
362
|
-
maxLatency: metrics.maxLatencyMs,
|
|
363
318
|
errorRate: metrics.errorRate,
|
|
364
319
|
apdex: metrics.apdexScore,
|
|
365
320
|
throughput: metrics.requestsPerSecond,
|
|
366
321
|
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
|
|
322
|
+
failedRequests: metrics.c4xx + metrics.c5xx
|
|
374
323
|
}];
|
|
375
324
|
let semanticReport = null;
|
|
376
325
|
if (config.clusterLogs) {
|
|
@@ -388,16 +337,7 @@ async function runStandardStressTest(config) {
|
|
|
388
337
|
tcp: metrics.avgTcpMs,
|
|
389
338
|
tls: metrics.avgTlsMs,
|
|
390
339
|
stdDev: metrics.stdDevMs,
|
|
391
|
-
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
|
|
340
|
+
saturationKneePoint: config.threads
|
|
401
341
|
};
|
|
402
342
|
const isPassed = metrics.apdexScore >= config.targetApdex && metrics.errorRate <= config.targetErrorRate;
|
|
403
343
|
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 +361,66 @@ function getFallbackClusterLogs() {
|
|
|
421
361
|
add("HTTP/1.1 504 Gateway Timeout on POST /api/v1/checkout - upstream socket drop after 30000ms", 19);
|
|
422
362
|
return logs;
|
|
423
363
|
}
|
|
424
|
-
function
|
|
364
|
+
function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
|
|
425
365
|
const data = [];
|
|
366
|
+
const totalRequests = Math.max(15, threads * durationSec * 30);
|
|
426
367
|
const startTime = Date.now();
|
|
427
|
-
const endTime = startTime + durationSec * 1e3;
|
|
428
368
|
const targetThresholdBreak = Math.max(5, Math.floor(maxAllowedLatencyMs * 0.05));
|
|
429
369
|
const isBreached = threads > targetThresholdBreak;
|
|
430
|
-
const stressFactor = isBreached ? Math.pow(threads / targetThresholdBreak, 1.
|
|
370
|
+
const stressFactor = isBreached ? Math.pow(threads / targetThresholdBreak, 1.5) : 1;
|
|
431
371
|
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;
|
|
372
|
+
for (let i = 0; i < totalRequests; i++) {
|
|
373
|
+
const isError = Math.random() < (isBreached ? 0.05 : 1e-3);
|
|
374
|
+
const baseLatency = (25 + Math.random() * 35) * stressFactor;
|
|
375
|
+
let errorMessage;
|
|
376
|
+
let statusCode = 200;
|
|
377
|
+
if (isError) {
|
|
378
|
+
statusCode = Math.random() > 0.3 ? 500 : 404;
|
|
379
|
+
errorMessage = errorPool[Math.floor(Math.random() * errorPool.length)];
|
|
468
380
|
}
|
|
381
|
+
data.push({
|
|
382
|
+
durationMs: isError ? baseLatency * 0.1 : baseLatency,
|
|
383
|
+
timestampMs: startTime + Math.random() * (durationSec * 1e3),
|
|
384
|
+
dnsTimeMs: 1 + Math.random() * 0.9,
|
|
385
|
+
tcpTimeMs: 4 + Math.random() * 1.5,
|
|
386
|
+
tlsTimeMs: 6 + Math.random() * 1.8,
|
|
387
|
+
statusCode,
|
|
388
|
+
errorMessage
|
|
389
|
+
});
|
|
469
390
|
}
|
|
470
391
|
return data.sort((a, b) => a.timestampMs - b.timestampMs);
|
|
471
392
|
}
|
|
472
|
-
function processMetricsTelemetry(requests, durationSec
|
|
393
|
+
function processMetricsTelemetry(requests, durationSec) {
|
|
473
394
|
const totalRequests = requests.length;
|
|
474
395
|
const durations = requests.map((r) => r.durationMs);
|
|
475
396
|
const avgLatencyMs = mathUtils.mean(durations);
|
|
476
397
|
const stdDevMs = mathUtils.stdDev(durations, avgLatencyMs);
|
|
477
398
|
const p95LatencyMs = mathUtils.percentile(durations, 95);
|
|
478
|
-
const minLatencyMs = mathUtils.min(durations);
|
|
479
|
-
const maxLatencyMs = mathUtils.max(durations);
|
|
480
399
|
const avgDnsMs = mathUtils.mean(requests.map((r) => r.dnsTimeMs || 0));
|
|
481
400
|
const avgTcpMs = mathUtils.mean(requests.map((r) => r.tcpTimeMs || 0));
|
|
482
401
|
const avgTlsMs = mathUtils.mean(requests.map((r) => r.tlsTimeMs || 0));
|
|
483
402
|
const avgTtfbMs = avgDnsMs + avgTcpMs + avgTlsMs + avgLatencyMs * 0.3;
|
|
484
403
|
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
404
|
requests.forEach((r) => {
|
|
489
405
|
if (r.statusCode >= 100 && r.statusCode < 200) c1xx++;
|
|
490
406
|
else if (r.statusCode >= 200 && r.statusCode < 300) c2xx++;
|
|
491
407
|
else if (r.statusCode >= 300 && r.statusCode < 400) c3xx++;
|
|
492
408
|
else if (r.statusCode >= 400 && r.statusCode < 500) c4xx++;
|
|
493
409
|
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
410
|
});
|
|
502
|
-
const codeRates = {};
|
|
503
|
-
Object.keys(codeCounts).forEach((code) => {
|
|
504
|
-
codeRates[code] = totalRequests === 0 ? 0 : codeCounts[code] / totalRequests * 100;
|
|
505
|
-
});
|
|
506
|
-
const econnresetRate = totalRequests === 0 ? 0 : econnresetCount / totalRequests * 100;
|
|
507
411
|
const errorCount = c4xx + c5xx;
|
|
508
412
|
const errorRate = totalRequests === 0 ? 0 : errorCount / totalRequests;
|
|
509
|
-
const
|
|
510
|
-
const
|
|
413
|
+
const T = 50;
|
|
414
|
+
const satisfied = requests.filter((r) => r.durationMs <= T && r.statusCode < 400).length;
|
|
415
|
+
const tolerating = requests.filter((r) => r.durationMs > T && r.durationMs <= T * 4 && r.statusCode < 400).length;
|
|
511
416
|
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));
|
|
417
|
+
const bandwidthMb = totalRequests * 1.2 / durationSec / 10;
|
|
516
418
|
return {
|
|
517
419
|
totalRequests,
|
|
518
420
|
requestsPerSecond: totalRequests / durationSec,
|
|
519
421
|
avgLatencyMs,
|
|
520
422
|
stdDevMs,
|
|
521
423
|
p95LatencyMs,
|
|
522
|
-
minLatencyMs,
|
|
523
|
-
maxLatencyMs,
|
|
524
424
|
errorRate,
|
|
525
425
|
apdexScore,
|
|
526
426
|
c1xx,
|
|
@@ -532,15 +432,7 @@ function processMetricsTelemetry(requests, durationSec, apdexT) {
|
|
|
532
432
|
avgTcpMs,
|
|
533
433
|
avgTlsMs,
|
|
534
434
|
avgTtfbMs,
|
|
535
|
-
bandwidthMb
|
|
536
|
-
lcpMs,
|
|
537
|
-
fidMs,
|
|
538
|
-
clsScore,
|
|
539
|
-
econnresetCount,
|
|
540
|
-
econnresetRate,
|
|
541
|
-
econnrefusedCount,
|
|
542
|
-
codeCounts,
|
|
543
|
-
codeRates
|
|
435
|
+
bandwidthMb
|
|
544
436
|
};
|
|
545
437
|
}
|
|
546
438
|
function printMatrixDashboard(m, threads) {
|
|
@@ -550,8 +442,6 @@ function printMatrixDashboard(m, threads) {
|
|
|
550
442
|
console.log(`-----------------------------------------------------------------------------------------`);
|
|
551
443
|
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
444
|
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
445
|
}
|
|
556
446
|
function generateFinalAgentReport(history) {
|
|
557
447
|
if (history.length === 0) return;
|
|
@@ -573,7 +463,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
573
463
|
const activeThreadsData = history.map((h) => h.threads);
|
|
574
464
|
const ttfTrendData = history.map((h) => h.avgLatency * 1.1);
|
|
575
465
|
const waveListItems = history.map((h) => {
|
|
576
|
-
return `<div class="wave-item">Wave (${h.threads} VUs):
|
|
466
|
+
return `<div class="wave-item">Wave (${h.threads} VUs): P95 ${h.p95Latency.toFixed(1)}ms, Errors ${(h.errorRate * 100).toFixed(1)}%</div>`;
|
|
577
467
|
}).join("");
|
|
578
468
|
const estimatedMonthlyCost = (cards.throughput * 0.045 * 24 * 30).toFixed(2);
|
|
579
469
|
const recommendedCpuCores = Math.max(2, Math.ceil(cards.saturationKneePoint / 75));
|
|
@@ -583,143 +473,6 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
583
473
|
x: Number(((r.timestampMs - firstTimestamp) / 1e3).toFixed(2)),
|
|
584
474
|
y: Number(((r.dnsTimeMs || 0) + (r.tcpTimeMs || 0) + (r.tlsTimeMs || 0) + r.durationMs * 0.3).toFixed(1))
|
|
585
475
|
}));
|
|
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
476
|
let logClustersHtml = "";
|
|
724
477
|
if (semanticReport) {
|
|
725
478
|
const totalLogCount = semanticReport.clusters.reduce((sum, c) => sum + c.count, 0);
|
|
@@ -727,14 +480,23 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
727
480
|
logClustersHtml = `
|
|
728
481
|
<div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
|
|
729
482
|
<h3 style="border-bottom: none; padding-bottom: 0rem; margin-bottom: 0.25rem; display: flex; align-items: center; gap: 0.5rem; color: #ffffff; font-size: 1.2rem;">
|
|
730
|
-
\u{1F9E0}
|
|
483
|
+
\u{1F9E0} Semantic Log Clustering & Error Classification
|
|
731
484
|
</h3>
|
|
732
485
|
<div style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 1.5rem;">
|
|
733
486
|
Captured <strong style="color: #f8fafc;">${totalLogCount}</strong> raw failures grouped into <strong style="color: #f8fafc;">${uniquePatternsCount}</strong> unique structural patterns.
|
|
734
487
|
</div>
|
|
735
488
|
|
|
736
|
-
<
|
|
737
|
-
|
|
489
|
+
<table style="width: 100%; border-collapse: collapse;">
|
|
490
|
+
<thead>
|
|
491
|
+
<tr style="border-bottom: 1px solid #1e293b;">
|
|
492
|
+
<th style="width: 8%; padding: 0.75rem; text-align: left; color: #64748b; font-size: 0.85rem; font-weight: bold; text-transform: none;">Count</th>
|
|
493
|
+
<th style="width: 22%; padding: 0.75rem; text-align: left; color: #64748b; font-size: 0.85rem; font-weight: bold; text-transform: none;">Classification Category</th>
|
|
494
|
+
<th style="width: 10%; padding: 0.75rem; text-align: left; color: #64748b; font-size: 0.85rem; font-weight: bold; text-transform: none;">Severity</th>
|
|
495
|
+
<th style="width: 60%; padding: 0.75rem; text-align: left; color: #64748b; font-size: 0.85rem; font-weight: bold; text-transform: none;">Structural Abstract Blueprint / Dynamic Log Fingerprint</th>
|
|
496
|
+
</tr>
|
|
497
|
+
</thead>
|
|
498
|
+
<tbody>
|
|
499
|
+
${semanticReport.clusters.map((c) => {
|
|
738
500
|
let catColor = "#fb923c";
|
|
739
501
|
if (c.category === "Authentication_Error") catColor = "#c084fc";
|
|
740
502
|
if (c.category === "Product_Error") catColor = "#f87171";
|
|
@@ -742,53 +504,25 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
742
504
|
let sevBg = c.severity === "CRITICAL" ? "#dc2626" : "#ea580c";
|
|
743
505
|
const rawTemplate = c.template || "";
|
|
744
506
|
const cleanedTemplate = rawTemplate.replace(/\[\d{4}-\d{2}-\d{2}.*?\]\s*/, "");
|
|
745
|
-
const clarityBonus = c.severity === "CRITICAL" ? 15 : 8;
|
|
746
|
-
const freqScore = Math.min(25, c.count * 0.5);
|
|
747
|
-
const confidenceScore = Math.min(99, Math.round(65 + freqScore + clarityBonus));
|
|
748
|
-
let confColor = "#10b981";
|
|
749
|
-
if (confidenceScore < 80) confColor = "#f59e0b";
|
|
750
|
-
if (confidenceScore < 70) confColor = "#ef4444";
|
|
751
|
-
let fixTitle = "Infrastructure Pool & Tuning Fix";
|
|
752
|
-
let fixCode = "const pool = new Pool({ max: 50, idleTimeoutMillis: 30000 });";
|
|
753
|
-
let explanation = "High concurrency is causing socket/connection starvation. Increase connection limits or enable pooling keep-alives.";
|
|
754
|
-
if (c.category === "Authentication_Error") {
|
|
755
|
-
fixTitle = "Auth Token / JWT Strategy";
|
|
756
|
-
fixCode = "// Implement token caching and silent rotation\\napp.use(authMiddleware({ cacheTtl: 300, allowGracePeriod: 60 }));";
|
|
757
|
-
explanation = "Frequent token verification failures under load. Extend signature verification cache or decouple auth validation check.";
|
|
758
|
-
} else if (c.category === "Product_Error") {
|
|
759
|
-
fixTitle = "Application Null-Safety / Guard Fix";
|
|
760
|
-
fixCode = "const configId = payload?.config_id ?? defaultConfigurationId;\\nif (!configId) throw new ValidationError('Missing config_id');";
|
|
761
|
-
explanation = "Runtime reference error for undefined payload field under race condition spikes. Add optional chaining and input contracts.";
|
|
762
|
-
}
|
|
763
|
-
const snippetId = `snippet-${idx}`;
|
|
764
507
|
return `
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
<
|
|
768
|
-
|
|
769
|
-
<span style="
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
</summary>
|
|
775
|
-
<div style="padding: 1.25rem; border-top: 1px solid #1e293b; background: #0f172a;">
|
|
776
|
-
<div style="margin-bottom: 1rem;">
|
|
777
|
-
<div style="color: #64748b; font-size: 0.75rem; text-transform: uppercase; font-weight: bold; margin-bottom: 0.25rem;">Detected Log Fingerprint</div>
|
|
778
|
-
<code style="color: #cbd5e1; font-family: monospace; font-size: 0.85rem; word-break: break-all;">${cleanedTemplate}</code>
|
|
779
|
-
</div>
|
|
780
|
-
<div style="margin-bottom: 1.25rem;">
|
|
781
|
-
<div style="color: #38bdf8; font-size: 0.9rem; font-weight: bold; margin-bottom: 0.25rem;">\u{1F4A1} Playbook Strategy: ${fixTitle}</div>
|
|
782
|
-
<div style="color: #94a3b8; font-size: 0.85rem; line-height: 1.4; margin-bottom: 0.75rem;">${explanation}</div>
|
|
783
|
-
<div style="position: relative; background: #0b111e; border: 1px solid #1e293b; border-radius: 4px; padding: 0.75rem;">
|
|
784
|
-
<button onclick="copyToClipboard('${snippetId}')" style="position: absolute; top: 0.5rem; right: 0.5rem; background: #1e293b; color: #38bdf8; border: none; padding: 0.2rem 0.5rem; border-radius: 3px; font-size: 0.7rem; cursor: pointer;">Copy Fix</button>
|
|
785
|
-
<pre id="${snippetId}" style="margin: 0; color: #4ade80; font-family: monospace; font-size: 0.85rem; white-space: pre-wrap;">${fixCode}</pre>
|
|
508
|
+
<tr style="border-bottom: 1px solid #1e293b; vertical-align: top;">
|
|
509
|
+
<td style="padding: 1.25rem 0.75rem; font-size: 1.2rem; font-weight: bold; color: #f8fafc;">${c.count}</td>
|
|
510
|
+
<td style="padding: 1.25rem 0.75rem; color: ${catColor}; font-weight: 600; font-size: 0.95rem;">${c.category}</td>
|
|
511
|
+
<td style="padding: 1.25rem 0.75rem;">
|
|
512
|
+
<span style="background: ${sevBg}; color: #ffffff; padding: 0.2rem 0.5rem; border-radius: 4px; font-size: 0.7rem; font-weight: bold; letter-spacing: 0.05em;">${c.severity}</span>
|
|
513
|
+
</td>
|
|
514
|
+
<td style="padding: 1.25rem 0.75rem;">
|
|
515
|
+
<div style="background: #090d16; border-radius: 4px; padding: 0.6rem 0.8rem; border-left: 3px solid #1e293b; margin-bottom: 0.5rem;">
|
|
516
|
+
<code style="color: #cbd5e1; font-family: monospace; font-size: 0.9rem; white-space: pre-wrap; word-break: break-all;">${cleanedTemplate}</code>
|
|
786
517
|
</div>
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
518
|
+
<div style="color: #64748b; font-size: 0.8rem; font-family: monospace; padding-left: 0.2rem; line-height: 1.4;">
|
|
519
|
+
<span style="color: #475569;">Real Example Trace:</span> ${rawTemplate}
|
|
520
|
+
</div>
|
|
521
|
+
</td>
|
|
522
|
+
</tr>`;
|
|
790
523
|
}).join("")}
|
|
791
|
-
|
|
524
|
+
</tbody>
|
|
525
|
+
</table>
|
|
792
526
|
</div>`;
|
|
793
527
|
}
|
|
794
528
|
const liveAdjusterHtml = `
|
|
@@ -846,9 +580,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
846
580
|
h1 { color: #38bdf8; margin: 0; font-size: 2rem; }
|
|
847
581
|
.meta { color: #94a3b8; font-size: 0.9rem; margin-top: 0.5rem; }
|
|
848
582
|
|
|
849
|
-
.section-title { font-size: 1.2rem; color: #cbd5e1; font-weight: bold; margin: 2rem 0 1rem 0; padding-bottom: 0.4rem; border-bottom: 1px solid #1e293b; }
|
|
850
583
|
.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); } }
|
|
852
584
|
.metric-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 6px; padding: 1.25rem; position: relative; }
|
|
853
585
|
.card-title { font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; }
|
|
854
586
|
.card-value { font-size: 1.6rem; font-weight: 700; color: #ffffff; }
|
|
@@ -856,11 +588,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
856
588
|
.card-value.green { color: #10b981; }
|
|
857
589
|
.card-value.orange { color: #f97316; }
|
|
858
590
|
.card-value.purple { color: #a855f7; }
|
|
859
|
-
.card-
|
|
860
|
-
.card-value.red { color: #f87171; }
|
|
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; }
|
|
591
|
+
.card-subtext { font-size: 0.8rem; color: #10b981; font-weight: 500; margin-left: 0.4rem; display: inline-block; }
|
|
864
592
|
|
|
865
593
|
.top-layout-grid { display: grid; grid-template-columns: 1fr 400px; gap: 1.5rem; margin-bottom: 2rem; }
|
|
866
594
|
.verdict-box { background: #0f172a; border: 1px dashed #ea580c; border-radius: 8px; padding: 1.25rem; }
|
|
@@ -901,24 +629,15 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
901
629
|
<div class="meta">Target Script: <code>${config.targetScript}</code></div>
|
|
902
630
|
</div>
|
|
903
631
|
|
|
904
|
-
<div class="section-title">\u{1F4CA} Core Server Metrics & Apdex Verification</div>
|
|
905
632
|
<div class="cards-wrapper">
|
|
906
633
|
<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
|
|
634
|
+
<div class="card-title">Apdex Score (T: 50ms)</div>
|
|
635
|
+
<div class="card-value green">${cards.apdex.toFixed(2)}<span class="card-subtext">(Good)</span></div>
|
|
909
636
|
</div>
|
|
910
637
|
<div class="metric-card">
|
|
911
638
|
<div class="card-title">Throughput</div>
|
|
912
639
|
<div class="card-value orange">${cards.throughput.toFixed(1)}<span class="unit">/s</span></div>
|
|
913
640
|
</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
641
|
<div class="metric-card">
|
|
923
642
|
<div class="card-title">Network Bandwidth</div>
|
|
924
643
|
<div class="card-value purple">${cards.bandwidth.toFixed(2)}<span class="unit">MB/s</span></div>
|
|
@@ -928,32 +647,23 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
928
647
|
<div class="card-value">${cards.ttfb.toFixed(1)}<span class="unit">ms</span></div>
|
|
929
648
|
</div>
|
|
930
649
|
<div class="metric-card">
|
|
931
|
-
<div class="card-title">
|
|
932
|
-
<div class="card-value
|
|
933
|
-
</div>
|
|
934
|
-
<div class="metric-card">
|
|
935
|
-
<div class="card-title">Stability (Std Dev)</div>
|
|
936
|
-
<div class="card-value">±${cards.stdDev.toFixed(1)}<span class="unit">ms</span></div>
|
|
650
|
+
<div class="card-title">DNS Lookup</div>
|
|
651
|
+
<div class="card-value">${cards.dns.toFixed(2)}<span class="unit">ms</span></div>
|
|
937
652
|
</div>
|
|
938
|
-
</div>
|
|
939
|
-
|
|
940
|
-
<div class="section-title">\u{1F310} Simulated Browser Core Web Vitals Equivalent</div>
|
|
941
|
-
<div class="cards-wrapper">
|
|
942
653
|
<div class="metric-card">
|
|
943
|
-
<div class="card-title">
|
|
944
|
-
<div class="card-value">${cards.
|
|
654
|
+
<div class="card-title">TCP Connect</div>
|
|
655
|
+
<div class="card-value">${cards.tcp.toFixed(2)}<span class="unit">ms</span></div>
|
|
945
656
|
</div>
|
|
946
657
|
<div class="metric-card">
|
|
947
|
-
<div class="card-title">
|
|
948
|
-
<div class="card-value">${cards.
|
|
658
|
+
<div class="card-title">TLS Handshake</div>
|
|
659
|
+
<div class="card-value">${cards.tls.toFixed(2)}<span class="unit">ms</span></div>
|
|
949
660
|
</div>
|
|
950
661
|
<div class="metric-card">
|
|
951
|
-
<div class="card-title">
|
|
952
|
-
<div class="card-value"
|
|
662
|
+
<div class="card-title">Stability (Std Dev)</div>
|
|
663
|
+
<div class="card-value">±${cards.stdDev.toFixed(1)}<span class="unit">ms</span></div>
|
|
953
664
|
</div>
|
|
954
665
|
</div>
|
|
955
666
|
|
|
956
|
-
<div class="section-title">\u26A1 Diagnostic Breakdowns</div>
|
|
957
667
|
<div class="top-layout-grid">
|
|
958
668
|
<div class="verdict-box">
|
|
959
669
|
<div class="verdict-title">\u{1F916} AI Stress-Testing Agent Verdict</div>
|
|
@@ -1025,8 +735,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
1025
735
|
<th>Throughput</th>
|
|
1026
736
|
<th>Avg Latency</th>
|
|
1027
737
|
<th>P95 Latency</th>
|
|
1028
|
-
<th>
|
|
1029
|
-
<th>RESET Rate</th>
|
|
738
|
+
<th>Error Rate</th>
|
|
1030
739
|
<th>Status</th>
|
|
1031
740
|
</tr>
|
|
1032
741
|
</thead>
|
|
@@ -1038,35 +747,24 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
1038
747
|
<td>${h.throughput.toFixed(0)} req/sec</td>
|
|
1039
748
|
<td>${h.avgLatency.toFixed(1)} ms</td>
|
|
1040
749
|
<td>${h.p95Latency.toFixed(1)} ms</td>
|
|
1041
|
-
<td
|
|
1042
|
-
<td>${h.econnresetRate.toFixed(2)}%</td>
|
|
750
|
+
<td>${(h.errorRate * 100).toFixed(2)}%</td>
|
|
1043
751
|
<td><span class="badge ${isPassed ? "badge-success" : "badge-fail"}">${isPassed ? "SLO PASS" : "SLO BREACH"}</span></td>
|
|
1044
752
|
</tr>`;
|
|
1045
753
|
}).join("")}
|
|
1046
754
|
</tbody>
|
|
1047
755
|
</table>
|
|
1048
756
|
</div>
|
|
1049
|
-
${seoPredictorPanelHtml}
|
|
1050
|
-
${connectionDiagnosticsPanelHtml}
|
|
1051
757
|
${liveAdjusterHtml}
|
|
1052
758
|
${rightSizingHtml}
|
|
1053
759
|
${logClustersHtml}
|
|
1054
760
|
</div>
|
|
1055
761
|
|
|
1056
762
|
<script>
|
|
1057
|
-
function copyToClipboard(containerId) {
|
|
1058
|
-
const text = document.getElementById(containerId).innerText;
|
|
1059
|
-
navigator.clipboard.writeText(text).then(() => {
|
|
1060
|
-
alert('Copied remediation snippet to clipboard!');
|
|
1061
|
-
}).catch(err => {
|
|
1062
|
-
console.error('Failed to copy text: ', err);
|
|
1063
|
-
});
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
763
|
const labels = ${JSON.stringify(labels)};
|
|
1067
764
|
const baseThroughput = ${cards.throughput};
|
|
1068
765
|
const baseLatency = ${cards.ttfb};
|
|
1069
766
|
|
|
767
|
+
// Live Concurrency Adjuster interactive client logic
|
|
1070
768
|
const slider = document.getElementById('concurrencySlider');
|
|
1071
769
|
const sliderVal = document.getElementById('sliderValue');
|
|
1072
770
|
const simThroughput = document.getElementById('simThroughput');
|
|
@@ -1151,8 +849,8 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
1151
849
|
},
|
|
1152
850
|
scales: {
|
|
1153
851
|
x: { grid: { color: '#1e293b' }, ticks: { color: '#64748b' } },
|
|
1154
|
-
|
|
1155
|
-
|
|
852
|
+
y_threads: { position: 'left', grid: { color: '#1e293b' }, ticks: { color: '#38bdf8' }, title: { display: true, text: 'VUs / Threads', color: '#38bdf8' } },
|
|
853
|
+
y_ttf: { position: 'right', grid: { drawOnChartArea: false }, ticks: { color: '#f43f5e' }, title: { display: true, text: 'TTF Index / Latency ms', color: '#f43f5e' } }
|
|
1156
854
|
}
|
|
1157
855
|
}
|
|
1158
856
|
});
|
|
@@ -1212,6 +910,6 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
1212
910
|
function printUsage() {
|
|
1213
911
|
console.log(`Blaze Core Performance Test CLI Engine
|
|
1214
912
|
Options:
|
|
1215
|
-
--threads <count> | --duration <seconds> | --agentic | --cluster-logs | --correlate
|
|
913
|
+
--threads <count> | --duration <seconds> | --agentic | --cluster-logs | --correlate`);
|
|
1216
914
|
}
|
|
1217
915
|
main().catch(console.error);
|
|
Binary file
|