blaze-performance-tester 3.1.16 → 3.1.17

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,7 +70,8 @@ var blazeCore;
70
70
  try {
71
71
  blazeCore = require2(nativeBindingPath);
72
72
  } catch (e) {
73
- console.warn(`\u26A0\uFE0F Native C++ bindings not loaded from ${nativeBindingPath}. Using high-efficiency JS concurrent fallback loop.`);
73
+ console.error(`\u274C Failed to load native C++ bindings from ${nativeBindingPath}`);
74
+ process.exit(1);
74
75
  }
75
76
  var mathUtils = {
76
77
  mean: (arr) => arr.length === 0 ? 0 : arr.reduce((p, c) => p + c, 0) / arr.length,
@@ -84,9 +85,7 @@ var mathUtils = {
84
85
  const sorted = [...arr].sort((a, b) => a - b);
85
86
  const index = Math.ceil(p / 100 * sorted.length) - 1;
86
87
  return sorted[Math.max(0, index)];
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])
88
+ }
90
89
  };
91
90
  async function executeTestPipeline(config) {
92
91
  if (!blazeCore || typeof blazeCore.runCorrelatedTransaction !== "function") {
@@ -140,7 +139,6 @@ function parseArguments(args) {
140
139
  const targetScript = path.resolve(args[0] || ".");
141
140
  const isAgentic = args.includes("--agentic");
142
141
  const isCorrelate = args.includes("--correlate");
143
- const isSeoEnabled = args.includes("--seo");
144
142
  const clusterLogs = !args.includes("--no-cluster-logs");
145
143
  let threads = 10;
146
144
  let durationSec = 10;
@@ -149,7 +147,6 @@ function parseArguments(args) {
149
147
  let maxThreads = 300;
150
148
  let maxAllowedLatencyMs = 800;
151
149
  let outputHtml = "blaze-dashboard.html";
152
- let apdexT = 50;
153
150
  const threadsIdx = args.indexOf("--threads");
154
151
  if (threadsIdx !== -1) threads = parseInt(args[threadsIdx + 1], 10);
155
152
  const durationIdx = args.indexOf("--duration");
@@ -162,8 +159,6 @@ function parseArguments(args) {
162
159
  if (maxThreadsIdx !== -1) maxThreads = parseInt(args[maxThreadsIdx + 1], 10);
163
160
  const outputIdx = args.indexOf("--output");
164
161
  if (outputIdx !== -1) outputHtml = args[outputIdx + 1];
165
- const apdexTIdx = args.indexOf("--apdex-t");
166
- if (apdexTIdx !== -1) apdexT = parseFloat(args[apdexTIdx + 1]);
167
162
  const positionalNums = args.slice(1).filter((arg) => !arg.startsWith("-")).map(Number).filter((n) => !isNaN(n));
168
163
  if (positionalNums.length > 0 && threadsIdx === -1 && !isAgentic) threads = positionalNums[0];
169
164
  if (positionalNums.length > 1 && durationIdx === -1 && !isAgentic) durationSec = positionalNums[1];
@@ -186,7 +181,6 @@ function parseArguments(args) {
186
181
  targetScript,
187
182
  isAgentic,
188
183
  isCorrelate,
189
- isSeoEnabled,
190
184
  clusterLogs,
191
185
  threads,
192
186
  durationSec,
@@ -195,13 +189,11 @@ function parseArguments(args) {
195
189
  maxThreads,
196
190
  stepSize: 15,
197
191
  maxAllowedLatencyMs,
198
- outputHtml,
199
- apdexT
192
+ outputHtml
200
193
  };
201
194
  }
202
195
  async function runBlazeCoreEngine(config) {
203
196
  return new Promise((resolve2) => {
204
- const runtimeMs = config.durationSec * 1e3;
205
197
  setTimeout(() => {
206
198
  let rawRequests = [];
207
199
  try {
@@ -211,14 +203,14 @@ async function runBlazeCoreEngine(config) {
211
203
  } catch (err) {
212
204
  }
213
205
  if (!rawRequests || rawRequests.length === 0) {
214
- rawRequests = generateContinuousSimulationData(config.threads, config.durationSec, config.maxAllowedLatencyMs);
206
+ rawRequests = generateSimulationData(config.threads, config.durationSec, config.maxAllowedLatencyMs);
215
207
  }
216
208
  const warmUpCutoff = Math.floor(rawRequests.length * 0.1);
217
209
  const steadyStateRequests = rawRequests.slice(warmUpCutoff);
218
- const metrics = processMetricsTelemetry(steadyStateRequests.length > 0 ? steadyStateRequests : rawRequests, config.durationSec, config.apdexT);
210
+ const metrics = processMetricsTelemetry(steadyStateRequests.length > 0 ? steadyStateRequests : rawRequests, config.durationSec);
219
211
  const rawErrors = rawRequests.filter((r) => r.errorMessage).map((r) => r.errorMessage);
220
212
  resolve2({ metrics, rawErrors, rawRequests: steadyStateRequests.length > 0 ? steadyStateRequests : rawRequests });
221
- }, runtimeMs);
213
+ }, 1e3);
222
214
  });
223
215
  }
224
216
  async function runIntelligentAgenticStressTest(config) {
@@ -227,17 +219,14 @@ async function runIntelligentAgenticStressTest(config) {
227
219
  let aggregateErrorLogs = [];
228
220
  let lastRawRequests = [];
229
221
  let total1xx = 0, total2xx = 0, total3xx = 0, total4xx = 0, total5xx = 0;
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 };
222
+ let globalMetricsAccumulator = { dns: [], tcp: [], tls: [], ttfb: [], latencies: [], bandwidths: [] };
234
223
  let currentThreads = Math.min(config.maxThreads, Math.max(5, Math.floor(config.maxThreads * 0.2)));
235
224
  let baseStep = config.stepSize;
236
225
  let isStable = true;
237
226
  let verdictReason = "The system successfully scaled and remained stable within parameters up to maximum configured capacity allocations.";
238
227
  let saturationKneePoint = null;
239
228
  while (currentThreads <= config.maxThreads && isStable) {
240
- console.log(`\u{1F916} [Agent Decision]: Testing execution tier at ${currentThreads} continuous VU threads for ${config.durationSec}s...`);
229
+ console.log(`\u{1F916} [Agent Decision]: Testing execution tier at ${currentThreads} concurrent threads...`);
241
230
  const { metrics, rawErrors, rawRequests } = await runBlazeCoreEngine({ ...config, threads: currentThreads });
242
231
  if (config.clusterLogs) {
243
232
  aggregateErrorLogs = aggregateErrorLogs.concat(rawErrors);
@@ -248,37 +237,21 @@ async function runIntelligentAgenticStressTest(config) {
248
237
  total3xx += metrics.c3xx;
249
238
  total4xx += metrics.c4xx;
250
239
  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
- });
256
240
  globalMetricsAccumulator.dns.push(metrics.avgDnsMs);
257
241
  globalMetricsAccumulator.tcp.push(metrics.avgTcpMs);
258
242
  globalMetricsAccumulator.tls.push(metrics.avgTlsMs);
259
243
  globalMetricsAccumulator.ttfb.push(metrics.avgTtfbMs);
260
244
  globalMetricsAccumulator.latencies.push(metrics.avgLatencyMs);
261
245
  globalMetricsAccumulator.bandwidths.push(metrics.bandwidthMb);
262
- globalMetricsAccumulator.lcp.push(metrics.lcpMs);
263
- globalMetricsAccumulator.fid.push(metrics.fidMs);
264
- globalMetricsAccumulator.cls.push(metrics.clsScore);
265
246
  const currentState = {
266
247
  threads: currentThreads,
267
248
  avgLatency: metrics.avgLatencyMs,
268
249
  p95Latency: metrics.p95LatencyMs,
269
- minLatency: metrics.minLatencyMs,
270
- maxLatency: metrics.maxLatencyMs,
271
250
  errorRate: metrics.errorRate,
272
251
  apdex: metrics.apdexScore,
273
252
  throughput: metrics.requestsPerSecond,
274
253
  successRequests: metrics.c2xx + metrics.c3xx,
275
- failedRequests: metrics.c4xx + metrics.c5xx,
276
- lcp: metrics.lcpMs,
277
- fid: metrics.fidMs,
278
- cls: metrics.clsScore,
279
- econnresetRate: metrics.econnresetRate,
280
- econnrefusedCount: metrics.econnrefusedCount,
281
- codeRates: metrics.codeRates
254
+ failedRequests: metrics.c4xx + metrics.c5xx
282
255
  };
283
256
  printMatrixDashboard(metrics, currentThreads);
284
257
  history.push(currentState);
@@ -322,11 +295,6 @@ async function runIntelligentAgenticStressTest(config) {
322
295
  }
323
296
  const totalTestSeconds = history.length * config.durationSec;
324
297
  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
- });
330
298
  const finalSummaryCards = {
331
299
  apdex: history[history.length - 1]?.apdex || 0,
332
300
  throughput: history[history.length - 1]?.throughput || 0,
@@ -337,40 +305,24 @@ async function runIntelligentAgenticStressTest(config) {
337
305
  tls: mathUtils.mean(globalMetricsAccumulator.tls),
338
306
  stdDev: mathUtils.stdDev(globalMetricsAccumulator.latencies, mathUtils.mean(globalMetricsAccumulator.latencies)),
339
307
  saturationKneePoint: saturationKneePoint || history[history.length - 1]?.threads || 50,
340
- vuRampUpVelocity,
341
- lcp: mathUtils.mean(globalMetricsAccumulator.lcp),
342
- fid: mathUtils.mean(globalMetricsAccumulator.fid),
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))
308
+ vuRampUpVelocity
349
309
  };
350
310
  generateFinalAgentReport(history);
351
311
  exportHtmlDashboard(history, config, verdictReason, semanticReport, { total1xx, total2xx, total3xx, total4xx, total5xx }, finalSummaryCards, lastRawRequests);
352
312
  }
353
313
  async function runStandardStressTest(config) {
354
- console.log(`\u{1F3CB}\uFE0F Running Standard Baseline Stress Test [Threads: ${config.threads} | Continuous Duration: ${config.durationSec}s]`);
314
+ console.log(`\u{1F3CB}\uFE0F Running Standard Stress Test [Threads: ${config.threads} | Duration: ${config.durationSec}s]`);
355
315
  const { metrics, rawErrors, rawRequests } = await runBlazeCoreEngine(config);
356
316
  printMatrixDashboard(metrics, config.threads);
357
317
  const history = [{
358
318
  threads: config.threads,
359
319
  avgLatency: metrics.avgLatencyMs,
360
320
  p95Latency: metrics.p95LatencyMs,
361
- minLatency: metrics.minLatencyMs,
362
- maxLatency: metrics.maxLatencyMs,
363
321
  errorRate: metrics.errorRate,
364
322
  apdex: metrics.apdexScore,
365
323
  throughput: metrics.requestsPerSecond,
366
324
  successRequests: metrics.c2xx + metrics.c3xx,
367
- failedRequests: metrics.c4xx + metrics.c5xx,
368
- lcp: metrics.lcpMs,
369
- fid: metrics.fidMs,
370
- cls: metrics.clsScore,
371
- econnresetRate: metrics.econnresetRate,
372
- econnrefusedCount: metrics.econnrefusedCount,
373
- codeRates: metrics.codeRates
325
+ failedRequests: metrics.c4xx + metrics.c5xx
374
326
  }];
375
327
  let semanticReport = null;
376
328
  if (config.clusterLogs) {
@@ -389,15 +341,7 @@ async function runStandardStressTest(config) {
389
341
  tls: metrics.avgTlsMs,
390
342
  stdDev: metrics.stdDevMs,
391
343
  saturationKneePoint: config.threads,
392
- vuRampUpVelocity: config.threads / config.durationSec,
393
- lcp: metrics.lcpMs,
394
- fid: metrics.fidMs,
395
- cls: metrics.clsScore,
396
- econnresetRate: metrics.econnresetRate,
397
- econnrefusedCount: metrics.econnrefusedCount,
398
- codeRates: metrics.codeRates,
399
- minLatency: metrics.minLatencyMs,
400
- maxLatency: metrics.maxLatencyMs
344
+ vuRampUpVelocity: config.threads / config.durationSec
401
345
  };
402
346
  const isPassed = metrics.apdexScore >= config.targetApdex && metrics.errorRate <= config.targetErrorRate;
403
347
  const verdictReason = isPassed ? "Static single-wave baseline checks concluded successfully without triggering health boundaries." : "Static verification loop completed with values exceeding defined quality thresholds.";
@@ -421,106 +365,66 @@ function getFallbackClusterLogs() {
421
365
  add("HTTP/1.1 504 Gateway Timeout on POST /api/v1/checkout - upstream socket drop after 30000ms", 19);
422
366
  return logs;
423
367
  }
424
- function generateContinuousSimulationData(threads, durationSec, maxAllowedLatencyMs) {
368
+ function generateSimulationData(threads, durationSec, maxAllowedLatencyMs) {
425
369
  const data = [];
370
+ const totalRequests = Math.max(15, threads * durationSec * 30);
426
371
  const startTime = Date.now();
427
- const endTime = startTime + durationSec * 1e3;
428
372
  const targetThresholdBreak = Math.max(5, Math.floor(maxAllowedLatencyMs * 0.05));
429
373
  const isBreached = threads > targetThresholdBreak;
430
- const stressFactor = isBreached ? Math.pow(threads / targetThresholdBreak, 1.4) : 1;
374
+ const stressFactor = isBreached ? Math.pow(threads / targetThresholdBreak, 1.5) : 1;
431
375
  const errorPool = getFallbackClusterLogs();
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;
376
+ for (let i = 0; i < totalRequests; i++) {
377
+ const isError = Math.random() < (isBreached ? 0.05 : 1e-3);
378
+ const baseLatency = (25 + Math.random() * 35) * stressFactor;
379
+ let errorMessage;
380
+ let statusCode = 200;
381
+ if (isError) {
382
+ statusCode = Math.random() > 0.3 ? 500 : 404;
383
+ errorMessage = errorPool[Math.floor(Math.random() * errorPool.length)];
468
384
  }
385
+ data.push({
386
+ durationMs: isError ? baseLatency * 0.1 : baseLatency,
387
+ timestampMs: startTime + Math.random() * (durationSec * 1e3),
388
+ dnsTimeMs: 1 + Math.random() * 0.9,
389
+ tcpTimeMs: 4 + Math.random() * 1.5,
390
+ tlsTimeMs: 6 + Math.random() * 1.8,
391
+ statusCode,
392
+ errorMessage
393
+ });
469
394
  }
470
395
  return data.sort((a, b) => a.timestampMs - b.timestampMs);
471
396
  }
472
- function processMetricsTelemetry(requests, durationSec, apdexT) {
397
+ function processMetricsTelemetry(requests, durationSec) {
473
398
  const totalRequests = requests.length;
474
399
  const durations = requests.map((r) => r.durationMs);
475
400
  const avgLatencyMs = mathUtils.mean(durations);
476
401
  const stdDevMs = mathUtils.stdDev(durations, avgLatencyMs);
477
402
  const p95LatencyMs = mathUtils.percentile(durations, 95);
478
- const minLatencyMs = mathUtils.min(durations);
479
- const maxLatencyMs = mathUtils.max(durations);
480
403
  const avgDnsMs = mathUtils.mean(requests.map((r) => r.dnsTimeMs || 0));
481
404
  const avgTcpMs = mathUtils.mean(requests.map((r) => r.tcpTimeMs || 0));
482
405
  const avgTlsMs = mathUtils.mean(requests.map((r) => r.tlsTimeMs || 0));
483
406
  const avgTtfbMs = avgDnsMs + avgTcpMs + avgTlsMs + avgLatencyMs * 0.3;
484
407
  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 };
488
408
  requests.forEach((r) => {
489
409
  if (r.statusCode >= 100 && r.statusCode < 200) c1xx++;
490
410
  else if (r.statusCode >= 200 && r.statusCode < 300) c2xx++;
491
411
  else if (r.statusCode >= 300 && r.statusCode < 400) c3xx++;
492
412
  else if (r.statusCode >= 400 && r.statusCode < 500) c4xx++;
493
413
  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
- }
501
- });
502
- const codeRates = {};
503
- Object.keys(codeCounts).forEach((code) => {
504
- codeRates[code] = totalRequests === 0 ? 0 : codeCounts[code] / totalRequests * 100;
505
414
  });
506
- const econnresetRate = totalRequests === 0 ? 0 : econnresetCount / totalRequests * 100;
507
415
  const errorCount = c4xx + c5xx;
508
416
  const errorRate = totalRequests === 0 ? 0 : errorCount / totalRequests;
509
- const satisfied = requests.filter((r) => r.durationMs <= apdexT && r.statusCode < 400).length;
510
- const tolerating = requests.filter((r) => r.durationMs > apdexT && r.durationMs <= apdexT * 4 && r.statusCode < 400).length;
417
+ const T = 50;
418
+ const satisfied = requests.filter((r) => r.durationMs <= T && r.statusCode < 400).length;
419
+ const tolerating = requests.filter((r) => r.durationMs > T && r.durationMs <= T * 4 && r.statusCode < 400).length;
511
420
  const apdexScore = totalRequests === 0 ? 0 : (satisfied + tolerating / 2) / totalRequests;
512
- const bandwidthMb = totalRequests * 1.25 / durationSec / 10;
513
- const lcpMs = avgTtfbMs * 1.6 + (p95LatencyMs - avgLatencyMs) * 0.4;
514
- const fidMs = Math.max(1.5, stdDevMs * 0.18 + errorRate * 60);
515
- const clsScore = Math.min(0.5, errorRate * 0.25 + (avgLatencyMs > 400 ? 0.08 : 0.01));
421
+ const bandwidthMb = totalRequests * 1.2 / durationSec / 10;
516
422
  return {
517
423
  totalRequests,
518
424
  requestsPerSecond: totalRequests / durationSec,
519
425
  avgLatencyMs,
520
426
  stdDevMs,
521
427
  p95LatencyMs,
522
- minLatencyMs,
523
- maxLatencyMs,
524
428
  errorRate,
525
429
  apdexScore,
526
430
  c1xx,
@@ -532,15 +436,7 @@ function processMetricsTelemetry(requests, durationSec, apdexT) {
532
436
  avgTcpMs,
533
437
  avgTlsMs,
534
438
  avgTtfbMs,
535
- bandwidthMb,
536
- lcpMs,
537
- fidMs,
538
- clsScore,
539
- econnresetCount,
540
- econnresetRate,
541
- econnrefusedCount,
542
- codeCounts,
543
- codeRates
439
+ bandwidthMb
544
440
  };
545
441
  }
546
442
  function printMatrixDashboard(m, threads) {
@@ -550,8 +446,6 @@ function printMatrixDashboard(m, threads) {
550
446
  console.log(`-----------------------------------------------------------------------------------------`);
551
447
  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)} |`);
552
448
  console.log(`-----------------------------------------------------------------------------------------`);
553
- console.log(`| Network & Floor -> RESET: ${m.econnresetRate.toFixed(1)}% | Min/Max Boundary: ${m.minLatencyMs.toFixed(1)}ms / ${m.maxLatencyMs.toFixed(1)}ms`);
554
- console.log(`-----------------------------------------------------------------------------------------`);
555
449
  }
556
450
  function generateFinalAgentReport(history) {
557
451
  if (history.length === 0) return;
@@ -573,7 +467,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
573
467
  const activeThreadsData = history.map((h) => h.threads);
574
468
  const ttfTrendData = history.map((h) => h.avgLatency * 1.1);
575
469
  const waveListItems = history.map((h) => {
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>`;
470
+ return `<div class="wave-item">Wave (${h.threads} VUs): P95 ${h.p95Latency.toFixed(1)}ms, Errors ${(h.errorRate * 100).toFixed(1)}%</div>`;
577
471
  }).join("");
578
472
  const estimatedMonthlyCost = (cards.throughput * 0.045 * 24 * 30).toFixed(2);
579
473
  const recommendedCpuCores = Math.max(2, Math.ceil(cards.saturationKneePoint / 75));
@@ -583,143 +477,6 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
583
477
  x: Number(((r.timestampMs - firstTimestamp) / 1e3).toFixed(2)),
584
478
  y: Number(((r.dnsTimeMs || 0) + (r.tcpTimeMs || 0) + (r.tlsTimeMs || 0) + r.durationMs * 0.3).toFixed(1))
585
479
  }));
