blaze-performance-tester 3.0.40 → 3.1.0

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
@@ -147,12 +147,15 @@ function parseArguments(args) {
147
147
  let maxThreads = 300;
148
148
  let maxAllowedLatencyMs = 800;
149
149
  let outputHtml = "blaze-dashboard.html";
150
+ let apdexT = 50;
150
151
  const threadsIdx = args.indexOf("--threads");
151
152
  if (threadsIdx !== -1) threads = parseInt(args[threadsIdx + 1], 10);
152
153
  const durationIdx = args.indexOf("--duration");
153
154
  if (durationIdx !== -1) durationSec = parseInt(args[durationIdx + 1], 10);
154
155
  const apdexIdx = args.indexOf("--target-apdex");
155
156
  if (apdexIdx !== -1) targetApdex = parseFloat(args[apdexIdx + 1]);
157
+ const apdexTIdx = args.indexOf("--apdex-t");
158
+ if (apdexTIdx !== -1) apdexT = parseInt(args[apdexTIdx + 1], 10);
156
159
  const errorIdx = args.indexOf("--target-error");
157
160
  if (errorIdx !== -1) targetErrorRate = parseFloat(args[errorIdx + 1]);
158
161
  const maxThreadsIdx = args.indexOf("--max-threads");
@@ -189,7 +192,8 @@ function parseArguments(args) {
189
192
  maxThreads,
190
193
  stepSize: 15,
191
194
  maxAllowedLatencyMs,
192
- outputHtml
195
+ outputHtml,
196
+ apdexT
193
197
  };
194
198
  }
195
199
  async function runBlazeCoreEngine(config) {
@@ -207,7 +211,11 @@ async function runBlazeCoreEngine(config) {
207
211
  }
208
212
  const warmUpCutoff = Math.floor(rawRequests.length * 0.1);
209
213
  const steadyStateRequests = rawRequests.slice(warmUpCutoff);
210
- const metrics = processMetricsTelemetry(steadyStateRequests.length > 0 ? steadyStateRequests : rawRequests, config.durationSec);
214
+ const metrics = processMetricsTelemetry(
215
+ steadyStateRequests.length > 0 ? steadyStateRequests : rawRequests,
216
+ config.durationSec,
217
+ config.apdexT
218
+ );
211
219
  const rawErrors = rawRequests.filter((r) => r.errorMessage).map((r) => r.errorMessage);
212
220
  resolve2({ metrics, rawErrors, rawRequests: steadyStateRequests.length > 0 ? steadyStateRequests : rawRequests });
213
221
  }, 1e3);
@@ -394,7 +402,7 @@ function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
394
402
  }
395
403
  return data.sort((a, b) => a.timestampMs - b.timestampMs);
396
404
  }
397
- function processMetricsTelemetry(requests, durationSec) {
405
+ function processMetricsTelemetry(requests, durationSec, apdexT) {
398
406
  const totalRequests = requests.length;
399
407
  const durations = requests.map((r) => r.durationMs);
400
408
  const avgLatencyMs = mathUtils.mean(durations);
@@ -414,7 +422,7 @@ function processMetricsTelemetry(requests, durationSec) {
414
422
  });
415
423
  const errorCount = c4xx + c5xx;
416
424
  const errorRate = totalRequests === 0 ? 0 : errorCount / totalRequests;
417
- const T = 50;
425
+ const T = apdexT;
418
426
  const satisfied = requests.filter((r) => r.durationMs <= T && r.statusCode < 400).length;
419
427
  const tolerating = requests.filter((r) => r.durationMs > T && r.durationMs <= T * 4 && r.statusCode < 400).length;
420
428
  const apdexScore = totalRequests === 0 ? 0 : (satisfied + tolerating / 2) / totalRequests;
@@ -477,6 +485,29 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
477
485
  x: Number(((r.timestampMs - firstTimestamp) / 1e3).toFixed(2)),
478
486
  y: Number(((r.dnsTimeMs || 0) + (r.tcpTimeMs || 0) + (r.tlsTimeMs || 0) + r.durationMs * 0.3).toFixed(1))
479
487
  }));
