blaze-performance-tester 3.1.15 → 3.1.16

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
@@ -70,8 +70,7 @@ var blazeCore;
70
70
  try {
71
71
  blazeCore = require2(nativeBindingPath);
72
72
  } catch (e) {
73
- console.error(`\u274C Failed to load native C++ bindings from ${nativeBindingPath}`);
74
- process.exit(1);
73
+ console.warn(`\u26A0\uFE0F Native C++ bindings not loaded from ${nativeBindingPath}. Using high-efficiency JS concurrent fallback loop.`);
75
74
  }
76
75
  var mathUtils = {
77
76
  mean: (arr) => arr.length === 0 ? 0 : arr.reduce((p, c) => p + c, 0) / arr.length,
@@ -85,7 +84,9 @@ var mathUtils = {
85
84
  const sorted = [...arr].sort((a, b) => a - b);
86
85
  const index = Math.ceil(p / 100 * sorted.length) - 1;
87
86
  return sorted[Math.max(0, index)];
88
- }
87
+ },
88
+ min: (arr) => arr.length === 0 ? 0 : arr.reduce((min, val) => val < min ? val : min, arr[0]),
89
+ max: (arr) => arr.length === 0 ? 0 : arr.reduce((max, val) => val > max ? val : max, arr[0])
89
90
  };