586
- const getLcpStatus = (val) => val <= 2500 ? { label: "Good", cls: "green" } : val <= 4e3 ? { label: "Needs Imp.", cls: "orange" } : { label: "Poor", cls: "badge-fail" };
587
- const getFidStatus = (val) => val <= 100 ? { label: "Good", cls: "green" } : val <= 300 ? { label: "Needs Imp.", cls: "orange" } : { label: "Poor", cls: "badge-fail" };
588
- const getClsStatus = (val) => val <= 0.1 ? { label: "Good", cls: "green" } : val <= 0.25 ? { label: "Needs Imp.", cls: "orange" } : { label: "Poor", cls: "badge-fail" };
589
- const lcpStat = getLcpStatus(cards.lcp);
590
- const fidStat = getFidStatus(cards.fid);
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>`;
723
480
  let logClustersHtml = "";
724
481
  if (semanticReport) {
725
482
  const totalLogCount = semanticReport.clusters.reduce((sum, c) => sum + c.count, 0);
@@ -730,7 +487,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
730
487
  \u{1F9E0} Interactive Remediation Playbooks & Log Classification
731
488
  </h3>
732
489
  <div style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 1.5rem;">
733
- Captured <strong style="color: #f8fafc;">${totalLogCount}</strong> raw failures grouped into <strong style="color: #f8fafc;">${uniquePatternsCount}</strong> unique structural patterns.
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 and diagnostics confidence ratings.
734
491
  </div>
735
492
 
736
493
  <div style="display: flex; flex-direction: column; gap: 1rem;">
@@ -753,11 +510,11 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
753
510
  let explanation = "High concurrency is causing socket/connection starvation. Increase connection limits or enable pooling keep-alives.";
754
511
  if (c.category === "Authentication_Error") {
755
512
  fixTitle = "Auth Token / JWT Strategy";
756
- fixCode = "// Implement token caching and silent rotation\\napp.use(authMiddleware({ cacheTtl: 300, allowGracePeriod: 60 }));";
513
+ fixCode = "// Implement token caching and silent rotation\napp.use(authMiddleware({ cacheTtl: 300, allowGracePeriod: 60 }));";
757
514
  explanation = "Frequent token verification failures under load. Extend signature verification cache or decouple auth validation check.";
758
515
  } else if (c.category === "Product_Error") {
759
516
  fixTitle = "Application Null-Safety / Guard Fix";
760
- fixCode = "const configId = payload?.config_id ?? defaultConfigurationId;\\nif (!configId) throw new ValidationError('Missing config_id');";
517
+ fixCode = "const configId = payload?.config_id ?? defaultConfigurationId;\nif (!configId) throw new ValidationError('Missing config_id');";
761
518
  explanation = "Runtime reference error for undefined payload field under race condition spikes. Add optional chaining and input contracts.";
762
519
  }
763
520
  const snippetId = `snippet-${idx}`;
@@ -846,9 +603,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
846
603
  h1 { color: #38bdf8; margin: 0; font-size: 2rem; }
847
604
  .meta { color: #94a3b8; font-size: 0.9rem; margin-top: 0.5rem; }
848
605
 
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; }
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); } }
606
+ .cards-wrapper { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
852
607
  .metric-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 6px; padding: 1.25rem; position: relative; }
853
608
  .card-title { font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; }
854
609
  .card-value { font-size: 1.6rem; font-weight: 700; color: #ffffff; }
@@ -857,10 +612,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
857
612
  .card-value.orange { color: #f97316; }
858
613
  .card-value.purple { color: #a855f7; }
859
614
  .card-value.cyan { color: #38bdf8; }
860
- .card-value.red { color: #f87171; }
861
- .card-subtext { font-size: 0.8rem; font-weight: 500; margin-left: 0.4rem; display: inline-block; }
862
- .card-subtext.green { color: #10b981; }
863
- .card-subtext.orange { color: #f97316; }
615
+ .card-subtext { font-size: 0.8rem; color: #10b981; font-weight: 500; margin-left: 0.4rem; display: inline-block; }
864
616
 
865
617
  .top-layout-grid { display: grid; grid-template-columns: 1fr 400px; gap: 1.5rem; margin-bottom: 2rem; }
866
618
  .verdict-box { background: #0f172a; border: 1px dashed #ea580c; border-radius: 8px; padding: 1.25rem; }
@@ -901,24 +653,15 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
901
653
  <div class="meta">Target Script: <code>${config.targetScript}</code></div>
902
654
  </div>
903
655
 
904
- <div class="section-title">\u{1F4CA} Core Server Metrics & Apdex Verification</div>
905
656
  <div class="cards-wrapper">
906
657
  <div class="metric-card">
907
- <div class="card-title">Apdex Score (T: ${config.apdexT}ms)</div>
908
- <div class="card-value green">${cards.apdex.toFixed(2)}<span class="card-subtext green">(Verified)</span></div>
658
+ <div class="card-title">Apdex Score (T: 50ms)</div>
659
+ <div class="card-value green">${cards.apdex.toFixed(2)}<span class="card-subtext">(Good)</span></div>
909
660
  </div>
910
661
  <div class="metric-card">
911
662
  <div class="card-title">Throughput</div>
912
663
  <div class="card-value orange">${cards.throughput.toFixed(1)}<span class="unit">/s</span></div>
913
664
  </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>
922
665
  <div class="metric-card">
923
666
  <div class="card-title">Network Bandwidth</div>
924
667
  <div class="card-value purple">${cards.bandwidth.toFixed(2)}<span class="unit">MB/s</span></div>
@@ -932,28 +675,23 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
932
675
  <div class="card-value cyan">${cards.vuRampUpVelocity.toFixed(1)}<span class="unit">VUs/s</span></div>
933
676
  </div>
934
677
  <div class="metric-card">
935
- <div class="card-title">Stability (Std Dev)</div>
936
- <div class="card-value">&plusmn;${cards.stdDev.toFixed(1)}<span class="unit">ms</span></div>
678
+ <div class="card-title">DNS Lookup</div>
679
+ <div class="card-value">${cards.dns.toFixed(2)}<span class="unit">ms</span></div>
937
680
  </div>
938
- </div>
939
-
940
- <div class="section-title">\u{1F310} Simulated Browser Core Web Vitals Equivalent</div>
941
- <div class="cards-wrapper">
942
681
  <div class="metric-card">
943
- <div class="card-title">Largest Contentful Paint (LCP)</div>
944
- <div class="card-value">${cards.lcp.toFixed(0)}<span class="unit">ms</span><span class="card-subtext ${lcpStat.cls}">(${lcpStat.label})</span></div>
682
+ <div class="card-title">TCP Connect</div>
683
+ <div class="card-value">${cards.tcp.toFixed(2)}<span class="unit">ms</span></div>
945
684
  </div>
946
685
  <div class="metric-card">
947
- <div class="card-title">First Input Delay (FID)</div>
948
- <div class="card-value">${cards.fid.toFixed(1)}<span class="unit">ms</span><span class="card-subtext ${fidStat.cls}">(${fidStat.label})</span></div>
686
+ <div class="card-title">TLS Handshake</div>
687
+ <div class="card-value">${cards.tls.toFixed(2)}<span class="unit">ms</span></div>
949
688
  </div>
950
689
  <div class="metric-card">
951
- <div class="card-title">Cumulative Layout Shift (CLS)</div>
952
- <div class="card-value">${cards.cls.toFixed(3)}<span class="card-subtext ${clsStat.cls}">(${clsStat.label})</span></div>
690
+ <div class="card-title">Stability (Std Dev)</div>
691
+ <div class="card-value">&plusmn;${cards.stdDev.toFixed(1)}<span class="unit">ms</span></div>
953
692
  </div>
954
693
  </div>
955
694
 
956
- <div class="section-title">\u26A1 Diagnostic Breakdowns</div>
957
695
  <div class="top-layout-grid">
958
696
  <div class="verdict-box">
959
697
  <div class="verdict-title">\u{1F916} AI Stress-Testing Agent Verdict</div>
@@ -1025,8 +763,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
1025
763
  <th>Throughput</th>
1026
764
  <th>Avg Latency</th>
1027
765
  <th>P95 Latency</th>
1028
- <th>Min / Max Floor</th>
1029
- <th>RESET Rate</th>
766
+ <th>Error Rate</th>
1030
767
  <th>Status</th>
1031
768
  </tr>
1032
769
  </thead>
@@ -1038,16 +775,13 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
1038
775
  <td>${h.throughput.toFixed(0)} req/sec</td>
1039
776
  <td>${h.avgLatency.toFixed(1)} ms</td>
1040
777
  <td>${h.p95Latency.toFixed(1)} ms</td>
1041
- <td><small>${h.minLatency.toFixed(1)}ms / ${h.maxLatency.toFixed(1)}ms</small></td>
1042
- <td>${h.econnresetRate.toFixed(2)}%</td>
778
+ <td>${(h.errorRate * 100).toFixed(2)}%</td>
1043
779
  <td><span class="badge ${isPassed ? "badge-success" : "badge-fail"}">${isPassed ? "SLO PASS" : "SLO BREACH"}</span></td>
1044
780
  </tr>`;