488
+ const naturalLanguageAssistantHtml = `
489
+ <div class="card" style="margin-top: 2rem; background: linear-gradient(135deg, #1e1b4b 0%, #111827 100%); border: 1px solid #4338ca;">
490
+ <h3 style="color: #818cf8; display: flex; align-items: center; gap: 0.6rem; font-size: 1.25rem; margin-bottom: 0.5rem; border-bottom: none; padding-bottom: 0;">
491
+ \u{1F52E} Ask the Test \u2014 Embedded Natural Language Assistant
492
+ </h3>
493
+ <div style="color: #94a3b8; font-size: 0.85rem; margin-bottom: 1.25rem;">
494
+ Query metrics, log topologies, and SLA compliance profiles instantly. Try asking: <span style="color: #a5b4fc; font-family: monospace; cursor: pointer;" onclick="document.getElementById('assistantInput').value=this.innerText; runAssistantQuery();">"What was our peak throughput capacity?"</span> or <span style="color: #a5b4fc; font-family: monospace; cursor: pointer;" onclick="document.getElementById('assistantInput').value=this.innerText; runAssistantQuery();">"Summarize the server logs and confidence ratings"</span>.
495
+ </div>
496
+ <div style="display: flex; gap: 0.75rem; margin-bottom: 1rem;">
497
+ <input type="text" id="assistantInput" placeholder="Ask a question regarding test telemetry... (e.g., 'Did we break the target Apdex limit?')"
498
+ style="flex: 1; background: #030712; border: 1px solid #374151; border-radius: 6px; padding: 0.75rem 1rem; color: #f9fafb; font-size: 0.9rem; outline: none;"
499
+ onkeydown="if(event.key === 'Enter') runAssistantQuery();" />
500
+ <button onclick="runAssistantQuery()" style="background: #4f46e5; color: #ffffff; border: none; padding: 0 1.5rem; border-radius: 6px; font-weight: 600; font-size: 0.9rem; cursor: pointer; transition: background 0.2s;">
501
+ Query Engine
502
+ </button>
503
+ </div>
504
+ <div id="assistantResponseContainer" style="display: none; background: #030712; border: 1px solid #1f2937; border-radius: 6px; padding: 1.25rem;">
505
+ <div style="font-size: 0.7rem; font-weight: bold; color: #6366f1; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; display: flex; align-items: center; gap: 0.3rem;">
506
+ <span>\u{1F916} AI Assistant Response</span>
507
+ </div>
508
+ <div id="assistantResponseText" style="font-size: 0.9rem; line-height: 1.5; color: #e5e7eb; white-space: pre-wrap;"></div>
509
+ </div>
510
+ </div>`;
480
511
  let logClustersHtml = "";
481
512
  if (semanticReport) {
482
513
  const totalLogCount = semanticReport.clusters.reduce((sum, c) => sum + c.count, 0);
@@ -517,7 +548,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
517
548
  fixCode = "const configId = payload?.config_id ?? defaultConfigurationId;\nif (!configId) throw new ValidationError('Missing config_id');";
518
549
  explanation = "Runtime reference error for undefined payload field under race condition spikes. Add optional chaining and input contracts.";
519
550
  }
520
- const snippetId = `snippet-${idx}`;
551
+ const snippetId = "snippet-" + idx;
521
552
  return `
522
553
  <details style="background: #090d16; border: 1px solid #1e293b; border-radius: 6px; overflow: hidden;">
523
554
  <summary style="padding: 1rem; cursor: pointer; display: flex; align-items: center; justify-content: space-between; user-select: none;">
@@ -546,7 +577,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
546
577
  </details>`;
547
578
  }).join("")}
548
579
  </div>
549
- </div>`;
580
+ `;
550
581
  }
