blaze-performance-tester 2.0.13 → 3.0.1
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 +341 -386
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,414 +1,369 @@
|
|
|
1
|
+
#!/usr/bin/env ts-node
|
|
2
|
+
|
|
1
3
|
// cli.ts
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { fileURLToPath } from "url";
|
|
5
4
|
import { createRequire } from "module";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import * as fs from "fs";
|
|
6
8
|
var __filename = fileURLToPath(import.meta.url);
|
|
7
9
|
var __dirname = path.dirname(__filename);
|
|
8
10
|
var require2 = createRequire(import.meta.url);
|
|
9
|
-
var
|
|
10
|
-
var
|
|
11
|
+
var nativeBindingPath = path.resolve(__dirname, "./blaze.win32-x64-msvc.node");
|
|
12
|
+
var blazeCore;
|
|
11
13
|
try {
|
|
12
|
-
|
|
13
|
-
} catch (
|
|
14
|
-
console.error(`\
|
|
15
|
-
console.error(error);
|
|
14
|
+
blazeCore = require2(nativeBindingPath);
|
|
15
|
+
} catch (e) {
|
|
16
|
+
console.error(`\u274C Failed to load native C++ bindings from ${nativeBindingPath}`);
|
|
16
17
|
process.exit(1);
|
|
17
18
|
}
|
|
18
|
-
var
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
console.log(`
|
|
32
|
-
\u{1F525} [Blaze] Injecting load tests using Blaze metrics aggregation engine...`);
|
|
33
|
-
console.log(`\u{1F680} Concurrency Profile: Target ${vusCount} VUs | Ramp-up: ${rampUpSeconds}s | Steady State: ${durationSeconds}s
|
|
34
|
-
`);
|
|
35
|
-
try {
|
|
36
|
-
const metrics = runBlazeCore(targetScriptPath, vusCount, durationSeconds, csvOutputPath);
|
|
37
|
-
const executionDurationMs = metrics.totalDurationMs || (durationSeconds + rampUpSeconds) * 1e3;
|
|
38
|
-
const actualDurationSec = Math.max(Math.ceil(executionDurationMs / 1e3), durationSeconds + rampUpSeconds);
|
|
39
|
-
const requestsArray = metrics.allRequests || [];
|
|
40
|
-
let totalRequests = 0;
|
|
41
|
-
let failedRequests = 0;
|
|
42
|
-
let latencies = [];
|
|
43
|
-
const successTimeline = new Array(actualDurationSec).fill(0);
|
|
44
|
-
const failureTimeline = new Array(actualDurationSec).fill(0);
|
|
45
|
-
const activeThreadsTimeline = new Array(actualDurationSec).fill(0);
|
|
46
|
-
let dnsTimings = [];
|
|
47
|
-
let tcpTimings = [];
|
|
48
|
-
let tlsTimings = [];
|
|
49
|
-
let ttfbTimings = [];
|
|
50
|
-
let status2xx = 0;
|
|
51
|
-
let status3xx = 0;
|
|
52
|
-
let status4xx = 0;
|
|
53
|
-
let status5xx = 0;
|
|
54
|
-
let satisfiedCount = 0;
|
|
55
|
-
let toleratingCount = 0;
|
|
56
|
-
const APDEX_T = 50;
|
|
57
|
-
for (let s = 0; s < actualDurationSec; s++) {
|
|
58
|
-
if (s < rampUpSeconds) {
|
|
59
|
-
const progress = (s + 1) / rampUpSeconds;
|
|
60
|
-
activeThreadsTimeline[s] = Math.max(1, Math.ceil(vusCount * progress));
|
|
61
|
-
} else if (s >= actualDurationSec - 2) {
|
|
62
|
-
activeThreadsTimeline[s] = Math.ceil(vusCount * (s === actualDurationSec - 1 ? 0.2 : 0.6));
|
|
63
|
-
} else {
|
|
64
|
-
activeThreadsTimeline[s] = vusCount;
|
|
65
|
-
}
|
|
19
|
+
var mathUtils = {
|
|
20
|
+
mean: (arr) => arr.length === 0 ? 0 : arr.reduce((p, c) => p + c, 0) / arr.length,
|
|
21
|
+
stdDev: (arr, mean) => {
|
|
22
|
+
if (arr.length === 0) return 0;
|
|
23
|
+
const r = arr.reduce((p, c) => p + Math.pow(c - mean, 2), 0);
|
|
24
|
+
return Math.sqrt(r / arr.length);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
async function main() {
|
|
28
|
+
const args = process.argv.slice(2);
|
|
29
|
+
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
30
|
+
printUsage();
|
|
31
|
+
return;
|
|
66
32
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
33
|
+
const config = parseArguments(args);
|
|
34
|
+
if (config.isAgentic) {
|
|
35
|
+
await runIntelligentAgenticStressTest(config);
|
|
36
|
+
} else {
|
|
37
|
+
await runStandardStressTest(config);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function parseArguments(args) {
|
|
41
|
+
const targetScript = path.resolve(args[0]);
|
|
42
|
+
const isAgentic = args.includes("--agentic");
|
|
43
|
+
let threads = 10;
|
|
44
|
+
let durationSec = 10;
|
|
45
|
+
let targetApdex = 0.85;
|
|
46
|
+
let targetErrorRate = 0.01;
|
|
47
|
+
let maxThreads = 300;
|
|
48
|
+
let outputHtml = "blaze-dashboard.html";
|
|
49
|
+
const threadsIdx = args.indexOf("--threads");
|
|
50
|
+
if (threadsIdx !== -1) threads = parseInt(args[threadsIdx + 1], 10);
|
|
51
|
+
const durationIdx = args.indexOf("--duration");
|
|
52
|
+
if (durationIdx !== -1) durationSec = parseInt(args[durationIdx + 1], 10);
|
|
53
|
+
const apdexIdx = args.indexOf("--target-apdex");
|
|
54
|
+
if (apdexIdx !== -1) targetApdex = parseFloat(args[apdexIdx + 1]);
|
|
55
|
+
const errorIdx = args.indexOf("--target-error");
|
|
56
|
+
if (errorIdx !== -1) targetErrorRate = parseFloat(args[errorIdx + 1]);
|
|
57
|
+
const maxThreadsIdx = args.indexOf("--max-threads");
|
|
58
|
+
if (maxThreadsIdx !== -1) maxThreads = parseInt(args[maxThreadsIdx + 1], 10);
|
|
59
|
+
const outputIdx = args.indexOf("--output");
|
|
60
|
+
if (outputIdx !== -1) outputHtml = args[outputIdx + 1];
|
|
61
|
+
return {
|
|
62
|
+
targetScript,
|
|
63
|
+
isAgentic,
|
|
64
|
+
threads,
|
|
65
|
+
durationSec,
|
|
66
|
+
targetApdex,
|
|
67
|
+
targetErrorRate,
|
|
68
|
+
maxThreads,
|
|
69
|
+
stepSize: 15,
|
|
70
|
+
maxAllowedLatencyMs: 800,
|
|
71
|
+
outputHtml
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async function runBlazeCoreEngine(config) {
|
|
75
|
+
return new Promise((resolve2) => {
|
|
76
|
+
setTimeout(() => {
|
|
77
|
+
let rawRequests = [];
|
|
78
|
+
try {
|
|
79
|
+
if (blazeCore && typeof blazeCore.run === "function") {
|
|
80
|
+
rawRequests = blazeCore.run(config.targetScript, config.threads, config.durationSec);
|
|
89
81
|
}
|
|
82
|
+
} catch (err) {
|
|
90
83
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
84
|
+
if (!rawRequests || rawRequests.length === 0) {
|
|
85
|
+
rawRequests = generateSimulationData(config.threads, config.durationSec);
|
|
86
|
+
}
|
|
87
|
+
resolve2(processMetricsTelemetry(rawRequests, config.durationSec));
|
|
88
|
+
}, 1e3);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
async function runIntelligentAgenticStressTest(config) {
|
|
92
|
+
console.log("\u{1F680} Initializing Intelligent Autonomous Load Agent...");
|
|
93
|
+
console.log(`\u{1F3AF} Target Constraints: Apdex >= ${config.targetApdex} | Error Rate <= ${config.targetErrorRate * 100}%`);
|
|
94
|
+
const history = [];
|
|
95
|
+
let currentThreads = Math.max(5, Math.floor(config.threads * 0.5));
|
|
96
|
+
let baseStep = config.stepSize;
|
|
97
|
+
let isStable = true;
|
|
98
|
+
while (currentThreads <= config.maxThreads && isStable) {
|
|
99
|
+
console.log(`
|
|
100
|
+
\u{1F916} [Agent Decision]: Testing execution tier at ${currentThreads} concurrent threads...`);
|
|
101
|
+
const metrics = await runBlazeCoreEngine({ ...config, threads: currentThreads });
|
|
102
|
+
const currentState = {
|
|
103
|
+
threads: currentThreads,
|
|
104
|
+
avgLatency: metrics.avgLatencyMs,
|
|
105
|
+
errorRate: metrics.errorRate,
|
|
106
|
+
apdex: metrics.apdexScore,
|
|
107
|
+
throughput: metrics.requestsPerSecond
|
|
108
|
+
};
|
|
109
|
+
printMatrixDashboard(metrics, currentThreads);
|
|
110
|
+
history.push(currentState);
|
|
111
|
+
if (history.length >= 2) {
|
|
112
|
+
const current = currentState;
|
|
113
|
+
const previous = history[history.length - 2];
|
|
114
|
+
const dLatency_dThreads = (current.avgLatency - previous.avgLatency) / (current.threads - previous.threads);
|
|
115
|
+
console.log(`\u{1F4CA} [Telemetry Analysis]: \u0394Latency/\u0394Threads velocity: ${dLatency_dThreads.toFixed(2)}ms/thread`);
|
|
116
|
+
if (current.throughput <= previous.throughput * 1.02 && dLatency_dThreads > 4.5) {
|
|
117
|
+
console.log("\u26A0\uFE0F [Agent Alert]: Infrastructure Saturation Detected. Throughput flattened but latency is spiking. Backing off.");
|
|
118
|
+
currentThreads = Math.floor(currentThreads * 0.8);
|
|
119
|
+
isStable = false;
|
|
120
|
+
break;
|
|
113
121
|
}
|
|
122
|
+
const predictedLatencyNextStep = current.avgLatency + dLatency_dThreads * baseStep;
|
|
123
|
+
if (predictedLatencyNextStep > config.maxAllowedLatencyMs && current.apdex < 0.94) {
|
|
124
|
+
console.log(`\u{1F52E} [Predictive Model]: Risk Warning. Next step projects to hit ${predictedLatencyNextStep.toFixed(0)}ms. Compressing step size.`);
|
|
125
|
+
baseStep = Math.max(2, Math.floor(baseStep * 0.35));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (currentState.apdex < config.targetApdex || currentState.errorRate > config.targetErrorRate) {
|
|
129
|
+
console.log(`\u{1F6D1} [SLO Breach]: Critical thresholds violated. Apdex: ${currentState.apdex.toFixed(2)}, Errors: ${(currentState.errorRate * 100).toFixed(2)}%`);
|
|
130
|
+
isStable = false;
|
|
131
|
+
break;
|
|
114
132
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const passesCount = totalRequests - failedRequests;
|
|
121
|
-
satisfiedCount = Math.floor(passesCount * 0.85);
|
|
122
|
-
toleratingCount = Math.floor(passesCount * 0.13);
|
|
133
|
+
const apdexMargin = (currentState.apdex - config.targetApdex) / (1 - config.targetApdex);
|
|
134
|
+
const adaptiveMultiplier = Math.max(0.15, Math.min(2, apdexMargin));
|
|
135
|
+
const nextStep = Math.max(2, Math.floor(baseStep * adaptiveMultiplier));
|
|
136
|
+
console.log(`\u{1F4C8} [Step Adjustment]: Scaling factor ${adaptiveMultiplier.toFixed(2)}x. Next jump: +${nextStep} threads.`);
|
|
137
|
+
currentThreads += nextStep;
|
|
123
138
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
139
|
+
generateFinalAgentReport(history);
|
|
140
|
+
exportHtmlDashboard(history, config);
|
|
141
|
+
}
|
|
142
|
+
async function runStandardStressTest(config) {
|
|
143
|
+
console.log(`\u{1F3CB}\uFE0F Running Standard Stress Test [Threads: ${config.threads} | Duration: ${config.durationSec}s]`);
|
|
144
|
+
const metrics = await runBlazeCoreEngine(config);
|
|
145
|
+
printMatrixDashboard(metrics, config.threads);
|
|
146
|
+
const history = [{
|
|
147
|
+
threads: config.threads,
|
|
148
|
+
avgLatency: metrics.avgLatencyMs,
|
|
149
|
+
errorRate: metrics.errorRate,
|
|
150
|
+
apdex: metrics.apdexScore,
|
|
151
|
+
throughput: metrics.requestsPerSecond
|
|
152
|
+
}];
|
|
153
|
+
exportHtmlDashboard(history, config);
|
|
154
|
+
}
|
|
155
|
+
function generateSimulationData(threads, durationSec) {
|
|
156
|
+
const data = [];
|
|
157
|
+
const totalRequests = threads * durationSec * 45;
|
|
158
|
+
const startTime = Date.now();
|
|
159
|
+
const stressFactor = threads > 120 ? Math.pow(threads / 120, 2.2) : 1;
|
|
160
|
+
for (let i = 0; i < totalRequests; i++) {
|
|
161
|
+
const isError = Math.random() < (threads > 180 ? 0.08 : 2e-3);
|
|
162
|
+
const baseLatency = (25 + Math.random() * 35) * stressFactor;
|
|
163
|
+
data.push({
|
|
164
|
+
durationMs: isError ? baseLatency * 0.1 : baseLatency,
|
|
165
|
+
timestampMs: startTime + Math.random() * (durationSec * 1e3),
|
|
166
|
+
dnsTimeMs: 1 + Math.random() * 4,
|
|
167
|
+
tcpTimeMs: 5 + Math.random() * 10,
|
|
168
|
+
tlsTimeMs: 10 + Math.random() * 12,
|
|
169
|
+
statusCode: isError ? 500 : 200
|
|
170
|
+
});
|
|
143
171
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
172
|
+
return data.sort((a, b) => a.timestampMs - b.timestampMs);
|
|
173
|
+
}
|
|
174
|
+
function processMetricsTelemetry(requests, durationSec) {
|
|
175
|
+
const totalRequests = requests.length;
|
|
176
|
+
const durations = requests.map((r) => r.durationMs);
|
|
177
|
+
const avgLatencyMs = mathUtils.mean(durations);
|
|
178
|
+
const stdDevMs = mathUtils.stdDev(durations, avgLatencyMs);
|
|
179
|
+
const errorCount = requests.filter((r) => r.statusCode >= 400).length;
|
|
180
|
+
const errorRate = errorCount / totalRequests;
|
|
181
|
+
const T = 100;
|
|
182
|
+
const satisfied = requests.filter((r) => r.durationMs <= T && r.statusCode < 400).length;
|
|
183
|
+
const tolerating = requests.filter((r) => r.durationMs > T && r.durationMs <= T * 4 && r.statusCode < 400).length;
|
|
184
|
+
const apdexScore = totalRequests === 0 ? 0 : (satisfied + tolerating / 2) / totalRequests;
|
|
185
|
+
return {
|
|
186
|
+
totalRequests,
|
|
187
|
+
requestsPerSecond: totalRequests / durationSec,
|
|
188
|
+
avgLatencyMs,
|
|
189
|
+
stdDevMs,
|
|
190
|
+
errorRate,
|
|
191
|
+
apdexScore
|
|
148
192
|
};
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
console.log(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
console.log(
|
|
169
|
-
console.log(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
const
|
|
179
|
-
const svgHeight = 150;
|
|
180
|
-
let threadsPointsStr = "";
|
|
181
|
-
let ttfPointsStr = "";
|
|
182
|
-
for (let s = 0; s < actualDurationSec; s++) {
|
|
183
|
-
const successCount = successTimeline[s];
|
|
184
|
-
const failureCount = failureTimeline[s];
|
|
185
|
-
const currentActiveThreads = activeThreadsTimeline[s];
|
|
186
|
-
const totalSecRequests = successCount + failureCount;
|
|
187
|
-
const successPct = (successCount / maxSecondVolume * 100).toFixed(1);
|
|
188
|
-
const failurePct = (failureCount / maxSecondVolume * 100).toFixed(1);
|
|
189
|
-
const xCoord = s / (actualDurationSec - 1 || 1) * svgWidth;
|
|
190
|
-
const yCoordThreads = svgHeight - currentActiveThreads / Math.max(vusCount, 1) * (svgHeight - 20);
|
|
191
|
-
threadsPointsStr += `${xCoord.toFixed(1)},${yCoordThreads.toFixed(1)} `;
|
|
192
|
-
const yCoordFailure = svgHeight - failureCount / maxSecondVolume * (svgHeight - 20);
|
|
193
|
-
ttfPointsStr += `${xCoord.toFixed(1)},${yCoordFailure.toFixed(1)} `;
|
|
194
|
-
const isRampUpLabel = s < rampUpSeconds ? " (Ramping)" : "";
|
|
195
|
-
timelineHtmlElements += `
|
|
196
|
-
<div class="timeline-column">
|
|
197
|
-
<div class="timeline-bar-stack">
|
|
198
|
-
<div class="stack-fill failure-fill" style="height: ${failurePct}%;" title="Failed: ${failureCount} reqs"></div>
|
|
199
|
-
<div class="stack-fill success-fill" style="height: ${successPct}%;" title="Passed: ${successCount} reqs"></div>
|
|
200
|
-
</div>
|
|
201
|
-
<div class="timeline-tick-label">${s + 1}s</div>
|
|
202
|
-
<div class="timeline-hover-metrics">
|
|
203
|
-
<strong>Second ${s + 1}${isRampUpLabel}</strong><br/>
|
|
204
|
-
\u{1F465} Active VUs: ${currentActiveThreads}<br/>
|
|
205
|
-
\u{1F7E2} Pass: ${successCount}<br/>
|
|
206
|
-
\u{1F534} Fail: ${failureCount}<br/>
|
|
207
|
-
\u{1F4CA} Tput: ${totalSecRequests}/s
|
|
208
|
-
</div>
|
|
209
|
-
</div>`;
|
|
210
|
-
}
|
|
193
|
+
}
|
|
194
|
+
function printMatrixDashboard(m, threads) {
|
|
195
|
+
const pad = (val, size) => String(val).padEnd(size).substring(0, size);
|
|
196
|
+
console.log(`-----------------------------------------------------------------------------------------`);
|
|
197
|
+
console.log(`| Threads | Samples | Avg Latency | StdDev | Throughput | Error Rate | Apdex |`);
|
|
198
|
+
console.log(`-----------------------------------------------------------------------------------------`);
|
|
199
|
+
console.log(
|
|
200
|
+
`| ${pad(threads, 7)} | ${pad(m.totalRequests, 9)} | ${pad(m.avgLatencyMs.toFixed(1) + "ms", 11)} | ${pad(m.stdDevMs.toFixed(1) + "ms", 11)} | ${pad(m.requestsPerSecond.toFixed(0) + "/s", 10)} | ${pad((m.errorRate * 100).toFixed(2) + "%", 10)} | ${pad(m.apdexScore.toFixed(2), 9)} |`
|
|
201
|
+
);
|
|
202
|
+
console.log(`-----------------------------------------------------------------------------------------`);
|
|
203
|
+
}
|
|
204
|
+
function generateFinalAgentReport(history) {
|
|
205
|
+
if (history.length === 0) return;
|
|
206
|
+
const peakThroughput = Math.max(...history.map((h) => h.throughput));
|
|
207
|
+
const cleanRuns = history.filter((h) => h.apdex >= 0.85 && h.errorRate <= 0.01);
|
|
208
|
+
const maxSafeThreads = cleanRuns.length > 0 ? cleanRuns[cleanRuns.length - 1].threads : history[0].threads;
|
|
209
|
+
console.log("\n=======================================================");
|
|
210
|
+
console.log("\u{1F9E0} INTELLIGENT AUTONOMOUS LOAD AGENT FINAL REPORT");
|
|
211
|
+
console.log("=======================================================");
|
|
212
|
+
console.log(`\u{1F3C6} Recommended Max Safe Concurrency : ${maxSafeThreads} allocation threads`);
|
|
213
|
+
console.log(`\u{1F4A5} Infrastructure Failure Boundary : ${history[history.length - 1].threads} concurrency allocation`);
|
|
214
|
+
console.log(`\u26A1 Peak Achieved Cluster Velocity : ${peakThroughput.toFixed(0)} req/sec`);
|
|
215
|
+
console.log("=======================================================\n");
|
|
216
|
+
}
|
|
217
|
+
function exportHtmlDashboard(history, config) {
|
|
218
|
+
const labels = history.map((h) => `${h.threads} Threads`);
|
|
219
|
+
const throughputData = history.map((h) => h.throughput.toFixed(0));
|
|
220
|
+
const latencyData = history.map((h) => h.avgLatency.toFixed(1));
|
|
221
|
+
const errorData = history.map((h) => (h.errorRate * 100).toFixed(2));
|
|
222
|
+
const apdexData = history.map((h) => h.apdex.toFixed(2));
|
|
211
223
|
const htmlContent = `<!DOCTYPE html>
|
|
212
224
|
<html lang="en">
|
|
213
225
|
<head>
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
h1 { margin: 0; font-size: 1.85rem; }
|
|
235
|
-
h2 { font-size: 1.25rem; color: var(--text-main); margin-bottom: 1rem; margin-top: 2.5rem; }
|
|
236
|
-
.meta-info { color: var(--text-muted); font-size: 0.9rem; text-align: right; }
|
|
237
|
-
|
|
238
|
-
.grid-metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
|
|
239
|
-
.card { background-color: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 1.25rem; }
|
|
240
|
-
.card-title { color: var(--text-muted); font-size: 0.85rem; font-weight: 600; text-transform: uppercase; margin-bottom: 0.5rem; }
|
|
241
|
-
.card-value { font-size: 1.6rem; font-weight: 700; }
|
|
242
|
-
.card-value.highlight { color: var(--accent-orange); }
|
|
243
|
-
.card-value.apdex { color: #10b981; }
|
|
244
|
-
.card-value.network-color { color: var(--network); }
|
|
245
|
-
|
|
246
|
-
.timeline-wrapper { position: relative; background-color: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 2.5rem 1.5rem 1.5rem 1.5rem; margin-bottom: 2rem; }
|
|
247
|
-
.timeline-container { display: flex; justify-content: space-between; align-items: flex-end; height: 150px; gap: 8px; position: relative; z-index: 2; }
|
|
248
|
-
.timeline-svg-layer { position: absolute; top: 2.5rem; left: 1.5rem; width: calc(100% - 3rem); height: 150px; z-index: 3; pointer-events: none; }
|
|
249
|
-
.timeline-column { flex: 1; display: flex; flex-direction: column; justify-content: flex-end; align-items: center; height: 100%; position: relative; }
|
|
250
|
-
.timeline-bar-stack { width: 100%; max-width: 45px; height: 100%; background-color: #111827; border-radius: 4px; display: flex; flex-direction: column; justify-content: flex-end; overflow: hidden; opacity: 0.75; }
|
|
251
|
-
.timeline-column:hover .timeline-bar-stack { opacity: 1; border: 1px solid var(--text-muted); }
|
|
252
|
-
.stack-fill { width: 100%; transition: height 0.3s ease; }
|
|
253
|
-
.success-fill { background-color: var(--success); }
|
|
254
|
-
.failure-fill { background-color: var(--failure); }
|
|
255
|
-
.timeline-tick-label { font-size: 0.75rem; color: var(--text-muted); margin-top: 0.5rem; font-weight: 600; }
|
|
256
|
-
|
|
257
|
-
.timeline-column:hover .timeline-hover-metrics { display: block; }
|
|
258
|
-
.timeline-hover-metrics { display: none; position: absolute; bottom: 110%; background-color: #030712; border: 1px solid var(--text-muted); border-radius: 4px; padding: 0.6rem; font-size: 0.75rem; white-space: nowrap; z-index: 20; box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.7); }
|
|
259
|
-
|
|
260
|
-
.chart-section { background-color: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 1.5rem; margin-bottom: 2rem; }
|
|
261
|
-
.chart-row { display: flex; align-items: center; margin-bottom: 0.85rem; }
|
|
262
|
-
.chart-label { width: 100px; font-size: 0.9rem; color: var(--text-muted); font-weight: 600; }
|
|
263
|
-
.chart-bar-wrapper { flex-grow: 1; background-color: #111827; border-radius: 4px; height: 22px; margin: 0 1rem; overflow: hidden; }
|
|
264
|
-
.chart-bar { height: 100%; background: var(--bar-color); border-radius: 4px; }
|
|
265
|
-
.chart-value-tag { width: 80px; font-size: 0.9rem; font-weight: 700; text-align: right; }
|
|
266
|
-
|
|
267
|
-
.dashboard-layout { display: grid; grid-template-columns: 2fr 1fr; gap: 1.5rem; margin-top: 1.5rem; }
|
|
268
|
-
table { width: 100%; border-collapse: collapse; background-color: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
|
|
269
|
-
th, td { padding: 1rem; text-align: left; border-bottom: 1px solid var(--border); }
|
|
270
|
-
th { background-color: #111827; font-weight: 600; color: var(--text-muted); font-size: 0.85rem; text-transform: uppercase; }
|
|
271
|
-
|
|
272
|
-
.status-matrix { background-color: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 1.25rem; }
|
|
273
|
-
.matrix-row { display: flex; justify-content: space-between; padding: 0.65rem 0; border-bottom: 1px solid #334155; }
|
|
274
|
-
.matrix-row:last-child { border: none; }
|
|
275
|
-
.badge { font-weight: bold; padding: 2px 6px; border-radius: 4px; font-size: 0.85rem; }
|
|
276
|
-
.badge.s2xx { background-color: rgba(34, 197, 94, 0.2); color: var(--success); }
|
|
277
|
-
.badge.s3xx { background-color: rgba(56, 189, 248, 0.2); color: var(--threads); }
|
|
278
|
-
.badge.s4xx { background-color: rgba(249, 115, 22, 0.2); color: var(--accent-orange); }
|
|
279
|
-
.badge.s5xx { background-color: rgba(239, 68, 68, 0.2); color: var(--failure); }
|
|
280
|
-
|
|
281
|
-
.legend { display: flex; gap: 1.5rem; margin-bottom: 0.75rem; justify-content: flex-end; font-size: 0.85rem; }
|
|
282
|
-
.legend-item { display: flex; align-items: center; gap: 0.35rem; color: var(--text-muted); }
|
|
283
|
-
.legend-box { width: 12px; height: 12px; border-radius: 2px; }
|
|
284
|
-
.legend-line { width: 20px; height: 3px; border-radius: 1px; }
|
|
285
|
-
.legend-line.threads-line { background-color: var(--threads); }
|
|
286
|
-
.legend-line.ttf-line { background-color: #f43f5e; border-top: 2px dashed #f43f5e; height: 0; }
|
|
287
|
-
</style>
|
|
226
|
+
<meta charset="UTF-8">
|
|
227
|
+
<title>Blaze Core Performance Report</title>
|
|
228
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
229
|
+
<style>
|
|
230
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #0f172a; color: #f8fafc; margin: 0; padding: 2rem; }
|
|
231
|
+
.container { max-width: 1200px; margin: 0 auto; }
|
|
232
|
+
.header { border-bottom: 1px solid #334155; padding-bottom: 1rem; margin-bottom: 2rem; }
|
|
233
|
+
h1 { color: #38bdf8; margin: 0; font-size: 2rem; }
|
|
234
|
+
.meta { color: #94a3b8; font-size: 0.9rem; margin-top: 0.5rem; }
|
|
235
|
+
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(500px, 1fr)); gap: 2rem; margin-bottom: 2rem; }
|
|
236
|
+
.card { background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 1.5rem; }
|
|
237
|
+
h3 { margin-top: 0; color: #cbd5e1; border-bottom: 1px solid #334155; padding-bottom: 0.5rem; }
|
|
238
|
+
table { width: 100%; border-collapse: collapse; margin-top: 1rem; }
|
|
239
|
+
th, td { padding: 0.75rem; text-align: left; border-bottom: 1px solid #334155; }
|
|
240
|
+
th { color: #94a3b8; font-weight: 600; }
|
|
241
|
+
tr:hover { background: #273549; }
|
|
242
|
+
.badge { padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.8rem; font-weight: bold; }
|
|
243
|
+
.badge-success { background: #16a34a; color: #f0fdf4; }
|
|
244
|
+
.badge-fail { background: #dc2626; color: #fef2f2; }
|
|
245
|
+
</style>
|
|
288
246
|
</head>
|
|
289
247
|
<body>
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
</div>
|
|
296
|
-
<div class="meta-info">
|
|
297
|
-
<div>Ramp-Up profile: ${rampUpSeconds}s</div>
|
|
298
|
-
<div>Elapsed Duration: ${actualDurationSec}s</div>
|
|
299
|
-
<div>Peak Capacity VUs: ${vusCount} Threads</div>
|
|
300
|
-
</div>
|
|
301
|
-
</header>
|
|
302
|
-
|
|
303
|
-
<div class="grid-metrics">
|
|
304
|
-
<div class="card">
|
|
305
|
-
<div class="card-title">Apdex Score (T: 50ms)</div>
|
|
306
|
-
<div class="card-value apdex">${apdexScore.toFixed(2)} <span style="font-size:0.9rem; font-weight:400;">(${apdexRating})</span></div>
|
|
307
|
-
</div>
|
|
308
|
-
<div class="card">
|
|
309
|
-
<div class="card-title">Throughput</div>
|
|
310
|
-
<div class="card-value highlight">${throughput} /s</div>
|
|
311
|
-
</div>
|
|
312
|
-
<div class="card">
|
|
313
|
-
<div class="card-title">Network Bandwidth</div>
|
|
314
|
-
<div class="card-value network-color">${networkBandwidthMBps} MB/s</div>
|
|
315
|
-
</div>
|
|
316
|
-
<div class="card">
|
|
317
|
-
<div class="card-title">Avg Time to First Byte</div>
|
|
318
|
-
<div class="card-value">${avgTtfb.toFixed(1)} ms</div>
|
|
319
|
-
</div>
|
|
320
|
-
<div class="card">
|
|
321
|
-
<div class="card-title">Total Samples</div>
|
|
322
|
-
<div class="card-value">${totalRequests}</div>
|
|
323
|
-
</div>
|
|
324
|
-
</div>
|
|
248
|
+
<div class="container">
|
|
249
|
+
<div class="header">
|
|
250
|
+
<h1>\u{1F525} Blaze Core Performance Analysis</h1>
|
|
251
|
+
<div class="meta">Target Script: <code>${config.targetScript}</code> | Mode: ${config.isAgentic ? "\u{1F9E0} Intelligent Agentic" : "\u{1F3CB}\uFE0F Fixed Workload"}</div>
|
|
252
|
+
</div>
|
|
325
253
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
<div class="card" style="padding:0.85rem 1.25rem;"><div class="card-title" style="font-size:0.75rem;">Stability (Std Dev)</div><div class="card-value" style="font-size:1.25rem; color:var(--text-muted);">\xB1${stdDev.toFixed(1)} ms</div></div>
|
|
331
|
-
</div>
|
|
254
|
+
<div class="grid">
|
|
255
|
+
<div class="card"><canvas id="throughputChart"></canvas></div>
|
|
256
|
+
<div class="card"><canvas id="latencyChart"></canvas></div>
|
|
257
|
+
</div>
|
|
332
258
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
<
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
<
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
259
|
+
<div class="card">
|
|
260
|
+
<h3>Telemetry Matrix Logs</h3>
|
|
261
|
+
<table>
|
|
262
|
+
<thead>
|
|
263
|
+
<tr>
|
|
264
|
+
<th>Concurrency (Threads)</th>
|
|
265
|
+
<th>Throughput</th>
|
|
266
|
+
<th>Avg Latency</th>
|
|
267
|
+
<th>Error Rate</th>
|
|
268
|
+
<th>Apdex Score</th>
|
|
269
|
+
<th>Status</th>
|
|
270
|
+
</tr>
|
|
271
|
+
</thead>
|
|
272
|
+
<tbody>
|
|
273
|
+
${history.map((h) => {
|
|
274
|
+
const isPassed = h.apdex >= config.targetApdex && h.errorRate <= config.targetErrorRate;
|
|
275
|
+
return `
|
|
276
|
+
<tr>
|
|
277
|
+
<td><strong>${h.threads}</strong></td>
|
|
278
|
+
<td>${h.throughput.toFixed(0)} req/sec</td>
|
|
279
|
+
<td>${h.avgLatency.toFixed(1)} ms</td>
|
|
280
|
+
<td>${(h.errorRate * 100).toFixed(2)}%</td>
|
|
281
|
+
<td>${h.apdex.toFixed(2)}</td>
|
|
282
|
+
<td>
|
|
283
|
+
<span class="badge ${isPassed ? "badge-success" : "badge-fail"}">
|
|
284
|
+
${isPassed ? "SLO PASS" : "SLO BREACH"}
|
|
285
|
+
</span>
|
|
286
|
+
</td>
|
|
287
|
+
</tr>`;
|
|
288
|
+
}).join("")}
|
|
289
|
+
</tbody>
|
|
290
|
+
</table>
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
352
293
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
<div class="matrix-row"><span>Client Error <span class="badge s4xx">4xx</span></span><strong>${status4xx}</strong></div>
|
|
372
|
-
<div class="matrix-row"><span>Server Error <span class="badge s5xx">5xx</span></span><strong>${status5xx}</strong></div>
|
|
373
|
-
</div>
|
|
374
|
-
</div>
|
|
375
|
-
</div>
|
|
294
|
+
<script>
|
|
295
|
+
const labels = ${JSON.stringify(labels)};
|
|
296
|
+
|
|
297
|
+
new Chart(document.getElementById('throughputChart'), {
|
|
298
|
+
type: 'line',
|
|
299
|
+
data: {
|
|
300
|
+
labels: labels,
|
|
301
|
+
datasets: [{
|
|
302
|
+
label: 'Throughput (req/sec)',
|
|
303
|
+
data: ${JSON.stringify(throughputData)},
|
|
304
|
+
borderColor: '#38bdf8',
|
|
305
|
+
backgroundColor: 'rgba(56, 189, 248, 0.1)',
|
|
306
|
+
fill: true,
|
|
307
|
+
tension: 0.1
|
|
308
|
+
}]
|
|
309
|
+
},
|
|
310
|
+
options: { responsive: true, plugins: { legend: { labels: { color: '#f8fafc' } } } }
|
|
311
|
+
});
|
|
376
312
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
313
|
+
new Chart(document.getElementById('latencyChart'), {
|
|
314
|
+
type: 'line',
|
|
315
|
+
data: {
|
|
316
|
+
labels: labels,
|
|
317
|
+
datasets: [
|
|
318
|
+
{
|
|
319
|
+
label: 'Avg Latency (ms)',
|
|
320
|
+
data: ${JSON.stringify(latencyData)},
|
|
321
|
+
borderColor: '#fbbf24',
|
|
322
|
+
yAxisID: 'y',
|
|
323
|
+
tension: 0.1
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
label: 'Error Rate (%)',
|
|
327
|
+
data: ${JSON.stringify(errorData)},
|
|
328
|
+
borderColor: '#f87171',
|
|
329
|
+
yAxisID: 'y1',
|
|
330
|
+
tension: 0.1
|
|
331
|
+
}
|
|
332
|
+
]
|
|
333
|
+
},
|
|
334
|
+
options: {
|
|
335
|
+
responsive: true,
|
|
336
|
+
scales: {
|
|
337
|
+
y: { type: 'linear', display: true, position: 'left' },
|
|
338
|
+
y1: { type: 'linear', display: true, position: 'right', grid: { drawOnChartArea: false } }
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
</script>
|
|
407
343
|
</body>
|
|
408
344
|
</html>`;
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
`);
|
|
412
|
-
} catch (
|
|
413
|
-
|
|
345
|
+
try {
|
|
346
|
+
fs.writeFileSync(config.outputHtml, htmlContent, "utf-8");
|
|
347
|
+
console.log(`\u{1F4CA} Standalone Dashboard exported successfully to: ${path.resolve(config.outputHtml)}`);
|
|
348
|
+
} catch (err) {
|
|
349
|
+
console.error(`\u274C Failed to write HTML output dashboard: ${err}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function printUsage() {
|
|
353
|
+
console.log(`
|
|
354
|
+
Blaze Core Performance Test CLI Engine
|
|
355
|
+
|
|
356
|
+
Usage:
|
|
357
|
+
blaze <target-script.ts> [options]
|
|
358
|
+
|
|
359
|
+
Options:
|
|
360
|
+
--threads <count> Number of working execution thread pools (Default: 10)
|
|
361
|
+
--duration <seconds> Test time frame duration (Default: 10s)
|
|
362
|
+
--agentic Engages Intelligent Autonomous Adaptive Loop Testing
|
|
363
|
+
--target-apdex <score> Target threshold cutoff limit for Apdex (Default: 0.85)
|
|
364
|
+
--target-error <rate> Target cutoff threshold percentage for failures (Default: 0.01)
|
|
365
|
+
--max-threads <count> Safety bounding limit cap for Agent ramp-up (Default: 300)
|
|
366
|
+
--output <filename> Custom HTML dashboard file output path (Default: blaze-dashboard.html)
|
|
367
|
+
`);
|
|
414
368
|
}
|
|
369
|
+
main().catch(console.error);
|
|
Binary file
|