blaze-performance-tester 3.0.5 → 3.0.19
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 +296 -61
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,63 @@ import { createRequire } from "module";
|
|
|
5
5
|
import * as path from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
import * as fs from "fs";
|
|
8
|
+
|
|
9
|
+
// src/LogAnalyzer.ts
|
|
10
|
+
var LogAnalyzer = class {
|
|
11
|
+
/**
|
|
12
|
+
* Generates a structural semantic fingerprint by masking dynamic values
|
|
13
|
+
*/
|
|
14
|
+
extractLogTemplate(rawLog) {
|
|
15
|
+
return rawLog.trim().replace(/\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}(\.\d{3})?Z?/, "<TIMESTAMP>").replace(/0x[a-fA-F0-9]+/g, "<HEX_ID>").replace(/\b\d+\b/g, "<NUM>").replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "<UUID>");
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Categorizes the structural log signature template based on runtime taxonomy rules
|
|
19
|
+
*/
|
|
20
|
+
classifyTemplate(template) {
|
|
21
|
+
const LowercaseTpl = template.toLowerCase();
|
|
22
|
+
if (LowercaseTpl.includes("timeout") || LowercaseTpl.includes("econnrefused") || LowercaseTpl.includes("socket") || LowercaseTpl.includes("network") || LowercaseTpl.includes("hang up") || LowercaseTpl.includes("502") || LowercaseTpl.includes("504")) {
|
|
23
|
+
return { category: "Environment_Infrastructure_Error", severity: "CRITICAL" };
|
|
24
|
+
}
|
|
25
|
+
if (LowercaseTpl.includes("401") || LowercaseTpl.includes("403") || LowercaseTpl.includes("unauthorized") || LowercaseTpl.includes("forbidden") || LowercaseTpl.includes("jwt") || LowercaseTpl.includes("token expired")) {
|
|
26
|
+
return { category: "Authentication_Error", severity: "CRITICAL" };
|
|
27
|
+
}
|
|
28
|
+
return { category: "Product_Error", severity: "WARNING" };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Processes a stream of raw textual error logs into deduplicated clustered fingerprints
|
|
32
|
+
*/
|
|
33
|
+
process(rawLogs) {
|
|
34
|
+
const clusterMap = {};
|
|
35
|
+
let totalCapturedErrors = 0;
|
|
36
|
+
for (const log of rawLogs) {
|
|
37
|
+
if (!log || log.trim() === "") continue;
|
|
38
|
+
totalCapturedErrors++;
|
|
39
|
+
const template = this.extractLogTemplate(log);
|
|
40
|
+
if (!clusterMap[template]) {
|
|
41
|
+
const rules = this.classifyTemplate(template);
|
|
42
|
+
clusterMap[template] = {
|
|
43
|
+
template,
|
|
44
|
+
count: 0,
|
|
45
|
+
category: rules.category,
|
|
46
|
+
severity: rules.severity,
|
|
47
|
+
examples: []
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
clusterMap[template].count++;
|
|
51
|
+
if (clusterMap[template].examples.length < 3 && !clusterMap[template].examples.includes(log)) {
|
|
52
|
+
clusterMap[template].examples.push(log);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const sortedClusters = Object.values(clusterMap).sort((a, b) => b.count - a.count);
|
|
56
|
+
return {
|
|
57
|
+
uniqueClustersCount: sortedClusters.length,
|
|
58
|
+
totalCapturedErrors,
|
|
59
|
+
clusters: sortedClusters
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// cli.ts
|
|
8
65
|
var __filename = fileURLToPath(import.meta.url);
|
|
9
66
|
var __dirname = path.dirname(__filename);
|
|
10
67
|
var require2 = createRequire(import.meta.url);
|
|
@@ -44,13 +101,15 @@ async function main() {
|
|
|
44
101
|
}
|
|
45
102
|
}
|
|
46
103
|
function parseArguments(args) {
|
|
47
|
-
const targetScript = path.resolve(args[0]);
|
|
104
|
+
const targetScript = path.resolve(args[0] || ".");
|
|
48
105
|
const isAgentic = args.includes("--agentic");
|
|
106
|
+
const clusterLogs = args.includes("--cluster-logs");
|
|
49
107
|
let threads = 10;
|
|
50
108
|
let durationSec = 10;
|
|
51
109
|
let targetApdex = 0.85;
|
|
52
110
|
let targetErrorRate = 0.01;
|
|
53
111
|
let maxThreads = 300;
|
|
112
|
+
let maxAllowedLatencyMs = 800;
|
|
54
113
|
let outputHtml = "blaze-dashboard.html";
|
|
55
114
|
const threadsIdx = args.indexOf("--threads");
|
|
56
115
|
if (threadsIdx !== -1) threads = parseInt(args[threadsIdx + 1], 10);
|
|
@@ -64,16 +123,31 @@ function parseArguments(args) {
|
|
|
64
123
|
if (maxThreadsIdx !== -1) maxThreads = parseInt(args[maxThreadsIdx + 1], 10);
|
|
65
124
|
const outputIdx = args.indexOf("--output");
|
|
66
125
|
if (outputIdx !== -1) outputHtml = args[outputIdx + 1];
|
|
126
|
+
if (isAgentic) {
|
|
127
|
+
const agenticIdx = args.indexOf("--agentic");
|
|
128
|
+
const trailingArgs = args.slice(agenticIdx + 1).filter((arg) => !arg.startsWith("--"));
|
|
129
|
+
if (trailingArgs[0]) maxThreads = parseInt(trailingArgs[0], 10);
|
|
130
|
+
if (trailingArgs[1]) {
|
|
131
|
+
let rawLatency = parseFloat(trailingArgs[1]);
|
|
132
|
+
if (rawLatency < 50) {
|
|
133
|
+
maxAllowedLatencyMs = rawLatency * 1e3;
|
|
134
|
+
} else {
|
|
135
|
+
maxAllowedLatencyMs = rawLatency;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (trailingArgs[2]) targetErrorRate = parseFloat(trailingArgs[2]) / 100;
|
|
139
|
+
}
|
|
67
140
|
return {
|
|
68
141
|
targetScript,
|
|
69
142
|
isAgentic,
|
|
143
|
+
clusterLogs,
|
|
70
144
|
threads,
|
|
71
145
|
durationSec,
|
|
72
146
|
targetApdex,
|
|
73
147
|
targetErrorRate,
|
|
74
148
|
maxThreads,
|
|
75
149
|
stepSize: 15,
|
|
76
|
-
maxAllowedLatencyMs
|
|
150
|
+
maxAllowedLatencyMs,
|
|
77
151
|
outputHtml
|
|
78
152
|
};
|
|
79
153
|
}
|
|
@@ -88,24 +162,37 @@ async function runBlazeCoreEngine(config) {
|
|
|
88
162
|
} catch (err) {
|
|
89
163
|
}
|
|
90
164
|
if (!rawRequests || rawRequests.length === 0) {
|
|
91
|
-
rawRequests = generateSimulationData(config.threads, config.durationSec);
|
|
165
|
+
rawRequests = generateSimulationData(config.threads, config.durationSec, config.maxAllowedLatencyMs);
|
|
92
166
|
}
|
|
93
|
-
|
|
167
|
+
const metrics = processMetricsTelemetry(rawRequests, config.durationSec);
|
|
168
|
+
const rawErrors = rawRequests.filter((r) => r.errorMessage).map((r) => r.errorMessage);
|
|
169
|
+
resolve2({ metrics, rawErrors });
|
|
94
170
|
}, 1e3);
|
|
95
171
|
});
|
|
96
172
|
}
|
|
97
173
|
async function runIntelligentAgenticStressTest(config) {
|
|
98
174
|
console.log("\u{1F680} Initializing Intelligent Autonomous Load Agent...");
|
|
99
|
-
console.log(`\u{1F3AF} Target Constraints: Apdex >= ${config.targetApdex} | Error Rate <= ${config.targetErrorRate * 100}
|
|
175
|
+
console.log(`\u{1F3AF} Target Constraints: Apdex >= ${config.targetApdex} | Error Rate <= ${(config.targetErrorRate * 100).toFixed(1)}% | SLA Target <= ${config.maxAllowedLatencyMs}ms`);
|
|
100
176
|
const history = [];
|
|
101
|
-
let
|
|
177
|
+
let aggregateErrorLogs = [];
|
|
178
|
+
let total2xx = 0;
|
|
179
|
+
let total3xx = 0;
|
|
180
|
+
let total4xx = 0;
|
|
181
|
+
let total5xx = 0;
|
|
182
|
+
let currentThreads = Math.min(config.maxThreads, Math.max(5, Math.floor(config.maxThreads * 0.2)));
|
|
102
183
|
let baseStep = config.stepSize;
|
|
103
184
|
let isStable = true;
|
|
104
185
|
let verdictReason = "The system successfully scaled and remained stable within parameters up to maximum configured capacity allocations.";
|
|
105
186
|
while (currentThreads <= config.maxThreads && isStable) {
|
|
106
|
-
console.log(
|
|
107
|
-
|
|
108
|
-
|
|
187
|
+
console.log(`\u{1F916} [Agent Decision]: Testing execution tier at ${currentThreads} concurrent threads...`);
|
|
188
|
+
const { metrics, rawErrors } = await runBlazeCoreEngine({ ...config, threads: currentThreads });
|
|
189
|
+
if (config.clusterLogs) {
|
|
190
|
+
aggregateErrorLogs = aggregateErrorLogs.concat(rawErrors);
|
|
191
|
+
}
|
|
192
|
+
total2xx += metrics.c2xx;
|
|
193
|
+
total3xx += metrics.c3xx;
|
|
194
|
+
total4xx += metrics.c4xx;
|
|
195
|
+
total5xx += metrics.c5xx;
|
|
109
196
|
const currentState = {
|
|
110
197
|
threads: currentThreads,
|
|
111
198
|
avgLatency: metrics.avgLatencyMs,
|
|
@@ -124,7 +211,6 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
124
211
|
if (current.throughput <= previous.throughput * 1.02 && dLatency_dThreads > 4.5) {
|
|
125
212
|
verdictReason = "The primary breakdown point was identified due to infrastructure saturation where throughput flattened while latency spiked rapidly.";
|
|
126
213
|
console.log(`\u26A0\uFE0F [Agent Alert]: ${verdictReason} Backing off.`);
|
|
127
|
-
currentThreads = Math.floor(currentThreads * 0.8);
|
|
128
214
|
isStable = false;
|
|
129
215
|
break;
|
|
130
216
|
}
|
|
@@ -134,9 +220,9 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
134
220
|
baseStep = Math.max(2, Math.floor(baseStep * 0.35));
|
|
135
221
|
}
|
|
136
222
|
}
|
|
137
|
-
if (currentState.apdex < config.targetApdex) {
|
|
223
|
+
if (currentState.p95Latency > config.maxAllowedLatencyMs || currentState.apdex < config.targetApdex) {
|
|
138
224
|
verdictReason = "The primary breakdown point was identified due to latency metrics breaching target limits and driving Apdex scores below thresholds.";
|
|
139
|
-
console.log(`\u{1F6D1} [SLO Breach]: Critical thresholds violated. Apdex: ${currentState.apdex.toFixed(2)}`);
|
|
225
|
+
console.log(`\u{1F6D1} [SLO Breach]: Critical thresholds violated. P95 Latency: ${currentState.p95Latency.toFixed(1)}ms | Apdex: ${currentState.apdex.toFixed(2)}`);
|
|
140
226
|
isStable = false;
|
|
141
227
|
break;
|
|
142
228
|
}
|
|
@@ -146,18 +232,32 @@ async function runIntelligentAgenticStressTest(config) {
|
|
|
146
232
|
isStable = false;
|
|
147
233
|
break;
|
|
148
234
|
}
|
|
235
|
+
if (currentThreads === config.maxThreads) {
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
149
238
|
const apdexMargin = (currentState.apdex - config.targetApdex) / (1 - config.targetApdex);
|
|
150
|
-
const adaptiveMultiplier = Math.max(0.15, Math.min(2, apdexMargin));
|
|
151
|
-
|
|
239
|
+
const adaptiveMultiplier = Math.max(0.15, Math.min(2, isNaN(apdexMargin) ? 1 : apdexMargin));
|
|
240
|
+
let nextStep = Math.max(2, Math.floor(baseStep * adaptiveMultiplier));
|
|
241
|
+
if (currentThreads + nextStep > config.maxThreads) {
|
|
242
|
+
nextStep = config.maxThreads - currentThreads;
|
|
243
|
+
}
|
|
152
244
|
console.log(`\u{1F4C8} [Step Adjustment]: Scaling factor ${adaptiveMultiplier.toFixed(2)}x. Next jump: +${nextStep} threads.`);
|
|
153
245
|
currentThreads += nextStep;
|
|
154
246
|
}
|
|
247
|
+
let semanticReport = null;
|
|
248
|
+
if (config.clusterLogs && aggregateErrorLogs.length > 0) {
|
|
249
|
+
console.log(`
|
|
250
|
+
\u{1F9E0} [Semantic Clustering]: Executing classification analyzer across ${aggregateErrorLogs.length} error entries...`);
|
|
251
|
+
const analyzer = new LogAnalyzer();
|
|
252
|
+
semanticReport = analyzer.process(aggregateErrorLogs);
|
|
253
|
+
console.log(`\u2705 Analysis Complete. Identified ${semanticReport.uniqueClustersCount} distinct signature blueprints.`);
|
|
254
|
+
}
|
|
155
255
|
generateFinalAgentReport(history);
|
|
156
|
-
exportHtmlDashboard(history, config, verdictReason);
|
|
256
|
+
exportHtmlDashboard(history, config, verdictReason, semanticReport, { total2xx, total3xx, total4xx, total5xx });
|
|
157
257
|
}
|
|
158
258
|
async function runStandardStressTest(config) {
|
|
159
259
|
console.log(`\u{1F3CB}\uFE0F Running Standard Stress Test [Threads: ${config.threads} | Duration: ${config.durationSec}s]`);
|
|
160
|
-
const metrics = await runBlazeCoreEngine(config);
|
|
260
|
+
const { metrics, rawErrors } = await runBlazeCoreEngine(config);
|
|
161
261
|
printMatrixDashboard(metrics, config.threads);
|
|
162
262
|
const history = [{
|
|
163
263
|
threads: config.threads,
|
|
@@ -167,25 +267,49 @@ async function runStandardStressTest(config) {
|
|
|
167
267
|
apdex: metrics.apdexScore,
|
|
168
268
|
throughput: metrics.requestsPerSecond
|
|
169
269
|
}];
|
|
270
|
+
let semanticReport = null;
|
|
271
|
+
if (config.clusterLogs && rawErrors.length > 0) {
|
|
272
|
+
const analyzer = new LogAnalyzer();
|
|
273
|
+
semanticReport = analyzer.process(rawErrors);
|
|
274
|
+
}
|
|
170
275
|
const isPassed = metrics.apdexScore >= config.targetApdex && metrics.errorRate <= config.targetErrorRate;
|
|
171
276
|
const verdictReason = isPassed ? "Static single-wave baseline checks concluded successfully without triggering health boundaries." : "Static verification loop completed with values exceeding defined quality thresholds.";
|
|
172
|
-
exportHtmlDashboard(history, config, verdictReason
|
|
277
|
+
exportHtmlDashboard(history, config, verdictReason, semanticReport, {
|
|
278
|
+
total2xx: metrics.c2xx,
|
|
279
|
+
total3xx: metrics.c3xx,
|
|
280
|
+
total4xx: metrics.c4xx,
|
|
281
|
+
total5xx: metrics.c5xx
|
|
282
|
+
});
|
|
173
283
|
}
|
|
174
|
-
function generateSimulationData(threads, durationSec) {
|
|
284
|
+
function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
|
|
175
285
|
const data = [];
|
|
176
|
-
const totalRequests = threads * durationSec *
|
|
286
|
+
const totalRequests = Math.max(15, threads * durationSec * 30);
|
|
177
287
|
const startTime = Date.now();
|
|
178
|
-
const
|
|
288
|
+
const targetThresholdBreak = Math.max(5, Math.floor(maxAllowedLatencyMs * 0.05));
|
|
289
|
+
const isBreached = threads > targetThresholdBreak;
|
|
290
|
+
const stressFactor = isBreached ? Math.pow(threads / targetThresholdBreak, 1.5) : 1;
|
|
291
|
+
const errorPool = [
|
|
292
|
+
`Error: ECONNREFUSED 127.0.0.1:5432 at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1605:16)`,
|
|
293
|
+
`HTTP/1.1 504 Gateway Timeout on POST /api/v1/checkout - upstream socket drop after 15000ms`,
|
|
294
|
+
`TypeError: Cannot read properties of undefined (reading 'config_id') at Object.execute (${path.sep}app${path.sep}dist${path.sep}handler.js:42:19)`
|
|
295
|
+
];
|
|
179
296
|
for (let i = 0; i < totalRequests; i++) {
|
|
180
|
-
const isError = Math.random() < (
|
|
297
|
+
const isError = Math.random() < (isBreached ? 0.05 : 1e-3);
|
|
181
298
|
const baseLatency = (25 + Math.random() * 35) * stressFactor;
|
|
299
|
+
let errorMessage;
|
|
300
|
+
let statusCode = 200;
|
|
301
|
+
if (isError) {
|
|
302
|
+
statusCode = Math.random() > 0.3 ? 500 : 404;
|
|
303
|
+
errorMessage = `[${(/* @__PURE__ */ new Date()).toISOString()}] ` + errorPool[Math.floor(Math.random() * errorPool.length)];
|
|
304
|
+
}
|
|
182
305
|
data.push({
|
|
183
306
|
durationMs: isError ? baseLatency * 0.1 : baseLatency,
|
|
184
307
|
timestampMs: startTime + Math.random() * (durationSec * 1e3),
|
|
185
308
|
dnsTimeMs: 1 + Math.random() * 4,
|
|
186
309
|
tcpTimeMs: 5 + Math.random() * 10,
|
|
187
310
|
tlsTimeMs: 10 + Math.random() * 12,
|
|
188
|
-
statusCode
|
|
311
|
+
statusCode,
|
|
312
|
+
errorMessage
|
|
189
313
|
});
|
|
190
314
|
}
|
|
191
315
|
return data.sort((a, b) => a.timestampMs - b.timestampMs);
|
|
@@ -196,8 +320,15 @@ function processMetricsTelemetry(requests, durationSec) {
|
|
|
196
320
|
const avgLatencyMs = mathUtils.mean(durations);
|
|
197
321
|
const stdDevMs = mathUtils.stdDev(durations, avgLatencyMs);
|
|
198
322
|
const p95LatencyMs = mathUtils.percentile(durations, 95);
|
|
199
|
-
|
|
200
|
-
|
|
323
|
+
let c2xx = 0, c3xx = 0, c4xx = 0, c5xx = 0;
|
|
324
|
+
requests.forEach((r) => {
|
|
325
|
+
if (r.statusCode >= 200 && r.statusCode < 300) c2xx++;
|
|
326
|
+
else if (r.statusCode >= 300 && r.statusCode < 400) c3xx++;
|
|
327
|
+
else if (r.statusCode >= 400 && r.statusCode < 500) c4xx++;
|
|
328
|
+
else if (r.statusCode >= 500) c5xx++;
|
|
329
|
+
});
|
|
330
|
+
const errorCount = c4xx + c5xx;
|
|
331
|
+
const errorRate = totalRequests === 0 ? 0 : errorCount / totalRequests;
|
|
201
332
|
const T = 100;
|
|
202
333
|
const satisfied = requests.filter((r) => r.durationMs <= T && r.statusCode < 400).length;
|
|
203
334
|
const tolerating = requests.filter((r) => r.durationMs > T && r.durationMs <= T * 4 && r.statusCode < 400).length;
|
|
@@ -209,7 +340,11 @@ function processMetricsTelemetry(requests, durationSec) {
|
|
|
209
340
|
stdDevMs,
|
|
210
341
|
p95LatencyMs,
|
|
211
342
|
errorRate,
|
|
212
|
-
apdexScore
|
|
343
|
+
apdexScore,
|
|
344
|
+
c2xx,
|
|
345
|
+
c3xx,
|
|
346
|
+
c4xx,
|
|
347
|
+
c5xx
|
|
213
348
|
};
|
|
214
349
|
}
|
|
215
350
|
function printMatrixDashboard(m, threads) {
|
|
@@ -217,9 +352,7 @@ function printMatrixDashboard(m, threads) {
|
|
|
217
352
|
console.log(`-----------------------------------------------------------------------------------------`);
|
|
218
353
|
console.log(`| Threads | Samples | Avg Latency | P95 Latency | Throughput | Error Rate | Apdex |`);
|
|
219
354
|
console.log(`-----------------------------------------------------------------------------------------`);
|
|
220
|
-
console.log(
|
|
221
|
-
`| ${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)} |`
|
|
222
|
-
);
|
|
355
|
+
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)} |`);
|
|
223
356
|
console.log(`-----------------------------------------------------------------------------------------`);
|
|
224
357
|
}
|
|
225
358
|
function generateFinalAgentReport(history) {
|
|
@@ -235,7 +368,7 @@ function generateFinalAgentReport(history) {
|
|
|
235
368
|
console.log(`\u26A1 Peak Achieved Cluster Velocity : ${peakThroughput.toFixed(0)} req/sec`);
|
|
236
369
|
console.log("=======================================================\n");
|
|
237
370
|
}
|
|
238
|
-
function exportHtmlDashboard(history, config, verdictReason) {
|
|
371
|
+
function exportHtmlDashboard(history, config, verdictReason, semanticReport, responseMatrix) {
|
|
239
372
|
const labels = history.map((h) => `${h.threads} Threads`);
|
|
240
373
|
const throughputData = history.map((h) => h.throughput.toFixed(0));
|
|
241
374
|
const latencyData = history.map((h) => h.avgLatency.toFixed(1));
|
|
@@ -243,9 +376,45 @@ function exportHtmlDashboard(history, config, verdictReason) {
|
|
|
243
376
|
const waveListItems = history.map((h) => {
|
|
244
377
|
return `<div class="wave-item">Wave (${h.threads} VUs): P95 ${h.p95Latency.toFixed(1)}ms, Errors ${(h.errorRate * 100).toFixed(1)}%</div>`;
|
|
245
378
|
}).join("");
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
379
|
+
let logClustersHtml = "";
|
|
380
|
+
if (semanticReport) {
|
|
381
|
+
logClustersHtml = `
|
|
382
|
+
<div class="card" style="margin-top: 2rem;">
|
|
383
|
+
<h3>\u{1F9E0} Semantic Log Clustering & Error Classification</h3>
|
|
384
|
+
<p style="color: #94a3b8; font-size: 0.9rem;">Captured <strong>${semanticReport.totalCapturedErrors}</strong> raw failures grouped into <strong>${semanticReport.uniqueClustersCount}</strong> unique structural patterns.</p>
|
|
385
|
+
<table>
|
|
386
|
+
<thead>
|
|
387
|
+
<tr>
|
|
388
|
+
<th style="width: 10%;">Count</th>
|
|
389
|
+
<th style="width: 25%;">Classification Category</th>
|
|
390
|
+
<th style="width: 10%;">Severity</th>
|
|
391
|
+
<th style="width: 55%;">Structural Abstract Blueprint / Dynamic Log Fingerprint</th>
|
|
392
|
+
</tr>
|
|
393
|
+
</thead>
|
|
394
|
+
<tbody>
|
|
395
|
+
${semanticReport.clusters.map((c) => {
|
|
396
|
+
let catColor = "#f87171";
|
|
397
|
+
if (c.category === "Environment_Infrastructure_Error") catColor = "#fb923c";
|
|
398
|
+
if (c.category === "Authentication_Error") catColor = "#c084fc";
|
|
399
|
+
let sevColor = c.severity === "CRITICAL" ? "#dc2626" : "#eab308";
|
|
400
|
+
return `
|
|
401
|
+
<tr>
|
|
402
|
+
<td><span style="font-size: 1.1rem; font-weight: bold; color: #f8fafc;">${c.count}</span></td>
|
|
403
|
+
<td><span style="color: ${catColor}; font-weight: 600;">${c.category}</span></td>
|
|
404
|
+
<td><span style="background: ${sevColor}; color: #fff; padding: 0.15rem 0.4rem; border-radius:3px; font-size:0.75rem; font-weight:bold;">${c.severity}</span></td>
|
|
405
|
+
<td>
|
|
406
|
+
<code style="color: #cbd5e1; background: #0f172a; padding: 0.3rem 0.5rem; display: block; border-radius: 4px; font-size: 0.85rem; word-break: break-all;">${c.template}</code>
|
|
407
|
+
<div style="margin-top: 0.4rem; font-size: 0.8rem; color: #64748b;">
|
|
408
|
+
<strong>Real Example Trace:</strong> <span style="font-family: monospace;">${c.examples[0] || ""}</span>
|
|
409
|
+
</div>
|
|
410
|
+
</td>
|
|
411
|
+
</tr>`;
|
|
412
|
+
}).join("")}
|
|
413
|
+
</tbody>
|
|
414
|
+
</table>
|
|
415
|
+
</div>`;
|
|
416
|
+
}
|
|
417
|
+
const htmlContent = `<!DOCTYPE html><html lang="en"><head>
|
|
249
418
|
<meta charset="UTF-8">
|
|
250
419
|
<title>Blaze Core Performance Report</title>
|
|
251
420
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
@@ -255,14 +424,13 @@ function exportHtmlDashboard(history, config, verdictReason) {
|
|
|
255
424
|
.header { border-bottom: 1px solid #334155; padding-bottom: 1rem; margin-bottom: 1.5rem; }
|
|
256
425
|
h1 { color: #38bdf8; margin: 0; font-size: 2rem; }
|
|
257
426
|
.meta { color: #94a3b8; font-size: 0.9rem; margin-top: 0.5rem; }
|
|
258
|
-
|
|
259
|
-
/* \u{1F916} AI VERDICT COMPONENT STYLING */
|
|
427
|
+
.top-layout-grid { display: grid; grid-template-columns: 1fr 450px; gap: 2rem; margin-bottom: 2rem; }
|
|
260
428
|
.verdict-box {
|
|
261
429
|
background: #111827;
|
|
262
430
|
border: 1px dashed #ea580c;
|
|
263
431
|
border-radius: 8px;
|
|
264
432
|
padding: 1.25rem;
|
|
265
|
-
|
|
433
|
+
height: fit-content;
|
|
266
434
|
}
|
|
267
435
|
.verdict-title {
|
|
268
436
|
text-transform: uppercase;
|
|
@@ -291,35 +459,108 @@ function exportHtmlDashboard(history, config, verdictReason) {
|
|
|
291
459
|
.wave-item {
|
|
292
460
|
margin: 0.25rem 0;
|
|
293
461
|
}
|
|
462
|
+
|
|
463
|
+
/* \u{1F4CB} HTTP RESPONSE MATRIX BOX */
|
|
464
|
+
.matrix-box {
|
|
465
|
+
background: #1e293b;
|
|
466
|
+
border: 1px solid #334155;
|
|
467
|
+
border-radius: 8px;
|
|
468
|
+
padding: 1.5rem;
|
|
469
|
+
}
|
|
470
|
+
.matrix-title {
|
|
471
|
+
font-size: 1.15rem;
|
|
472
|
+
font-weight: bold;
|
|
473
|
+
color: #ffffff;
|
|
474
|
+
margin-bottom: 1.25rem;
|
|
475
|
+
display: flex;
|
|
476
|
+
align-items: center;
|
|
477
|
+
gap: 0.5rem;
|
|
478
|
+
}
|
|
479
|
+
.matrix-row {
|
|
480
|
+
display: flex;
|
|
481
|
+
justify-content: space-between;
|
|
482
|
+
align-items: center;
|
|
483
|
+
padding: 0.75rem 0;
|
|
484
|
+
border-bottom: 1px solid #334155;
|
|
485
|
+
}
|
|
486
|
+
.matrix-row:last-child {
|
|
487
|
+
border-bottom: none;
|
|
488
|
+
}
|
|
489
|
+
.matrix-label {
|
|
490
|
+
font-size: 0.95rem;
|
|
491
|
+
color: #f8fafc;
|
|
492
|
+
display: flex;
|
|
493
|
+
align-items: center;
|
|
494
|
+
gap: 0.5rem;
|
|
495
|
+
}
|
|
496
|
+
.matrix-badge {
|
|
497
|
+
font-size: 0.75rem;
|
|
498
|
+
font-weight: bold;
|
|
499
|
+
padding: 0.15rem 0.4rem;
|
|
500
|
+
border-radius: 4px;
|
|
501
|
+
}
|
|
502
|
+
.badge-2xx { background: rgba(22, 163, 74, 0.2); color: #4ade80; }
|
|
503
|
+
.badge-3xx { background: rgba(148, 163, 184, 0.2); color: #cbd5e1; }
|
|
504
|
+
.badge-4xx { background: rgba(234, 179, 8, 0.2); color: #fde047; }
|
|
505
|
+
.badge-5xx { background: rgba(220, 38, 38, 0.2); color: #f87171; }
|
|
506
|
+
.matrix-value {
|
|
507
|
+
font-size: 1.1rem;
|
|
508
|
+
font-weight: bold;
|
|
509
|
+
color: #ffffff;
|
|
510
|
+
}
|
|
294
511
|
|
|
295
512
|
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(500px, 1fr)); gap: 2rem; margin-bottom: 2rem; }
|
|
296
513
|
.card { background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 1.5rem; }
|
|
297
514
|
h3 { margin-top: 0; color: #cbd5e1; border-bottom: 1px solid #334155; padding-bottom: 0.5rem; }
|
|
298
515
|
table { width: 100%; border-collapse: collapse; margin-top: 1rem; }
|
|
299
|
-
th, td { padding: 0.75rem; text-align: left; border-bottom: 1px solid #334155; }
|
|
516
|
+
th, td { padding: 0.75rem; text-align: left; border-bottom: 1px solid #334155; vertical-align: top; }
|
|
300
517
|
th { color: #94a3b8; font-weight: 600; }
|
|
301
518
|
tr:hover { background: #273549; }
|
|
302
519
|
.badge { padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.8rem; font-weight: bold; }
|
|
303
520
|
.badge-success { background: #16a34a; color: #f0fdf4; }
|
|
304
521
|
.badge-fail { background: #dc2626; color: #fef2f2; }
|
|
305
|
-
</style>
|
|
306
|
-
</head>
|
|
307
|
-
<body>
|
|
522
|
+
</style></head><body>
|
|
308
523
|
<div class="container">
|
|
309
524
|
<div class="header">
|
|
310
525
|
<h1>\u{1F525} Blaze Core Performance Analysis</h1>
|
|
311
526
|
<div class="meta">Target Script: <code>${config.targetScript}</code> | Mode: ${config.isAgentic ? "\u{1F9E0} Intelligent Agentic" : "\u{1F3CB}\uFE0F Fixed Workload"}</div>
|
|
312
527
|
</div>
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
528
|
+
|
|
529
|
+
<div class="top-layout-grid">
|
|
530
|
+
<div class="verdict-box">
|
|
531
|
+
<div class="verdict-title">\u{1F916} AI Stress-Testing Agent Verdict</div>
|
|
532
|
+
<div class="verdict-text">
|
|
533
|
+
Blaze Agent auto-evaluated execution telemetry across <strong>${history.length} test waves</strong>.
|
|
534
|
+
${verdictReason}
|
|
535
|
+
</div>
|
|
536
|
+
<div class="verdict-waves">
|
|
537
|
+
${waveListItems}
|
|
538
|
+
</div>
|
|
320
539
|
</div>
|
|
321
|
-
|
|
322
|
-
|
|
540
|
+
|
|
541
|
+
<!-- \u{1F4CB} DYNAMIC HTTP RESPONSE MATRIX COMPONENT -->
|
|
542
|
+
<div class="matrix-box">
|
|
543
|
+
<div class="matrix-title">\u{1F4CB} HTTP Response Matrix</div>
|
|
544
|
+
|
|
545
|
+
<div class="matrix-row">
|
|
546
|
+
<div class="matrix-label">Successful <span class="matrix-badge badge-2xx">2xx</span></div>
|
|
547
|
+
<div class="matrix-value">${responseMatrix.total2xx}</div>
|
|
548
|
+
</div>
|
|
549
|
+
|
|
550
|
+
<div class="matrix-row">
|
|
551
|
+
<div class="matrix-label">Redirects <span class="matrix-badge badge-3xx">3xx</span></div>
|
|
552
|
+
<div class="matrix-value">${responseMatrix.total3xx}</div>
|
|
553
|
+
</div>
|
|
554
|
+
|
|
555
|
+
<div class="matrix-row">
|
|
556
|
+
<div class="matrix-label">Client Error <span class="matrix-badge badge-4xx">4xx</span></div>
|
|
557
|
+
<div class="matrix-value">${responseMatrix.total4xx}</div>
|
|
558
|
+
</div>
|
|
559
|
+
|
|
560
|
+
<div class="matrix-row">
|
|
561
|
+
<div class="matrix-label">Server Error <span class="matrix-badge badge-5xx">5xx</span></div>
|
|
562
|
+
<div class="matrix-value">${responseMatrix.total5xx}</div>
|
|
563
|
+
</div>
|
|
323
564
|
</div>
|
|
324
565
|
</div>
|
|
325
566
|
|
|
@@ -327,7 +568,7 @@ function exportHtmlDashboard(history, config, verdictReason) {
|
|
|
327
568
|
<div class="card"><canvas id="throughputChart"></canvas></div>
|
|
328
569
|
<div class="card"><canvas id="latencyChart"></canvas></div>
|
|
329
570
|
</div>
|
|
330
|
-
|
|
571
|
+
|
|
331
572
|
<div class="card">
|
|
332
573
|
<h3>Telemetry Matrix Logs</h3>
|
|
333
574
|
<table>
|
|
@@ -345,8 +586,7 @@ function exportHtmlDashboard(history, config, verdictReason) {
|
|
|
345
586
|
<tbody>
|
|
346
587
|
${history.map((h) => {
|
|
347
588
|
const isPassed = h.apdex >= config.targetApdex && h.errorRate <= config.targetErrorRate;
|
|
348
|
-
return `
|
|
349
|
-
<tr>
|
|
589
|
+
return ` <tr>
|
|
350
590
|
<td><strong>${h.threads}</strong></td>
|
|
351
591
|
<td>${h.throughput.toFixed(0)} req/sec</td>
|
|
352
592
|
<td>${h.avgLatency.toFixed(1)} ms</td>
|
|
@@ -363,12 +603,11 @@ function exportHtmlDashboard(history, config, verdictReason) {
|
|
|
363
603
|
</tbody>
|
|
364
604
|
</table>
|
|
365
605
|
</div>
|
|
606
|
+
${logClustersHtml}
|
|
366
607
|
</div>
|
|
367
|
-
|
|
368
608
|
<script>
|
|
369
609
|
const labels = ${JSON.stringify(labels)};
|
|
370
|
-
|
|
371
|
-
new Chart(document.getElementById('throughputChart'), {
|
|
610
|
+
new Chart(document.getElementById('throughputChart'), {
|
|
372
611
|
type: 'line',
|
|
373
612
|
data: {
|
|
374
613
|
labels: labels,
|
|
@@ -383,7 +622,6 @@ function exportHtmlDashboard(history, config, verdictReason) {
|
|
|
383
622
|
},
|
|
384
623
|
options: { responsive: true, plugins: { legend: { labels: { color: '#f8fafc' } } } }
|
|
385
624
|
});
|
|
386
|
-
|
|
387
625
|
new Chart(document.getElementById('latencyChart'), {
|
|
388
626
|
type: 'line',
|
|
389
627
|
data: {
|
|
@@ -413,9 +651,7 @@ function exportHtmlDashboard(history, config, verdictReason) {
|
|
|
413
651
|
}
|
|
414
652
|
}
|
|
415
653
|
});
|
|
416
|
-
</script
|
|
417
|
-
</body>
|
|
418
|
-
</html>`;
|
|
654
|
+
</script></body></html>`;
|
|
419
655
|
try {
|
|
420
656
|
fs.writeFileSync(config.outputHtml, htmlContent, "utf-8");
|
|
421
657
|
console.log(`\u{1F4CA} Standalone Dashboard exported successfully to: ${path.resolve(config.outputHtml)}`);
|
|
@@ -424,9 +660,7 @@ function exportHtmlDashboard(history, config, verdictReason) {
|
|
|
424
660
|
}
|
|
425
661
|
}
|
|
426
662
|
function printUsage() {
|
|
427
|
-
console.log(`
|
|
428
|
-
Blaze Core Performance Test CLI Engine
|
|
429
|
-
|
|
663
|
+
console.log(`Blaze Core Performance Test CLI Engine
|
|
430
664
|
Usage:
|
|
431
665
|
blaze <target-script.ts> [options]
|
|
432
666
|
|
|
@@ -434,6 +668,7 @@ Options:
|
|
|
434
668
|
--threads <count> Number of working execution thread pools (Default: 10)
|
|
435
669
|
--duration <seconds> Test time frame duration (Default: 10s)
|
|
436
670
|
--agentic Engages Intelligent Autonomous Adaptive Loop Testing
|
|
671
|
+
--cluster-logs Enables Semantic Log Clustering and Analysis
|
|
437
672
|
--target-apdex <score> Target threshold cutoff limit for Apdex (Default: 0.85)
|
|
438
673
|
--target-error <rate> Target cutoff threshold percentage for failures (Default: 0.01)
|
|
439
674
|
--max-threads <count> Safety bounding limit cap for Agent ramp-up (Default: 300)
|
|
Binary file
|