551
582
  const liveAdjusterHtml = `
552
583
  <div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
@@ -655,7 +686,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
655
686
 
656
687
  <div class="cards-wrapper">
657
688
  <div class="metric-card">
658
- <div class="card-title">Apdex Score (T: 50ms)</div>
689
+ <div class="card-title">Apdex Score (T: ${config.apdexT}ms)</div>
659
690
  <div class="card-value green">${cards.apdex.toFixed(2)}<span class="card-subtext">(Good)</span></div>
660
691
  </div>
661
692
  <div class="metric-card">
@@ -724,6 +755,8 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
724
755
  </div>
725
756
  </div>
726
757
 
758
+ ${naturalLanguageAssistantHtml}
759
+
727
760
  <div class="charts-grid">
728
761
  <div class="graph-card">
729
762
  <div class="graph-title">\u{1F4CA} Throughput Velocity & Workload Volume</div>
@@ -800,8 +833,111 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
800
833
  const labels = ${JSON.stringify(labels)};
801
834
  const baseThroughput = ${cards.throughput};
802
835
  const baseLatency = ${cards.ttfb};
836
+ const telemetryHistory = ${JSON.stringify(history)};
837
+ const responseMatrixData = ${JSON.stringify(responseMatrix)};
838
+ const globalSummaryCards = ${JSON.stringify(cards)};
839
+ const semanticReportData = ${JSON.stringify(semanticReport)};
840
+ const agentVerdictText = ${JSON.stringify(verdictReason)};
841
+
842
+ function runAssistantQuery() {
843
+ const inputEl = document.getElementById('assistantInput');
844
+ const containerEl = document.getElementById('assistantResponseContainer');
845
+ const textEl = document.getElementById('assistantResponseText');
846
+ const query = inputEl.value.toLowerCase().trim();
847
+
848
+ if (!query) return;
849
+
850
+ let answer = "";
851
+ const match = (keywords) => keywords.some(k => query.includes(k));
852
+
853
+ if (match(['throughput', 'rps', 'requests per second', 'capacity', 'volume'])) {
854
+ const peakT = Math.max(...telemetryHistory.map(h => h.throughput)).toFixed(0);
855
+ const finalT = globalSummaryCards.throughput.toFixed(1);
856
+ answer = "\u{1F4C8} **Throughput Assessment:**
857
+ " +
858
+ "- The peak throughput velocity achieved during this test run was **" + peakT + " req/sec**.
859
+ " +
860
+ "- The normalized/final execution throughput tracking shows **" + finalT + " req/sec**.
861
+ " +
862
+ "If your infrastructure limits drop below these values, upstream buffers will choke, precipitating service level drops.";
863
+ }
864
+ else if (match(['latency', 'p95', 'average latency', 'slow', 'speed', 'ttfb', 'delay'])) {
865
+ const maxP95 = Math.max(...telemetryHistory.map(h => h.p95Latency)).toFixed(1);
866
+ answer = "\u23F1\uFE0F **Latency Profiling Telemetry:**
867
+ " +
868
+ "- Average Time-to-First-Byte (TTFB) registered across workloads: **" + globalSummaryCards.ttfb.toFixed(1) + " ms**.
869
+ " +
870
+ "- Peak Worst-Case P95 Latency boundary reached: **" + maxP95 + " ms**.
871
+ " +
872
+ "- Handshake Breakdown: DNS Lookup [" + globalSummaryCards.dns.toFixed(2) + "ms] | TCP Connect [" + globalSummaryCards.tcp.toFixed(2) + "ms] | TLS Handshake [" + globalSummaryCards.tls.toFixed(2) + "ms].
873
+ " +
874
+ "Spikes are heavily correlated to network layer pooling exhaustion rather than payload compute constraints.";
875
+ }
876
+ else if (match(['error', 'fail', 'crash', 'broken', '5xx', '4xx', 'econnrefused', 'timeout'])) {
877
+ const totalErrs = responseMatrixData.total4xx + responseMatrixData.total5xx;
878
+ answer = "\u{1F6A8} **Failure Breakdown & Topology Mapping:**
879
+ " +
880
+ "- A total of **" + totalErrs + " operations failed** during execution loops (" + responseMatrixData.total4xx + " Client 4xx Errors, " + responseMatrixData.total5xx + " Server 5xx Errors).
881
+ ";
882
+
883
+ if (semanticReportData && semanticReportData.clusters && semanticReportData.clusters.length > 0) {
884
+ answer += "- **Primary Log Clusters Captured:**
885
+ ";
886
+ semanticReportData.clusters.forEach((c) => {
887
+ const rawTemplate = c.template || '';
888
+ const idxClose = rawTemplate.indexOf(']');
889
+ const cleaned = idxClose !== -1 ? rawTemplate.substring(idxClose + 1).trim().substring(0, 80) + "..." : rawTemplate.substring(0, 80) + "...";
890
+ const freqScore = Math.min(25, c.count * 0.5);
891
+ const clarityBonus = c.severity === 'CRITICAL' ? 15 : 8;
892
+ const confidenceScore = Math.min(99, Math.round(65 + freqScore + clarityBonus));
893
+ answer += " * [" + c.count + "x] **" + c.category + "** (" + c.severity + ") -> Confidence Rating: *" + confidenceScore + "%*
894
+ \`" + cleaned + "\`
895
+ ";
896
+ });
897
+ } else {
898
+ answer += "- No structural failure entries were recorded in stdout/stderr diagnostic streams.";
899
+ }
900
+ }
901
+ else if (match(['apdex', 'satisfaction', 'slo', 'breach', 'limit', 'pass', 'target'])) {
902
+ const passStatus = globalSummaryCards.apdex >= ${config.targetApdex};
903
+ answer = "\u{1F3AF} **SLO Verification Profile:**
904
+ " +
905
+ "- Target Configured Apdex Floor: **" + (${config.targetApdex}).toFixed(2) + "**
906
+ " +
907
+ "- Actual Achieved Runtime Apdex: **" + globalSummaryCards.apdex.toFixed(2) + "**
908
+ " +
909
+ "- **Status Status:** " + (passStatus ? "\u2705 **SLO PASS**" : "\u274C **SLO BREACHED**") + "
910
+ " +
911
+ "Verdict Context: " + agentVerdictText;
912
+ }
913
+ else if (match(['summary', 'verdict', 'recommendation', 'cost', 'cloud', 'sizing', 'what happened'])) {
914
+ answer = "\u{1F9E0} **AI Executive Summary Directive:**
915
+ " +
916
+ "- **System Verdict:** " + agentVerdictText + "
917
+ " +
918
+ "- **Recommended Max Safe Scaled Capacity:** " + globalSummaryCards.saturationKneePoint + " Virtual User Threads Allocation.
919
+ " +
920
+ "- **Infrastructure Target Allocation Strategy:** Right-size infrastructure to **" + Math.max(2, Math.ceil(globalSummaryCards.saturationKneePoint / 75)) + " vCores** and **" + (Math.max(2, Math.ceil(globalSummaryCards.saturationKneePoint / 75)) * 2) + " GB RAM** to minimize the projected cloud operating budget (~$" + (globalSummaryCards.throughput * 0.045 * 24 * 30).toFixed(2) + "/mo).";
921
+ }
922
+ else {
923
+ answer = "\u2753 **Intent Routing Fallback:**
924
+ " +
925
+ "I didn't quite catch that context parameter. Try asking specifically about:
926
+ " +
927
+ "\u2022 *'What was our peak throughput capacity?'*
928
+ " +
929
+ "\u2022 *'Show me the latency performance limits and handshake timings'*
930
+ " +
931
+ "\u2022 *'Summarize the server logs and confidence ratings'*
932
+ " +
933
+ "\u2022 *'Did we pass or breach our target SLO settings?'*";
934
+ }
935
+
936
+ textEl.innerHTML = answer.replace(/
937
+ /g, '<br>').replace(/**(.*?)**/g, '<strong>$1</strong>');
938
+ containerEl.style.display = 'block';
939
+ }
803
940
 
804
- // Live Concurrency Adjuster interactive client logic
805
941
  const slider = document.getElementById('concurrencySlider');
806
942
  const sliderVal = document.getElementById('sliderValue');
807
943
  const simThroughput = document.getElementById('simThroughput');
@@ -864,7 +1000,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
864
1000
  borderWidth: 2.5,
865
1001
  pointRadius: 4,
866
1002
  fill: true,
867
- yAxisID: 'yThreads'
1003
+ yAxisID: 'y_threads'
868
1004
  },
869
1005
  {
870
1006
  label: 'Time-to-Failure (TTF) Trend',
@@ -874,7 +1010,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
874
1010
  borderDash: [5, 5],
875
1011
  pointRadius: 3,
876
1012
  fill: false,
877
- yAxisID: 'yTtf'
1013
+ yAxisID: 'y_ttf'
878
1014
  }
879
1015
  ]
880
1016
  },
@@ -947,6 +1083,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
947
1083
  function printUsage() {
948
1084
  console.log(`Blaze Core Performance Test CLI Engine
