blaze-performance-tester 3.1.54 → 3.1.56

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
@@ -151,6 +151,31 @@ var mathUtils = {
151
151
  return sorted[Math.max(0, index)];
152
152
  }
153
153
  };
154
+ function getLoadMultiplier(elapsedMs, rampUpMs, steadyMs, rampDownMs) {
155
+ const totalDurationMs = rampUpMs + steadyMs + rampDownMs;
156
+ if (elapsedMs < rampUpMs && rampUpMs > 0) {
157
+ return elapsedMs / rampUpMs;
158
+ } else if (elapsedMs < rampUpMs + steadyMs) {
159
+ return 1;
160
+ } else if (elapsedMs < totalDurationMs && rampDownMs > 0) {
161
+ const timeIntoRampDown = elapsedMs - (rampUpMs + steadyMs);
162
+ return 1 - timeIntoRampDown / rampDownMs;
163
+ }
164
+ return elapsedMs >= totalDurationMs ? 0 : 1;
165
+ }
166
+ function loadBaselineData(filePath) {
167
+ if (!filePath) return null;
168
+ try {
169
+ const resolved = path.resolve(filePath);
170
+ if (fs.existsSync(resolved)) {
171
+ console.log(`\u{1F4C1} Loaded baseline comparison telemetry from: ${resolved}`);
172
+ return JSON.parse(fs.readFileSync(resolved, "utf-8"));
173
+ }
174
+ } catch (e) {
175
+ console.error(`\u274C Failed to parse baseline telemetry file: ${e}`);
176
+ }
177
+ return null;
178
+ }
154
179
  async function generateGeminiRCA(history, finalSummary, errorLogs) {
155
180
  const apiKey = process.env.GEMINI_API_KEY;
156
181
  if (!apiKey) {
@@ -272,23 +297,32 @@ function parseArguments(args) {
272
297
  const clusterLogs = !args.includes("--no-cluster-logs");
273
298
  let threads = 10;
274
299
  let durationSec = 10;
300
+ let rampUpSec = 0;
301
+ let rampDownSec = 0;
275
302
  let targetApdex = 0.85;
276
303
  let targetErrorRate = 0.01;
277
304
  let maxThreads = 300;
278
305
  let maxAllowedLatencyMs = 800;
279
306
  let outputHtml = "blaze-dashboard.html";
307
+ let baselinePath = null;
280
308
  const threadsIdx = args.indexOf("--threads");
281
309
  if (threadsIdx !== -1) threads = parseInt(args[threadsIdx + 1], 10);
282
310
  const durationIdx = args.indexOf("--duration");
283
311
  if (durationIdx !== -1) durationSec = parseInt(args[durationIdx + 1], 10);
312
+ const rampUpIdx = args.indexOf("--ramp-up");
313
+ if (rampUpIdx !== -1) rampUpSec = parseInt(args[rampUpIdx + 1], 10);
314
+ const rampDownIdx = args.indexOf("--ramp-down");
315
+ if (rampDownIdx !== -1) rampDownSec = parseInt(args[rampDownIdx + 1], 10);
284
316
  const apdexIdx = args.indexOf("--target-apdex");
285
- if (apdexIdx !== -1) targetApdex = parseFloat(args[apdexIdx + 1]);
317
+ if (apdexIdx !== -1) targetApdex = parseFloat(apdexIdx + 1);
286
318
  const errorIdx = args.indexOf("--target-error");
287
319
  if (errorIdx !== -1) targetErrorRate = parseFloat(args[errorIdx + 1]);
288
320
  const maxThreadsIdx = args.indexOf("--max-threads");
289
321
  if (maxThreadsIdx !== -1) maxThreads = parseInt(args[maxThreadsIdx + 1], 10);
290
322
  const outputIdx = args.indexOf("--output");
291
323
  if (outputIdx !== -1) outputHtml = args[outputIdx + 1];
324
+ const baselineIdx = args.indexOf("--baseline");
325
+ if (baselineIdx !== -1) baselinePath = args[baselineIdx + 1];
292
326
  const positionalNums = args.slice(1).filter((arg) => !arg.startsWith("-")).map(Number).filter((n) => !isNaN(n));
293
327
  if (positionalNums.length > 0 && threadsIdx === -1 && !isAgentic) threads = positionalNums[0];
294
328
  if (positionalNums.length > 1 && durationIdx === -1 && !isAgentic) durationSec = positionalNums[1];
@@ -315,12 +349,15 @@ function parseArguments(args) {
315
349
  clusterLogs,
316
350
  threads,
317
351
  durationSec,
352
+ rampUpSec,
353
+ rampDownSec,
318
354
  targetApdex,
319
355
  targetErrorRate,
320
356
  maxThreads,
321
357
  stepSize: 15,
322
358
  maxAllowedLatencyMs,
323
- outputHtml
359
+ outputHtml,
360
+ baselinePath
324
361
  };
325
362
  }
326
363
  async function runBlazeCoreEngine(config) {
@@ -334,7 +371,7 @@ async function runBlazeCoreEngine(config) {
334
371
  } catch (err) {
335
372
  }
336
373
  if (!rawRequests || rawRequests.length === 0) {
337
- rawRequests = generateSimulationData(config.threads, config.durationSec, config.maxAllowedLatencyMs);
374
+ rawRequests = generateSimulationData(config.threads, config.durationSec, config.maxAllowedLatencyMs, config.rampUpSec, config.rampDownSec);
338
375
  }
339
376
  const warmUpCutoff = Math.floor(rawRequests.length * 0.1);
340
377
  const steadyStateRequests = rawRequests.slice(warmUpCutoff);
@@ -475,7 +512,9 @@ async function runIntelligentAgenticStressTest(config) {
475
512
  tls: mathUtils.mean(globalMetricsAccumulator.tls),
476
513
  stdDev: mathUtils.stdDev(globalMetricsAccumulator.latencies, mathUtils.mean(globalMetricsAccumulator.latencies)),
477
514
  saturationKneePoint: saturationKneePoint || finalState.threads || 50,
478
- healthScore
515
+ healthScore,
516
+ errorRate: finalState.errorRate,
517
+ p95Latency: finalState.p95Latency
479
518
  };
480
519
  generateFinalAgentReport(history, healthScore);
481
520
  const geminiAnalysisHtml = await generateGeminiRCA(history, finalSummaryCards, aggregateErrorLogs);
@@ -489,10 +528,11 @@ async function runIntelligentAgenticStressTest(config) {
489
528
  const code = Number(codeStr);
490
529
  breakdownRates[code] = totalRequestsAccumulator === 0 ? 0 : globalCodeCounts[code] / totalRequestsAccumulator * 100;
491
530
  });
492
- exportHtmlDashboard(history, config, verdictReason, semanticReport, { total1xx, total2xx, total3xx, total4xx, total5xx, breakdownRates }, finalSummaryCards, lastRawRequests, geminiAnalysisHtml);
531
+ const baselineData = loadBaselineData(config.baselinePath);
532
+ exportHtmlDashboard(history, config, verdictReason, semanticReport, { total1xx, total2xx, total3xx, total4xx, total5xx, breakdownRates }, finalSummaryCards, lastRawRequests, geminiAnalysisHtml, baselineData);
493
533
  }
494
534
  async function runStandardStressTest(config) {
495
- console.log(`\u{1F3CB}\uFE0F Running Standard Stress Test [Threads: ${config.threads} | Duration: ${config.durationSec}s]`);
535
+ console.log(`\u{1F3CB}\uFE0F Running Standard Stress Test [Threads: ${config.threads} | Duration: ${config.durationSec}s | RampUp: ${config.rampUpSec}s | RampDown: ${config.rampDownSec}s]`);
496
536
  const { metrics, rawErrors, rawRequests } = await runBlazeCoreEngine(config);
497
537
  printMatrixDashboard(metrics, config.threads);
498
538
  const healthScore = computeHealthScoreValue(metrics.apdexScore, metrics.errorRate, metrics.p95LatencyMs, config.maxAllowedLatencyMs);
@@ -524,7 +564,9 @@ async function runStandardStressTest(config) {
524
564
  tls: metrics.avgTlsMs,
525
565
  stdDev: metrics.stdDevMs,
526
566
  saturationKneePoint: config.threads,
527
- healthScore
567
+ healthScore,
568
+ errorRate: metrics.errorRate,
569
+ p95Latency: metrics.p95LatencyMs
528
570
  };
529
571
  const isPassed = metrics.apdexScore >= config.targetApdex && metrics.errorRate <= config.targetErrorRate;
530
572
  const verdictReason = isPassed ? "Static single-wave baseline checks concluded successfully without triggering health boundaries." : "Static verification loop completed with values exceeding defined quality thresholds.";
@@ -538,6 +580,7 @@ async function runStandardStressTest(config) {
538
580
  const code = Number(codeStr);
539
581
  breakdownRates[code] = metrics.totalRequests === 0 ? 0 : metrics.codeCounts[code] / metrics.totalRequests * 100;
540
582
  });
583
+ const baselineData = loadBaselineData(config.baselinePath);
541
584
  exportHtmlDashboard(history, config, verdictReason, semanticReport, {
542
585
  total1xx: metrics.c1xx,
543
586
  total2xx: metrics.c2xx,
@@ -545,7 +588,7 @@ async function runStandardStressTest(config) {
545
588
  total4xx: metrics.c4xx,
546
589
  total5xx: metrics.c5xx,
547
590
  breakdownRates
548
- }, finalSummaryCards, rawRequests, geminiAnalysisHtml);
591
+ }, finalSummaryCards, rawRequests, geminiAnalysisHtml, baselineData);
549
592
  }
