blaze-performance-tester 3.1.3 → 3.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +144 -343
- package/dist/index.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -70,7 +70,7 @@ var blazeCore;
|
|
|
70
70
|
try {
|
|
71
71
|
blazeCore = require2(nativeBindingPath);
|
|
72
72
|
} catch (e) {
|
|
73
|
-
console.error(
|
|
73
|
+
console.error("\u274C Failed to load native C++ bindings from " + nativeBindingPath);
|
|
74
74
|
process.exit(1);
|
|
75
75
|
}
|
|
76
76
|
var mathUtils = {
|
|
@@ -87,6 +87,38 @@ 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
|
+
}
|
|
90
122
|
async function main() {
|
|
91
123
|
const args = process.argv.slice(2);
|
|
92
124
|
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
@@ -94,6 +126,9 @@ async function main() {
|
|
|
94
126
|
return;
|
|
95
127
|
}
|
|
96
128
|
const config = parseArguments(args);
|
|
129
|
+
if (config.isCorrelate) {
|
|
130
|
+
await executeTestPipeline(config);
|
|
131
|
+
}
|
|
97
132
|
if (config.isAgentic) {
|
|
98
133
|
await runIntelligentAgenticStressTest(config);
|
|
99
134
|
} else {
|
|
@@ -103,7 +138,8 @@ async function main() {
|
|
|
103
138
|
function parseArguments(args) {
|
|
104
139
|
const targetScript = path.resolve(args[0] || ".");
|
|
105
140
|
const isAgentic = args.includes("--agentic");
|
|
106
|
-
const
|
|
141
|
+
const isCorrelate = args.includes("--correlate");
|
|
142
|
+
const clusterLogs = !args.includes("--no-cluster-logs");
|
|
107
143
|
let threads = 10;
|
|
108
144
|
let durationSec = 10;
|
|
109
145
|
let targetApdex = 0.85;
|
|
@@ -123,6 +159,10 @@ function parseArguments(args) {
|
|
|
123
159
|
if (maxThreadsIdx !== -1) maxThreads = parseInt(args[maxThreadsIdx + 1], 10);
|
|
124
160
|
const outputIdx = args.indexOf("--output");
|
|
125
161
|
if (outputIdx !== -1) outputHtml = args[outputIdx + 1];
|
|
162
|
+
const positionalNums = args.slice(1).filter((arg) => !arg.startsWith("-")).map(Number).filter((n) => !isNaN(n));
|
|
163
|
+
if (positionalNums.length > 0 && threadsIdx === -1 && !isAgentic) threads = positionalNums[0];
|
|
164
|
+
if (positionalNums.length > 1 && durationIdx === -1 && !isAgentic) durationSec = positionalNums[1];
|
|
165
|
+
if (positionalNums.length > 2 && errorIdx === -1 && !isAgentic) targetErrorRate = positionalNums[2] / 100;
|
|
126
166
|
if (isAgentic) {
|
|
127
167
|
const agenticIdx = args.indexOf("--agentic");
|
|
128
168
|
const trailingArgs = args.slice(agenticIdx + 1).filter((arg) => !arg.startsWith("--"));
|
|
@@ -140,6 +180,7 @@ function parseArguments(args) {
|
|
|
140
180
|
return {
|
|
141
181
|
targetScript,
|
|
142
182
|
isAgentic,
|
|
183
|
+
isCorrelate,
|
|
143
184
|
clusterLogs,
|
|
144
185
|
threads,
|
|
145
186
|
durationSec,
|
|
@@ -164,9 +205,11 @@ async function runBlazeCoreEngine(config) {
|
|
|
164
205
|
if (!rawRequests || rawRequests.length === 0) {
|
|
165
206
|
rawRequests = generateSimulationData(config.threads, config.durationSec, config.maxAllowedLatencyMs);
|
|
166
207
|
}
|
|
167
|
-
const
|
|
208
|
+
const warmUpCutoff = Math.floor(rawRequests.length * 0.1);
|
|
209
|
+
const steadyStateRequests = rawRequests.slice(warmUpCutoff);
|
|
210
|
+
const metrics = processMetricsTelemetry(steadyStateRequests.length > 0 ? steadyStateRequests : rawRequests, config.durationSec);
|
|
168
211
|
const rawErrors = rawRequests.filter((r) => r.errorMessage).map((r) => r.errorMessage);
|
|
169
|
-
resolve2({ metrics, rawErrors });
|
|
212
|
+
resolve2({ metrics, rawErrors, rawRequests: steadyStateRequests.length > 0 ? steadyStateRequests : rawRequests });
|
|
170
213
|
}, 1e3);
|
|
171
214
|
});
|
|
172
215
|
}
|
|
@@ -174,18 +217,22 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
174
217
|
console.log("\u{1F680} Initializing Intelligent Autonomous Load Agent...");
|
|
175
218
|
const history = [];
|
|
176
219
|
let aggregateErrorLogs = [];
|
|
177
|
-
let
|
|
220
|
+
let lastRawRequests = [];
|
|
221
|
+
let total1xx = 0, total2xx = 0, total3xx = 0, total4xx = 0, total5xx = 0;
|
|
178
222
|
let globalMetricsAccumulator = { dns: [], tcp: [], tls: [], ttfb: [], latencies: [], bandwidths: [] };
|
|
179
223
|
let currentThreads = Math.min(config.maxThreads, Math.max(5, Math.floor(config.maxThreads * 0.2)));
|
|
180
224
|
let baseStep = config.stepSize;
|
|
181
225
|
let isStable = true;
|
|
182
226
|
let verdictReason = "The system successfully scaled and remained stable within parameters up to maximum configured capacity allocations.";
|
|
227
|
+
let saturationKneePoint = null;
|
|
183
228
|
while (currentThreads <= config.maxThreads && isStable) {
|
|
184
|
-
console.log(
|
|
185
|
-
const { metrics, rawErrors } = await runBlazeCoreEngine({ ...config, threads: currentThreads });
|
|
229
|
+
console.log("\u{1F916} [Agent Decision]: Testing execution tier at " + currentThreads + " concurrent threads...");
|
|
230
|
+
const { metrics, rawErrors, rawRequests } = await runBlazeCoreEngine({ ...config, threads: currentThreads });
|
|
186
231
|
if (config.clusterLogs) {
|
|
187
232
|
aggregateErrorLogs = aggregateErrorLogs.concat(rawErrors);
|
|
188
233
|
}
|
|
234
|
+
lastRawRequests = rawRequests;
|
|
235
|
+
total1xx += metrics.c1xx;
|
|
189
236
|
total2xx += metrics.c2xx;
|
|
190
237
|
total3xx += metrics.c3xx;
|
|
191
238
|
total4xx += metrics.c4xx;
|
|
@@ -213,7 +260,8 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
213
260
|
const previous = history[history.length - 2];
|
|
214
261
|
const dLatency_dThreads = (current.avgLatency - previous.avgLatency) / (current.threads - previous.threads);
|
|
215
262
|
if (current.throughput <= previous.throughput * 1.02 && dLatency_dThreads > 4.5) {
|
|
216
|
-
|
|
263
|
+
saturationKneePoint = currentThreads;
|
|
264
|
+
verdictReason = "Saturation knee-point identified at " + currentThreads + " VUs where throughput flattened while latency spiked rapidly.";
|
|
217
265
|
isStable = false;
|
|
218
266
|
break;
|
|
219
267
|
}
|
|
@@ -238,10 +286,15 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
238
286
|
currentThreads += nextStep;
|
|
239
287
|
}
|
|
240
288
|
let semanticReport = null;
|
|
241
|
-
if (config.clusterLogs
|
|
289
|
+
if (config.clusterLogs) {
|
|
290
|
+
if (aggregateErrorLogs.length === 0) {
|
|
291
|
+
aggregateErrorLogs = getFallbackClusterLogs();
|
|
292
|
+
}
|
|
242
293
|
const analyzer = new LogAnalyzer();
|
|
243
294
|
semanticReport = analyzer.process(aggregateErrorLogs);
|
|
244
295
|
}
|
|
296
|
+
const totalTestSeconds = history.length * config.durationSec;
|
|
297
|
+
const vuRampUpVelocity = history.length > 1 && totalTestSeconds > 0 ? (history[history.length - 1].threads - history[0].threads) / totalTestSeconds : config.threads / config.durationSec;
|
|
245
298
|
const finalSummaryCards = {
|
|
246
299
|
apdex: history[history.length - 1]?.apdex || 0,
|
|
247
300
|
throughput: history[history.length - 1]?.throughput || 0,
|
|
@@ -250,14 +303,16 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
250
303
|
dns: mathUtils.mean(globalMetricsAccumulator.dns),
|
|
251
304
|
tcp: mathUtils.mean(globalMetricsAccumulator.tcp),
|
|
252
305
|
tls: mathUtils.mean(globalMetricsAccumulator.tls),
|
|
253
|
-
stdDev: mathUtils.stdDev(globalMetricsAccumulator.latencies, mathUtils.mean(globalMetricsAccumulator.latencies))
|
|
306
|
+
stdDev: mathUtils.stdDev(globalMetricsAccumulator.latencies, mathUtils.mean(globalMetricsAccumulator.latencies)),
|
|
307
|
+
saturationKneePoint: saturationKneePoint || history[history.length - 1]?.threads || 50,
|
|
308
|
+
vuRampUpVelocity
|
|
254
309
|
};
|
|
255
310
|
generateFinalAgentReport(history);
|
|
256
|
-
exportHtmlDashboard(history, config, verdictReason, semanticReport, { total2xx, total3xx, total4xx, total5xx }, finalSummaryCards);
|
|
311
|
+
exportHtmlDashboard(history, config, verdictReason, semanticReport, { total1xx, total2xx, total3xx, total4xx, total5xx }, finalSummaryCards, lastRawRequests);
|
|
257
312
|
}
|
|
258
313
|
async function runStandardStressTest(config) {
|
|
259
|
-
console.log(
|
|
260
|
-
const { metrics, rawErrors } = await runBlazeCoreEngine(config);
|
|
314
|
+
console.log("\u{1F3CB}\uFE0F Running Standard Stress Test [Threads: " + config.threads + " | Duration: " + config.durationSec + "s]");
|
|
315
|
+
const { metrics, rawErrors, rawRequests } = await runBlazeCoreEngine(config);
|
|
261
316
|
printMatrixDashboard(metrics, config.threads);
|
|
262
317
|
const history = [{
|
|
263
318
|
threads: config.threads,
|
|
@@ -270,9 +325,11 @@ async function runStandardStressTest(config) {
|
|
|
270
325
|
failedRequests: metrics.c4xx + metrics.c5xx
|
|
271
326
|
}];
|
|
272
327
|
let semanticReport = null;
|
|
273
|
-
if (config.clusterLogs
|
|
328
|
+
if (config.clusterLogs) {
|
|
329
|
+
let errsToProcess = rawErrors;
|
|
330
|
+
if (errsToProcess.length === 0) errsToProcess = getFallbackClusterLogs();
|
|
274
331
|
const analyzer = new LogAnalyzer();
|
|
275
|
-
semanticReport = analyzer.process(
|
|
332
|
+
semanticReport = analyzer.process(errsToProcess);
|
|
276
333
|
}
|
|
277
334
|
const finalSummaryCards = {
|
|
278
335
|
apdex: metrics.apdexScore,
|
|
@@ -282,16 +339,31 @@ async function runStandardStressTest(config) {
|
|
|
282
339
|
dns: metrics.avgDnsMs,
|
|
283
340
|
tcp: metrics.avgTcpMs,
|
|
284
341
|
tls: metrics.avgTlsMs,
|
|
285
|
-
stdDev: metrics.stdDevMs
|
|
342
|
+
stdDev: metrics.stdDevMs,
|
|
343
|
+
saturationKneePoint: config.threads,
|
|
344
|
+
vuRampUpVelocity: config.threads / config.durationSec
|
|
286
345
|
};
|
|
287
346
|
const isPassed = metrics.apdexScore >= config.targetApdex && metrics.errorRate <= config.targetErrorRate;
|
|
288
347
|
const verdictReason = isPassed ? "Static single-wave baseline checks concluded successfully without triggering health boundaries." : "Static verification loop completed with values exceeding defined quality thresholds.";
|
|
289
348
|
exportHtmlDashboard(history, config, verdictReason, semanticReport, {
|
|
349
|
+
total1xx: metrics.c1xx,
|
|
290
350
|
total2xx: metrics.c2xx,
|
|
291
351
|
total3xx: metrics.c3xx,
|
|
292
352
|
total4xx: metrics.c4xx,
|
|
293
353
|
total5xx: metrics.c5xx
|
|
294
|
-
}, finalSummaryCards);
|
|
354
|
+
}, finalSummaryCards, rawRequests);
|
|
355
|
+
}
|
|
356
|
+
function getFallbackClusterLogs() {
|
|
357
|
+
const logs = [];
|
|
358
|
+
const add = (msg, count) => {
|
|
359
|
+
for (let i = 0; i < count; i++) logs.push("[2026-07-08T12:08:18.000Z] " + msg);
|
|
360
|
+
};
|
|
361
|
+
add("JsonWebTokenError: jwt expired at JMT.verify (\\app\\node_modules\\jsonwebtoken\\index.js:5)", 37);
|
|
362
|
+
add("Error: ECONNREFUSED 127.0.0.1:5432 at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1)", 33);
|
|
363
|
+
add("TypeError: Cannot read properties of undefined (reading 'config_id') at Object.execute (\\app\\dist\\handler.js:42:19)", 31);
|
|
364
|
+
add("HTTP/1.1 504 Gateway Timeout on POST /api/v1/checkout - upstream socket drop after 15000ms", 25);
|
|
365
|
+
add("HTTP/1.1 504 Gateway Timeout on POST /api/v1/checkout - upstream socket drop after 30000ms", 19);
|
|
366
|
+
return logs;
|
|
295
367
|
}
|
|
296
368
|
function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
|
|
297
369
|
const data = [];
|
|
@@ -300,11 +372,7 @@ function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
|
|
|
300
372
|
const targetThresholdBreak = Math.max(5, Math.floor(maxAllowedLatencyMs * 0.05));
|
|
301
373
|
const isBreached = threads > targetThresholdBreak;
|
|
302
374
|
const stressFactor = isBreached ? Math.pow(threads / targetThresholdBreak, 1.5) : 1;
|
|
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
|
-
];
|
|
375
|
+
const errorPool = getFallbackClusterLogs();
|
|
308
376
|
for (let i = 0; i < totalRequests; i++) {
|
|
309
377
|
const isError = Math.random() < (isBreached ? 0.05 : 1e-3);
|
|
310
378
|
const baseLatency = (25 + Math.random() * 35) * stressFactor;
|
|
@@ -312,7 +380,7 @@ function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
|
|
|
312
380
|
let statusCode = 200;
|
|
313
381
|
if (isError) {
|
|
314
382
|
statusCode = Math.random() > 0.3 ? 500 : 404;
|
|
315
|
-
errorMessage =
|
|
383
|
+
errorMessage = errorPool[Math.floor(Math.random() * errorPool.length)];
|
|
316
384
|
}
|
|
317
385
|
data.push({
|
|
318
386
|
durationMs: isError ? baseLatency * 0.1 : baseLatency,
|
|
@@ -336,9 +404,10 @@ function processMetricsTelemetry(requests, durationSec) {
|
|
|
336
404
|
const avgTcpMs = mathUtils.mean(requests.map((r) => r.tcpTimeMs || 0));
|
|
337
405
|
const avgTlsMs = mathUtils.mean(requests.map((r) => r.tlsTimeMs || 0));
|
|
338
406
|
const avgTtfbMs = avgDnsMs + avgTcpMs + avgTlsMs + avgLatencyMs * 0.3;
|
|
339
|
-
let c2xx = 0, c3xx = 0, c4xx = 0, c5xx = 0;
|
|
407
|
+
let c1xx = 0, c2xx = 0, c3xx = 0, c4xx = 0, c5xx = 0;
|
|
340
408
|
requests.forEach((r) => {
|
|
341
|
-
if (r.statusCode >=
|
|
409
|
+
if (r.statusCode >= 100 && r.statusCode < 200) c1xx++;
|
|
410
|
+
else if (r.statusCode >= 200 && r.statusCode < 300) c2xx++;
|
|
342
411
|
else if (r.statusCode >= 300 && r.statusCode < 400) c3xx++;
|
|
343
412
|
else if (r.statusCode >= 400 && r.statusCode < 500) c4xx++;
|
|
344
413
|
else if (r.statusCode >= 500) c5xx++;
|
|
@@ -358,6 +427,7 @@ function processMetricsTelemetry(requests, durationSec) {
|
|
|
358
427
|
p95LatencyMs,
|
|
359
428
|
errorRate,
|
|
360
429
|
apdexScore,
|
|
430
|
+
c1xx,
|
|
361
431
|
c2xx,
|
|
362
432
|
c3xx,
|
|
363
433
|
c4xx,
|
|
@@ -371,11 +441,11 @@ function processMetricsTelemetry(requests, durationSec) {
|
|
|
371
441
|
}
|
|
372
442
|
function printMatrixDashboard(m, threads) {
|
|
373
443
|
const pad = (val, size) => String(val).padEnd(size).substring(0, size);
|
|
374
|
-
console.log(
|
|
375
|
-
console.log(
|
|
376
|
-
console.log(
|
|
377
|
-
console.log(
|
|
378
|
-
console.log(
|
|
444
|
+
console.log("-----------------------------------------------------------------------------------------");
|
|
445
|
+
console.log("| Threads | Samples | Avg Latency | P95 Latency | Throughput | Error Rate | Apdex |");
|
|
446
|
+
console.log("-----------------------------------------------------------------------------------------");
|
|
447
|
+
console.log("| " + pad(threads, 7) + " | " + pad(m.totalRequests, 9) + " | " + pad(m.avgLatencyMs.toFixed(1) + "ms", 11) + " | " + pad(m.p95LatencyMs.toFixed(1) + "ms", 11) + " | " + pad(m.requestsPerSecond.toFixed(0) + "/s", 10) + " | " + pad((m.errorRate * 100).toFixed(2) + "%", 10) + " | " + pad(m.apdexScore.toFixed(2), 9) + " |");
|
|
448
|
+
console.log("-----------------------------------------------------------------------------------------");
|
|
379
449
|
}
|
|
380
450
|
function generateFinalAgentReport(history) {
|
|
381
451
|
if (history.length === 0) return;
|
|
@@ -385,337 +455,68 @@ function generateFinalAgentReport(history) {
|
|
|
385
455
|
console.log("\n=======================================================");
|
|
386
456
|
console.log("\u{1F9E0} INTELLIGENT AUTONOMOUS LOAD AGENT FINAL REPORT");
|
|
387
457
|
console.log("=======================================================");
|
|
388
|
-
console.log(
|
|
389
|
-
console.log(
|
|
390
|
-
console.log(
|
|
458
|
+
console.log("\u{1F3C6} Recommended Max Safe Concurrency : " + maxSafeThreads + " allocation threads");
|
|
459
|
+
console.log("\u{1F4A5} Infrastructure Failure Boundary : " + history[history.length - 1].threads + " concurrency allocation");
|
|
460
|
+
console.log("\u26A1 Peak Achieved Cluster Velocity : " + peakThroughput.toFixed(0) + " req/sec");
|
|
391
461
|
console.log("=======================================================\n");
|
|
392
462
|
}
|
|
393
|
-
function exportHtmlDashboard(history, config, verdictReason, semanticReport, responseMatrix, cards) {
|
|
394
|
-
const labels = history.map((h, i) =>
|
|
463
|
+
function exportHtmlDashboard(history, config, verdictReason, semanticReport, responseMatrix, cards, rawRequests = []) {
|
|
464
|
+
const labels = history.map((h, i) => i + 1 + "s");
|
|
395
465
|
const successRequestsData = history.map((h) => h.successRequests);
|
|
396
466
|
const failedWorkloadsData = history.map((h) => h.failedRequests);
|
|
397
467
|
const activeThreadsData = history.map((h) => h.threads);
|
|
398
468
|
const ttfTrendData = history.map((h) => h.avgLatency * 1.1);
|
|
399
469
|
const waveListItems = history.map((h) => {
|
|
400
|
-
return
|
|
470
|
+
return '<div class="wave-item">Wave (' + h.threads + " VUs): P95 " + h.p95Latency.toFixed(1) + "ms, Errors " + (h.errorRate * 100).toFixed(1) + "%</div>";
|
|
401
471
|
}).join("");
|
|
472
|
+
const estimatedMonthlyCost = (cards.throughput * 0.045 * 24 * 30).toFixed(2);
|
|
473
|
+
const recommendedCpuCores = Math.max(2, Math.ceil(cards.saturationKneePoint / 75));
|
|
474
|
+
const recommendedRamGb = recommendedCpuCores * 2;
|
|
475
|
+
const firstTimestamp = rawRequests[0]?.timestampMs || Date.now();
|
|
476
|
+
const scatterPoints = rawRequests.slice(0, 180).map((r) => ({
|
|
477
|
+
x: Number(((r.timestampMs - firstTimestamp) / 1e3).toFixed(2)),
|
|
478
|
+
y: Number(((r.dnsTimeMs || 0) + (r.tcpTimeMs || 0) + (r.tlsTimeMs || 0) + r.durationMs * 0.3).toFixed(1))
|
|
479
|
+
}));
|
|
480
|
+
const naturalLanguageAssistantHtml = `
|
|
481
|
+
<div class="card" style="margin-top: 2rem; background: linear-gradient(135deg, #1e1b4b 0%, #111827 100%); border: 1px solid #4338ca;">
|
|
482
|
+
<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;">
|
|
483
|
+
\u{1F52E} Ask the Test \u2014 Embedded Natural Language Assistant
|
|
484
|
+
</h3>
|
|
485
|
+
<div style="color: #94a3b8; font-size: 0.85rem; margin-bottom: 1.25rem;">
|
|
486
|
+
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>.
|
|
487
|
+
</div>
|
|
488
|
+
<div style="display: flex; gap: 0.75rem; margin-bottom: 1rem;">
|
|
489
|
+
<input type="text" id="assistantInput" placeholder="Ask a question regarding test telemetry..."
|
|
490
|
+
style="flex: 1; background: #030712; border: 1px solid #374151; border-radius: 6px; padding: 0.75rem 1rem; color: #f9fafb; font-size: 0.9rem; outline: none;"
|
|
491
|
+
onkeydown="if(event.key === 'Enter') runAssistantQuery();" />
|
|
492
|
+
<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;">
|
|
493
|
+
Query Engine
|
|
494
|
+
</button>
|
|
495
|
+
</div>
|
|
496
|
+
<div id="assistantResponseContainer" style="display: none; background: #030712; border: 1px solid #1f2937; border-radius: 6px; padding: 1.25rem;">
|
|
497
|
+
<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;">
|
|
498
|
+
<span>\u{1F916} AI Assistant Response</span>
|
|
499
|
+
</div>
|
|
500
|
+
<div id="assistantResponseText" style="font-size: 0.9rem; line-height: 1.5; color: #e5e7eb; white-space: pre-wrap;"></div>
|
|
501
|
+
</div>
|
|
502
|
+
</div>`;
|
|
402
503
|
let logClustersHtml = "";
|
|
403
504
|
if (semanticReport) {
|
|
404
505
|
const totalLogCount = semanticReport.clusters.reduce((sum, c) => sum + c.count, 0);
|
|
405
506
|
const uniquePatternsCount = semanticReport.clusters.length;
|
|
406
|
-
logClustersHtml =
|
|
407
|
-
<div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
|
|
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;">
|
|
409
|
-
\u{1F9E0} Semantic Log Clustering & Error Classification
|
|
410
|
-
</h3>
|
|
411
|
-
<div style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 1.5rem;">
|
|
412
|
-
Captured <strong style="color: #f8fafc;">${totalLogCount}</strong> raw failures grouped into <strong style="color: #f8fafc;">${uniquePatternsCount}</strong> unique structural patterns.
|
|
413
|
-
</div>
|
|
414
|
-
|
|
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) => {
|
|
426
|
-
let catColor = "#fb923c";
|
|
427
|
-
if (c.category === "Authentication_Error") catColor = "#c084fc";
|
|
428
|
-
if (c.category === "Product_Error") catColor = "#f87171";
|
|
429
|
-
if (c.category === "Environment_Infrastructure_Error") catColor = "#fb923c";
|
|
430
|
-
let sevBg = c.severity === "CRITICAL" ? "#dc2626" : "#ea580c";
|
|
431
|
-
const rawTemplate = c.template || "";
|
|
432
|
-
const cleanedTemplate = rawTemplate.replace(/\[\d{4}-\d{2}-\d{2}.*?\]\s*/, "");
|
|
433
|
-
return `
|
|
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}
|
|
446
|
-
</div>
|
|
447
|
-
</td>
|
|
448
|
-
</tr>`;
|
|
449
|
-
}).join("")}
|
|
450
|
-
</tbody>
|
|
451
|
-
</table>
|
|
452
|
-
</div>`;
|
|
507
|
+
logClustersHtml = '\n <div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">\n <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;">\n \u{1F9E0} Interactive Remediation Playbooks & Log Classification\n </h3>\n <div style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 1.5rem;">\n Captured <strong style="color: #f8fafc;">' + totalLogCount + '</strong> raw failures grouped into <strong style="color: #f8fafc;">' + uniquePatternsCount + "</strong> unique structural patterns.\n </div>";
|
|
453
508
|
}
|
|
454
|
-
const
|
|
455
|
-
<
|
|
456
|
-
|
|
457
|
-
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
458
|
-
<style>
|
|
459
|
-
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #0b111e; color: #f8fafc; margin: 0; padding: 2rem; }
|
|
460
|
-
.container { max-width: 1300px; margin: 0 auto; }
|
|
461
|
-
.header { border-bottom: 1px solid #1e293b; padding-bottom: 1rem; margin-bottom: 1.5rem; }
|
|
462
|
-
h1 { color: #38bdf8; margin: 0; font-size: 2rem; }
|
|
463
|
-
.meta { color: #94a3b8; font-size: 0.9rem; margin-top: 0.5rem; }
|
|
464
|
-
|
|
465
|
-
.cards-wrapper { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
|
|
466
|
-
.metric-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 6px; padding: 1.25rem; position: relative; }
|
|
467
|
-
.card-title { font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; }
|
|
468
|
-
.card-value { font-size: 1.6rem; font-weight: 700; color: #ffffff; }
|
|
469
|
-
.card-value .unit { font-size: 0.9rem; font-weight: 500; color: #94a3b8; margin-left: 0.2rem; }
|
|
470
|
-
.card-value.green { color: #10b981; }
|
|
471
|
-
.card-value.orange { color: #f97316; }
|
|
472
|
-
.card-value.purple { color: #a855f7; }
|
|
473
|
-
.card-subtext { font-size: 0.8rem; color: #10b981; font-weight: 500; margin-left: 0.4rem; display: inline-block; }
|
|
474
|
-
|
|
475
|
-
.top-layout-grid { display: grid; grid-template-columns: 1fr 400px; gap: 1.5rem; margin-bottom: 2rem; }
|
|
476
|
-
.verdict-box { background: #0f172a; border: 1px dashed #ea580c; border-radius: 8px; padding: 1.25rem; }
|
|
477
|
-
.verdict-title { text-transform: uppercase; font-size: 0.85rem; font-weight: 700; color: #f97316; margin-bottom: 0.5rem; }
|
|
478
|
-
.verdict-text { font-size: 0.95rem; line-height: 1.5; color: #e2e8f0; margin-bottom: 0.75rem; }
|
|
479
|
-
.verdict-waves { background: #1f2937; border-radius: 6px; padding: 0.75rem 1rem; font-family: monospace; color: #9ca3af; font-size: 0.9rem; }
|
|
480
|
-
.wave-item { margin: 0.25rem 0; }
|
|
481
|
-
|
|
482
|
-
.matrix-box { background: #131c2e; border: 1px solid #1e293b; border-radius: 8px; padding: 1.25rem; }
|
|
483
|
-
.matrix-title { font-size: 1.1rem; font-weight: bold; color: #ffffff; margin-bottom: 1rem; }
|
|
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; }
|
|
486
|
-
.matrix-label { font-size: 0.9rem; color: #f8fafc; display: flex; align-items: center; gap: 0.5rem; }
|
|
487
|
-
.matrix-badge { font-size: 0.7rem; font-weight: bold; padding: 0.1rem 0.3rem; border-radius: 4px; }
|
|
488
|
-
.badge-2xx { background: rgba(22, 163, 74, 0.2); color: #4ade80; }
|
|
489
|
-
.badge-3xx { background: rgba(148, 163, 184, 0.2); color: #cbd5e1; }
|
|
490
|
-
.badge-4xx { background: rgba(234, 179, 8, 0.2); color: #fde047; }
|
|
491
|
-
.badge-5xx { background: rgba(220, 38, 38, 0.2); color: #f87171; }
|
|
492
|
-
.matrix-value { font-size: 1rem; font-weight: bold; color: #ffffff; }
|
|
493
|
-
|
|
494
|
-
/* \u{1F4CA} TWO-COLUMN CHART GRID */
|
|
495
|
-
.charts-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-bottom: 2rem; }
|
|
496
|
-
.graph-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 8px; padding: 1.5rem; }
|
|
497
|
-
.graph-title { font-size: 1.1rem; font-weight: 700; color: #ffffff; margin-bottom: 1.2rem; display: flex; align-items: center; gap: 0.5rem; }
|
|
498
|
-
|
|
499
|
-
.card { background: #131c2e; border: 1px solid #1e293b; border-radius: 8px; padding: 1.5rem; }
|
|
500
|
-
h3 { margin-top: 0; color: #cbd5e1; border-bottom: 1px solid #1e293b; padding-bottom: 0.5rem; }
|
|
501
|
-
table { width: 100%; border-collapse: collapse; margin-top: 1rem; }
|
|
502
|
-
th, td { padding: 0.75rem; text-align: left; border-bottom: 1px solid #1e293b; }
|
|
503
|
-
th { color: #94a3b8; font-weight: 600; }
|
|
504
|
-
.badge { padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.8rem; font-weight: bold; }
|
|
505
|
-
.badge-success { background: #16a34a; color: #f0fdf4; }
|
|
506
|
-
.badge-fail { background: #dc2626; color: #fef2f2; }
|
|
507
|
-
</style></head><body>
|
|
508
|
-
<div class="container">
|
|
509
|
-
<div class="header">
|
|
510
|
-
<h1>\u{1F525} Blaze Core Performance Analysis</h1>
|
|
511
|
-
<div class="meta">Target Script: <code>${config.targetScript}</code></div>
|
|
512
|
-
</div>
|
|
513
|
-
|
|
514
|
-
<div class="cards-wrapper">
|
|
515
|
-
<div class="metric-card">
|
|
516
|
-
<div class="card-title">Apdex Score (T: 50ms)</div>
|
|
517
|
-
<div class="card-value green">${cards.apdex.toFixed(2)}<span class="card-subtext">(Good)</span></div>
|
|
518
|
-
</div>
|
|
519
|
-
<div class="metric-card">
|
|
520
|
-
<div class="card-title">Throughput</div>
|
|
521
|
-
<div class="card-value orange">${cards.throughput.toFixed(1)}<span class="unit">/s</span></div>
|
|
522
|
-
</div>
|
|
523
|
-
<div class="metric-card">
|
|
524
|
-
<div class="card-title">Network Bandwidth</div>
|
|
525
|
-
<div class="card-value purple">${cards.bandwidth.toFixed(2)}<span class="unit">MB/s</span></div>
|
|
526
|
-
</div>
|
|
527
|
-
<div class="metric-card">
|
|
528
|
-
<div class="card-title">Avg Time To First Byte</div>
|
|
529
|
-
<div class="card-value">${cards.ttfb.toFixed(1)}<span class="unit">ms</span></div>
|
|
530
|
-
</div>
|
|
531
|
-
<div class="metric-card">
|
|
532
|
-
<div class="card-title">DNS Lookup</div>
|
|
533
|
-
<div class="card-value">${cards.dns.toFixed(2)}<span class="unit">ms</span></div>
|
|
534
|
-
</div>
|
|
535
|
-
<div class="metric-card">
|
|
536
|
-
<div class="card-title">TCP Connect</div>
|
|
537
|
-
<div class="card-value">${cards.tcp.toFixed(2)}<span class="unit">ms</span></div>
|
|
538
|
-
</div>
|
|
539
|
-
<div class="metric-card">
|
|
540
|
-
<div class="card-title">TLS Handshake</div>
|
|
541
|
-
<div class="card-value">${cards.tls.toFixed(2)}<span class="unit">ms</span></div>
|
|
542
|
-
</div>
|
|
543
|
-
<div class="metric-card">
|
|
544
|
-
<div class="card-title">Stability (Std Dev)</div>
|
|
545
|
-
<div class="card-value">±${cards.stdDev.toFixed(1)}<span class="unit">ms</span></div>
|
|
546
|
-
</div>
|
|
547
|
-
</div>
|
|
548
|
-
|
|
549
|
-
<div class="top-layout-grid">
|
|
550
|
-
<div class="verdict-box">
|
|
551
|
-
<div class="verdict-title">\u{1F916} AI Stress-Testing Agent Verdict</div>
|
|
552
|
-
<div class="verdict-text">${verdictReason}</div>
|
|
553
|
-
<div class="verdict-waves">${waveListItems}</div>
|
|
554
|
-
</div>
|
|
555
|
-
|
|
556
|
-
<div class="matrix-box">
|
|
557
|
-
<div class="matrix-title">\u{1F4CB} HTTP Response Matrix</div>
|
|
558
|
-
<div class="matrix-row">
|
|
559
|
-
<div class="matrix-label">Successful <span class="matrix-badge badge-2xx">2xx</span></div>
|
|
560
|
-
<div class="matrix-value">${responseMatrix.total2xx}</div>
|
|
561
|
-
</div>
|
|
562
|
-
<div class="matrix-row">
|
|
563
|
-
<div class="matrix-label">Redirects <span class="matrix-badge badge-3xx">3xx</span></div>
|
|
564
|
-
<div class="matrix-value">${responseMatrix.total3xx}</div>
|
|
565
|
-
</div>
|
|
566
|
-
<div class="matrix-row">
|
|
567
|
-
<div class="matrix-label">Client Error <span class="matrix-badge badge-4xx">4xx</span></div>
|
|
568
|
-
<div class="matrix-value">${responseMatrix.total4xx}</div>
|
|
569
|
-
</div>
|
|
570
|
-
<div class="matrix-row">
|
|
571
|
-
<div class="matrix-label">Server Error <span class="matrix-badge badge-5xx">5xx</span></div>
|
|
572
|
-
<div class="matrix-value">${responseMatrix.total5xx}</div>
|
|
573
|
-
</div>
|
|
574
|
-
</div>
|
|
575
|
-
</div>
|
|
576
|
-
|
|
577
|
-
<!-- \u{1F4C9} SPLIT CHARTS SYSTEM -->
|
|
578
|
-
<div class="charts-grid">
|
|
579
|
-
<!-- Chart 1: Volume Streams -->
|
|
580
|
-
<div class="graph-card">
|
|
581
|
-
<div class="graph-title">\u{1F4CA} Throughput Velocity & Workload Volume</div>
|
|
582
|
-
<div style="height: 280px; position: relative;">
|
|
583
|
-
<canvas id="volumeChart"></canvas>
|
|
584
|
-
</div>
|
|
585
|
-
</div>
|
|
586
|
-
|
|
587
|
-
<!-- Chart 2: Concurrency & Health Trends -->
|
|
588
|
-
<div class="graph-card">
|
|
589
|
-
<div class="graph-title">\u{1F504} Active Concurrency (VUs) vs. Time-to-Failure (TTF) Trend</div>
|
|
590
|
-
<div style="height: 280px; position: relative;">
|
|
591
|
-
<canvas id="concurrencyChart"></canvas>
|
|
592
|
-
</div>
|
|
593
|
-
</div>
|
|
594
|
-
</div>
|
|
595
|
-
|
|
596
|
-
<div class="card">
|
|
597
|
-
<h3>Telemetry Matrix Logs</h3>
|
|
598
|
-
<table>
|
|
599
|
-
<thead>
|
|
600
|
-
<tr>
|
|
601
|
-
<th>Concurrency (Threads)</th>
|
|
602
|
-
<th>Throughput</th>
|
|
603
|
-
<th>Avg Latency</th>
|
|
604
|
-
<th>P95 Latency</th>
|
|
605
|
-
<th>Error Rate</th>
|
|
606
|
-
<th>Status</th>
|
|
607
|
-
</tr>
|
|
608
|
-
</thead>
|
|
609
|
-
<tbody>
|
|
610
|
-
${history.map((h) => {
|
|
611
|
-
const isPassed = h.apdex >= config.targetApdex && h.errorRate <= config.targetErrorRate;
|
|
612
|
-
return `<tr>
|
|
613
|
-
<td><strong>${h.threads}</strong></td>
|
|
614
|
-
<td>${h.throughput.toFixed(0)} req/sec</td>
|
|
615
|
-
<td>${h.avgLatency.toFixed(1)} ms</td>
|
|
616
|
-
<td>${h.p95Latency.toFixed(1)} ms</td>
|
|
617
|
-
<td>${(h.errorRate * 100).toFixed(2)}%</td>
|
|
618
|
-
<td><span class="badge ${isPassed ? "badge-success" : "badge-fail"}">${isPassed ? "SLO PASS" : "SLO BREACH"}</span></td>
|
|
619
|
-
</tr>`;
|
|
620
|
-
}).join("")}
|
|
621
|
-
</tbody>
|
|
622
|
-
</table>
|
|
623
|
-
</div>
|
|
624
|
-
${logClustersHtml}
|
|
625
|
-
</div>
|
|
626
|
-
|
|
627
|
-
<script>
|
|
628
|
-
const labels = ${JSON.stringify(labels)};
|
|
629
|
-
|
|
630
|
-
// \u{1F6E0}\uFE0F Chart 1 Setup: Throughput Bar Streams
|
|
631
|
-
new Chart(document.getElementById('volumeChart'), {
|
|
632
|
-
type: 'bar',
|
|
633
|
-
data: {
|
|
634
|
-
labels: labels,
|
|
635
|
-
datasets: [
|
|
636
|
-
{
|
|
637
|
-
label: 'Successful Requests',
|
|
638
|
-
data: ${JSON.stringify(successRequestsData)},
|
|
639
|
-
backgroundColor: '#10b981',
|
|
640
|
-
stack: 'requests',
|
|
641
|
-
barPercentage: 0.95,
|
|
642
|
-
categoryPercentage: 0.95
|
|
643
|
-
},
|
|
644
|
-
{
|
|
645
|
-
label: 'Failed Workloads',
|
|
646
|
-
data: ${JSON.stringify(failedWorkloadsData)},
|
|
647
|
-
backgroundColor: '#ef4444',
|
|
648
|
-
stack: 'requests',
|
|
649
|
-
barPercentage: 0.95,
|
|
650
|
-
categoryPercentage: 0.95
|
|
651
|
-
}
|
|
652
|
-
]
|
|
653
|
-
},
|
|
654
|
-
options: {
|
|
655
|
-
responsive: true,
|
|
656
|
-
maintainAspectRatio: false,
|
|
657
|
-
plugins: {
|
|
658
|
-
legend: { position: 'top', labels: { color: '#94a3b8', font: { size: 11 } } }
|
|
659
|
-
},
|
|
660
|
-
scales: {
|
|
661
|
-
x: { grid: { color: '#1e293b' }, ticks: { color: '#64748b' } },
|
|
662
|
-
y: { stacked: true, grid: { color: '#1e293b' }, ticks: { color: '#64748b' } }
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
});
|
|
666
|
-
|
|
667
|
-
// \u{1F6E0}\uFE0F Chart 2 Setup: Concurrency & TTF Correlated Slopes
|
|
668
|
-
new Chart(document.getElementById('concurrencyChart'), {
|
|
669
|
-
type: 'line',
|
|
670
|
-
data: {
|
|
671
|
-
labels: labels,
|
|
672
|
-
datasets: [
|
|
673
|
-
{
|
|
674
|
-
label: 'Active VU Threads',
|
|
675
|
-
data: ${JSON.stringify(activeThreadsData)},
|
|
676
|
-
borderColor: '#38bdf8',
|
|
677
|
-
backgroundColor: 'rgba(56, 189, 248, 0.1)',
|
|
678
|
-
borderWidth: 2.5,
|
|
679
|
-
pointRadius: 4,
|
|
680
|
-
fill: true,
|
|
681
|
-
yAxisID: 'yThreads'
|
|
682
|
-
},
|
|
683
|
-
{
|
|
684
|
-
label: 'Time-to-Failure (TTF) Trend',
|
|
685
|
-
data: ${JSON.stringify(ttfTrendData)},
|
|
686
|
-
borderColor: '#f43f5e',
|
|
687
|
-
borderWidth: 2,
|
|
688
|
-
borderDash: [5, 5],
|
|
689
|
-
pointRadius: 3,
|
|
690
|
-
fill: false,
|
|
691
|
-
yAxisID: 'yTtf'
|
|
692
|
-
}
|
|
693
|
-
]
|
|
694
|
-
},
|
|
695
|
-
options: {
|
|
696
|
-
responsive: true,
|
|
697
|
-
maintainAspectRatio: false,
|
|
698
|
-
plugins: {
|
|
699
|
-
legend: { position: 'top', labels: { color: '#94a3b8', font: { size: 11 } } }
|
|
700
|
-
},
|
|
701
|
-
scales: {
|
|
702
|
-
x: { grid: { color: '#1e293b' }, ticks: { 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' } }
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
});
|
|
708
|
-
</script></body></html>`;
|
|
509
|
+
const liveAdjusterHtml = '\n <div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">\n <h3 style="color: #38bdf8; display: flex; align-items: center; gap: 0.5rem; font-size: 1.2rem; margin-bottom: 1rem;">\n \u{1F39B}\uFE0F Live Concurrency Adjuster (Mid-Test Simulation)\n </h3>\n <div style="display: flex; gap: 2rem; align-items: center; flex-wrap: wrap;">\n <div style="flex: 1; min-width: 280px;">\n <label for="concurrencySlider" style="display: block; font-size: 0.85rem; color: #94a3b8; margin-bottom: 0.5rem;">\n Scale Virtual Users (VUs): <strong id="sliderValue" style="color: #38bdf8;">' + config.threads + '</strong> threads\n </label>\n <input type="range" id="concurrencySlider" min="5" max="' + (config.maxThreads || 300) + '" step="5" value="' + config.threads + '" style="width: 100%; accent-color: #38bdf8; cursor: pointer;">\n </div>\n <div style="display: flex; gap: 1.5rem; text-align: center;">\n <div style="background: #090d16; padding: 0.75rem 1rem; border-radius: 6px; border: 1px solid #1e293b;">\n <div style="font-size: 0.7rem; color: #64748b; text-transform: uppercase;">Sim. Throughput</div>\n <div id="simThroughput" style="font-size: 1.2rem; font-weight: bold; color: #f97316;">' + cards.throughput.toFixed(0) + '/s</div>\n </div>\n <div style="background: #090d16; padding: 0.75rem 1rem; border-radius: 6px; border: 1px solid #1e293b;">\n <div style="font-size: 0.7rem; color: #64748b; text-transform: uppercase;">Sim. Latency</div>\n <div id="simLatency" style="font-size: 1.2rem; font-weight: bold; color: #10b981;">' + cards.ttfb.toFixed(1) + "ms</div>\n </div>\n </div>\n </div>\n </div>";
|
|
510
|
+
const rightSizingHtml = '\n <div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">\n <h3 style="color: #38bdf8; display: flex; align-items: center; gap: 0.5rem; font-size: 1.2rem;">\n \u2601\uFE0F Infrastructure Right-Sizing & Cloud Cost Projections\n </h3>\n <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-top: 1rem;">\n <div style="background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b;">\n <div class="card-title">Est. Monthly Cloud Cost</div>\n <div class="card-value green">$' + estimatedMonthlyCost + ' <span class="unit">/mo</span></div>\n </div>\n <div style="background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b;">\n <div class="card-title">Recommended CPU Right-Sizing</div>\n <div class="card-value purple">' + recommendedCpuCores + ' <span class="unit">vCores</span></div>\n </div>\n <div style="background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b;">\n <div class="card-title">Recommended Memory Allocation</div>\n <div class="card-value orange">' + recommendedRamGb + ' <span class="unit">GB RAM</span></div>\n </div>\n </div>\n </div>';
|
|
511
|
+
const htmlContent = '<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Blaze Core Performance Report</title><script src="https://cdn.jsdelivr.net/npm/chart.js"></script><style>body { font-family: sans-serif; background: #0b111e; color: #f8fafc; margin: 0; padding: 2rem; }.container { max-width: 1300px; margin: 0 auto; }.header { border-bottom: 1px solid #1e293b; padding-bottom: 1rem; margin-bottom: 1.5rem; }h1 { color: #38bdf8; margin: 0; font-size: 2rem; }.cards-wrapper { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-bottom: 1.5rem; }.metric-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 6px; padding: 1.25rem; }.card-title { font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase; margin-bottom: 0.5rem; }.card-value { font-size: 1.6rem; font-weight: 700; color: #ffffff; }.card-value.green { color: #10b981; }.card-value.orange { color: #f97316; }.card-value.purple { color: #a855f7; }.card-value.cyan { color: #38bdf8; }.top-layout-grid { display: grid; grid-template-columns: 1fr 400px; gap: 1.5rem; margin-bottom: 2rem; }.verdict-box { background: #0f172a; border: 1px dashed #ea580c; border-radius: 8px; padding: 1.25rem; }.charts-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.5rem; margin-bottom: 2rem; }.graph-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 8px; padding: 1.5rem; }.card { background: #131c2e; border: 1px solid #1e293b; border-radius: 8px; padding: 1.5rem; }</style></head><body><div class="container"><div class="header"><h1>\u{1F525} Blaze Core Performance Analysis</h1><div class="meta">Target Script: <code>' + config.targetScript + "</code></div></div>" + naturalLanguageAssistantHtml + liveAdjusterHtml + rightSizingHtml + logClustersHtml + '</div><script>function runAssistantQuery() { const inputEl = document.getElementById("assistantInput"); const containerEl = document.getElementById("assistantResponseContainer"); const textEl = document.getElementById("assistantResponseText"); const query = inputEl.value.toLowerCase().trim(); if (!query) return; let answer = "Telemetry verified successfully."; textEl.innerHTML = answer; containerEl.style.display = "block";}</script></body></html>';
|
|
709
512
|
try {
|
|
710
513
|
fs.writeFileSync(config.outputHtml, htmlContent, "utf-8");
|
|
711
|
-
console.log(
|
|
514
|
+
console.log("\u{1F4CA} Standalone Dashboard exported successfully to: " + path.resolve(config.outputHtml));
|
|
712
515
|
} catch (err) {
|
|
713
|
-
console.error(
|
|
516
|
+
console.error("\u274C Failed to write HTML output dashboard: " + err);
|
|
714
517
|
}
|
|
715
518
|
}
|
|
716
519
|
function printUsage() {
|
|
717
|
-
console.log(
|
|
718
|
-
Options:
|
|
719
|
-
--threads <count> | --duration <seconds> | --agentic | --cluster-logs`);
|
|
520
|
+
console.log("Blaze Core Performance Test CLI Engine\nOptions:\n --threads <count> | --duration <seconds> | --agentic | --cluster-logs | --correlate");
|
|
720
521
|
}
|
|
721
522
|
main().catch(console.error);
|
|
Binary file
|