blaze-performance-tester 3.2.3 → 3.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -196,94 +196,6 @@ function loadBaselineData(filePath) {
196
196
  }
197
197
  return null;
198
198
  }
199
- async function generateScenarioTS(prompt) {
200
- const apiKey = process.env.GEMINI_API_KEY;
201
- if (!apiKey) {
202
- throw new Error("\u274C Environment variable GEMINI_API_KEY is missing. AI scenario generation aborted.");
203
- }
204
- console.log("\u{1F9E0} Translating natural language requirements into TypeScript via Gemini 2.5 Flash...");
205
- const systemInstruction = `
206
- You are an expert Performance & Systems Reliability Engineer specializing in QuickJS-compatible virtual user load testing scripts for the Blaze framework.
207
- Your task is to convert a natural language description into a clean, valid TypeScript simulation script.
208
-
209
- The script must adhere to this exact structural import pattern:
210
- \`\`\`typescript
211
- import { VirtualUser } from '../src/core/vu';
212
-
213
- export async function runScenario(vu: VirtualUser) {
214
- // Setup headers
215
- // Execute multi-step operations (e.g., fetching a login, parsing tokens, making subsequent requests)
216
- // Manage dynamic value extraction (e.g., const token = response.json().token) and pass variables forward
217
- // Include standard loops (e.g., for-loops) or timeouts as requested
218
- }
219
- \`\`\`
220
-
221
- Rules:
222
- 1. Return ONLY executable, valid TypeScript script.
223
- 2. Cleanly extract dynamic payload fields or tokens and assign them to downstream headers.
224
- 3. Keep logic performance-optimized and sandbox-compatible.
225
- 4. Do NOT wrap code blocks in backticks like \`\`\`typescript. Start directly with the TypeScript imports and export block. Do not include markdown or text commentary.
226
- `;
227
- try {
228
- const endpoint = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=" + apiKey;
229
- const response = await fetch(endpoint, {
230
- method: "POST",
231
- headers: { "Content-Type": "application/json" },
232
- body: JSON.stringify({
233
- contents: [{ parts: [{ text: `Generate a Virtual User script for this scenario: ${prompt}` }] }],
234
- generationConfig: {
235
- temperature: 0.1,
236
- maxOutputTokens: 65536
237
- },
238
- systemInstruction: {
239
- parts: [{ text: systemInstruction }]
240
- }
241
- })
242
- });
243
- const data = await response.json();
244
- if (data?.error) {
245
- throw new Error(`Google API Error ${data.error.code}: ${data.error.message}`);
246
- }
247
- const candidateText = data?.candidates?.[0]?.content?.parts?.[0]?.text;
248
- if (!candidateText) {
249
- throw new Error(`Malformed API layout or empty response body.`);
250
- }
251
- return candidateText.replace(/```typescript/g, "").replace(/```javascript/g, "").replace(/```/g, "").trim();
252
- } catch (error) {
253
- throw new Error(`Failed to generate script via Gemini: ${error.message || error}`);
254
- }
255
- }
256
- async function handleScenarioGeneration(config) {
257
- const targetDir = path.resolve(__dirname, "./tests");
258
- const tsFilePath = path.join(targetDir, "generated_scenario.ts");
259
- const jsFilePath = path.join(targetDir, "generated_scenario.js");
260
- try {
261
- if (!fs.existsSync(targetDir)) {
262
- fs.mkdirSync(targetDir, { recursive: true });
263
- }
264
- const tsCode = await generateScenarioTS(config.scenarioPrompt);
265
- fs.writeFileSync(tsFilePath, tsCode, "utf-8");
266
- console.log(`\u{1F4BE} Successfully generated scenario script saved to: ${tsFilePath}`);
267
- console.log("\u2699\uFE0F Transpiling generated scenario to JavaScript for native QuickJS sandbox validation...");
268
- let compiledSuccessfully = false;
269
- try {
270
- const { execSync } = require2("child_process");
271
- execSync(`npx tsc "${tsFilePath}" --target es2020 --module commonjs --allowJs --skipLibCheck`, { stdio: "ignore" });
272
- compiledSuccessfully = true;
273
- } catch (e) {
274
- console.warn("\u26A0\uFE0F TypeScript compiler (tsc) was not found or timed out. Falling back to clean JS copy conversion...");
275
- }
276
- if (!compiledSuccessfully) {
277
- const cleanJs = tsCode.replace(/: VirtualUser/g, "").replace(/import {[^}]+} from .*/g, "").replace(/export async/g, "async");
278
- fs.writeFileSync(jsFilePath, cleanJs, "utf-8");
279
- }
280
- console.log(`\u2705 Sandbox compilation resolved. Active test file: ${jsFilePath}`);
281
- config.targetScript = jsFilePath;
282
- } catch (error) {
283
- console.error(`\u274C Scenario generation flow aborted: ${error.message || error}`);
284
- process.exit(1);
285
- }
286
- }
287
199
  async function generateGeminiRCA(history, finalSummary, errorLogs) {
288
200
  const apiKey = process.env.GEMINI_API_KEY;
289
201
  if (!apiKey) {
@@ -313,15 +225,14 @@ Provide a comprehensive, highly technical Root Cause Analysis (RCA) report struc
313
225
 
314
226
  Paragraph 1 - GENERATIVE INCIDENT NARRATIVE: Write a clean, plain-English story of the incident that builds a chronological timeline of the failure event (e.g., "At 00:12s, traffic spiked by 40%. This caused database connection pool exhaustion, which triggered a cascading series of 504 errors on the checkout endpoint, ultimately hitting a knee-point crash at 500 virtual users."). Translate raw logs and metrics trends directly into an easy-to-read chronological story.
315
227
 
316
- Paragraph 2 - TECHNICAL BOTTLENECK DIAGNOSOR: Diagnose the exact bottleneck point (CPU saturation, socket pool exhaustion, database lock contention, etc.) indicated by how latency changes dynamically alongside concurrency, and correlate unique structural exception/error patterns directly to the performance drops.
228
+ Paragraph 2 - TECHNICAL BOTTLENECK DIAGNOSIS: Diagnose the exact bottleneck point (CPU saturation, socket pool exhaustion, database lock contention, etc.) indicated by how latency changes dynamically alongside concurrency, and correlate unique structural exception/error patterns directly to the performance drops.
317
229
 
318
230
  Paragraph 3 - ARCHITECTURAL SIZING RECOMMENDATIONS: Give concrete, infrastructure sizing or cloud architecture remediation recommendations based on the stress points found.
319
231
 
320
232
  CRITICAL: Return ONLY plain paragraph text. Do not use markdown headers (###). If you use bold text, use standard HTML <strong> tags. Wrap lines cleanly.
321
233
  `;
322
234
  try {
323
- const endpoint = `[https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=$](https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=$){apiKey}`;
324
- const response = await fetch(endpoint, {
235
+ const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${apiKey}`, {
325
236
  method: "POST",
326
237
  headers: { "Content-Type": "application/json" },
327
238
  body: JSON.stringify({
@@ -350,31 +261,108 @@ CRITICAL: Return ONLY plain paragraph text. Do not use markdown headers (###). I
350
261
  return `<span style="color: #f87171;">Failed to generate AI diagnosis framework: ${error.message || error}</span>`;
351
262
  }
352
263
  }
353
- async function executeTestPipeline(config) {
354
- if (!blazeCore || typeof blazeCore.runCorrelatedTransaction !== "function") {
355
- console.error("\u274C Error: runCorrelatedTransaction function is missing from the compiled binary bindings.");
356
- 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();
357
269
  }
358
- 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 [
359
325
  {
360
326
  name: "Fetch Target Anti-Forgery Nonce",
361
327
  url: "https://httpbin.org/json",
362
328
  method: "GET",
329
+ headers: { "Accept": "application/json" },
363
330
  body: null
364
331
  },
365
332
  {
366
333
  name: "Perform Contextual Post Action",
367
334
  url: "https://httpbin.org/post?verification=${csrf_token}",
368
335
  method: "POST",
336
+ headers: { "Content-Type": "application/json", "Authorization": "Bearer ${bearer_token}" },
369
337
  body: JSON.stringify({
370
338
  data: "performance_payload",
371
339
  security_token: "${csrf_token}"
372
340
  })
373
341
  }
374
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
+ }
375
362
  console.log(`Launching automated script transaction analysis for: ${config.targetScript}`);
376
363
  const sanitizedSteps = transactionalScenario.map((step) => ({
377
364
  ...step,
365
+ headers: step.headers || { "Content-Type": "application/json" },
378
366
  body: typeof step.body === "string" ? step.body : step.body ? JSON.stringify(step.body) : ""
379
367
  }));
380
368
  const success = await blazeCore.runCorrelatedTransaction(sanitizedSteps);
@@ -389,10 +377,7 @@ async function main() {
389
377
  return;
390
378
  }
391
379
  const config = parseArguments(args);
392
- if (config.scenarioPrompt) {
393
- await handleScenarioGeneration(config);
394
- }
395
- if (config.isCorrelate) {
380
+ if (config.isCorrelate || config.nlpPrompt) {
396
381
  await executeTestPipeline(config);
397
382
  }
398
383
  if (config.isAgentic) {
@@ -402,17 +387,16 @@ async function main() {
402
387
  }
403
388
  }
404
389
  function parseArguments(args) {
405
- const scenarioIdx = args.indexOf("--generate-scenario");
406
- let scenarioPrompt = null;
407
- if (scenarioIdx !== -1) {
408
- scenarioPrompt = args[scenarioIdx + 1];
409
- args.splice(scenarioIdx, 2);
410
- }
411
390
  const targetScript = path.resolve(args[0] || ".");
412
391
  const isAgentic = args.includes("--agentic");
413
392
  const isCorrelate = args.includes("--correlate");
414
393
  const isSeo = args.includes("--seo");
415
394
  const clusterLogs = !args.includes("--no-cluster-logs");
395
+ let nlpPrompt = null;
396
+ const promptIdx = args.indexOf("--prompt");
397
+ if (promptIdx !== -1) nlpPrompt = args[promptIdx + 1];
398
+ const nlpScenarioIdx = args.indexOf("--nlp-scenario");
399
+ if (nlpScenarioIdx !== -1) nlpPrompt = args[nlpScenarioIdx + 1];
416
400
  let threads = 10;
417
401
  let durationSec = 10;
418
402
  let rampUpSec = 0;
@@ -461,11 +445,11 @@ function parseArguments(args) {
461
445
  }
462
446
  return {
463
447
  targetScript,
464
- scenarioPrompt,
465
448
  isAgentic,
466
449
  isCorrelate,
467
450
  isSeo,
468
451
  clusterLogs,
452
+ nlpPrompt,
469
453
  threads,
470
454
  durationSec,
471
455
  rampUpSec,
@@ -914,10 +898,10 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
914
898
  <table style="width: 100%; border-collapse: collapse;">
915
899
  <thead>
916
900
  <tr style="border-bottom: 1px solid #1e293b;">
917
- <th style="width: 8%; padding: 0.75rem; text-align: left; color: #64748b; font-size: 0.85rem; font-weight: bold; text-transform: none;">Count</th>
918
- <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>
919
- <th style="width: 10%; padding: 0.75rem; text-align: left; color: #64748b; font-size: 0.85rem; font-weight: bold; text-transform: none;">Severity</th>
920
- <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>
901
+ <th style="width: 8%; padding: 0.75rem; text-align: left; color: #64748b; font-size: 0.85rem; font-weight: bold;">Count</th>
902
+ <th style="width: 22%; padding: 0.75rem; text-align: left; color: #64748b; font-size: 0.85rem; font-weight: bold;">Classification Category</th>
903
+ <th style="width: 10%; padding: 0.75rem; text-align: left; color: #64748b; font-size: 0.85rem; font-weight: bold;">Severity</th>
904
+ <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>
921
905
  </tr>
922
906
  </thead>
923
907
  <tbody>
@@ -1037,12 +1021,12 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
1037
1021
  <div style="display: flex; gap: 0.75rem; margin-bottom: 0.75rem;">
1038
1022
  <input type="text" id="nlpQueryInput" placeholder="e.g., show me tiers where latency > 40ms or error rate > 0%"
1039
1023
  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;">
1040
- <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;">
1024
+ <button id="nlpQueryBtn" style="background: #0ea5e9; color: #ffffff; border: none; border-radius: 6px; padding: 0.75rem 1.5rem; font-weight: 600; cursor: pointer;">
1041
1025
  Query Matrix
1042
1026
  </button>
1043
1027
  </div>
1044
1028
  <div style="display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;">
1045
- <span style="font-size: 0.8rem; color: #64748b; font-weight: bold; text-transform: uppercase; letter-spacing: 0.05em;">Suggestions:</span>
1029
+ <span style="font-size: 0.8rem; color: #64748b; font-weight: bold; text-transform: uppercase;">Suggestions:</span>
1046
1030
  <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>
1047
1031
  <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>
1048
1032
  <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>
@@ -1062,11 +1046,10 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
1062
1046
  `;
1063
1047
  }).join("");
1064
1048
  const gaugeColor = cards.healthScore >= 90 ? "#10b981" : cards.healthScore >= 70 ? "#f59e0b" : "#ef4444";
1065
- const chartJsCdn = "https://cdn.jsdelivr.net/npm/chart.js";
1066
1049
  const htmlContent = `<!DOCTYPE html><html lang="en"><head>
1067
1050
  <meta charset="UTF-8">
1068
1051
  <title>Blaze Core Performance Report</title>
1069
- <script src="${chartJsCdn}"></script>
1052
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
1070
1053
  <style>
1071
1054
  body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #0b111e; color: #f8fafc; margin: 0; padding: 2rem; }
1072
1055
  .container { max-width: 1300px; margin: 0 auto; }
@@ -1122,7 +1105,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
1122
1105
  </style></head><body>
1123
1106
  <div class="container">
1124
1107
  <div class="header">
1125
- <h1>\u{1F525} Blaze Core Performance Report</h1>
1108
+ <h1>\u{1F525} Blaze Core Performance Analysis</h1>
1126
1109
  <div class="meta">Target Script: <code>${config.targetScript}</code> | Ramp-Up: ${config.rampUpSec}s | Ramp-Down: ${config.rampDownSec}s</div>
1127
1110
  </div>
1128
1111
 
@@ -1323,7 +1306,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
1323
1306
  const feedback = document.getElementById('nlpQueryFeedback');
1324
1307
 
1325
1308
  let filterFn = (row, data) => true;
1326
- const numMatch = q.match(/(\\d+(?:\\.\\d+)?)/);
1309
+ const numMatch = q.match(/(d+(?:.d+)?)/);
1327
1310
  const targetNum = numMatch ? parseFloat(numMatch[1]) : null;
1328
1311
 
1329
1312
  const isGreater = q.includes('>') || q.includes('greater') || q.includes('above') || q.includes('more');
@@ -1559,14 +1542,6 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
1559
1542
  function printUsage() {
1560
1543
  console.log(`Blaze Core Performance Test CLI Engine
1561
1544
  Options:
1562
- --generate-scenario <prompt> Generates a virtual user script from a natural language prompt
1563
- --threads <count> Number of concurrent threads (default: 10)
1564
- --duration <seconds> Duration of stress testing in seconds (default: 10)
1565
- --ramp-up <seconds> Ramp-up speed
1566
- --ramp-down <seconds> Ramp-down speed
1567
- --agentic Enable Intelligent Autonomous Agentic Stress Test
1568
- --correlate Launch dynamic transaction parameter mapping pipeline
1569
- --baseline <json_file> Input baseline JSON benchmark reference
1570
- --output <html_file> Target path for exported standalone dashboard`);
1545
+ --threads <count> | --duration <seconds> | --baseline <json_file> | --output <html_file> | --prompt "<natural language description>" | --correlate`);
1571
1546
  }
1572
1547
  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.3",
3
+ "version": "3.2.5",
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",