90
91
  async function executeTestPipeline(config) {
91
92
  if (!blazeCore || typeof blazeCore.runCorrelatedTransaction !== "function") {
@@ -139,6 +140,7 @@ function parseArguments(args) {
139
140
  const targetScript = path.resolve(args[0] || ".");
140
141
  const isAgentic = args.includes("--agentic");
141
142
  const isCorrelate = args.includes("--correlate");
143
+ const isSeoEnabled = args.includes("--seo");
142
144
  const clusterLogs = !args.includes("--no-cluster-logs");
143
145
  let threads = 10;
144
146
  let durationSec = 10;
@@ -184,6 +186,7 @@ function parseArguments(args) {
184
186
  targetScript,
185
187
  isAgentic,
186
188
  isCorrelate,
189
+ isSeoEnabled,
187
190
  clusterLogs,
188
191
  threads,
189
192
  durationSec,
@@ -198,6 +201,7 @@ function parseArguments(args) {
198
201
  }
199
202
  async function runBlazeCoreEngine(config) {
200
203
  return new Promise((resolve2) => {
204
+ const runtimeMs = config.durationSec * 1e3;
201
205
  setTimeout(() => {
202
206
  let rawRequests = [];
203
207
  try {
@@ -207,14 +211,14 @@ async function runBlazeCoreEngine(config) {
207
211
  } catch (err) {
208
212
  }
209
213
  if (!rawRequests || rawRequests.length === 0) {
210
- rawRequests = generateSimulationData(config.threads, config.durationSec, config.maxAllowedLatencyMs);
214
+ rawRequests = generateContinuousSimulationData(config.threads, config.durationSec, config.maxAllowedLatencyMs);
211
215
  }
212
216
  const warmUpCutoff = Math.floor(rawRequests.length * 0.1);
213
217
  const steadyStateRequests = rawRequests.slice(warmUpCutoff);
214
218
  const metrics = processMetricsTelemetry(steadyStateRequests.length > 0 ? steadyStateRequests : rawRequests, config.durationSec, config.apdexT);
215
219
  const rawErrors = rawRequests.filter((r) => r.errorMessage).map((r) => r.errorMessage);
216
220
  resolve2({ metrics, rawErrors, rawRequests: steadyStateRequests.length > 0 ? steadyStateRequests : rawRequests });
217
- }, 1e3);
221
+ }, runtimeMs);
218
222
  });
219
223
  }
220
224
  async function runIntelligentAgenticStressTest(config) {
@@ -224,13 +228,16 @@ async function runIntelligentAgenticStressTest(config) {
224
228
  let lastRawRequests = [];
225
229
  let total1xx = 0, total2xx = 0, total3xx = 0, total4xx = 0, total5xx = 0;
226
230
  let globalMetricsAccumulator = { dns: [], tcp: [], tls: [], ttfb: [], latencies: [], bandwidths: [], lcp: [], fid: [], cls: [] };
231
+ let totalEconnreset = 0;
232
+ let totalEconnrefused = 0;
233
+ const globalCodeCounts = { 400: 0, 401: 0, 403: 0, 404: 0, 429: 0, 500: 0, 502: 0, 503: 0, 504: 0 };
227
234
  let currentThreads = Math.min(config.maxThreads, Math.max(5, Math.floor(config.maxThreads * 0.2)));
228
235
  let baseStep = config.stepSize;
229
236
  let isStable = true;
230
237
  let verdictReason = "The system successfully scaled and remained stable within parameters up to maximum configured capacity allocations.";
231
238
  let saturationKneePoint = null;
232
239
  while (currentThreads <= config.maxThreads && isStable) {
233
- console.log(`\u{1F916} [Agent Decision]: Testing execution tier at ${currentThreads} concurrent threads...`);
240
+ console.log(`\u{1F916} [Agent Decision]: Testing execution tier at ${currentThreads} continuous VU threads for ${config.durationSec}s...`);
234
241
  const { metrics, rawErrors, rawRequests } = await runBlazeCoreEngine({ ...config, threads: currentThreads });
235
242
  if (config.clusterLogs) {
236
243
  aggregateErrorLogs = aggregateErrorLogs.concat(rawErrors);
@@ -241,6 +248,11 @@ async function runIntelligentAgenticStressTest(config) {
241
248
  total3xx += metrics.c3xx;
242
249
  total4xx += metrics.c4xx;
243
250
  total5xx += metrics.c5xx;
251
+ totalEconnreset += metrics.econnresetCount;
252
+ totalEconnrefused += metrics.econnrefusedCount;
253
+ Object.keys(globalCodeCounts).forEach((code) => {
254
+ globalCodeCounts[code] += metrics.codeCounts[code] || 0;
255
+ });
244
256
  globalMetricsAccumulator.dns.push(metrics.avgDnsMs);
245
257
  globalMetricsAccumulator.tcp.push(metrics.avgTcpMs);
246
258
  globalMetricsAccumulator.tls.push(metrics.avgTlsMs);
@@ -254,6 +266,8 @@ async function runIntelligentAgenticStressTest(config) {
254
266
  threads: currentThreads,
255
267
  avgLatency: metrics.avgLatencyMs,
256
268
  p95Latency: metrics.p95LatencyMs,
269
+ minLatency: metrics.minLatencyMs,
270
+ maxLatency: metrics.maxLatencyMs,
257
271
  errorRate: metrics.errorRate,
258
272
  apdex: metrics.apdexScore,
259
273
  throughput: metrics.requestsPerSecond,
@@ -261,7 +275,10 @@ async function runIntelligentAgenticStressTest(config) {
261
275
  failedRequests: metrics.c4xx + metrics.c5xx,
262
276
  lcp: metrics.lcpMs,
263
277
  fid: metrics.fidMs,
264
- cls: metrics.clsScore
278
+ cls: metrics.clsScore,
279
+ econnresetRate: metrics.econnresetRate,
280
+ econnrefusedCount: metrics.econnrefusedCount,
281
+ codeRates: metrics.codeRates
265
282
  };
266
283
  printMatrixDashboard(metrics, currentThreads);
267
284
  history.push(currentState);
@@ -305,6 +322,11 @@ async function runIntelligentAgenticStressTest(config) {
305
322
  }
306
323
  const totalTestSeconds = history.length * config.durationSec;
307
324
  const vuRampUpVelocity = history.length > 1 && totalTestSeconds > 0 ? (history[history.length - 1].threads - history[0].threads) / totalTestSeconds : config.threads / config.durationSec;
325
+ const totalGlobalRequests = total1xx + total2xx + total3xx + total4xx + total5xx;
326
+ const aggregatedCodeRates = {};
327
+ Object.keys(globalCodeCounts).forEach((code) => {
328
+ aggregatedCodeRates[code] = totalGlobalRequests === 0 ? 0 : globalCodeCounts[code] / totalGlobalRequests * 100;
329
+ });
308
330
  const finalSummaryCards = {
309
331
  apdex: history[history.length - 1]?.apdex || 0,
310
332
  throughput: history[history.length - 1]?.throughput || 0,
@@ -318,19 +340,26 @@ async function runIntelligentAgenticStressTest(config) {
318
340
  vuRampUpVelocity,
319
341
  lcp: mathUtils.mean(globalMetricsAccumulator.lcp),
320
342
  fid: mathUtils.mean(globalMetricsAccumulator.fid),
321
- cls: mathUtils.mean(globalMetricsAccumulator.cls)
343
+ cls: mathUtils.mean(globalMetricsAccumulator.cls),
344
+ econnresetRate: totalGlobalRequests === 0 ? 0 : totalEconnreset / totalGlobalRequests * 100,
345
+ econnrefusedCount: totalEconnrefused,
346
+ codeRates: aggregatedCodeRates,
347
+ minLatency: history.length === 0 ? 0 : Math.min(...history.map((h) => h.minLatency)),
348
+ maxLatency: history.length === 0 ? 0 : Math.max(...history.map((h) => h.maxLatency))
322
349
  };
323
350
  generateFinalAgentReport(history);
324
351
  exportHtmlDashboard(history, config, verdictReason, semanticReport, { total1xx, total2xx, total3xx, total4xx, total5xx }, finalSummaryCards, lastRawRequests);
325
352
  }
326
353
  async function runStandardStressTest(config) {
327
- console.log(`\u{1F3CB}\uFE0F Running Standard Stress Test [Threads: ${config.threads} | Duration: ${config.durationSec}s]`);
354
+ console.log(`\u{1F3CB}\uFE0F Running Standard Baseline Stress Test [Threads: ${config.threads} | Continuous Duration: ${config.durationSec}s]`);
328
355
  const { metrics, rawErrors, rawRequests } = await runBlazeCoreEngine(config);
329
356
  printMatrixDashboard(metrics, config.threads);
330
357
  const history = [{
331
358
  threads: config.threads,
332
359
  avgLatency: metrics.avgLatencyMs,
333
360
  p95Latency: metrics.p95LatencyMs,
361
+ minLatency: metrics.minLatencyMs,
362
+ maxLatency: metrics.maxLatencyMs,
334
363
  errorRate: metrics.errorRate,
335
364
  apdex: metrics.apdexScore,
336
365
  throughput: metrics.requestsPerSecond,
@@ -338,7 +367,10 @@ async function runStandardStressTest(config) {
338
367
  failedRequests: metrics.c4xx + metrics.c5xx,
339
368
  lcp: metrics.lcpMs,
340
369
  fid: metrics.fidMs,
341
- cls: metrics.clsScore
370
+ cls: metrics.clsScore,
371
+ econnresetRate: metrics.econnresetRate,
372
+ econnrefusedCount: metrics.econnrefusedCount,
373
+ codeRates: metrics.codeRates
342
374
  }];
343
375
  let semanticReport = null;
344
376
  if (config.clusterLogs) {
@@ -360,7 +392,12 @@ async function runStandardStressTest(config) {
360
392
  vuRampUpVelocity: config.threads / config.durationSec,
361
393
  lcp: metrics.lcpMs,
362
394
  fid: metrics.fidMs,
363
- cls: metrics.clsScore
395
+ cls: metrics.clsScore,
396
+ econnresetRate: metrics.econnresetRate,
397
+ econnrefusedCount: metrics.econnrefusedCount,
398
+ codeRates: metrics.codeRates,
399
+ minLatency: metrics.minLatencyMs,
400
+ maxLatency: metrics.maxLatencyMs
364
401
  };
365
402
  const isPassed = metrics.apdexScore >= config.targetApdex && metrics.errorRate <= config.targetErrorRate;
366
403
  const verdictReason = isPassed ? "Static single-wave baseline checks concluded successfully without triggering health boundaries." : "Static verification loop completed with values exceeding defined quality thresholds.";
@@ -384,32 +421,51 @@ function getFallbackClusterLogs() {
384
421
  add("HTTP/1.1 504 Gateway Timeout on POST /api/v1/checkout - upstream socket drop after 30000ms", 19);
385
422
  return logs;
386
423
  }
387
- function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
424
+ function generateContinuousSimulationData(threads, durationSec, maxAllowedLatencyMs) {
388
425
  const data = [];
389
- const totalRequests = Math.max(15, threads * durationSec * 30);
390
426
  const startTime = Date.now();
427
+ const endTime = startTime + durationSec * 1e3;
391
428
  const targetThresholdBreak = Math.max(5, Math.floor(maxAllowedLatencyMs * 0.05));
392
429
  const isBreached = threads > targetThresholdBreak;
393
- const stressFactor = isBreached ? Math.pow(threads / targetThresholdBreak, 1.5) : 1;
430
+ const stressFactor = isBreached ? Math.pow(threads / targetThresholdBreak, 1.4) : 1;
394
431
  const errorPool = getFallbackClusterLogs();
395
- for (let i = 0; i < totalRequests; i++) {
396
- const isError = Math.random() < (isBreached ? 0.05 : 1e-3);
397
- const baseLatency = (25 + Math.random() * 35) * stressFactor;
398
- let errorMessage;
399
- let statusCode = 200;
400
- if (isError) {
401
- statusCode = Math.random() > 0.3 ? 500 : 404;
402
- errorMessage = errorPool[Math.floor(Math.random() * errorPool.length)];
432
+ const targetFailureCodes = [400, 401, 403, 404, 429, 500, 502, 503, 504];
433
+ for (let vu = 0; vu < threads; vu++) {
434
+ let currentTimelineOffsetMs = startTime + Math.random() * 150;
435
+ while (currentTimelineOffsetMs < endTime) {
436
+ const isError = Math.random() < (isBreached ? 0.09 : 4e-3);
437
+ const baseLatency = (30 + Math.random() * 40) * stressFactor;
438
+ let errorMessage;
439
+ let statusCode = 200;
440
+ let latencyValue = baseLatency + Math.random() * 25;
441
+ if (isError) {
442
+ const coin = Math.random();
443
+ if (coin < 0.3) {
444
+ statusCode = 502;
445
+ errorMessage = "Error: ECONNRESET socket hang up abruptly by remote host";
446
+ } else if (coin < 0.55) {
447
+ statusCode = 503;
448
+ errorMessage = "Error: ECONNREFUSED 127.0.0.1:8080 - connection rejected by destination target";
449
+ } else {
450
+ statusCode = targetFailureCodes[Math.floor(Math.random() * targetFailureCodes.length)];
451
+ errorMessage = errorPool[Math.floor(Math.random() * errorPool.length)];
452
+ }
453
+ latencyValue = baseLatency * 0.15 + 3;
454
+ } else {
455
+ if (Math.random() < 0.02) statusCode = 302;
456
+ }
457
+ data.push({
458
+ durationMs: latencyValue,
459
+ timestampMs: currentTimelineOffsetMs,
460
+ dnsTimeMs: 0.8 + Math.random() * 1.2,
461
+ tcpTimeMs: 3.5 + Math.random() * 2.1,
462
+ tlsTimeMs: 5 + Math.random() * 2.5,
463
+ statusCode,
464
+ errorMessage
465
+ });
466
+ const thinkTime = 10 + Math.random() * 20;
467
+ currentTimelineOffsetMs += latencyValue + thinkTime;
403
468
  }
404
- data.push({
405
- durationMs: isError ? baseLatency * 0.1 : baseLatency,
406
- timestampMs: startTime + Math.random() * (durationSec * 1e3),
407
- dnsTimeMs: 1 + Math.random() * 0.9,
408
- tcpTimeMs: 4 + Math.random() * 1.5,
409
- tlsTimeMs: 6 + Math.random() * 1.8,
410
- statusCode,
411
- errorMessage
412
- });
413
469
  }
414
470
  return data.sort((a, b) => a.timestampMs - b.timestampMs);
415
471
  }
@@ -419,24 +475,41 @@ function processMetricsTelemetry(requests, durationSec, apdexT) {
419
475
  const avgLatencyMs = mathUtils.mean(durations);
420
476
  const stdDevMs = mathUtils.stdDev(durations, avgLatencyMs);
421
477
  const p95LatencyMs = mathUtils.percentile(durations, 95);
478
+ const minLatencyMs = mathUtils.min(durations);
479
+ const maxLatencyMs = mathUtils.max(durations);
422
480
  const avgDnsMs = mathUtils.mean(requests.map((r) => r.dnsTimeMs || 0));
423
481
  const avgTcpMs = mathUtils.mean(requests.map((r) => r.tcpTimeMs || 0));
424
482
  const avgTlsMs = mathUtils.mean(requests.map((r) => r.tlsTimeMs || 0));
425
483
  const avgTtfbMs = avgDnsMs + avgTcpMs + avgTlsMs + avgLatencyMs * 0.3;
426
484
  let c1xx = 0, c2xx = 0, c3xx = 0, c4xx = 0, c5xx = 0;
485
+ let econnresetCount = 0;
486
+ let econnrefusedCount = 0;
487
+ const codeCounts = { 400: 0, 401: 0, 403: 0, 404: 0, 429: 0, 500: 0, 502: 0, 503: 0, 504: 0 };
427
488
  requests.forEach((r) => {
428
489
  if (r.statusCode >= 100 && r.statusCode < 200) c1xx++;
429
490
  else if (r.statusCode >= 200 && r.statusCode < 300) c2xx++;
430
491
  else if (r.statusCode >= 300 && r.statusCode < 400) c3xx++;
431
492
  else if (r.statusCode >= 400 && r.statusCode < 500) c4xx++;
432
493
  else if (r.statusCode >= 500) c5xx++;
494
+ if (r.statusCode in codeCounts) {
495
+ codeCounts[r.statusCode]++;
496
+ }
497
+ if (r.errorMessage) {
498
+ if (r.errorMessage.includes("ECONNRESET")) econnresetCount++;
499
+ if (r.errorMessage.includes("ECONNREFUSED")) econnrefusedCount++;
500
+ }
433
501
  });
502
+ const codeRates = {};
503
+ Object.keys(codeCounts).forEach((code) => {
504
+ codeRates[code] = totalRequests === 0 ? 0 : codeCounts[code] / totalRequests * 100;
505
+ });
506
+ const econnresetRate = totalRequests === 0 ? 0 : econnresetCount / totalRequests * 100;
434
507
  const errorCount = c4xx + c5xx;
435
508
  const errorRate = totalRequests === 0 ? 0 : errorCount / totalRequests;
436
509
  const satisfied = requests.filter((r) => r.durationMs <= apdexT && r.statusCode < 400).length;
437
510
  const tolerating = requests.filter((r) => r.durationMs > apdexT && r.durationMs <= apdexT * 4 && r.statusCode < 400).length;
438
511
  const apdexScore = totalRequests === 0 ? 0 : (satisfied + tolerating / 2) / totalRequests;
439
- const bandwidthMb = totalRequests * 1.2 / durationSec / 10;
512
+ const bandwidthMb = totalRequests * 1.25 / durationSec / 10;
440
513
  const lcpMs = avgTtfbMs * 1.6 + (p95LatencyMs - avgLatencyMs) * 0.4;
441
514
  const fidMs = Math.max(1.5, stdDevMs * 0.18 + errorRate * 60);
442
515
  const clsScore = Math.min(0.5, errorRate * 0.25 + (avgLatencyMs > 400 ? 0.08 : 0.01));
@@ -446,6 +519,8 @@ function processMetricsTelemetry(requests, durationSec, apdexT) {
446
519
  avgLatencyMs,
447
520
  stdDevMs,
448
521
  p95LatencyMs,
522
+ minLatencyMs,
523
+ maxLatencyMs,
449
524
  errorRate,
450
525
  apdexScore,
451
526
  c1xx,
@@ -460,7 +535,12 @@ function processMetricsTelemetry(requests, durationSec, apdexT) {
460
535
  bandwidthMb,
461
536
  lcpMs,
462
537
  fidMs,
463
- clsScore
538
+ clsScore,
539
+ econnresetCount,
540
+ econnresetRate,
541
+ econnrefusedCount,
542
+ codeCounts,
543
+ codeRates
464
544
  };
465
545
  }
466
546
  function printMatrixDashboard(m, threads) {
@@ -470,7 +550,7 @@ function printMatrixDashboard(m, threads) {
470
550
  console.log(`-----------------------------------------------------------------------------------------`);
471
551
  console.log(`| ${pad(threads, 7)} | ${pad(m.totalRequests, 9)} | ${pad(m.avgLatencyMs.toFixed(1) + "ms", 11)} | ${pad(m.p95LatencyMs.toFixed(1) + "ms", 11)} | ${pad(m.requestsPerSecond.toFixed(0) + "/s", 10)} | ${pad((m.errorRate * 100).toFixed(2) + "%", 10)} | ${pad(m.apdexScore.toFixed(2), 9)} |`);
472
552
  console.log(`-----------------------------------------------------------------------------------------`);
473
- console.log(`| Simulated Web Vitals Equivalent -> LCP: ${m.lcpMs.toFixed(0)}ms | FID: ${m.fidMs.toFixed(1)}ms | CLS: ${m.clsScore.toFixed(3)} |`);
553
+ console.log(`| Network & Floor -> RESET: ${m.econnresetRate.toFixed(1)}% | Min/Max Boundary: ${m.minLatencyMs.toFixed(1)}ms / ${m.maxLatencyMs.toFixed(1)}ms`);
474
554
  console.log(`-----------------------------------------------------------------------------------------`);
475
555
  }
476
556
  function generateFinalAgentReport(history) {
@@ -493,7 +573,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
493
573
  const activeThreadsData = history.map((h) => h.threads);
494
574
  const ttfTrendData = history.map((h) => h.avgLatency * 1.1);
495
575
  const waveListItems = history.map((h) => {
496
- return `<div class="wave-item">Wave (${h.threads} VUs): P95 ${h.p95Latency.toFixed(1)}ms, Errors ${(h.errorRate * 100).toFixed(1)}%, LCP ${h.lcp.toFixed(0)}ms</div>`;
576
+ return `<div class="wave-item">Wave (${h.threads} VUs): Floor ${h.minLatency.toFixed(1)}ms | Ceil ${h.maxLatency.toFixed(1)}ms | P95 ${h.p95Latency.toFixed(1)}ms</div>`;
497
577
  }).join("");
498
578
  const estimatedMonthlyCost = (cards.throughput * 0.045 * 24 * 30).toFixed(2);
499
579
  const recommendedCpuCores = Math.max(2, Math.ceil(cards.saturationKneePoint / 75));
@@ -509,6 +589,137 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
509
589
  const lcpStat = getLcpStatus(cards.lcp);
510
590
  const fidStat = getFidStatus(cards.fid);
511
591
  const clsStat = getClsStatus(cards.cls);
592
+ let seoPredictorPanelHtml = "";
593
+ if (config.isSeoEnabled) {
594
+ const lcp = cards.lcp;
595
+ let seoScore = 100;
596
+ let visibilityLossPct = 0;
597
+ let trafficDropPct = 0;
598
+ let rankDropPositions = 0;
599
+ if (lcp > 2500) {
600
+ if (lcp <= 4e3) {
601
+ const ratio = (lcp - 2500) / 1500;
602
+ seoScore = 100 - ratio * 30;
603
+ visibilityLossPct = ratio * 14.5;
604
+ trafficDropPct = ratio * 18.2;
605
+ rankDropPositions = Number((ratio * 1.2).toFixed(1));
606
+ } else {
607
+ const ratio = Math.min(1, (lcp - 4e3) / 4e3);
608
+ seoScore = Math.max(8, 70 - ratio * 62);
609
+ visibilityLossPct = 14.5 + ratio * 52;
610
+ trafficDropPct = 18.2 + ratio * 58.5;
611
+ rankDropPositions = Number((1.2 + ratio * 4.6).toFixed(1));
612
+ }
613
+ }
614
+ let seoColor = "#10b981";
615
+ let seoStatus = "EXCELLENT";
616
+ if (seoScore < 90) {
617
+ seoColor = "#f59e0b";
618
+ seoStatus = "NEEDS IMPROVEMENT";
619
+ }
620
+ if (seoScore < 60) {
621
+ seoColor = "#ef4444";
622
+ seoStatus = "CRITICAL RISK / PENALIZED";
623
+ }
624
+ seoPredictorPanelHtml = `
625
+ <div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
626
+ <h3 style="color: #38bdf8; font-size: 1.2rem; border-bottom: none; margin-bottom: 0.25rem;">
627
+ \u{1F50D} Google SEO Rank & Search Visibility Impact Predictor
628
+ </h3>
629
+ <div style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 1.5rem;">
630
+ Predictive algorithmic simulation modeling response degradation metrics directly against Google Core Web Vitals signal rules.
631
+ </div>
632
+
633
+ <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; flex-wrap: wrap;">
634
+ <div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b; text-align: center;">
635
+ <div style="font-size: 0.75rem; color: #64748b; text-transform: uppercase; font-weight: bold;">Core Web Vitals SEO Score</div>
636
+ <div style="font-size: 1.8rem; font-weight: bold; color: ${seoColor}; margin-top: 0.25rem;">
637
+ ${seoScore.toFixed(0)}<span style="font-size: 1rem; color: #94a3b8;">/100</span>
638
+ </div>
639
+ <div style="font-size: 0.75rem; font-weight: bold; margin-top: 0.4rem; color: ${seoColor};">${seoStatus}</div>
640
+ </div>
641
+
642
+ <div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b; text-align: center;">
643
+ <div style="font-size: 0.75rem; color: #64748b; text-transform: uppercase; font-weight: bold;">Search Visibility Loss</div>
644
+ <div style="font-size: 1.8rem; font-weight: bold; color: ${visibilityLossPct > 0 ? "#ef4444" : "#10b981"}; margin-top: 0.25rem;">
645
+ -${visibilityLossPct.toFixed(1)}%
646
+ </div>
647
+ <div style="font-size: 0.75rem; color: #64748b; margin-top: 0.4rem;">Projected SERP Impression Drop</div>
648
+ </div>
649
+
650
+ <div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b; text-align: center;">
651
+ <div style="font-size: 0.75rem; color: #64748b; text-transform: uppercase; font-weight: bold;">Funnel Conversion Loss</div>
652
+ <div style="font-size: 1.8rem; font-weight: bold; color: ${trafficDropPct > 0 ? "#ef4444" : "#10b981"}; margin-top: 0.25rem;">
653
+ -${trafficDropPct.toFixed(1)}%
654
+ </div>
655
+ <div style="font-size: 0.75rem; color: #64748b; margin-top: 0.4rem;">Expected funnel volume drop</div>
656
+ </div>
657
+
658
+ <div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b; text-align: center;">
659
+ <div style="font-size: 0.75rem; color: #64748b; text-transform: uppercase; font-weight: bold;">Avg SERP Position Shift</div>
660
+ <div style="font-size: 1.8rem; font-weight: bold; color: ${rankDropPositions > 0 ? "#f59e0b" : "#10b981"}; margin-top: 0.25rem;">
661
+ ${rankDropPositions > 0 ? `+${rankDropPositions}` : "0.0"}
662
+ </div>
663
+ <div style="font-size: 0.75rem; color: #64748b; margin-top: 0.4rem;">Rank places dropped down standard index</div>
664
+ </div>
665
+ </div>
666
+
667
+ <div style="margin-top: 1rem; background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b; font-size: 0.85rem; line-height: 1.5; color: #cbd5e1;">
668
+ <strong>\u{1F52E} SEO Engine Intelligence Breakdown:</strong>
669
+ ${lcp <= 2500 ? "Your simulated Largest Contentful Paint equivalent falls safely within Google's 'Good' range (&le; 2500ms). The parsing layer calculates zero performance-based rank penalties under current system load profiles." : lcp <= 4e3 ? `Warning: Current concurrency load has pushed LCP to ${lcp.toFixed(0)}ms, landing in Google's 'Needs Improvement' window. This response drag risks triggering index rank adjustments, potentially demoting listings down by ~${rankDropPositions} positions and sacrificing around ${trafficDropPct.toFixed(0)}% of organic top-of-funnel users.` : `Critical Signal Breach: Load performance has forced LCP to ${lcp.toFixed(0)}ms, severely violating Google's 'Poor' performance ceiling (&gt; 4000ms). At this level, ranking algorithms actively de-weight domains. Expect structural search visibility erosion up to ${visibilityLossPct.toFixed(0)}% and immediate organic traffic redirection.`}
670
+ </div>
671
+ </div>`;
672
+ }
673
+ const connectionDiagnosticsPanelHtml = `
674
+ <div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
675
+ <h3 style="color: #38bdf8; font-size: 1.2rem; border-bottom: none; margin-bottom: 0.25rem;">
676
+ \u{1F50C} Network Connection & Detailed HTTP Status Diagnostics
677
+ </h3>
678
+ <div style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 1.5rem;">
679
+ Real-time breakdown tracking transport anomalies and precise layer-7 distribution indicators.
680
+ </div>
681
+
682
+ <div style="display: grid; grid-template-columns: 1fr 2fr; gap: 2rem; flex-wrap: wrap;">
683
+ <div style="display: flex; flex-direction: column; gap: 1rem;">
684
+ <div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b;">
685
+ <div style="font-size: 0.75rem; color: #64748b; text-transform: uppercase; font-weight: bold;">ECONNRESET Rate</div>
686
+ <div style="font-size: 1.8rem; font-weight: bold; color: ${cards.econnresetRate > 1 ? "#ef4444" : "#10b981"};">
687
+ ${cards.econnresetRate.toFixed(2)}<span style="font-size: 1rem; color: #94a3b8;"> %</span>
688
+ </div>
689
+ <div style="font-size: 0.8rem; color: #64748b; margin-top: 0.25rem;">Abrupt remote host termination frequency</div>
690
+ </div>
691
+
692
+ <div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b;">
693
+ <div style="font-size: 0.75rem; color: #64748b; text-transform: uppercase; font-weight: bold;">ECONNREFUSED Count</div>
694
+ <div style="font-size: 1.8rem; font-weight: bold; color: ${cards.econnrefusedCount > 0 ? "#f59e0b" : "#10b981"};">
695
+ ${cards.econnrefusedCount}<span style="font-size: 1rem; color: #94a3b8;"> drops</span>
696
+ </div>
697
+ <div style="font-size: 0.8rem; color: #64748b; margin-top: 0.25rem;">Total complete backend port link rejections</div>
698
+ </div>
699
+ </div>
700
+
701
+ <div style="background: #090d16; padding: 1.25rem; border-radius: 6px; border: 1px solid #1e293b;">
702
+ <div style="font-size: 0.85rem; font-weight: bold; color: #f8fafc; margin-bottom: 1rem;">Target Response Code Matrix Rates</div>
703
+ <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem;">
704
+ ${Object.keys(cards.codeRates).map((code) => {
705
+ const percentage = cards.codeRates[code];
706
+ let textColor = "#cbd5e1";
707
+ if (percentage > 0) {
708
+ textColor = code.startsWith("5") ? "#f87171" : "#fde047";
709
+ }
710
+ return `
711
+ <div style="background: #131c2e; padding: 0.75rem; border-radius: 4px; border: 1px solid #1e293b; text-align: center;">
712
+ <div style="font-size: 0.8rem; font-weight: bold; color: #94a3b8;">Code ${code}</div>
713
+ <div style="font-size: 1.1rem; font-weight: bold; margin-top: 0.25rem; color: ${textColor};">
714
+ ${percentage.toFixed(2)}%
715
+ </div>
716
+ </div>
717
+ `;
718
+ }).join("")}
719
+ </div>
720
+ </div>
721
+ </div>
722
+ </div>`;
512
723
  let logClustersHtml = "";
513
724
  if (semanticReport) {
514
725
  const totalLogCount = semanticReport.clusters.reduce((sum, c) => sum + c.count, 0);
@@ -519,7 +730,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
519
730
  \u{1F9E0} Interactive Remediation Playbooks & Log Classification
520
731
  </h3>
521
732
  <div style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 1.5rem;">
522
- 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.
733
+ Captured <strong style="color: #f8fafc;">${totalLogCount}</strong> raw failures grouped into <strong style="color: #f8fafc;">${uniquePatternsCount}</strong> unique structural patterns.
523
734
  </div>
524
735
 
525
736
  <div style="display: flex; flex-direction: column; gap: 1rem;">
@@ -542,11 +753,11 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
542
753
  let explanation = "High concurrency is causing socket/connection starvation. Increase connection limits or enable pooling keep-alives.";
543
754
  if (c.category === "Authentication_Error") {
544
755
  fixTitle = "Auth Token / JWT Strategy";
545
- fixCode = "// Implement token caching and silent rotation\napp.use(authMiddleware({ cacheTtl: 300, allowGracePeriod: 60 }));";
756
+ fixCode = "// Implement token caching and silent rotation\\napp.use(authMiddleware({ cacheTtl: 300, allowGracePeriod: 60 }));";
546
757
  explanation = "Frequent token verification failures under load. Extend signature verification cache or decouple auth validation check.";
547
758
  } else if (c.category === "Product_Error") {
548
759
  fixTitle = "Application Null-Safety / Guard Fix";
549
- fixCode = "const configId = payload?.config_id ?? defaultConfigurationId;\nif (!configId) throw new ValidationError('Missing config_id');";
760
+ fixCode = "const configId = payload?.config_id ?? defaultConfigurationId;\\nif (!configId) throw new ValidationError('Missing config_id');";
550
761
  explanation = "Runtime reference error for undefined payload field under race condition spikes. Add optional chaining and input contracts.";
551
762
  }
552
763
  const snippetId = `snippet-${idx}`;
@@ -636,7 +847,8 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
636
847
  .meta { color: #94a3b8; font-size: 0.9rem; margin-top: 0.5rem; }
637
848
 
638
849
  .section-title { font-size: 1.2rem; color: #cbd5e1; font-weight: bold; margin: 2rem 0 1rem 0; padding-bottom: 0.4rem; border-bottom: 1px solid #1e293b; }
639
- .cards-wrapper { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
850
+ .cards-wrapper { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
851
+ @media (max-width: 1024px) { .cards-wrapper { grid-template-columns: repeat(2, 1fr); } }
640
852
  .metric-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 6px; padding: 1.25rem; position: relative; }
641
853
  .card-title { font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; }
642
854
  .card-value { font-size: 1.6rem; font-weight: 700; color: #ffffff; }
@@ -645,6 +857,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
645
857
  .card-value.orange { color: #f97316; }
646
858
  .card-value.purple { color: #a855f7; }
647
859
  .card-value.cyan { color: #38bdf8; }
860
+ .card-value.red { color: #f87171; }
648
861
  .card-subtext { font-size: 0.8rem; font-weight: 500; margin-left: 0.4rem; display: inline-block; }
649
862
  .card-subtext.green { color: #10b981; }
650
863
  .card-subtext.orange { color: #f97316; }
@@ -692,12 +905,20 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
692
905
  <div class="cards-wrapper">
693
906
  <div class="metric-card">
694
907
  <div class="card-title">Apdex Score (T: ${config.apdexT}ms)</div>
695
- <div class="card-value green">${cards.apdex.toFixed(2)}<span class="card-subtext green">(Target Verified)</span></div>
908
+ <div class="card-value green">${cards.apdex.toFixed(2)}<span class="card-subtext green">(Verified)</span></div>
696
909
  </div>
697
910
  <div class="metric-card">
698
911
  <div class="card-title">Throughput</div>
699
912
  <div class="card-value orange">${cards.throughput.toFixed(1)}<span class="unit">/s</span></div>
700
913
  </div>
914
+ <div class="metric-card">
915
+ <div class="card-title">Minimum Response Time</div>
916
+ <div class="card-value green">${cards.minLatency.toFixed(1)}<span class="unit">ms</span></div>
917
+ </div>
918
+ <div class="metric-card">
919
+ <div class="card-title">Maximum Response Time</div>
920
+ <div class="card-value red">${cards.maxLatency.toFixed(1)}<span class="unit">ms</span></div>
921
+ </div>
701
922
  <div class="metric-card">
702
923
  <div class="card-title">Network Bandwidth</div>
703
924
  <div class="card-value purple">${cards.bandwidth.toFixed(2)}<span class="unit">MB/s</span></div>
@@ -804,8 +1025,8 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
804
1025
  <th>Throughput</th>
805
1026
  <th>Avg Latency</th>
806
1027
  <th>P95 Latency</th>
807
- <th>Simulated LCP</th>
808
- <th>Error Rate</th>
1028
+ <th>Min / Max Floor</th>
1029
+ <th>RESET Rate</th>
809
1030
  <th>Status</th>
810
1031
  </tr>
811
1032
  </thead>
@@ -817,14 +1038,16 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
817
1038
  <td>${h.throughput.toFixed(0)} req/sec</td>
818
1039
  <td>${h.avgLatency.toFixed(1)} ms</td>
819
1040
  <td>${h.p95Latency.toFixed(1)} ms</td>
820
- <td>${h.lcp.toFixed(0)} ms</td>
821
- <td>${(h.errorRate * 100).toFixed(2)}%</td>
1041
+ <td><small>${h.minLatency.toFixed(1)}ms / ${h.maxLatency.toFixed(1)}ms</small></td>
1042
+ <td>${h.econnresetRate.toFixed(2)}%</td>
822
1043
  <td><span class="badge ${isPassed ? "badge-success" : "badge-fail"}">${isPassed ? "SLO PASS" : "SLO BREACH"}</span></td>
823
1044
  </tr>`;
824
1045
  }).join("")}
825
1046
  </tbody>
826
1047
  </table>
827
1048
  </div>
1049
+ ${seoPredictorPanelHtml}
1050
+ ${connectionDiagnosticsPanelHtml}
828
1051
  ${liveAdjusterHtml}
829
1052
  ${rightSizingHtml}
830
1053
  ${logClustersHtml}
@@ -844,7 +1067,6 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
844
1067
  const baseThroughput = ${cards.throughput};
845
1068
  const baseLatency = ${cards.ttfb};
846
1069
 
847
- // Live Concurrency Adjuster interactive client logic
848
1070
  const slider = document.getElementById('concurrencySlider');
849
1071
  const sliderVal = document.getElementById('sliderValue');
850
1072
  const simThroughput = document.getElementById('simThroughput');
@@ -929,8 +1151,8 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
929
1151
  },
930
1152
  scales: {
931
1153
  x: { grid: { color: '#1e293b' }, ticks: { color: '#64748b' } },
932
- y_threads: { position: 'left', grid: { color: '#1e293b' }, ticks: { color: '#38bdf8' }, title: { display: true, text: 'VUs / Threads', color: '#38bdf8' } },
933
- y_ttf: { position: 'right', grid: { drawOnChartArea: false }, ticks: { color: '#f43f5e' }, title: { display: true, text: 'TTF Index / Latency ms', color: '#f43f5e' } }
1154
+ yThreads: { position: 'left', grid: { color: '#1e293b' }, ticks: { color: '#38bdf8' }, title: { display: true, text: 'VUs / Threads', color: '#38bdf8' } },
1155
+ yTtf: { position: 'right', grid: { drawOnChartArea: false }, ticks: { color: '#f43f5e' }, title: { display: true, text: 'TTF Index / Latency ms', color: '#f43f5e' } }
934
1156
  }
935
1157
  }
936
1158
  });
@@ -990,6 +1212,6 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
990
1212
  function printUsage() {
991
1213
  console.log(`Blaze Core Performance Test CLI Engine
992
1214
  Options:
993
- --threads <count> | --duration <seconds> | --agentic | --cluster-logs | --correlate | --apdex-t <ms>`);
1215
+ --threads <count> | --duration <seconds> | --agentic | --cluster-logs | --correlate | --apdex-t <ms> | --seo`);
994
1216
  }
995
1217
  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.15",
3
+ "version": "3.1.16",
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",