949
1085
  Options:
950
- --threads <count> | --duration <seconds> | --agentic | --cluster-logs | --correlate`);
1086
+ --threads <count> | --duration <seconds> | --agentic | --cluster-logs | --correlate
1087
+ --apdex-t <ms> Set customizable T-Threshold window (Default: 50ms)`);
951
1088
  }
952
1089
  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.0.40",
3
+ "version": "3.1.0",
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",
@@ -18,10 +18,10 @@
18
18
  "blaze.win32-x64-msvc.node"
19
19
  ],
20
20
  "scripts": {
21
- "prebuild": "node -e \"if (!fs.existsSync('dist')) fs.mkdirSync('dist')\"",
22
- "build:rust": "napi build --platform --release --output-dir dist --js index.js --dts index.d.ts",
23
- "build:cli": "esbuild cli.ts --bundle --platform=node --format=esm --packages=external --outfile=dist/cli.js",
24
- "build": "npm run prebuild && npm run build:rust && npm run build:cli"
21
+ "prebuild": "node -e \"if (!fs.existsSync('dist')) fs.mkdirSync('dist')\"",
22
+ "build:rust": "napi build --platform --release --output-dir dist --js index.js --dts index.d.ts",
23
+ "build:cli": "esbuild cli.ts --bundle --platform=node --format=esm --packages=external --outfile=dist/cli.js",
24
+ "build": "npm run prebuild && npm run build:rust && npm run build:cli"
25
25
  },
26
26
  "dependencies": {
27
27
  "esbuild": "^0.20.0"