blaze-performance-tester 3.2.4 → 3.2.6

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 CHANGED
@@ -1,10 +1,10 @@
1
- #!/usr/bin/env ts-node
2
-
3
1
  // cli.ts
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import yargs from "yargs";
5
+ import { hideBin } from "yargs/helpers";
4
6
  import { createRequire } from "module";
5
- import * as path from "path";
6
7
  import { fileURLToPath } from "url";
7
- import * as fs from "fs";
8
8
 
9
9
  // src/LogAnalyzer.ts
10
10
  var LogAnalyzer = class {
@@ -261,31 +261,108 @@ 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 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;
264
+ async function generateNlpToTsScenario(promptText) {
265
+ const apiKey = process.env.GEMINI_API_KEY;
266
+ if (!apiKey) {
267
+ console.warn("\u26A0\uFE0F GEMINI_API_KEY missing. Falling back to default transactional scenario.");
268
+ return getDefaultTransactionalScenario();
268
269
  }
269
- const transactionalScenario = [
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();
321
+ }
322
+ }
323
+ function getDefaultTransactionalScenario() {
324
+ return [
270
325
  {
271
326
  name: "Fetch Target Anti-Forgery Nonce",
272
327
  url: "https://httpbin.org/json",
273
328
  method: "GET",
329
+ headers: { "Accept": "application/json" },
274
330
  body: null
275
331
  },
276
332
  {
277
333
  name: "Perform Contextual Post Action",
278
334
  url: "https://httpbin.org/post?verification=${csrf_token}",
279
335
  method: "POST",
336
+ headers: { "Content-Type": "application/json", "Authorization": "Bearer ${bearer_token}" },
280
337
  body: JSON.stringify({
281
338
  data: "performance_payload",
282
339
  security_token: "${csrf_token}"
283
340
  })
284
341
  }
285
342
  ];
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
+ }
286
362
  console.log(`Launching automated script transaction analysis for: ${config.targetScript}`);
287
363
  const sanitizedSteps = transactionalScenario.map((step) => ({
288
364
  ...step,
365
+ headers: step.headers || { "Content-Type": "application/json" },
289
366
  body: typeof step.body === "string" ? step.body : step.body ? JSON.stringify(step.body) : ""
290
367
  }));
291
368
  const success = await blazeCore.runCorrelatedTransaction(sanitizedSteps);
@@ -293,93 +370,6 @@ async function executeTestPipeline(config) {
293
370
  console.log("Pipeline run complete. Parameter tracking successfully mitigated breaking changes.");
294
371
  }
295
372
  }
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
- }
383
373
  async function runBlazeCoreEngine(config) {
384
374
  return new Promise((resolve2) => {
385
375
  setTimeout(() => {
@@ -815,10 +805,10 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
815
805
  <table style="width: 100%; border-collapse: collapse;">
816
806
  <thead>
817
807
  <tr style="border-bottom: 1px solid #1e293b;">
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>
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>
822
812
  </tr>
823
813
  </thead>
824
814
  <tbody>
@@ -938,12 +928,12 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
938
928
  <div style="display: flex; gap: 0.75rem; margin-bottom: 0.75rem;">
939
929
  <input type="text" id="nlpQueryInput" placeholder="e.g., show me tiers where latency > 40ms or error rate > 0%"
940
930
  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;">
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;">
931
+ <button id="nlpQueryBtn" style="background: #0ea5e9; color: #ffffff; border: none; border-radius: 6px; padding: 0.75rem 1.5rem; font-weight: 600; cursor: pointer;">
942
932
  Query Matrix
943
933
  </button>
944
934
  </div>
945
935
  <div style="display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;">
946
- <span style="font-size: 0.8rem; color: #64748b; font-weight: bold; text-transform: uppercase; letter-spacing: 0.05em;">Suggestions:</span>
936
+ <span style="font-size: 0.8rem; color: #64748b; font-weight: bold; text-transform: uppercase;">Suggestions:</span>
947
937
  <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>
948
938
  <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>
949
939
  <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>
@@ -1223,7 +1213,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
1223
1213
  const feedback = document.getElementById('nlpQueryFeedback');
1224
1214
 
1225
1215
  let filterFn = (row, data) => true;
1226
- const numMatch = q.match(/(\\d+(?:\\.\\d+)?)/);
1216
+ const numMatch = q.match(/(d+(?:.d+)?)/);
1227
1217
  const targetNum = numMatch ? parseFloat(numMatch[1]) : null;
1228
1218
 
1229
1219
  const isGreater = q.includes('>') || q.includes('greater') || q.includes('above') || q.includes('more');
@@ -1459,6 +1449,44 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
1459
1449
  function printUsage() {
1460
1450
  console.log(`Blaze Core Performance Test CLI Engine
1461
1451
  Options:
1462
- --threads <count> | --duration <seconds> | --baseline <json_file> | --output <html_file>`);
1452
+ --threads <count> | --duration <seconds> | --baseline <json_file> | --output <html_file> | --prompt "<natural language description>" | --correlate`);
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
+ }
1463
1491
  }
1464
1492
  main().catch(console.error);
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blaze-performance-tester",
3
- "version": "3.2.4",
3
+ "version": "3.2.6",
4
4
  "description": "A high-performance, multi-threaded load testing engine built with Rust and QuickJS.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",