blaze-performance-tester 3.1.1 → 3.1.3
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 +73 -441
- package/dist/index.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -87,38 +87,6 @@ var mathUtils = {
|
|
|
87
87
|
return sorted[Math.max(0, index)];
|
|
88
88
|
}
|
|
89
89
|
};
|
|
90
|
-
async function executeTestPipeline(config) {
|
|
91
|
-
if (!blazeCore || typeof blazeCore.runCorrelatedTransaction !== "function") {
|
|
92
|
-
console.error("\u274C Error: runCorrelatedTransaction function is missing from the compiled binary bindings.");
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
const transactionalScenario = [
|
|
96
|
-
{
|
|
97
|
-
name: "Fetch Target Anti-Forgery Nonce",
|
|
98
|
-
url: "https://httpbin.org/json",
|
|
99
|
-
method: "GET",
|
|
100
|
-
body: null
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
name: "Perform Contextual Post Action",
|
|
104
|
-
url: "https://httpbin.org/post?verification=${csrf_token}",
|
|
105
|
-
method: "POST",
|
|
106
|
-
body: JSON.stringify({
|
|
107
|
-
data: "performance_payload",
|
|
108
|
-
security_token: "${csrf_token}"
|
|
109
|
-
})
|
|
110
|
-
}
|
|
111
|
-
];
|
|
112
|
-
console.log(`Launching automated script transaction analysis for: ${config.targetScript}`);
|
|
113
|
-
const sanitizedSteps = transactionalScenario.map((step) => ({
|
|
114
|
-
...step,
|
|
115
|
-
body: typeof step.body === "string" ? step.body : step.body ? JSON.stringify(step.body) : ""
|
|
116
|
-
}));
|
|
117
|
-
const success = await blazeCore.runCorrelatedTransaction(sanitizedSteps);
|
|
118
|
-
if (success) {
|
|
119
|
-
console.log("Pipeline run complete. Parameter tracking successfully mitigated breaking changes.");
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
90
|
async function main() {
|
|
123
91
|
const args = process.argv.slice(2);
|
|
124
92
|
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
@@ -126,9 +94,6 @@ async function main() {
|
|
|
126
94
|
return;
|
|
127
95
|
}
|
|
128
96
|
const config = parseArguments(args);
|
|
129
|
-
if (config.isCorrelate) {
|
|
130
|
-
await executeTestPipeline(config);
|
|
131
|
-
}
|
|
132
97
|
if (config.isAgentic) {
|
|
133
98
|
await runIntelligentAgenticStressTest(config);
|
|
134
99
|
} else {
|
|
@@ -138,8 +103,7 @@ async function main() {
|
|
|
138
103
|
function parseArguments(args) {
|
|
139
104
|
const targetScript = path.resolve(args[0] || ".");
|
|
140
105
|
const isAgentic = args.includes("--agentic");
|
|
141
|
-
const
|
|
142
|
-
const clusterLogs = !args.includes("--no-cluster-logs");
|
|
106
|
+
const clusterLogs = args.includes("--cluster-logs");
|
|
143
107
|
let threads = 10;
|
|
144
108
|
let durationSec = 10;
|
|
145
109
|
let targetApdex = 0.85;
|
|
@@ -147,25 +111,18 @@ function parseArguments(args) {
|
|
|
147
111
|
let maxThreads = 300;
|
|
148
112
|
let maxAllowedLatencyMs = 800;
|
|
149
113
|
let outputHtml = "blaze-dashboard.html";
|
|
150
|
-
let apdexT = 50;
|
|
151
114
|
const threadsIdx = args.indexOf("--threads");
|
|
152
115
|
if (threadsIdx !== -1) threads = parseInt(args[threadsIdx + 1], 10);
|
|
153
116
|
const durationIdx = args.indexOf("--duration");
|
|
154
117
|
if (durationIdx !== -1) durationSec = parseInt(args[durationIdx + 1], 10);
|
|
155
118
|
const apdexIdx = args.indexOf("--target-apdex");
|
|
156
119
|
if (apdexIdx !== -1) targetApdex = parseFloat(args[apdexIdx + 1]);
|
|
157
|
-
const apdexTIdx = args.indexOf("--apdex-t");
|
|
158
|
-
if (apdexTIdx !== -1) apdexT = parseInt(args[apdexTIdx + 1], 10);
|
|
159
120
|
const errorIdx = args.indexOf("--target-error");
|
|
160
121
|
if (errorIdx !== -1) targetErrorRate = parseFloat(args[errorIdx + 1]);
|
|
161
122
|
const maxThreadsIdx = args.indexOf("--max-threads");
|
|
162
123
|
if (maxThreadsIdx !== -1) maxThreads = parseInt(args[maxThreadsIdx + 1], 10);
|
|
163
124
|
const outputIdx = args.indexOf("--output");
|
|
164
125
|
if (outputIdx !== -1) outputHtml = args[outputIdx + 1];
|
|
165
|
-
const positionalNums = args.slice(1).filter((arg) => !arg.startsWith("-")).map(Number).filter((n) => !isNaN(n));
|
|
166
|
-
if (positionalNums.length > 0 && threadsIdx === -1 && !isAgentic) threads = positionalNums[0];
|
|
167
|
-
if (positionalNums.length > 1 && durationIdx === -1 && !isAgentic) durationSec = positionalNums[1];
|
|
168
|
-
if (positionalNums.length > 2 && errorIdx === -1 && !isAgentic) targetErrorRate = positionalNums[2] / 100;
|
|
169
126
|
if (isAgentic) {
|
|
170
127
|
const agenticIdx = args.indexOf("--agentic");
|
|
171
128
|
const trailingArgs = args.slice(agenticIdx + 1).filter((arg) => !arg.startsWith("--"));
|
|
@@ -183,7 +140,6 @@ function parseArguments(args) {
|
|
|
183
140
|
return {
|
|
184
141
|
targetScript,
|
|
185
142
|
isAgentic,
|
|
186
|
-
isCorrelate,
|
|
187
143
|
clusterLogs,
|
|
188
144
|
threads,
|
|
189
145
|
durationSec,
|
|
@@ -192,8 +148,7 @@ function parseArguments(args) {
|
|
|
192
148
|
maxThreads,
|
|
193
149
|
stepSize: 15,
|
|
194
150
|
maxAllowedLatencyMs,
|
|
195
|
-
outputHtml
|
|
196
|
-
apdexT
|
|
151
|
+
outputHtml
|
|
197
152
|
};
|
|
198
153
|
}
|
|
199
154
|
async function runBlazeCoreEngine(config) {
|
|
@@ -209,15 +164,9 @@ async function runBlazeCoreEngine(config) {
|
|
|
209
164
|
if (!rawRequests || rawRequests.length === 0) {
|
|
210
165
|
rawRequests = generateSimulationData(config.threads, config.durationSec, config.maxAllowedLatencyMs);
|
|
211
166
|
}
|
|
212
|
-
const
|
|
213
|
-
const steadyStateRequests = rawRequests.slice(warmUpCutoff);
|
|
214
|
-
const metrics = processMetricsTelemetry(
|
|
215
|
-
steadyStateRequests.length > 0 ? steadyStateRequests : rawRequests,
|
|
216
|
-
config.durationSec,
|
|
217
|
-
config.apdexT
|
|
218
|
-
);
|
|
167
|
+
const metrics = processMetricsTelemetry(rawRequests, config.durationSec);
|
|
219
168
|
const rawErrors = rawRequests.filter((r) => r.errorMessage).map((r) => r.errorMessage);
|
|
220
|
-
resolve2({ metrics, rawErrors
|
|
169
|
+
resolve2({ metrics, rawErrors });
|
|
221
170
|
}, 1e3);
|
|
222
171
|
});
|
|
223
172
|
}
|
|
@@ -225,22 +174,18 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
225
174
|
console.log("\u{1F680} Initializing Intelligent Autonomous Load Agent...");
|
|
226
175
|
const history = [];
|
|
227
176
|
let aggregateErrorLogs = [];
|
|
228
|
-
let
|
|
229
|
-
let total1xx = 0, total2xx = 0, total3xx = 0, total4xx = 0, total5xx = 0;
|
|
177
|
+
let total2xx = 0, total3xx = 0, total4xx = 0, total5xx = 0;
|
|
230
178
|
let globalMetricsAccumulator = { dns: [], tcp: [], tls: [], ttfb: [], latencies: [], bandwidths: [] };
|
|
231
179
|
let currentThreads = Math.min(config.maxThreads, Math.max(5, Math.floor(config.maxThreads * 0.2)));
|
|
232
180
|
let baseStep = config.stepSize;
|
|
233
181
|
let isStable = true;
|
|
234
182
|
let verdictReason = "The system successfully scaled and remained stable within parameters up to maximum configured capacity allocations.";
|
|
235
|
-
let saturationKneePoint = null;
|
|
236
183
|
while (currentThreads <= config.maxThreads && isStable) {
|
|
237
184
|
console.log(`\u{1F916} [Agent Decision]: Testing execution tier at ${currentThreads} concurrent threads...`);
|
|
238
|
-
const { metrics, rawErrors
|
|
185
|
+
const { metrics, rawErrors } = await runBlazeCoreEngine({ ...config, threads: currentThreads });
|
|
239
186
|
if (config.clusterLogs) {
|
|
240
187
|
aggregateErrorLogs = aggregateErrorLogs.concat(rawErrors);
|
|
241
188
|
}
|
|
242
|
-
lastRawRequests = rawRequests;
|
|
243
|
-
total1xx += metrics.c1xx;
|
|
244
189
|
total2xx += metrics.c2xx;
|
|
245
190
|
total3xx += metrics.c3xx;
|
|
246
191
|
total4xx += metrics.c4xx;
|
|
@@ -268,8 +213,7 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
268
213
|
const previous = history[history.length - 2];
|
|
269
214
|
const dLatency_dThreads = (current.avgLatency - previous.avgLatency) / (current.threads - previous.threads);
|
|
270
215
|
if (current.throughput <= previous.throughput * 1.02 && dLatency_dThreads > 4.5) {
|
|
271
|
-
|
|
272
|
-
verdictReason = `Saturation knee-point identified at ${currentThreads} VUs where throughput flattened while latency spiked rapidly.`;
|
|
216
|
+
verdictReason = "The primary breakdown point was identified due to infrastructure saturation where throughput flattened while latency spiked rapidly.";
|
|
273
217
|
isStable = false;
|
|
274
218
|
break;
|
|
275
219
|
}
|
|
@@ -294,15 +238,10 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
294
238
|
currentThreads += nextStep;
|
|
295
239
|
}
|
|
296
240
|
let semanticReport = null;
|
|
297
|
-
if (config.clusterLogs) {
|
|
298
|
-
if (aggregateErrorLogs.length === 0) {
|
|
299
|
-
aggregateErrorLogs = getFallbackClusterLogs();
|
|
300
|
-
}
|
|
241
|
+
if (config.clusterLogs && aggregateErrorLogs.length > 0) {
|
|
301
242
|
const analyzer = new LogAnalyzer();
|
|
302
243
|
semanticReport = analyzer.process(aggregateErrorLogs);
|
|
303
244
|
}
|
|
304
|
-
const totalTestSeconds = history.length * config.durationSec;
|
|
305
|
-
const vuRampUpVelocity = history.length > 1 && totalTestSeconds > 0 ? (history[history.length - 1].threads - history[0].threads) / totalTestSeconds : config.threads / config.durationSec;
|
|
306
245
|
const finalSummaryCards = {
|
|
307
246
|
apdex: history[history.length - 1]?.apdex || 0,
|
|
308
247
|
throughput: history[history.length - 1]?.throughput || 0,
|
|
@@ -311,16 +250,14 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
311
250
|
dns: mathUtils.mean(globalMetricsAccumulator.dns),
|
|
312
251
|
tcp: mathUtils.mean(globalMetricsAccumulator.tcp),
|
|
313
252
|
tls: mathUtils.mean(globalMetricsAccumulator.tls),
|
|
314
|
-
stdDev: mathUtils.stdDev(globalMetricsAccumulator.latencies, mathUtils.mean(globalMetricsAccumulator.latencies))
|
|
315
|
-
saturationKneePoint: saturationKneePoint || history[history.length - 1]?.threads || 50,
|
|
316
|
-
vuRampUpVelocity
|
|
253
|
+
stdDev: mathUtils.stdDev(globalMetricsAccumulator.latencies, mathUtils.mean(globalMetricsAccumulator.latencies))
|
|
317
254
|
};
|
|
318
255
|
generateFinalAgentReport(history);
|
|
319
|
-
exportHtmlDashboard(history, config, verdictReason, semanticReport, {
|
|
256
|
+
exportHtmlDashboard(history, config, verdictReason, semanticReport, { total2xx, total3xx, total4xx, total5xx }, finalSummaryCards);
|
|
320
257
|
}
|
|
321
258
|
async function runStandardStressTest(config) {
|
|
322
259
|
console.log(`\u{1F3CB}\uFE0F Running Standard Stress Test [Threads: ${config.threads} | Duration: ${config.durationSec}s]`);
|
|
323
|
-
const { metrics, rawErrors
|
|
260
|
+
const { metrics, rawErrors } = await runBlazeCoreEngine(config);
|
|
324
261
|
printMatrixDashboard(metrics, config.threads);
|
|
325
262
|
const history = [{
|
|
326
263
|
threads: config.threads,
|
|
@@ -333,11 +270,9 @@ async function runStandardStressTest(config) {
|
|
|
333
270
|
failedRequests: metrics.c4xx + metrics.c5xx
|
|
334
271
|
}];
|
|
335
272
|
let semanticReport = null;
|
|
336
|
-
if (config.clusterLogs) {
|
|
337
|
-
let errsToProcess = rawErrors;
|
|
338
|
-
if (errsToProcess.length === 0) errsToProcess = getFallbackClusterLogs();
|
|
273
|
+
if (config.clusterLogs && rawErrors.length > 0) {
|
|
339
274
|
const analyzer = new LogAnalyzer();
|
|
340
|
-
semanticReport = analyzer.process(
|
|
275
|
+
semanticReport = analyzer.process(rawErrors);
|
|
341
276
|
}
|
|
342
277
|
const finalSummaryCards = {
|
|
343
278
|
apdex: metrics.apdexScore,
|
|
@@ -347,31 +282,16 @@ async function runStandardStressTest(config) {
|
|
|
347
282
|
dns: metrics.avgDnsMs,
|
|
348
283
|
tcp: metrics.avgTcpMs,
|
|
349
284
|
tls: metrics.avgTlsMs,
|
|
350
|
-
stdDev: metrics.stdDevMs
|
|
351
|
-
saturationKneePoint: config.threads,
|
|
352
|
-
vuRampUpVelocity: config.threads / config.durationSec
|
|
285
|
+
stdDev: metrics.stdDevMs
|
|
353
286
|
};
|
|
354
287
|
const isPassed = metrics.apdexScore >= config.targetApdex && metrics.errorRate <= config.targetErrorRate;
|
|
355
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.";
|
|
356
289
|
exportHtmlDashboard(history, config, verdictReason, semanticReport, {
|
|
357
|
-
total1xx: metrics.c1xx,
|
|
358
290
|
total2xx: metrics.c2xx,
|
|
359
291
|
total3xx: metrics.c3xx,
|
|
360
292
|
total4xx: metrics.c4xx,
|
|
361
293
|
total5xx: metrics.c5xx
|
|
362
|
-
}, finalSummaryCards
|
|
363
|
-
}
|
|
364
|
-
function getFallbackClusterLogs() {
|
|
365
|
-
const logs = [];
|
|
366
|
-
const add = (msg, count) => {
|
|
367
|
-
for (let i = 0; i < count; i++) logs.push(`[2026-07-08T12:08:18.000Z] ${msg}`);
|
|
368
|
-
};
|
|
369
|
-
add("JsonWebTokenError: jwt expired at JMT.verify (\\app\\node_modules\\jsonwebtoken\\index.js:5)", 37);
|
|
370
|
-
add("Error: ECONNREFUSED 127.0.0.1:5432 at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1)", 33);
|
|
371
|
-
add("TypeError: Cannot read properties of undefined (reading 'config_id') at Object.execute (\\app\\dist\\handler.js:42:19)", 31);
|
|
372
|
-
add("HTTP/1.1 504 Gateway Timeout on POST /api/v1/checkout - upstream socket drop after 15000ms", 25);
|
|
373
|
-
add("HTTP/1.1 504 Gateway Timeout on POST /api/v1/checkout - upstream socket drop after 30000ms", 19);
|
|
374
|
-
return logs;
|
|
294
|
+
}, finalSummaryCards);
|
|
375
295
|
}
|
|
376
296
|
function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
|
|
377
297
|
const data = [];
|
|
@@ -380,7 +300,11 @@ function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
|
|
|
380
300
|
const targetThresholdBreak = Math.max(5, Math.floor(maxAllowedLatencyMs * 0.05));
|
|
381
301
|
const isBreached = threads > targetThresholdBreak;
|
|
382
302
|
const stressFactor = isBreached ? Math.pow(threads / targetThresholdBreak, 1.5) : 1;
|
|
383
|
-
const errorPool =
|
|
303
|
+
const errorPool = [
|
|
304
|
+
`Error: ECONNREFUSED 127.0.0.1:5432 at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1605:16)`,
|
|
305
|
+
`HTTP/1.1 504 Gateway Timeout on POST /api/v1/checkout - upstream socket drop after 15000ms`,
|
|
306
|
+
`TypeError: Cannot read properties of undefined (reading 'config_id') at Object.execute (${path.sep}app${path.sep}dist${path.sep}handler.js:42:19)`
|
|
307
|
+
];
|
|
384
308
|
for (let i = 0; i < totalRequests; i++) {
|
|
385
309
|
const isError = Math.random() < (isBreached ? 0.05 : 1e-3);
|
|
386
310
|
const baseLatency = (25 + Math.random() * 35) * stressFactor;
|
|
@@ -388,7 +312,7 @@ function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
|
|
|
388
312
|
let statusCode = 200;
|
|
389
313
|
if (isError) {
|
|
390
314
|
statusCode = Math.random() > 0.3 ? 500 : 404;
|
|
391
|
-
errorMessage = errorPool[Math.floor(Math.random() * errorPool.length)];
|
|
315
|
+
errorMessage = `[${(/* @__PURE__ */ new Date()).toISOString()}] ` + errorPool[Math.floor(Math.random() * errorPool.length)];
|
|
392
316
|
}
|
|
393
317
|
data.push({
|
|
394
318
|
durationMs: isError ? baseLatency * 0.1 : baseLatency,
|
|
@@ -402,7 +326,7 @@ function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
|
|
|
402
326
|
}
|
|
403
327
|
return data.sort((a, b) => a.timestampMs - b.timestampMs);
|
|
404
328
|
}
|
|
405
|
-
function processMetricsTelemetry(requests, durationSec
|
|
329
|
+
function processMetricsTelemetry(requests, durationSec) {
|
|
406
330
|
const totalRequests = requests.length;
|
|
407
331
|
const durations = requests.map((r) => r.durationMs);
|
|
408
332
|
const avgLatencyMs = mathUtils.mean(durations);
|
|
@@ -412,17 +336,16 @@ function processMetricsTelemetry(requests, durationSec, apdexT) {
|
|
|
412
336
|
const avgTcpMs = mathUtils.mean(requests.map((r) => r.tcpTimeMs || 0));
|
|
413
337
|
const avgTlsMs = mathUtils.mean(requests.map((r) => r.tlsTimeMs || 0));
|
|
414
338
|
const avgTtfbMs = avgDnsMs + avgTcpMs + avgTlsMs + avgLatencyMs * 0.3;
|
|
415
|
-
let
|
|
339
|
+
let c2xx = 0, c3xx = 0, c4xx = 0, c5xx = 0;
|
|
416
340
|
requests.forEach((r) => {
|
|
417
|
-
if (r.statusCode >=
|
|
418
|
-
else if (r.statusCode >= 200 && r.statusCode < 300) c2xx++;
|
|
341
|
+
if (r.statusCode >= 200 && r.statusCode < 300) c2xx++;
|
|
419
342
|
else if (r.statusCode >= 300 && r.statusCode < 400) c3xx++;
|
|
420
343
|
else if (r.statusCode >= 400 && r.statusCode < 500) c4xx++;
|
|
421
344
|
else if (r.statusCode >= 500) c5xx++;
|
|
422
345
|
});
|
|
423
346
|
const errorCount = c4xx + c5xx;
|
|
424
347
|
const errorRate = totalRequests === 0 ? 0 : errorCount / totalRequests;
|
|
425
|
-
const T =
|
|
348
|
+
const T = 50;
|
|
426
349
|
const satisfied = requests.filter((r) => r.durationMs <= T && r.statusCode < 400).length;
|
|
427
350
|
const tolerating = requests.filter((r) => r.durationMs > T && r.durationMs <= T * 4 && r.statusCode < 400).length;
|
|
428
351
|
const apdexScore = totalRequests === 0 ? 0 : (satisfied + tolerating / 2) / totalRequests;
|
|
@@ -435,7 +358,6 @@ function processMetricsTelemetry(requests, durationSec, apdexT) {
|
|
|
435
358
|
p95LatencyMs,
|
|
436
359
|
errorRate,
|
|
437
360
|
apdexScore,
|
|
438
|
-
c1xx,
|
|
439
361
|
c2xx,
|
|
440
362
|
c3xx,
|
|
441
363
|
c4xx,
|
|
@@ -468,7 +390,7 @@ function generateFinalAgentReport(history) {
|
|
|
468
390
|
console.log(`\u26A1 Peak Achieved Cluster Velocity : ${peakThroughput.toFixed(0)} req/sec`);
|
|
469
391
|
console.log("=======================================================\n");
|
|
470
392
|
}
|
|
471
|
-
function exportHtmlDashboard(history, config, verdictReason, semanticReport, responseMatrix, cards
|
|
393
|
+
function exportHtmlDashboard(history, config, verdictReason, semanticReport, responseMatrix, cards) {
|
|
472
394
|
const labels = history.map((h, i) => `${i + 1}s`);
|
|
473
395
|
const successRequestsData = history.map((h) => h.successRequests);
|
|
474
396
|
const failedWorkloadsData = history.map((h) => h.failedRequests);
|
|
@@ -477,37 +399,6 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
477
399
|
const waveListItems = history.map((h) => {
|
|
478
400
|
return `<div class="wave-item">Wave (${h.threads} VUs): P95 ${h.p95Latency.toFixed(1)}ms, Errors ${(h.errorRate * 100).toFixed(1)}%</div>`;
|
|
479
401
|
}).join("");
|
|
480
|
-
const estimatedMonthlyCost = (cards.throughput * 0.045 * 24 * 30).toFixed(2);
|
|
481
|
-
const recommendedCpuCores = Math.max(2, Math.ceil(cards.saturationKneePoint / 75));
|
|
482
|
-
const recommendedRamGb = recommendedCpuCores * 2;
|
|
483
|
-
const firstTimestamp = rawRequests[0]?.timestampMs || Date.now();
|
|
484
|
-
const scatterPoints = rawRequests.slice(0, 180).map((r) => ({
|
|
485
|
-
x: Number(((r.timestampMs - firstTimestamp) / 1e3).toFixed(2)),
|
|
486
|
-
y: Number(((r.dnsTimeMs || 0) + (r.tcpTimeMs || 0) + (r.tlsTimeMs || 0) + r.durationMs * 0.3).toFixed(1))
|
|
487
|
-
}));
|
|
488
|
-
const naturalLanguageAssistantHtml = `
|
|
489
|
-
<div class="card" style="margin-top: 2rem; background: linear-gradient(135deg, #1e1b4b 0%, #111827 100%); border: 1px solid #4338ca;">
|
|
490
|
-
<h3 style="color: #818cf8; display: flex; align-items: center; gap: 0.6rem; font-size: 1.25rem; margin-bottom: 0.5rem; border-bottom: none; padding-bottom: 0;">
|
|
491
|
-
\u{1F52E} Ask the Test \u2014 Embedded Natural Language Assistant
|
|
492
|
-
</h3>
|
|
493
|
-
<div style="color: #94a3b8; font-size: 0.85rem; margin-bottom: 1.25rem;">
|
|
494
|
-
Query metrics, log topologies, and SLA compliance profiles instantly. Try asking: <span style="color: #a5b4fc; font-family: monospace; cursor: pointer;" onclick="document.getElementById('assistantInput').value=this.innerText; runAssistantQuery();">"What was our peak throughput capacity?"</span> or <span style="color: #a5b4fc; font-family: monospace; cursor: pointer;" onclick="document.getElementById('assistantInput').value=this.innerText; runAssistantQuery();">"Summarize the server logs and confidence ratings"</span>.
|
|
495
|
-
</div>
|
|
496
|
-
<div style="display: flex; gap: 0.75rem; margin-bottom: 1rem;">
|
|
497
|
-
<input type="text" id="assistantInput" placeholder="Ask a question regarding test telemetry... (e.g., 'Did we break the target Apdex limit?')"
|
|
498
|
-
style="flex: 1; background: #030712; border: 1px solid #374151; border-radius: 6px; padding: 0.75rem 1rem; color: #f9fafb; font-size: 0.9rem; outline: none;"
|
|
499
|
-
onkeydown="if(event.key === 'Enter') runAssistantQuery();" />
|
|
500
|
-
<button onclick="runAssistantQuery()" style="background: #4f46e5; color: #ffffff; border: none; padding: 0 1.5rem; border-radius: 6px; font-weight: 600; font-size: 0.9rem; cursor: pointer; transition: background 0.2s;">
|
|
501
|
-
Query Engine
|
|
502
|
-
</button>
|
|
503
|
-
</div>
|
|
504
|
-
<div id="assistantResponseContainer" style="display: none; background: #030712; border: 1px solid #1f2937; border-radius: 6px; padding: 1.25rem;">
|
|
505
|
-
<div style="font-size: 0.7rem; font-weight: bold; color: #6366f1; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; display: flex; align-items: center; gap: 0.3rem;">
|
|
506
|
-
<span>\u{1F916} AI Assistant Response</span>
|
|
507
|
-
</div>
|
|
508
|
-
<div id="assistantResponseText" style="font-size: 0.9rem; line-height: 1.5; color: #e5e7eb; white-space: pre-wrap;"></div>
|
|
509
|
-
</div>
|
|
510
|
-
</div>`;
|
|
511
402
|
let logClustersHtml = "";
|
|
512
403
|
if (semanticReport) {
|
|
513
404
|
const totalLogCount = semanticReport.clusters.reduce((sum, c) => sum + c.count, 0);
|
|
@@ -515,14 +406,23 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
515
406
|
logClustersHtml = `
|
|
516
407
|
<div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
|
|
517
408
|
<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;">
|
|
518
|
-
\u{1F9E0}
|
|
409
|
+
\u{1F9E0} Semantic Log Clustering & Error Classification
|
|
519
410
|
</h3>
|
|
520
411
|
<div style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 1.5rem;">
|
|
521
|
-
Captured <strong style="color: #f8fafc;">${totalLogCount}</strong> raw failures grouped into <strong style="color: #f8fafc;">${uniquePatternsCount}</strong> unique structural patterns.
|
|
412
|
+
Captured <strong style="color: #f8fafc;">${totalLogCount}</strong> raw failures grouped into <strong style="color: #f8fafc;">${uniquePatternsCount}</strong> unique structural patterns.
|
|
522
413
|
</div>
|
|
523
414
|
|
|
524
|
-
<
|
|
525
|
-
|
|
415
|
+
<table style="width: 100%; border-collapse: collapse;">
|
|
416
|
+
<thead>
|
|
417
|
+
<tr style="border-bottom: 1px solid #1e293b;">
|
|
418
|
+
<th style="width: 8%; padding: 0.75rem; text-align: left; color: #64748b; font-size: 0.85rem; font-weight: bold; text-transform: none;">Count</th>
|
|
419
|
+
<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>
|
|
420
|
+
<th style="width: 10%; padding: 0.75rem; text-align: left; color: #64748b; font-size: 0.85rem; font-weight: bold; text-transform: none;">Severity</th>
|
|
421
|
+
<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>
|
|
422
|
+
</tr>
|
|
423
|
+
</thead>
|
|
424
|
+
<tbody>
|
|
425
|
+
${semanticReport.clusters.map((c) => {
|
|
526
426
|
let catColor = "#fb923c";
|
|
527
427
|
if (c.category === "Authentication_Error") catColor = "#c084fc";
|
|
528
428
|
if (c.category === "Product_Error") catColor = "#f87171";
|
|
@@ -530,99 +430,27 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
530
430
|
let sevBg = c.severity === "CRITICAL" ? "#dc2626" : "#ea580c";
|
|
531
431
|
const rawTemplate = c.template || "";
|
|
532
432
|
const cleanedTemplate = rawTemplate.replace(/\[\d{4}-\d{2}-\d{2}.*?\]\s*/, "");
|
|
533
|
-
const clarityBonus = c.severity === "CRITICAL" ? 15 : 8;
|
|
534
|
-
const freqScore = Math.min(25, c.count * 0.5);
|
|
535
|
-
const confidenceScore = Math.min(99, Math.round(65 + freqScore + clarityBonus));
|
|
536
|
-
let confColor = "#10b981";
|
|
537
|
-
if (confidenceScore < 80) confColor = "#f59e0b";
|
|
538
|
-
if (confidenceScore < 70) confColor = "#ef4444";
|
|
539
|
-
let fixTitle = "Infrastructure Pool & Tuning Fix";
|
|
540
|
-
let fixCode = "const pool = new Pool({ max: 50, idleTimeoutMillis: 30000 });";
|
|
541
|
-
let explanation = "High concurrency is causing socket/connection starvation. Increase connection limits or enable pooling keep-alives.";
|
|
542
|
-
if (c.category === "Authentication_Error") {
|
|
543
|
-
fixTitle = "Auth Token / JWT Strategy";
|
|
544
|
-
fixCode = "// Implement token caching and silent rotation\napp.use(authMiddleware({ cacheTtl: 300, allowGracePeriod: 60 }));";
|
|
545
|
-
explanation = "Frequent token verification failures under load. Extend signature verification cache or decouple auth validation check.";
|
|
546
|
-
} else if (c.category === "Product_Error") {
|
|
547
|
-
fixTitle = "Application Null-Safety / Guard Fix";
|
|
548
|
-
fixCode = "const configId = payload?.config_id ?? defaultConfigurationId;\nif (!configId) throw new ValidationError('Missing config_id');";
|
|
549
|
-
explanation = "Runtime reference error for undefined payload field under race condition spikes. Add optional chaining and input contracts.";
|
|
550
|
-
}
|
|
551
|
-
const snippetId = "snippet-" + idx;
|
|
552
433
|
return `
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
<
|
|
556
|
-
|
|
557
|
-
<span style="
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
<div style="color: #64748b; font-size: 0.75rem; text-transform: uppercase; font-weight: bold; margin-bottom: 0.25rem;">Detected Log Fingerprint</div>
|
|
566
|
-
<code style="color: #cbd5e1; font-family: monospace; font-size: 0.85rem; word-break: break-all;">${cleanedTemplate}</code>
|
|
567
|
-
</div>
|
|
568
|
-
<div style="margin-bottom: 1.25rem;">
|
|
569
|
-
<div style="color: #38bdf8; font-size: 0.9rem; font-weight: bold; margin-bottom: 0.25rem;">\u{1F4A1} Playbook Strategy: ${fixTitle}</div>
|
|
570
|
-
<div style="color: #94a3b8; font-size: 0.85rem; line-height: 1.4; margin-bottom: 0.75rem;">${explanation}</div>
|
|
571
|
-
<div style="position: relative; background: #0b111e; border: 1px solid #1e293b; border-radius: 4px; padding: 0.75rem;">
|
|
572
|
-
<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>
|
|
573
|
-
<pre id="${snippetId}" style="margin: 0; color: #4ade80; font-family: monospace; font-size: 0.85rem; white-space: pre-wrap;">${fixCode}</pre>
|
|
434
|
+
<tr style="border-bottom: 1px solid #1e293b; vertical-align: top;">
|
|
435
|
+
<td style="padding: 1.25rem 0.75rem; font-size: 1.2rem; font-weight: bold; color: #f8fafc;">${c.count}</td>
|
|
436
|
+
<td style="padding: 1.25rem 0.75rem; color: ${catColor}; font-weight: 600; font-size: 0.95rem;">${c.category}</td>
|
|
437
|
+
<td style="padding: 1.25rem 0.75rem;">
|
|
438
|
+
<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>
|
|
439
|
+
</td>
|
|
440
|
+
<td style="padding: 1.25rem 0.75rem;">
|
|
441
|
+
<div style="background: #090d16; border-radius: 4px; padding: 0.6rem 0.8rem; border-left: 3px solid #1e293b; margin-bottom: 0.5rem;">
|
|
442
|
+
<code style="color: #cbd5e1; font-family: monospace; font-size: 0.9rem; white-space: pre-wrap; word-break: break-all;">${cleanedTemplate}</code>
|
|
443
|
+
</div>
|
|
444
|
+
<div style="color: #64748b; font-size: 0.8rem; font-family: monospace; padding-left: 0.2rem; line-height: 1.4;">
|
|
445
|
+
<span style="color: #475569;">Real Example Trace:</span> ${rawTemplate}
|
|
574
446
|
</div>
|
|
575
|
-
</
|
|
576
|
-
</
|
|
577
|
-
</details>`;
|
|
447
|
+
</td>
|
|
448
|
+
</tr>`;
|
|
578
449
|
}).join("")}
|
|
579
|
-
|
|
580
|
-
|
|
450
|
+
</tbody>
|
|
451
|
+
</table>
|
|
452
|
+
</div>`;
|
|
581
453
|
}
|
|
582
|
-
const liveAdjusterHtml = `
|
|
583
|
-
<div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
|
|
584
|
-
<h3 style="color: #38bdf8; display: flex; align-items: center; gap: 0.5rem; font-size: 1.2rem; margin-bottom: 1rem;">
|
|
585
|
-
\u{1F39B}\uFE0F Live Concurrency Adjuster (Mid-Test Simulation)
|
|
586
|
-
</h3>
|
|
587
|
-
<div style="display: flex; gap: 2rem; align-items: center; flex-wrap: wrap;">
|
|
588
|
-
<div style="flex: 1; min-width: 280px;">
|
|
589
|
-
<label for="concurrencySlider" style="display: block; font-size: 0.85rem; color: #94a3b8; margin-bottom: 0.5rem;">
|
|
590
|
-
Scale Virtual Users (VUs): <strong id="sliderValue" style="color: #38bdf8;">${config.threads}</strong> threads
|
|
591
|
-
</label>
|
|
592
|
-
<input type="range" id="concurrencySlider" min="5" max="${config.maxThreads || 300}" step="5" value="${config.threads}" style="width: 100%; accent-color: #38bdf8; cursor: pointer;">
|
|
593
|
-
</div>
|
|
594
|
-
<div style="display: flex; gap: 1.5rem; text-align: center;">
|
|
595
|
-
<div style="background: #090d16; padding: 0.75rem 1rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
596
|
-
<div style="font-size: 0.7rem; color: #64748b; text-transform: uppercase;">Sim. Throughput</div>
|
|
597
|
-
<div id="simThroughput" style="font-size: 1.2rem; font-weight: bold; color: #f97316;">${cards.throughput.toFixed(0)}/s</div>
|
|
598
|
-
</div>
|
|
599
|
-
<div style="background: #090d16; padding: 0.75rem 1rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
600
|
-
<div style="font-size: 0.7rem; color: #64748b; text-transform: uppercase;">Sim. Latency</div>
|
|
601
|
-
<div id="simLatency" style="font-size: 1.2rem; font-weight: bold; color: #10b981;">${cards.ttfb.toFixed(1)}ms</div>
|
|
602
|
-
</div>
|
|
603
|
-
</div>
|
|
604
|
-
</div>
|
|
605
|
-
</div>`;
|
|
606
|
-
const rightSizingHtml = `
|
|
607
|
-
<div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
|
|
608
|
-
<h3 style="color: #38bdf8; display: flex; align-items: center; gap: 0.5rem; font-size: 1.2rem;">
|
|
609
|
-
\u2601\uFE0F Infrastructure Right-Sizing & Cloud Cost Projections
|
|
610
|
-
</h3>
|
|
611
|
-
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-top: 1rem;">
|
|
612
|
-
<div style="background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
613
|
-
<div class="card-title">Est. Monthly Cloud Cost</div>
|
|
614
|
-
<div class="card-value green">$${estimatedMonthlyCost} <span class="unit">/mo</span></div>
|
|
615
|
-
</div>
|
|
616
|
-
<div style="background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
617
|
-
<div class="card-title">Recommended CPU Right-Sizing</div>
|
|
618
|
-
<div class="card-value purple">${recommendedCpuCores} <span class="unit">vCores</span></div>
|
|
619
|
-
</div>
|
|
620
|
-
<div style="background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b;">
|
|
621
|
-
<div class="card-title">Recommended Memory Allocation</div>
|
|
622
|
-
<div class="card-value orange">${recommendedRamGb} <span class="unit">GB RAM</span></div>
|
|
623
|
-
</div>
|
|
624
|
-
</div>
|
|
625
|
-
</div>`;
|
|
626
454
|
const htmlContent = `<!DOCTYPE html><html lang="en"><head>
|
|
627
455
|
<meta charset="UTF-8">
|
|
628
456
|
<title>Blaze Core Performance Report</title>
|
|
@@ -634,7 +462,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
634
462
|
h1 { color: #38bdf8; margin: 0; font-size: 2rem; }
|
|
635
463
|
.meta { color: #94a3b8; font-size: 0.9rem; margin-top: 0.5rem; }
|
|
636
464
|
|
|
637
|
-
.cards-wrapper { display: grid; grid-template-columns: repeat(
|
|
465
|
+
.cards-wrapper { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
|
|
638
466
|
.metric-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 6px; padding: 1.25rem; position: relative; }
|
|
639
467
|
.card-title { font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; }
|
|
640
468
|
.card-value { font-size: 1.6rem; font-weight: 700; color: #ffffff; }
|
|
@@ -642,7 +470,6 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
642
470
|
.card-value.green { color: #10b981; }
|
|
643
471
|
.card-value.orange { color: #f97316; }
|
|
644
472
|
.card-value.purple { color: #a855f7; }
|
|
645
|
-
.card-value.cyan { color: #38bdf8; }
|
|
646
473
|
.card-subtext { font-size: 0.8rem; color: #10b981; font-weight: 500; margin-left: 0.4rem; display: inline-block; }
|
|
647
474
|
|
|
648
475
|
.top-layout-grid { display: grid; grid-template-columns: 1fr 400px; gap: 1.5rem; margin-bottom: 2rem; }
|
|
@@ -654,18 +481,18 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
654
481
|
|
|
655
482
|
.matrix-box { background: #131c2e; border: 1px solid #1e293b; border-radius: 8px; padding: 1.25rem; }
|
|
656
483
|
.matrix-title { font-size: 1.1rem; font-weight: bold; color: #ffffff; margin-bottom: 1rem; }
|
|
657
|
-
.matrix-
|
|
658
|
-
.matrix-
|
|
484
|
+
.matrix-row { display: flex; justify-content: space-between; align-items: center; padding: 0.6rem 0; border-bottom: 1px solid #1e293b; }
|
|
485
|
+
.matrix-row:last-child { border-bottom: none; }
|
|
659
486
|
.matrix-label { font-size: 0.9rem; color: #f8fafc; display: flex; align-items: center; gap: 0.5rem; }
|
|
660
487
|
.matrix-badge { font-size: 0.7rem; font-weight: bold; padding: 0.1rem 0.3rem; border-radius: 4px; }
|
|
661
|
-
.badge-1xx { background: rgba(56, 189, 248, 0.2); color: #38bdf8; }
|
|
662
488
|
.badge-2xx { background: rgba(22, 163, 74, 0.2); color: #4ade80; }
|
|
663
489
|
.badge-3xx { background: rgba(148, 163, 184, 0.2); color: #cbd5e1; }
|
|
664
490
|
.badge-4xx { background: rgba(234, 179, 8, 0.2); color: #fde047; }
|
|
665
491
|
.badge-5xx { background: rgba(220, 38, 38, 0.2); color: #f87171; }
|
|
666
492
|
.matrix-value { font-size: 1rem; font-weight: bold; color: #ffffff; }
|
|
667
493
|
|
|
668
|
-
|
|
494
|
+
/* \u{1F4CA} TWO-COLUMN CHART GRID */
|
|
495
|
+
.charts-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-bottom: 2rem; }
|
|
669
496
|
.graph-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 8px; padding: 1.5rem; }
|
|
670
497
|
.graph-title { font-size: 1.1rem; font-weight: 700; color: #ffffff; margin-bottom: 1.2rem; display: flex; align-items: center; gap: 0.5rem; }
|
|
671
498
|
|
|
@@ -686,7 +513,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
686
513
|
|
|
687
514
|
<div class="cards-wrapper">
|
|
688
515
|
<div class="metric-card">
|
|
689
|
-
<div class="card-title">Apdex Score (T:
|
|
516
|
+
<div class="card-title">Apdex Score (T: 50ms)</div>
|
|
690
517
|
<div class="card-value green">${cards.apdex.toFixed(2)}<span class="card-subtext">(Good)</span></div>
|
|
691
518
|
</div>
|
|
692
519
|
<div class="metric-card">
|
|
@@ -701,10 +528,6 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
701
528
|
<div class="card-title">Avg Time To First Byte</div>
|
|
702
529
|
<div class="card-value">${cards.ttfb.toFixed(1)}<span class="unit">ms</span></div>
|
|
703
530
|
</div>
|
|
704
|
-
<div class="metric-card">
|
|
705
|
-
<div class="card-title">VU Ramp-up Velocity</div>
|
|
706
|
-
<div class="card-value cyan">${cards.vuRampUpVelocity.toFixed(1)}<span class="unit">VUs/s</span></div>
|
|
707
|
-
</div>
|
|
708
531
|
<div class="metric-card">
|
|
709
532
|
<div class="card-title">DNS Lookup</div>
|
|
710
533
|
<div class="card-value">${cards.dns.toFixed(2)}<span class="unit">ms</span></div>
|
|
@@ -732,10 +555,6 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
732
555
|
|
|
733
556
|
<div class="matrix-box">
|
|
734
557
|
<div class="matrix-title">\u{1F4CB} HTTP Response Matrix</div>
|
|
735
|
-
<div class="matrix-row">
|
|
736
|
-
<div class="matrix-label">Informational <span class="matrix-badge badge-1xx">1xx</span></div>
|
|
737
|
-
<div class="matrix-value">${responseMatrix.total1xx}</div>
|
|
738
|
-
</div>
|
|
739
558
|
<div class="matrix-row">
|
|
740
559
|
<div class="matrix-label">Successful <span class="matrix-badge badge-2xx">2xx</span></div>
|
|
741
560
|
<div class="matrix-value">${responseMatrix.total2xx}</div>
|
|
@@ -755,9 +574,9 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
755
574
|
</div>
|
|
756
575
|
</div>
|
|
757
576
|
|
|
758
|
-
|
|
759
|
-
|
|
577
|
+
<!-- \u{1F4C9} SPLIT CHARTS SYSTEM -->
|
|
760
578
|
<div class="charts-grid">
|
|
579
|
+
<!-- Chart 1: Volume Streams -->
|
|
761
580
|
<div class="graph-card">
|
|
762
581
|
<div class="graph-title">\u{1F4CA} Throughput Velocity & Workload Volume</div>
|
|
763
582
|
<div style="height: 280px; position: relative;">
|
|
@@ -765,26 +584,13 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
765
584
|
</div>
|
|
766
585
|
</div>
|
|
767
586
|
|
|
587
|
+
<!-- Chart 2: Concurrency & Health Trends -->
|
|
768
588
|
<div class="graph-card">
|
|
769
|
-
<div class="graph-title">\u{1F504} Active Concurrency (VUs) vs. TTF Trend</div>
|
|
589
|
+
<div class="graph-title">\u{1F504} Active Concurrency (VUs) vs. Time-to-Failure (TTF) Trend</div>
|
|
770
590
|
<div style="height: 280px; position: relative;">
|
|
771
591
|
<canvas id="concurrencyChart"></canvas>
|
|
772
592
|
</div>
|
|
773
593
|
</div>
|
|
774
|
-
|
|
775
|
-
<div class="graph-card">
|
|
776
|
-
<div class="graph-title">\u{1F369} HTTP Status Code Proportions</div>
|
|
777
|
-
<div style="height: 280px; position: relative;">
|
|
778
|
-
<canvas id="statusDonutChart"></canvas>
|
|
779
|
-
</div>
|
|
780
|
-
</div>
|
|
781
|
-
|
|
782
|
-
<div class="graph-card">
|
|
783
|
-
<div class="graph-title">\u{1F4C8} Time-to-First-Byte Scatter Plot</div>
|
|
784
|
-
<div style="height: 280px; position: relative;">
|
|
785
|
-
<canvas id="ttfbScatterChart"></canvas>
|
|
786
|
-
</div>
|
|
787
|
-
</div>
|
|
788
594
|
</div>
|
|
789
595
|
|
|
790
596
|
<div class="card">
|
|
@@ -815,142 +621,13 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
815
621
|
</tbody>
|
|
816
622
|
</table>
|
|
817
623
|
</div>
|
|
818
|
-
${liveAdjusterHtml}
|
|
819
|
-
${rightSizingHtml}
|
|
820
624
|
${logClustersHtml}
|
|
821
625
|
</div>
|
|
822
626
|
|
|
823
627
|
<script>
|
|
824
|
-
function copyToClipboard(containerId) {
|
|
825
|
-
const text = document.getElementById(containerId).innerText;
|
|
826
|
-
navigator.clipboard.writeText(text).then(() => {
|
|
827
|
-
alert('Copied remediation snippet to clipboard!');
|
|
828
|
-
}).catch(err => {
|
|
829
|
-
console.error('Failed to copy text: ', err);
|
|
830
|
-
});
|
|
831
|
-
}
|
|
832
|
-
|
|
833
628
|
const labels = ${JSON.stringify(labels)};
|
|
834
|
-
const baseThroughput = ${cards.throughput};
|
|
835
|
-
const baseLatency = ${cards.ttfb};
|
|
836
|
-
const telemetryHistory = ${JSON.stringify(history)};
|
|
837
|
-
const responseMatrixData = ${JSON.stringify(responseMatrix)};
|
|
838
|
-
const globalSummaryCards = ${JSON.stringify(cards)};
|
|
839
|
-
const semanticReportData = ${JSON.stringify(semanticReport)};
|
|
840
|
-
const agentVerdictText = ${JSON.stringify(verdictReason)};
|
|
841
|
-
|
|
842
|
-
function runAssistantQuery() {
|
|
843
|
-
const inputEl = document.getElementById('assistantInput');
|
|
844
|
-
const containerEl = document.getElementById('assistantResponseContainer');
|
|
845
|
-
const textEl = document.getElementById('assistantResponseText');
|
|
846
|
-
const query = inputEl.value.toLowerCase().trim();
|
|
847
|
-
|
|
848
|
-
if (!query) return;
|
|
849
|
-
|
|
850
|
-
let answer = "";
|
|
851
|
-
const match = (keywords) => keywords.some(k => query.includes(k));
|
|
852
|
-
|
|
853
|
-
if (match(['throughput', 'rps', 'requests per second', 'capacity', 'volume'])) {
|
|
854
|
-
const peakT = Math.max(...telemetryHistory.map(h => h.throughput)).toFixed(0);
|
|
855
|
-
const finalT = globalSummaryCards.throughput.toFixed(1);
|
|
856
|
-
answer = "\u{1F4C8} **Throughput Assessment:**
|
|
857
|
-
" +
|
|
858
|
-
"- The peak throughput velocity achieved during this test run was **" + peakT + " req/sec**.
|
|
859
|
-
" +
|
|
860
|
-
"- The normalized/final execution throughput tracking shows **" + finalT + " req/sec**.
|
|
861
|
-
" +
|
|
862
|
-
"If your infrastructure limits drop below these values, upstream buffers will choke, precipitating service level drops.";
|
|
863
|
-
}
|
|
864
|
-
else if (match(['latency', 'p95', 'average latency', 'slow', 'speed', 'ttfb', 'delay'])) {
|
|
865
|
-
const maxP95 = Math.max(...telemetryHistory.map(h => h.p95Latency)).toFixed(1);
|
|
866
|
-
answer = "\u23F1\uFE0F **Latency Profiling Telemetry:**
|
|
867
|
-
" +
|
|
868
|
-
"- Average Time-to-First-Byte (TTFB) registered across workloads: **" + globalSummaryCards.ttfb.toFixed(1) + " ms**.
|
|
869
|
-
" +
|
|
870
|
-
"- Peak Worst-Case P95 Latency boundary reached: **" + maxP95 + " ms**.
|
|
871
|
-
" +
|
|
872
|
-
"- Handshake Breakdown: DNS Lookup [" + globalSummaryCards.dns.toFixed(2) + "ms] | TCP Connect [" + globalSummaryCards.tcp.toFixed(2) + "ms] | TLS Handshake [" + globalSummaryCards.tls.toFixed(2) + "ms].
|
|
873
|
-
" +
|
|
874
|
-
"Spikes are heavily correlated to network layer pooling exhaustion rather than payload compute constraints.";
|
|
875
|
-
}
|
|
876
|
-
else if (match(['error', 'fail', 'crash', 'broken', '5xx', '4xx', 'econnrefused', 'timeout'])) {
|
|
877
|
-
const totalErrs = responseMatrixData.total4xx + responseMatrixData.total5xx;
|
|
878
|
-
answer = "\u{1F6A8} **Failure Breakdown & Topology Mapping:**
|
|
879
|
-
" +
|
|
880
|
-
"- A total of **" + totalErrs + " operations failed** during execution loops (" + responseMatrixData.total4xx + " Client 4xx Errors, " + responseMatrixData.total5xx + " Server 5xx Errors).
|
|
881
|
-
";
|
|
882
|
-
|
|
883
|
-
if (semanticReportData && semanticReportData.clusters && semanticReportData.clusters.length > 0) {
|
|
884
|
-
answer += "- **Primary Log Clusters Captured:**
|
|
885
|
-
";
|
|
886
|
-
semanticReportData.clusters.forEach((c) => {
|
|
887
|
-
const rawTemplate = c.template || '';
|
|
888
|
-
const idxClose = rawTemplate.indexOf(']');
|
|
889
|
-
const cleaned = idxClose !== -1 ? rawTemplate.substring(idxClose + 1).trim().substring(0, 80) + "..." : rawTemplate.substring(0, 80) + "...";
|
|
890
|
-
const freqScore = Math.min(25, c.count * 0.5);
|
|
891
|
-
const clarityBonus = c.severity === 'CRITICAL' ? 15 : 8;
|
|
892
|
-
const confidenceScore = Math.min(99, Math.round(65 + freqScore + clarityBonus));
|
|
893
|
-
answer += " * [" + c.count + "x] **" + c.category + "** (" + c.severity + ") -> Confidence Rating: *" + confidenceScore + "%*
|
|
894
|
-
\`" + cleaned + "\`
|
|
895
|
-
";
|
|
896
|
-
});
|
|
897
|
-
} else {
|
|
898
|
-
answer += "- No structural failure entries were recorded in stdout/stderr diagnostic streams.";
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
else if (match(['apdex', 'satisfaction', 'slo', 'breach', 'limit', 'pass', 'target'])) {
|
|
902
|
-
const passStatus = globalSummaryCards.apdex >= ${config.targetApdex};
|
|
903
|
-
answer = "\u{1F3AF} **SLO Verification Profile:**
|
|
904
|
-
" +
|
|
905
|
-
"- Target Configured Apdex Floor: **" + (${config.targetApdex}).toFixed(2) + "**
|
|
906
|
-
" +
|
|
907
|
-
"- Actual Achieved Runtime Apdex: **" + globalSummaryCards.apdex.toFixed(2) + "**
|
|
908
|
-
" +
|
|
909
|
-
"- **Status Status:** " + (passStatus ? "\u2705 **SLO PASS**" : "\u274C **SLO BREACHED**") + "
|
|
910
|
-
" +
|
|
911
|
-
"Verdict Context: " + agentVerdictText;
|
|
912
|
-
}
|
|
913
|
-
else if (match(['summary', 'verdict', 'recommendation', 'cost', 'cloud', 'sizing', 'what happened'])) {
|
|
914
|
-
answer = "\u{1F9E0} **AI Executive Summary Directive:**
|
|
915
|
-
" +
|
|
916
|
-
"- **System Verdict:** " + agentVerdictText + "
|
|
917
|
-
" +
|
|
918
|
-
"- **Recommended Max Safe Scaled Capacity:** " + globalSummaryCards.saturationKneePoint + " Virtual User Threads Allocation.
|
|
919
|
-
" +
|
|
920
|
-
"- **Infrastructure Target Allocation Strategy:** Right-size infrastructure to **" + Math.max(2, Math.ceil(globalSummaryCards.saturationKneePoint / 75)) + " vCores** and **" + (Math.max(2, Math.ceil(globalSummaryCards.saturationKneePoint / 75)) * 2) + " GB RAM** to minimize the projected cloud operating budget (~$" + (globalSummaryCards.throughput * 0.045 * 24 * 30).toFixed(2) + "/mo).";
|
|
921
|
-
}
|
|
922
|
-
else {
|
|
923
|
-
answer = "\u2753 **Intent Routing Fallback:**
|
|
924
|
-
" +
|
|
925
|
-
"I didn't quite catch that context parameter. Try asking specifically about:
|
|
926
|
-
" +
|
|
927
|
-
"\u2022 *'What was our peak throughput capacity?'*
|
|
928
|
-
" +
|
|
929
|
-
"\u2022 *'Show me the latency performance limits and handshake timings'*
|
|
930
|
-
" +
|
|
931
|
-
"\u2022 *'Summarize the server logs and confidence ratings'*
|
|
932
|
-
" +
|
|
933
|
-
"\u2022 *'Did we pass or breach our target SLO settings?'*";
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
textEl.innerHTML = answer.replace(/
|
|
937
|
-
/g, '<br>').replace(/**(.*?)**/g, '<strong>$1</strong>');
|
|
938
|
-
containerEl.style.display = 'block';
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
const slider = document.getElementById('concurrencySlider');
|
|
942
|
-
const sliderVal = document.getElementById('sliderValue');
|
|
943
|
-
const simThroughput = document.getElementById('simThroughput');
|
|
944
|
-
const simLatency = document.getElementById('simLatency');
|
|
945
|
-
|
|
946
|
-
slider.addEventListener('input', (e) => {
|
|
947
|
-
const val = parseInt(e.target.value, 10);
|
|
948
|
-
sliderVal.textContent = val;
|
|
949
|
-
const factor = val / ${config.threads || 10};
|
|
950
|
-
simThroughput.textContent = (baseThroughput * Math.min(factor, 2.5)).toFixed(0) + '/s';
|
|
951
|
-
simLatency.textContent = (baseLatency * Math.pow(factor, 0.8)).toFixed(1) + 'ms';
|
|
952
|
-
});
|
|
953
629
|
|
|
630
|
+
// \u{1F6E0}\uFE0F Chart 1 Setup: Throughput Bar Streams
|
|
954
631
|
new Chart(document.getElementById('volumeChart'), {
|
|
955
632
|
type: 'bar',
|
|
956
633
|
data: {
|
|
@@ -987,6 +664,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
987
664
|
}
|
|
988
665
|
});
|
|
989
666
|
|
|
667
|
+
// \u{1F6E0}\uFE0F Chart 2 Setup: Concurrency & TTF Correlated Slopes
|
|
990
668
|
new Chart(document.getElementById('concurrencyChart'), {
|
|
991
669
|
type: 'line',
|
|
992
670
|
data: {
|
|
@@ -1000,7 +678,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
1000
678
|
borderWidth: 2.5,
|
|
1001
679
|
pointRadius: 4,
|
|
1002
680
|
fill: true,
|
|
1003
|
-
yAxisID: '
|
|
681
|
+
yAxisID: 'yThreads'
|
|
1004
682
|
},
|
|
1005
683
|
{
|
|
1006
684
|
label: 'Time-to-Failure (TTF) Trend',
|
|
@@ -1010,7 +688,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
1010
688
|
borderDash: [5, 5],
|
|
1011
689
|
pointRadius: 3,
|
|
1012
690
|
fill: false,
|
|
1013
|
-
yAxisID: '
|
|
691
|
+
yAxisID: 'yTtf'
|
|
1014
692
|
}
|
|
1015
693
|
]
|
|
1016
694
|
},
|
|
@@ -1022,53 +700,8 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
1022
700
|
},
|
|
1023
701
|
scales: {
|
|
1024
702
|
x: { grid: { color: '#1e293b' }, ticks: { color: '#64748b' } },
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
});
|
|
1030
|
-
|
|
1031
|
-
new Chart(document.getElementById('statusDonutChart'), {
|
|
1032
|
-
type: 'doughnut',
|
|
1033
|
-
data: {
|
|
1034
|
-
labels: ['1xx Info', '2xx Success', '3xx Redirect', '4xx Client Err', '5xx Server Err'],
|
|
1035
|
-
datasets: [{
|
|
1036
|
-
data: [${responseMatrix.total1xx}, ${responseMatrix.total2xx}, ${responseMatrix.total3xx}, ${responseMatrix.total4xx}, ${responseMatrix.total5xx}],
|
|
1037
|
-
backgroundColor: ['#38bdf8', '#4ade80', '#cbd5e1', '#fde047', '#f87171'],
|
|
1038
|
-
borderWidth: 1,
|
|
1039
|
-
borderColor: '#1e293b'
|
|
1040
|
-
}]
|
|
1041
|
-
},
|
|
1042
|
-
options: {
|
|
1043
|
-
responsive: true,
|
|
1044
|
-
maintainAspectRatio: false,
|
|
1045
|
-
plugins: {
|
|
1046
|
-
legend: { position: 'top', labels: { color: '#94a3b8', font: { size: 11 } } }
|
|
1047
|
-
},
|
|
1048
|
-
cutout: '65%'
|
|
1049
|
-
}
|
|
1050
|
-
});
|
|
1051
|
-
|
|
1052
|
-
new Chart(document.getElementById('ttfbScatterChart'), {
|
|
1053
|
-
type: 'scatter',
|
|
1054
|
-
data: {
|
|
1055
|
-
datasets: [{
|
|
1056
|
-
label: 'Request Processing Delay',
|
|
1057
|
-
data: ${JSON.stringify(scatterPoints)},
|
|
1058
|
-
backgroundColor: '#a855f7',
|
|
1059
|
-
pointRadius: 4,
|
|
1060
|
-
pointHoverRadius: 6
|
|
1061
|
-
}]
|
|
1062
|
-
},
|
|
1063
|
-
options: {
|
|
1064
|
-
responsive: true,
|
|
1065
|
-
maintainAspectRatio: false,
|
|
1066
|
-
plugins: {
|
|
1067
|
-
legend: { position: 'top', labels: { color: '#94a3b8', font: { size: 11 } } }
|
|
1068
|
-
},
|
|
1069
|
-
scales: {
|
|
1070
|
-
x: { grid: { color: '#1e293b' }, ticks: { color: '#64748b' }, title: { display: true, text: 'Time offset (s)', color: '#64748b' } },
|
|
1071
|
-
y: { grid: { color: '#1e293b' }, ticks: { color: '#64748b' }, title: { display: true, text: 'TTFB / Delay (ms)', color: '#64748b' } }
|
|
703
|
+
yThreads: { position: 'left', grid: { color: '#1e293b' }, ticks: { color: '#38bdf8' }, title: { display: true, text: 'VUs / Threads', color: '#38bdf8' } },
|
|
704
|
+
yTtf: { position: 'right', grid: { drawOnChartArea: false }, ticks: { color: '#f43f5e' }, title: { display: true, text: 'TTF Index / Latency ms', color: '#f43f5e' } }
|
|
1072
705
|
}
|
|
1073
706
|
}
|
|
1074
707
|
});
|
|
@@ -1083,7 +716,6 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
1083
716
|
function printUsage() {
|
|
1084
717
|
console.log(`Blaze Core Performance Test CLI Engine
|
|
1085
718
|
Options:
|
|
1086
|
-
--threads <count> | --duration <seconds> | --agentic | --cluster-logs
|
|
1087
|
-
--apdex-t <ms> Set customizable T-Threshold window (Default: 50ms)`);
|
|
719
|
+
--threads <count> | --duration <seconds> | --agentic | --cluster-logs`);
|
|
1088
720
|
}
|
|
1089
721
|
main().catch(console.error);
|
|
Binary file
|