blaze-performance-tester 3.2.6 → 3.2.8
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 +104 -132
- package/dist/index.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env ts-node
|
|
2
|
+
|
|
1
3
|
// cli.ts
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import yargs from "yargs";
|
|
5
|
-
import { hideBin } from "yargs/helpers";
|
|
6
4
|
import { createRequire } from "module";
|
|
5
|
+
import * as path from "path";
|
|
7
6
|
import { fileURLToPath } from "url";
|
|
7
|
+
import * as fs from "fs";
|
|
8
8
|
|
|
9
9
|
// src/LogAnalyzer.ts
|
|
10
10
|
var LogAnalyzer = class {
|
|
@@ -261,108 +261,31 @@ CRITICAL: Return ONLY plain paragraph text. Do not use markdown headers (###). I
|
|
|
261
261
|
return `<span style="color: #f87171;">Failed to generate AI diagnosis framework: ${error.message || error}</span>`;
|
|
262
262
|
}
|
|
263
263
|
}
|
|
264
|
-
async function
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
return getDefaultTransactionalScenario();
|
|
269
|
-
}
|
|
270
|
-
console.log(`\u{1F916} [NLP Scenario Generator]: Translating natural language prompt into TS simulation scenario via Gemini 2.5 Flash...`);
|
|
271
|
-
console.log(`Prompt: "${promptText}"`);
|
|
272
|
-
const systemPrompt = `
|
|
273
|
-
You are an expert Performance & Load Testing Architect. Your task is to convert the following natural language description of a user journey into a strict JSON array of HTTP transaction steps for a TypeScript load testing script.
|
|
274
|
-
|
|
275
|
-
Each step in the array must be an object with the following fields:
|
|
276
|
-
- "name": string (descriptive name of the step)
|
|
277
|
-
- "url": string (target URL, supporting dynamic token interpolation like \${token_name})
|
|
278
|
-
- "method": string ("GET", "POST", "PUT", "DELETE", etc.)
|
|
279
|
-
- "headers": object (optional key-value pairs of HTTP headers, e.g. { "Authorization": "Bearer \${auth_token}", "Content-Type": "application/json" })
|
|
280
|
-
- "body": string or null (stringified JSON or payload, supporting dynamic tokens)
|
|
281
|
-
- "extractors": object (optional key-value pairs to extract dynamic tokens from response headers/body)
|
|
282
|
-
|
|
283
|
-
Natural Language Journey Description:
|
|
284
|
-
"${promptText}"
|
|
285
|
-
|
|
286
|
-
CRITICAL REQUIREMENTS:
|
|
287
|
-
1. Return ONLY a valid JSON array. No markdown code blocks, no preamble, no explanatory text.
|
|
288
|
-
2. Ensure proper handling of headers, dynamic tokens, and multi-step transaction loops.
|
|
289
|
-
3. If the prompt implies multiple iterations or steps, sequence them logically in the array.
|
|
290
|
-
`;
|
|
291
|
-
try {
|
|
292
|
-
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${apiKey}`, {
|
|
293
|
-
method: "POST",
|
|
294
|
-
headers: { "Content-Type": "application/json" },
|
|
295
|
-
body: JSON.stringify({
|
|
296
|
-
contents: [{ parts: [{ text: systemPrompt }] }],
|
|
297
|
-
generationConfig: {
|
|
298
|
-
temperature: 0.1,
|
|
299
|
-
maxOutputTokens: 8192
|
|
300
|
-
}
|
|
301
|
-
})
|
|
302
|
-
});
|
|
303
|
-
const data = await response.json();
|
|
304
|
-
if (data?.error) {
|
|
305
|
-
throw new Error(`Google API Error ${data.error.code}: ${data.error.message}`);
|
|
306
|
-
}
|
|
307
|
-
const candidateText = data?.candidates?.[0]?.content?.parts?.[0]?.text;
|
|
308
|
-
if (!candidateText) {
|
|
309
|
-
throw new Error("Empty response body from Gemini API.");
|
|
310
|
-
}
|
|
311
|
-
const cleanedJson = candidateText.replace(/```json/g, "").replace(/```/g, "").trim();
|
|
312
|
-
const parsedScenario = JSON.parse(cleanedJson);
|
|
313
|
-
if (!Array.isArray(parsedScenario) || parsedScenario.length === 0) {
|
|
314
|
-
throw new Error("Generated scenario is not a valid non-empty array.");
|
|
315
|
-
}
|
|
316
|
-
console.log(`\u2705 Successfully generated ${parsedScenario.length} transaction steps from natural language.`);
|
|
317
|
-
return parsedScenario;
|
|
318
|
-
} catch (error) {
|
|
319
|
-
console.error(`\u274C NLP Scenario Generation failed: ${error.message || error}. Falling back to default scenario.`);
|
|
320
|
-
return getDefaultTransactionalScenario();
|
|
264
|
+
async function executeTestPipeline(config) {
|
|
265
|
+
if (!blazeCore || typeof blazeCore.runCorrelatedTransaction !== "function") {
|
|
266
|
+
console.error("\u274C Error: runCorrelatedTransaction function is missing from the compiled binary bindings.");
|
|
267
|
+
return;
|
|
321
268
|
}
|
|
322
|
-
|
|
323
|
-
function getDefaultTransactionalScenario() {
|
|
324
|
-
return [
|
|
269
|
+
const transactionalScenario = [
|
|
325
270
|
{
|
|
326
271
|
name: "Fetch Target Anti-Forgery Nonce",
|
|
327
272
|
url: "https://httpbin.org/json",
|
|
328
273
|
method: "GET",
|
|
329
|
-
headers: { "Accept": "application/json" },
|
|
330
274
|
body: null
|
|
331
275
|
},
|
|
332
276
|
{
|
|
333
277
|
name: "Perform Contextual Post Action",
|
|
334
278
|
url: "https://httpbin.org/post?verification=${csrf_token}",
|
|
335
279
|
method: "POST",
|
|
336
|
-
headers: { "Content-Type": "application/json", "Authorization": "Bearer ${bearer_token}" },
|
|
337
280
|
body: JSON.stringify({
|
|
338
281
|
data: "performance_payload",
|
|
339
282
|
security_token: "${csrf_token}"
|
|
340
283
|
})
|
|
341
284
|
}
|
|
342
285
|
];
|
|
343
|
-
}
|
|
344
|
-
async function executeTestPipeline(config) {
|
|
345
|
-
if (!blazeCore || typeof blazeCore.runCorrelatedTransaction !== "function") {
|
|
346
|
-
console.error("\u274C Error: runCorrelatedTransaction function is missing from the compiled binary bindings.");
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
let transactionalScenario;
|
|
350
|
-
if (config.nlpPrompt) {
|
|
351
|
-
transactionalScenario = await generateNlpToTsScenario(config.nlpPrompt);
|
|
352
|
-
} else if (config.targetScript && fs.existsSync(config.targetScript) && !fs.lstatSync(config.targetScript).isDirectory()) {
|
|
353
|
-
const fileContent = fs.readFileSync(config.targetScript, "utf-8");
|
|
354
|
-
if (fileContent.length < 1e3 && !fileContent.includes("import ")) {
|
|
355
|
-
transactionalScenario = await generateNlpToTsScenario(fileContent);
|
|
356
|
-
} else {
|
|
357
|
-
transactionalScenario = getDefaultTransactionalScenario();
|
|
358
|
-
}
|
|
359
|
-
} else {
|
|
360
|
-
transactionalScenario = getDefaultTransactionalScenario();
|
|
361
|
-
}
|
|
362
286
|
console.log(`Launching automated script transaction analysis for: ${config.targetScript}`);
|
|
363
287
|
const sanitizedSteps = transactionalScenario.map((step) => ({
|
|
364
288
|
...step,
|
|
365
|
-
headers: step.headers || { "Content-Type": "application/json" },
|
|
366
289
|
body: typeof step.body === "string" ? step.body : step.body ? JSON.stringify(step.body) : ""
|
|
367
290
|
}));
|
|
368
291
|
const success = await blazeCore.runCorrelatedTransaction(sanitizedSteps);
|
|
@@ -370,6 +293,93 @@ async function executeTestPipeline(config) {
|
|
|
370
293
|
console.log("Pipeline run complete. Parameter tracking successfully mitigated breaking changes.");
|
|
371
294
|
}
|
|
372
295
|
}
|
|
296
|
+
async function main() {
|
|
297
|
+
const args = process.argv.slice(2);
|
|
298
|
+
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
299
|
+
printUsage();
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const config = parseArguments(args);
|
|
303
|
+
if (config.isCorrelate) {
|
|
304
|
+
await executeTestPipeline(config);
|
|
305
|
+
}
|
|
306
|
+
if (config.isAgentic) {
|
|
307
|
+
await runIntelligentAgenticStressTest(config);
|
|
308
|
+
} else {
|
|
309
|
+
await runStandardStressTest(config);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function parseArguments(args) {
|
|
313
|
+
const targetScript = path.resolve(args[0] || ".");
|
|
314
|
+
const isAgentic = args.includes("--agentic");
|
|
315
|
+
const isCorrelate = args.includes("--correlate");
|
|
316
|
+
const isSeo = args.includes("--seo");
|
|
317
|
+
const clusterLogs = !args.includes("--no-cluster-logs");
|
|
318
|
+
let threads = 10;
|
|
319
|
+
let durationSec = 10;
|
|
320
|
+
let rampUpSec = 0;
|
|
321
|
+
let rampDownSec = 0;
|
|
322
|
+
let targetApdex = 0.85;
|
|
323
|
+
let targetErrorRate = 0.01;
|
|
324
|
+
let maxThreads = 300;
|
|
325
|
+
let maxAllowedLatencyMs = 800;
|
|
326
|
+
let outputHtml = "blaze-dashboard.html";
|
|
327
|
+
let baselinePath = null;
|
|
328
|
+
const threadsIdx = args.indexOf("--threads");
|
|
329
|
+
if (threadsIdx !== -1) threads = parseInt(args[threadsIdx + 1], 10);
|
|
330
|
+
const durationIdx = args.indexOf("--duration");
|
|
331
|
+
if (durationIdx !== -1) durationSec = parseInt(args[durationIdx + 1], 10);
|
|
332
|
+
const rampUpIdx = args.indexOf("--ramp-up");
|
|
333
|
+
if (rampUpIdx !== -1) rampUpSec = parseInt(args[rampUpIdx + 1], 10);
|
|
334
|
+
const rampDownIdx = args.indexOf("--ramp-down");
|
|
335
|
+
if (rampDownIdx !== -1) rampDownSec = parseInt(args[rampDownIdx + 1], 10);
|
|
336
|
+
const apdexIdx = args.indexOf("--target-apdex");
|
|
337
|
+
if (apdexIdx !== -1) targetApdex = parseFloat(args[apdexIdx + 1]);
|
|
338
|
+
const errorIdx = args.indexOf("--target-error");
|
|
339
|
+
if (errorIdx !== -1) targetErrorRate = parseFloat(args[errorIdx + 1]);
|
|
340
|
+
const maxThreadsIdx = args.indexOf("--max-threads");
|
|
341
|
+
if (maxThreadsIdx !== -1) maxThreads = parseInt(args[maxThreadsIdx + 1], 10);
|
|
342
|
+
const outputIdx = args.indexOf("--output");
|
|
343
|
+
if (outputIdx !== -1) outputHtml = args[outputIdx + 1];
|
|
344
|
+
const baselineIdx = args.indexOf("--baseline");
|
|
345
|
+
if (baselineIdx !== -1) baselinePath = args[baselineIdx + 1];
|
|
346
|
+
const positionalNums = args.slice(1).filter((arg) => !arg.startsWith("-")).map(Number).filter((n) => !isNaN(n));
|
|
347
|
+
if (positionalNums.length > 0 && threadsIdx === -1 && !isAgentic) threads = positionalNums[0];
|
|
348
|
+
if (positionalNums.length > 1 && durationIdx === -1 && !isAgentic) durationSec = positionalNums[1];
|
|
349
|
+
if (positionalNums.length > 2 && errorIdx === -1 && !isAgentic) targetErrorRate = positionalNums[2] / 100;
|
|
350
|
+
if (isAgentic) {
|
|
351
|
+
const agenticIdx = args.indexOf("--agentic");
|
|
352
|
+
const trailingArgs = args.slice(agenticIdx + 1).filter((arg) => !arg.startsWith("--"));
|
|
353
|
+
if (trailingArgs[0]) maxThreads = parseInt(trailingArgs[0], 10);
|
|
354
|
+
if (trailingArgs[1]) {
|
|
355
|
+
let rawLatency = parseFloat(trailingArgs[1]);
|
|
356
|
+
if (rawLatency < 50) {
|
|
357
|
+
maxAllowedLatencyMs = rawLatency * 1e3;
|
|
358
|
+
} else {
|
|
359
|
+
maxAllowedLatencyMs = rawLatency;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (trailingArgs[2]) targetErrorRate = parseFloat(trailingArgs[2]) / 100;
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
targetScript,
|
|
366
|
+
isAgentic,
|
|
367
|
+
isCorrelate,
|
|
368
|
+
isSeo,
|
|
369
|
+
clusterLogs,
|
|
370
|
+
threads,
|
|
371
|
+
durationSec,
|
|
372
|
+
rampUpSec,
|
|
373
|
+
rampDownSec,
|
|
374
|
+
targetApdex,
|
|
375
|
+
targetErrorRate,
|
|
376
|
+
maxThreads,
|
|
377
|
+
stepSize: 15,
|
|
378
|
+
maxAllowedLatencyMs,
|
|
379
|
+
outputHtml,
|
|
380
|
+
baselinePath
|
|
381
|
+
};
|
|
382
|
+
}
|
|
373
383
|
async function runBlazeCoreEngine(config) {
|
|
374
384
|
return new Promise((resolve2) => {
|
|
375
385
|
setTimeout(() => {
|
|
@@ -805,10 +815,10 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
805
815
|
<table style="width: 100%; border-collapse: collapse;">
|
|
806
816
|
<thead>
|
|
807
817
|
<tr style="border-bottom: 1px solid #1e293b;">
|
|
808
|
-
<th style="width: 8%; padding: 0.75rem; text-align: left; color: #64748b; font-size: 0.85rem; font-weight: bold;">Count</th>
|
|
809
|
-
<th style="width: 22%; padding: 0.75rem; text-align: left; color: #64748b; font-size: 0.85rem; font-weight: bold;">Classification Category</th>
|
|
810
|
-
<th style="width: 10%; padding: 0.75rem; text-align: left; color: #64748b; font-size: 0.85rem; font-weight: bold;">Severity</th>
|
|
811
|
-
<th style="width: 60%; padding: 0.75rem; text-align: left; color: #64748b; font-size: 0.85rem; font-weight: bold;">Structural Abstract Blueprint / Dynamic Log Fingerprint</th>
|
|
818
|
+
<th style="width: 8%; padding: 0.75rem; text-align: left; color: #64748b; font-size: 0.85rem; font-weight: bold; text-transform: none;">Count</th>
|
|
819
|
+
<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>
|
|
820
|
+
<th style="width: 10%; padding: 0.75rem; text-align: left; color: #64748b; font-size: 0.85rem; font-weight: bold; text-transform: none;">Severity</th>
|
|
821
|
+
<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>
|
|
812
822
|
</tr>
|
|
813
823
|
</thead>
|
|
814
824
|
<tbody>
|
|
@@ -928,12 +938,12 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
928
938
|
<div style="display: flex; gap: 0.75rem; margin-bottom: 0.75rem;">
|
|
929
939
|
<input type="text" id="nlpQueryInput" placeholder="e.g., show me tiers where latency > 40ms or error rate > 0%"
|
|
930
940
|
style="flex: 1; background: #090d16; border: 1px solid #1e293b; border-radius: 6px; padding: 0.75rem 1rem; color: #ffffff; font-size: 0.95rem; outline: none; transition: border-color 0.2s;">
|
|
931
|
-
<button id="nlpQueryBtn" style="background: #0ea5e9; color: #ffffff; border: none; border-radius: 6px; padding: 0.75rem 1.5rem; font-weight: 600; cursor: pointer;">
|
|
941
|
+
<button id="nlpQueryBtn" style="background: #0ea5e9; color: #ffffff; border: none; border-radius: 6px; padding: 0.75rem 1.5rem; font-weight: 600; cursor: pointer; transition: background 0.2s;">
|
|
932
942
|
Query Matrix
|
|
933
943
|
</button>
|
|
934
944
|
</div>
|
|
935
945
|
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;">
|
|
936
|
-
<span style="font-size: 0.8rem; color: #64748b; font-weight: bold; text-transform: uppercase;">Suggestions:</span>
|
|
946
|
+
<span style="font-size: 0.8rem; color: #64748b; font-weight: bold; text-transform: uppercase; letter-spacing: 0.05em;">Suggestions:</span>
|
|
937
947
|
<button onclick="setNlpQuery('Show P95 latency > 50ms')" style="background: #1e293b; color: #cbd5e1; border: 1px solid #334155; border-radius: 4px; padding: 0.25rem 0.5rem; font-size: 0.8rem; cursor: pointer;">P95 Latency > 50ms</button>
|
|
938
948
|
<button onclick="setNlpQuery('Find tiers with errors > 0%')" style="background: #1e293b; color: #cbd5e1; border: 1px solid #334155; border-radius: 4px; padding: 0.25rem 0.5rem; font-size: 0.8rem; cursor: pointer;">Errors > 0%</button>
|
|
939
949
|
<button onclick="setNlpQuery('High concurrency > 25 VUs')" style="background: #1e293b; color: #cbd5e1; border: 1px solid #334155; border-radius: 4px; padding: 0.25rem 0.5rem; font-size: 0.8rem; cursor: pointer;">Concurrency > 25 VUs</button>
|
|
@@ -1213,7 +1223,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
1213
1223
|
const feedback = document.getElementById('nlpQueryFeedback');
|
|
1214
1224
|
|
|
1215
1225
|
let filterFn = (row, data) => true;
|
|
1216
|
-
const numMatch = q.match(/(d+(
|
|
1226
|
+
const numMatch = q.match(/(\\d+(?:\\.\\d+)?)/);
|
|
1217
1227
|
const targetNum = numMatch ? parseFloat(numMatch[1]) : null;
|
|
1218
1228
|
|
|
1219
1229
|
const isGreater = q.includes('>') || q.includes('greater') || q.includes('above') || q.includes('more');
|
|
@@ -1449,44 +1459,6 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
|
|
|
1449
1459
|
function printUsage() {
|
|
1450
1460
|
console.log(`Blaze Core Performance Test CLI Engine
|
|
1451
1461
|
Options:
|
|
1452
|
-
--threads <count> | --duration <seconds> | --baseline <json_file> | --output <html_file
|
|
1453
|
-
}
|
|
1454
|
-
async function main() {
|
|
1455
|
-
const argv = yargs(hideBin(process.argv)).scriptName("blaze-cli").usage("$0 [targetScript] [options]").positional("targetScript", {
|
|
1456
|
-
type: "string",
|
|
1457
|
-
default: ".",
|
|
1458
|
-
describe: "Target script file or directory"
|
|
1459
|
-
}).option("prompt", { type: "string", describe: "Natural language description of the test" }).option("nlp-scenario", { type: "string", describe: "NLP scenario description" }).option("threads", { type: "number", default: 10, describe: "Number of concurrent threads / VUs" }).option("duration", { type: "number", default: 10, describe: "Duration of test in seconds" }).option("ramp-up", { type: "number", default: 0, describe: "Ramp-up duration in seconds" }).option("ramp-down", { type: "number", default: 0, describe: "Ramp-down duration in seconds" }).option("agentic", { type: "boolean", default: false, describe: "Run intelligent autonomous load agent" }).option("correlate", { type: "boolean", default: false, describe: "Run correlated transaction pipeline" }).option("seo", { type: "boolean", default: false, describe: "Include SEO rank impact analysis" }).option("no-cluster-logs", { type: "boolean", default: false, describe: "Disable log clustering" }).option("target-apdex", { type: "number", default: 0.85, describe: "Target Apdex score threshold" }).option("target-error", { type: "number", default: 0.01, describe: "Target max error rate" }).option("max-threads", { type: "number", default: 300, describe: "Max threads for agentic scaling" }).option("max-allowed-latency", { type: "number", default: 800, describe: "Max allowed latency in ms" }).option("output", { type: "string", default: "blaze-dashboard.html", describe: "Output HTML report path" }).option("baseline", { type: "string", default: null, describe: "Baseline comparison telemetry JSON path" }).help().alias("help", "h").parseSync();
|
|
1460
|
-
const config = {
|
|
1461
|
-
targetScript: path.resolve(argv._[0] ? String(argv._[0]) : "."),
|
|
1462
|
-
isAgentic: argv.agentic,
|
|
1463
|
-
isCorrelate: argv.correlate,
|
|
1464
|
-
isSeo: argv.seo,
|
|
1465
|
-
clusterLogs: !argv.noClusterLogs,
|
|
1466
|
-
nlpPrompt: argv.prompt || argv.nlpScenario || null,
|
|
1467
|
-
threads: argv.threads,
|
|
1468
|
-
durationSec: argv.duration,
|
|
1469
|
-
rampUpSec: argv.rampUp,
|
|
1470
|
-
rampDownSec: argv.rampDown,
|
|
1471
|
-
targetApdex: argv.targetApdex,
|
|
1472
|
-
targetErrorRate: argv.targetError,
|
|
1473
|
-
maxThreads: argv.maxThreads,
|
|
1474
|
-
stepSize: 15,
|
|
1475
|
-
maxAllowedLatencyMs: argv.maxAllowedLatency,
|
|
1476
|
-
outputHtml: argv.output,
|
|
1477
|
-
baselinePath: argv.baseline
|
|
1478
|
-
};
|
|
1479
|
-
if (argv.help || process.argv.slice(2).length === 0) {
|
|
1480
|
-
printUsage();
|
|
1481
|
-
return;
|
|
1482
|
-
}
|
|
1483
|
-
if (config.isCorrelate || config.nlpPrompt) {
|
|
1484
|
-
await executeTestPipeline(config);
|
|
1485
|
-
}
|
|
1486
|
-
if (config.isAgentic) {
|
|
1487
|
-
await runIntelligentAgenticStressTest(config);
|
|
1488
|
-
} else {
|
|
1489
|
-
await runStandardStressTest(config);
|
|
1490
|
-
}
|
|
1462
|
+
--threads <count> | --duration <seconds> | --baseline <json_file> | --output <html_file>`);
|
|
1491
1463
|
}
|
|
1492
1464
|
main().catch(console.error);
|
|
Binary file
|