550
593
  function getFallbackClusterLogs() {
551
594
  const logs = [];
@@ -559,15 +602,23 @@ function getFallbackClusterLogs() {
559
602
  add("HTTP/1.1 504 Gateway Timeout on POST /api/v1/checkout - upstream socket drop after 30000ms", 19);
560
603
  return logs;
561
604
  }
562
- function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
605
+ function generateSimulationData(threads, durationSec, maxAllowedLatencyMs, rampUpSec = 0, rampDownSec = 0) {
563
606
  const data = [];
564
607
  const totalRequests = Math.max(15, threads * durationSec * 30);
565
608
  const startTime = Date.now();
609
+ const rampUpMs = rampUpSec * 1e3;
610
+ const steadyMs = durationSec * 1e3;
611
+ const rampDownMs = rampDownSec * 1e3;
612
+ const totalTestMs = rampUpMs + steadyMs + rampDownMs;
566
613
  const targetThresholdBreak = Math.max(5, Math.floor(maxAllowedLatencyMs * 0.05));
567
- const isBreached = threads > targetThresholdBreak;
568
- const stressFactor = isBreached ? Math.pow(threads / targetThresholdBreak, 1.5) : 1;
569
614
  const errorPool = getFallbackClusterLogs();
570
615
  for (let i = 0; i < totalRequests; i++) {
616
+ const offsetMs = Math.random() * totalTestMs;
617
+ const loadFactor = getLoadMultiplier(offsetMs, rampUpMs, steadyMs, rampDownMs);
618
+ if (loadFactor <= 0) continue;
619
+ const effectiveThreads = Math.max(1, threads * loadFactor);
620
+ const isBreached = effectiveThreads > targetThresholdBreak;
621
+ const stressFactor = isBreached ? Math.pow(effectiveThreads / targetThresholdBreak, 1.5) : 1;
571
622
  const isError = Math.random() < (isBreached ? 0.05 : 1e-3);
572
623
  const baseLatency = (25 + Math.random() * 35) * stressFactor;
573
624
  let errorMessage;
@@ -579,7 +630,7 @@ function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
579
630
  }
580
631
  data.push({
581
632
  durationMs: isError ? baseLatency * 0.1 : baseLatency,
582
- timestampMs: startTime + Math.random() * (durationSec * 1e3),
633
+ timestampMs: startTime + offsetMs,
583
634
  dnsTimeMs: 1 + Math.random() * 0.9,
584
635
  tcpTimeMs: 4 + Math.random() * 1.5,
585
636
  tlsTimeMs: 6 + Math.random() * 1.8,
@@ -662,7 +713,12 @@ function generateFinalAgentReport(history, score) {
662
713
  console.log(`\u26A1 Peak Achieved Cluster Velocity : ${peakThroughput.toFixed(0)} req/sec`);
663
714
  console.log("=======================================================\n");
664
715
  }
665
- function exportHtmlDashboard(history, config, verdictReason, semanticReport, responseMatrix, cards, rawRequests = [], geminiAnalysisHtml = "") {
716
+ function exportHtmlDashboard(history, config, verdictReason, semanticReport, responseMatrix, cards, rawRequests = [], geminiAnalysisHtml = "", baselineData = null) {
717
+ const summaryArtifactPath = config.outputHtml.replace(/\.html$/, "-summary.json");
718
+ try {
719
+ fs.writeFileSync(summaryArtifactPath, JSON.stringify(cards, null, 2), "utf-8");
720
+ } catch (e) {
721
+ }
666
722
  const labels = history.map((h, i) => `${i + 1}s`);
667
723
  const successRequestsData = history.map((h) => h.successRequests);
668
724
  const failedWorkloadsData = history.map((h) => h.failedRequests);
@@ -679,6 +735,50 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
679
735
  x: Number(((r.timestampMs - firstTimestamp) / 1e3).toFixed(2)),
680
736
  y: Number(((r.dnsTimeMs || 0) + (r.tcpTimeMs || 0) + (r.tlsTimeMs || 0) + r.durationMs * 0.3).toFixed(1))
681
737
  }));
738
+ let baselineComparisonHtml = "";
739
+ if (baselineData) {
740
+ const diffApdex = cards.apdex - baselineData.apdex;
741
+ const diffThroughput = (cards.throughput - baselineData.throughput) / (baselineData.throughput || 1) * 100;
742
+ const diffTtfb = (cards.ttfb - baselineData.ttfb) / (baselineData.ttfb || 1) * 100;
743
+ const diffHealth = cards.healthScore - baselineData.healthScore;
744
+ const formatDelta = (val, isInverse = false) => {
745
+ const good = isInverse ? val <= 0 : val >= 0;
746
+ const color = good ? "#10b981" : "#ef4444";
747
+ const sign = val > 0 ? "+" : "";
748
+ return `<span style="color: ${color}; font-weight: bold;">${sign}${val.toFixed(1)}%</span>`;
749
+ };
750
+ baselineComparisonHtml = `
751
+ <div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #7c3aed;">
752
+ <h3 style="color: #a78bfa; display: flex; align-items: center; gap: 0.6rem; font-size: 1.2rem; border-bottom: 1px solid #1e293b; padding-bottom: 0.5rem;">
753
+ \u{1F4CA} Baseline Regression Comparison (Delta Engine)
754
+ </h3>
755
+ <p style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 1.5rem;">
756
+ Comparing current execution metrics directly against reference payload from baseline source.
757
+ </p>
758
+ <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem;">
759
+ <div style="background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b;">
760
+ <div class="card-title">Apdex Shift</div>
761
+ <div style="font-size: 1.4rem; font-weight: bold; color: #ffffff;">${cards.apdex.toFixed(2)}</div>
762
+ <div style="font-size: 0.8rem; margin-top: 0.2rem;">Delta: <span style="color: ${diffApdex >= 0 ? "#10b981" : "#ef4444"}; font-weight: bold;">${diffApdex >= 0 ? "+" : ""}${diffApdex.toFixed(2)}</span></div>
763
+ </div>
764
+ <div style="background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b;">
765
+ <div class="card-title">Throughput Delta</div>
766
+ <div style="font-size: 1.4rem; font-weight: bold; color: #ffffff;">${cards.throughput.toFixed(0)}/s</div>
767
+ <div style="font-size: 0.8rem; margin-top: 0.2rem;">Delta: ${formatDelta(diffThroughput, false)}</div>
768
+ </div>
769
+ <div style="background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b;">
770
+ <div class="card-title">TTFB Latency Shift</div>
771
+ <div style="font-size: 1.4rem; font-weight: bold; color: #ffffff;">${cards.ttfb.toFixed(1)}ms</div>
772
+ <div style="font-size: 0.8rem; margin-top: 0.2rem;">Delta: ${formatDelta(diffTtfb, true)}</div>
773
+ </div>
774
+ <div style="background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b;">
775
+ <div class="card-title">Health Score Diff</div>
776
+ <div style="font-size: 1.4rem; font-weight: bold; color: #ffffff;">${cards.healthScore}/100</div>
777
+ <div style="font-size: 0.8rem; margin-top: 0.2rem;">Delta: <span style="color: ${diffHealth >= 0 ? "#10b981" : "#ef4444"}; font-weight: bold;">${diffHealth >= 0 ? "+" : ""}${diffHealth} pts</span></div>
778
+ </div>
779
+ </div>
780
+ </div>`;
781
+ }
682
782
  let logClustersHtml = "";
683
783
  if (semanticReport) {
684
784
  const totalLogCount = semanticReport.clusters.reduce((sum, c) => sum + c.count, 0);
@@ -903,7 +1003,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
903
1003
  <div class="container">
904
1004
  <div class="header">
905
1005
  <h1>\u{1F525} Blaze Core Performance Analysis</h1>
906
- <div class="meta">Target Script: <code>${config.targetScript}</code></div>
1006
+ <div class="meta">Target Script: <code>${config.targetScript}</code> | Ramp-Up: ${config.rampUpSec}s | Ramp-Down: ${config.rampDownSec}s</div>
907
1007
  </div>
908
1008
 
909
1009
  <div class="cards-wrapper">
@@ -941,6 +1041,8 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
941
1041
  </div>
942
1042
  </div>
943
1043
 
1044
+ ${baselineComparisonHtml}
1045
+
944
1046
  <div class="top-layout-grid">
945
1047
  <div class="verdict-box">
946
1048
  <div class="verdict-title">\u{1F916} AI Stress-Testing Agent Verdict</div>
@@ -1095,7 +1197,6 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
1095
1197
  });
1096
1198
  }
1097
1199
 
1098
- // Client-side NLP Parser Engine Execution Implementation
1099
1200
  function executeNlpQuery(queryString) {
1100
1201
  const q = queryString.toLowerCase().trim();
1101
1202
  const rows = document.querySelectorAll('#telemetryTableBody tr');
@@ -1140,7 +1241,8 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
1140
1241
  }
1141
1242
  });
1142
1243
 
1143
- feedback.innerHTML = q === '' ? '' : '\u{1F50D} Match verification resolved <strong>' + matchCount + '</strong> parameters inside telemetry array.'; }
1244
+ feedback.innerHTML = q === '' ? '' : '\u{1F50D} Match verification resolved <strong>' + matchCount + '</strong> parameters inside telemetry array.';
1245
+ }
1144
1246
 
1145
1247
  function setNlpQuery(str) {
1146
1248
  const input = document.getElementById('nlpQueryInput');
@@ -1337,6 +1439,6 @@ feedback.innerHTML = q === '' ? '' : '\u{1F50D} Match verification resolved <str
1337
1439
  function printUsage() {
1338
1440
  console.log(`Blaze Core Performance Test CLI Engine
1339
1441
  Options:
1340
- --threads <count> | --duration <seconds> | --agentic | --cluster-logs | --correlate | --seo`);
1442
+ --threads <count> | --duration <seconds> | --baseline <json_file> | --output <html_file>`);
1341
1443
  }
1342
1444
  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.1.54",
3
+ "version": "3.1.56",
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",