blaze-performance-tester 3.0.39 → 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);
@@ -487,7 +518,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
487
518
  \u{1F9E0} Interactive Remediation Playbooks & Log Classification
488
519
  </h3>
489
520
  <div style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 1.5rem;">
490
- Captured <strong style="color: #f8fafc;">${totalLogCount}</strong> raw failures grouped into <strong style="color: #f8fafc;">${uniquePatternsCount}</strong> unique structural patterns. Expand a playbook to inspect automated remediation strategies.
521
+ Captured <strong style="color: #f8fafc;">${totalLogCount}</strong> raw failures grouped into <strong style="color: #f8fafc;">${uniquePatternsCount}</strong> unique structural patterns. Expand a playbook to inspect automated remediation strategies and diagnostics confidence ratings.
491
522
  </div>
492
523
 
493
524
  <div style="display: flex; flex-direction: column; gap: 1rem;">
@@ -499,6 +530,12 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
499
530
  let sevBg = c.severity === "CRITICAL" ? "#dc2626" : "#ea580c";
500
531
  const rawTemplate = c.template || "";
501
532
  const cleanedTemplate = rawTemplate.replace(/\[\d{4}-\d{2}-\d{2}.*?\]\s*/, "");
533
+ const clarityBonus = c.severity === "CRITICAL" ? 15 : 8;
534
+ const freqScore = Math.min(25, c.count * 0.5);
535
+ const confidenceScore = Math.min(99, Math.round(65 + freqScore + clarityBonus));
536
+ let confColor = "#10b981";
537
+ if (confidenceScore < 80) confColor = "#f59e0b";
538
+ if (confidenceScore < 70) confColor = "#ef4444";
502
539
  let fixTitle = "Infrastructure Pool & Tuning Fix";
503
540
  let fixCode = "const pool = new Pool({ max: 50, idleTimeoutMillis: 30000 });";
504
541
  let explanation = "High concurrency is causing socket/connection starvation. Increase connection limits or enable pooling keep-alives.";
@@ -511,14 +548,15 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
511
548
  fixCode = "const configId = payload?.config_id ?? defaultConfigurationId;\nif (!configId) throw new ValidationError('Missing config_id');";
512
549
  explanation = "Runtime reference error for undefined payload field under race condition spikes. Add optional chaining and input contracts.";
513
550
  }
514
- const snippetId = `snippet-${idx}`;
551
+ const snippetId = "snippet-" + idx;
515
552
  return `
516
553
  <details style="background: #090d16; border: 1px solid #1e293b; border-radius: 6px; overflow: hidden;">
517
554
  <summary style="padding: 1rem; cursor: pointer; display: flex; align-items: center; justify-content: space-between; user-select: none;">
518
- <div style="display: flex; align-items: center; gap: 1rem;">
555
+ <div style="display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;">
519
556
  <span style="font-size: 1.1rem; font-weight: bold; color: #f8fafc; background: #131c2e; padding: 0.2rem 0.6rem; border-radius: 4px;">${c.count}x</span>
520
557
  <span style="color: ${catColor}; font-weight: 600; font-size: 0.95rem;">${c.category}</span>
521
558
  <span style="background: ${sevBg}; color: #ffffff; padding: 0.15rem 0.4rem; border-radius: 4px; font-size: 0.65rem; font-weight: bold; letter-spacing: 0.05em;">${c.severity}</span>
559
+ <span style="background: rgba(16, 185, 129, 0.15); color: ${confColor}; border: 1px solid ${confColor}40; padding: 0.15rem 0.5rem; border-radius: 4px; font-size: 0.7rem; font-weight: bold;">AI Confidence: ${confidenceScore}%</span>
522
560
  </div>
523
561
  <div style="color: #64748b; font-size: 0.85rem; font-family: monospace;">Expand Playbook \u25BC</div>
524
562
  </summary>
@@ -539,7 +577,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
539
577
  </details>`;
540
578
  }).join("")}
541
579
  </div>
542
- </div>`;
580
+ `;
543
581
  }
544
582
  const liveAdjusterHtml = `
545
583
  <div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
@@ -648,7 +686,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
648
686
 
649
687
  <div class="cards-wrapper">
650
688
  <div class="metric-card">
651
- <div class="card-title">Apdex Score (T: 50ms)</div>
689
+ <div class="card-title">Apdex Score (T: ${config.apdexT}ms)</div>
652
690
  <div class="card-value green">${cards.apdex.toFixed(2)}<span class="card-subtext">(Good)</span></div>
653
691
  </div>
654
692
  <div class="metric-card">
@@ -717,6 +755,8 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
717
755
  </div>
718
756
  </div>
719
757
 
758
+ ${naturalLanguageAssistantHtml}
759
+
720
760
  <div class="charts-grid">
721
761
  <div class="graph-card">
722
762
  <div class="graph-title">\u{1F4CA} Throughput Velocity & Workload Volume</div>
@@ -793,8 +833,111 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
793
833
  const labels = ${JSON.stringify(labels)};
794
834
  const baseThroughput = ${cards.throughput};
795
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
+ }
796
940
 
797
- // Live Concurrency Adjuster interactive client logic
798
941
  const slider = document.getElementById('concurrencySlider');
799
942
  const sliderVal = document.getElementById('sliderValue');
800
943
  const simThroughput = document.getElementById('simThroughput');
@@ -857,7 +1000,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
857
1000
  borderWidth: 2.5,
858
1001
  pointRadius: 4,
859
1002
  fill: true,
860
- yAxisID: 'yThreads'
1003
+ yAxisID: 'y_threads'
861
1004
  },
862
1005
  {
863
1006
  label: 'Time-to-Failure (TTF) Trend',
@@ -867,7 +1010,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
867
1010
  borderDash: [5, 5],
868
1011
  pointRadius: 3,
869
1012
  fill: false,
870
- yAxisID: 'yTtf'
1013
+ yAxisID: 'y_ttf'
871
1014
  }
872
1015
  ]
873
1016
  },
@@ -940,6 +1083,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
940
1083
  function printUsage() {
941
1084
  console.log(`Blaze Core Performance Test CLI Engine
942
1085
  Options:
943
- --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)`);
944
1088
  }
945
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.39",
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"