blaze-performance-tester 3.1.52 → 3.1.55

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,18 @@ 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
+ }
154
166
  async function generateGeminiRCA(history, finalSummary, errorLogs) {
155
167
  const apiKey = process.env.GEMINI_API_KEY;
156
168
  if (!apiKey) {
@@ -272,6 +284,8 @@ function parseArguments(args) {
272
284
  const clusterLogs = !args.includes("--no-cluster-logs");
273
285
  let threads = 10;
274
286
  let durationSec = 10;
287
+ let rampUpSec = 0;
288
+ let rampDownSec = 0;
275
289
  let targetApdex = 0.85;
276
290
  let targetErrorRate = 0.01;
277
291
  let maxThreads = 300;
@@ -281,6 +295,10 @@ function parseArguments(args) {
281
295
  if (threadsIdx !== -1) threads = parseInt(args[threadsIdx + 1], 10);
282
296
  const durationIdx = args.indexOf("--duration");
283
297
  if (durationIdx !== -1) durationSec = parseInt(args[durationIdx + 1], 10);
298
+ const rampUpIdx = args.indexOf("--ramp-up");
299
+ if (rampUpIdx !== -1) rampUpSec = parseInt(args[rampUpIdx + 1], 10);
300
+ const rampDownIdx = args.indexOf("--ramp-down");
301
+ if (rampDownIdx !== -1) rampDownSec = parseInt(args[rampDownIdx + 1], 10);
284
302
  const apdexIdx = args.indexOf("--target-apdex");
285
303
  if (apdexIdx !== -1) targetApdex = parseFloat(args[apdexIdx + 1]);
286
304
  const errorIdx = args.indexOf("--target-error");
@@ -315,6 +333,8 @@ function parseArguments(args) {
315
333
  clusterLogs,
316
334
  threads,
317
335
  durationSec,
336
+ rampUpSec,
337
+ rampDownSec,
318
338
  targetApdex,
319
339
  targetErrorRate,
320
340
  maxThreads,
@@ -334,7 +354,7 @@ async function runBlazeCoreEngine(config) {
334
354
  } catch (err) {
335
355
  }
336
356
  if (!rawRequests || rawRequests.length === 0) {
337
- rawRequests = generateSimulationData(config.threads, config.durationSec, config.maxAllowedLatencyMs);
357
+ rawRequests = generateSimulationData(config.threads, config.durationSec, config.maxAllowedLatencyMs, config.rampUpSec, config.rampDownSec);
338
358
  }
339
359
  const warmUpCutoff = Math.floor(rawRequests.length * 0.1);
340
360
  const steadyStateRequests = rawRequests.slice(warmUpCutoff);
@@ -492,7 +512,7 @@ async function runIntelligentAgenticStressTest(config) {
492
512
  exportHtmlDashboard(history, config, verdictReason, semanticReport, { total1xx, total2xx, total3xx, total4xx, total5xx, breakdownRates }, finalSummaryCards, lastRawRequests, geminiAnalysisHtml);
493
513
  }
494
514
  async function runStandardStressTest(config) {
495
- console.log(`\u{1F3CB}\uFE0F Running Standard Stress Test [Threads: ${config.threads} | Duration: ${config.durationSec}s]`);
515
+ 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
516
  const { metrics, rawErrors, rawRequests } = await runBlazeCoreEngine(config);
497
517
  printMatrixDashboard(metrics, config.threads);
498
518
  const healthScore = computeHealthScoreValue(metrics.apdexScore, metrics.errorRate, metrics.p95LatencyMs, config.maxAllowedLatencyMs);
@@ -559,15 +579,23 @@ function getFallbackClusterLogs() {
559
579
  add("HTTP/1.1 504 Gateway Timeout on POST /api/v1/checkout - upstream socket drop after 30000ms", 19);
560
580
  return logs;
561
581
  }
562
- function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
582
+ function generateSimulationData(threads, durationSec, maxAllowedLatencyMs, rampUpSec = 0, rampDownSec = 0) {
563
583
  const data = [];
564
584
  const totalRequests = Math.max(15, threads * durationSec * 30);
565
585
  const startTime = Date.now();
586
+ const rampUpMs = rampUpSec * 1e3;
587
+ const steadyMs = durationSec * 1e3;
588
+ const rampDownMs = rampDownSec * 1e3;
589
+ const totalTestMs = rampUpMs + steadyMs + rampDownMs;
566
590
  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
591
  const errorPool = getFallbackClusterLogs();
570
592
  for (let i = 0; i < totalRequests; i++) {
593
+ const offsetMs = Math.random() * totalTestMs;
594
+ const loadFactor = getLoadMultiplier(offsetMs, rampUpMs, steadyMs, rampDownMs);
595
+ if (loadFactor <= 0) continue;
596
+ const effectiveThreads = Math.max(1, threads * loadFactor);
597
+ const isBreached = effectiveThreads > targetThresholdBreak;
598
+ const stressFactor = isBreached ? Math.pow(effectiveThreads / targetThresholdBreak, 1.5) : 1;
571
599
  const isError = Math.random() < (isBreached ? 0.05 : 1e-3);
572
600
  const baseLatency = (25 + Math.random() * 35) * stressFactor;
573
601
  let errorMessage;
@@ -579,7 +607,7 @@ function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
579
607
  }
580
608
  data.push({
581
609
  durationMs: isError ? baseLatency * 0.1 : baseLatency,
582
- timestampMs: startTime + Math.random() * (durationSec * 1e3),
610
+ timestampMs: startTime + offsetMs,
583
611
  dnsTimeMs: 1 + Math.random() * 0.9,
584
612
  tcpTimeMs: 4 + Math.random() * 1.5,
585
613
  tlsTimeMs: 6 + Math.random() * 1.8,
@@ -807,6 +835,30 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
807
835
  </div>
808
836
  </div>
809
837
  </div>`;
838
+ const nlpQueryEngineHtml = `
839
+ <div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #0ea5e9; border-left: 4px solid #0ea5e9;">
840
+ <h3 style="color: #0ea5e9; display: flex; align-items: center; gap: 0.5rem; font-size: 1.2rem; margin-bottom: 0.5rem; border-bottom: none; padding-bottom: 0;">
841
+ \u{1F916} Natural Language Telemetry Insights Query Engine
842
+ </h3>
843
+ <p style="color: #94a3b8; font-size: 0.9rem; margin: 0 0 1rem 0;">
844
+ Ask questions of your load test results using natural phrasing to isolate and filter metric rows dynamically.
845
+ </p>
846
+ <div style="display: flex; gap: 0.75rem; margin-bottom: 0.75rem;">
847
+ <input type="text" id="nlpQueryInput" placeholder="e.g., show me tiers where latency > 40ms or error rate > 0%"
848
+ 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;">
849
+ <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;">
850
+ Query Matrix
851
+ </button>
852
+ </div>
853
+ <div style="display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;">
854
+ <span style="font-size: 0.8rem; color: #64748b; font-weight: bold; text-transform: uppercase; letter-spacing: 0.05em;">Suggestions:</span>
855
+ <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>
856
+ <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>
857
+ <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>
858
+ <button onclick="setNlpQuery('')" style="background: #1c1917; color: #f43f5e; border: 1px solid #44403c; border-radius: 4px; padding: 0.25rem 0.5rem; font-size: 0.8rem; cursor: pointer;">Reset Filter</button>
859
+ </div>
860
+ <div id="nlpQueryFeedback" style="margin-top: 0.75rem; font-size: 0.85rem; color: #38bdf8;"></div>
861
+ </div>`;
810
862
  const breakdownRates = responseMatrix.breakdownRates || {};
811
863
  const breakdownGridHtml = [400, 401, 403, 404, 429, 500, 502, 503, 504].map((code) => {
812
864
  const rate = breakdownRates[code] || 0;
@@ -879,7 +931,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
879
931
  <div class="container">
880
932
  <div class="header">
881
933
  <h1>\u{1F525} Blaze Core Performance Analysis</h1>
882
- <div class="meta">Target Script: <code>${config.targetScript}</code></div>
934
+ <div class="meta">Target Script: <code>${config.targetScript}</code> | Ramp-Up: ${config.rampUpSec}s | Ramp-Down: ${config.rampDownSec}s</div>
883
935
  </div>
884
936
 
885
937
  <div class="cards-wrapper">
@@ -1013,8 +1065,10 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
1013
1065
  </div>
1014
1066
  </div>
1015
1067
  </div>
1068
+
1069
+ ${nlpQueryEngineHtml}
1016
1070
 
1017
- <div class="card">
1071
+ <div class="card" style="margin-top: 2rem;">
1018
1072
  <h3>Telemetry Matrix Logs</h3>
1019
1073
  <table>
1020
1074
  <thead>
@@ -1027,7 +1081,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
1027
1081
  <th>Status</th>
1028
1082
  </tr>
1029
1083
  </thead>
1030
- <tbody>
1084
+ <tbody id="telemetryTableBody">
1031
1085
  ${history.map((h) => {
1032
1086
  const isPassed = h.apdex >= config.targetApdex && h.errorRate <= config.targetErrorRate;
1033
1087
  return `<tr>
@@ -1049,6 +1103,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
1049
1103
  </div>
1050
1104
 
1051
1105
  <script>
1106
+ const telemetryHistory = ${JSON.stringify(history)};
1052
1107
  const labels = ${JSON.stringify(labels)};
1053
1108
  const baseThroughput = ${cards.throughput};
1054
1109
  const baseLatency = ${cards.ttfb};
@@ -1058,12 +1113,75 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
1058
1113
  const simThroughput = document.getElementById('simThroughput');
1059
1114
  const simLatency = document.getElementById('simLatency');
1060
1115
 
1061
- slider.addEventListener('input', (e) => {
1062
- const val = parseInt(e.target.value, 10);
1063
- sliderVal.textContent = val;
1064
- const factor = val / ${config.threads || 10};
1065
- simThroughput.textContent = (baseThroughput * Math.min(factor, 2.5)).toFixed(0) + '/s';
1066
- simLatency.textContent = (baseLatency * Math.pow(factor, 0.8)).toFixed(1) + 'ms';
1116
+ if (slider) {
1117
+ slider.addEventListener('input', (e) => {
1118
+ const val = parseInt(e.target.value, 10);
1119
+ sliderVal.textContent = val;
1120
+ const factor = val / ${config.threads || 10};
1121
+ simThroughput.textContent = (baseThroughput * Math.min(factor, 2.5)).toFixed(0) + '/s';
1122
+ simLatency.textContent = (baseLatency * Math.pow(factor, 0.8)).toFixed(1) + 'ms';
1123
+ });
1124
+ }
1125
+
1126
+ function executeNlpQuery(queryString) {
1127
+ const q = queryString.toLowerCase().trim();
1128
+ const rows = document.querySelectorAll('#telemetryTableBody tr');
1129
+ const feedback = document.getElementById('nlpQueryFeedback');
1130
+
1131
+ let filterFn = (row, data) => true;
1132
+ const numMatch = q.match(/(d+(?:.d+)?)/);
1133
+ const targetNum = numMatch ? parseFloat(numMatch[1]) : null;
1134
+
1135
+ const isGreater = q.includes('>') || q.includes('greater') || q.includes('above') || q.includes('more');
1136
+ const isLess = q.includes('<') || q.includes('less') || q.includes('below') || q.includes('under');
1137
+
1138
+ if (q.includes('latency') || q.includes('p95') || q.includes('ms')) {
1139
+ if (targetNum !== null) {
1140
+ filterFn = (row, data) => isLess ? (data.p95Latency < targetNum) : (data.p95Latency > targetNum);
1141
+ } else {
1142
+ filterFn = (row, data) => data.p95Latency > 0;
1143
+ }
1144
+ } else if (q.includes('error') || q.includes('fail') || q.includes('%')) {
1145
+ if (targetNum !== null) {
1146
+ const searchVal = q.includes('%') ? targetNum / 100 : targetNum;
1147
+ filterFn = (row, data) => isLess ? (data.errorRate < searchVal) : (data.errorRate > searchVal);
1148
+ } else {
1149
+ filterFn = (row, data) => data.errorRate > 0;
1150
+ }
1151
+ } else if (q.includes('concurrency') || q.includes('thread') || q.includes('vu')) {
1152
+ if (targetNum !== null) {
1153
+ filterFn = (row, data) => isLess ? (data.threads < targetNum) : (data.threads > targetNum);
1154
+ }
1155
+ }
1156
+
1157
+ let matchCount = 0;
1158
+ rows.forEach((row, idx) => {
1159
+ const data = telemetryHistory[idx];
1160
+ if (!data) return;
1161
+ if (filterFn(row, data)) {
1162
+ row.style.display = '';
1163
+ row.style.background = q !== '' ? 'rgba(14, 165, 233, 0.15)' : '';
1164
+ matchCount++;
1165
+ } else {
1166
+ row.style.display = 'none';
1167
+ }
1168
+ });
1169
+
1170
+ feedback.innerHTML = q === '' ? '' : '\u{1F50D} Match verification resolved <strong>' + matchCount + '</strong> parameters inside telemetry array.';
1171
+ }
1172
+
1173
+ function setNlpQuery(str) {
1174
+ const input = document.getElementById('nlpQueryInput');
1175
+ input.value = str;
1176
+ executeNlpQuery(str);
1177
+ }
1178
+
1179
+ document.getElementById('nlpQueryBtn').addEventListener('click', () => {
1180
+ executeNlpQuery(document.getElementById('nlpQueryInput').value);
1181
+ });
1182
+
1183
+ document.getElementById('nlpQueryInput').addEventListener('keyup', (e) => {
1184
+ if (e.key === 'Enter') executeNlpQuery(e.target.value);
1067
1185
  });
1068
1186
 
1069
1187
  new Chart(document.getElementById('volumeChart'), {
@@ -1131,7 +1249,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
1131
1249
  const threadStep = ${config.stepSize || 15};
1132
1250
 
1133
1251
  for (let i = 1; i <= 3; i++) {
1134
- extendedLabels.push(\`+\${i * 5}s (AI Forecast)\`);
1252
+ extendedLabels.push('+' + (i * 5) + 's (AI Forecast)');
1135
1253
  extendedThreads.push(lastThreads + (threadStep * i));
1136
1254
  extendedTtf.push(null);
1137
1255
  aiForecastData.push(Number((slope * (historicalCount + i - 1) + intercept).toFixed(1)));
@@ -1247,6 +1365,6 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
1247
1365
  function printUsage() {
1248
1366
  console.log(`Blaze Core Performance Test CLI Engine
1249
1367
  Options:
1250
- --threads <count> | --duration <seconds> | --agentic | --cluster-logs | --correlate | --seo`);
1368
+ --threads <count> | --duration <seconds> | --ramp-up <seconds> | --ramp-down <seconds> | --agentic | --cluster-logs | --correlate | --seo`);
1251
1369
  }
1252
1370
  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.52",
3
+ "version": "3.1.55",
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",