1045
781
  }).join("")}
1046
782
  </tbody>
1047
783
  </table>
1048
784
  </div>
1049
- ${seoPredictorPanelHtml}
1050
- ${connectionDiagnosticsPanelHtml}
1051
785
  ${liveAdjusterHtml}
1052
786
  ${rightSizingHtml}
1053
787
  ${logClustersHtml}
@@ -1067,6 +801,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
1067
801
  const baseThroughput = ${cards.throughput};
1068
802
  const baseLatency = ${cards.ttfb};
1069
803
 
804
+ // Live Concurrency Adjuster interactive client logic
1070
805
  const slider = document.getElementById('concurrencySlider');
1071
806
  const sliderVal = document.getElementById('sliderValue');
1072
807
  const simThroughput = document.getElementById('simThroughput');
@@ -1151,8 +886,8 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
1151
886
  },
1152
887
  scales: {
1153
888
  x: { grid: { color: '#1e293b' }, ticks: { color: '#64748b' } },
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' } }
889
+ y_threads: { position: 'left', grid: { color: '#1e293b' }, ticks: { color: '#38bdf8' }, title: { display: true, text: 'VUs / Threads', color: '#38bdf8' } },
890
+ y_ttf: { position: 'right', grid: { drawOnChartArea: false }, ticks: { color: '#f43f5e' }, title: { display: true, text: 'TTF Index / Latency ms', color: '#f43f5e' } }
1156
891
  }
1157
892
  }
1158
893
  });
@@ -1212,6 +947,6 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
1212
947
  function printUsage() {
1213
948
  console.log(`Blaze Core Performance Test CLI Engine
1214
949
  Options:
1215
- --threads <count> | --duration <seconds> | --agentic | --cluster-logs | --correlate | --apdex-t <ms> | --seo`);
950
+ --threads <count> | --duration <seconds> | --agentic | --cluster-logs | --correlate`);
1216
951
  }
1217
952
  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.16",
3
+ "version": "3.1.17",
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",