@loadstrike/loadstrike-sdk 1.0.31001 → 1.0.32601

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.
Files changed (45) hide show
  1. package/README.md +16 -2
  2. package/dist/cjs/internal/prometheus-remote-write.js +37 -0
  3. package/dist/cjs/internal/reporting-sink-http-error.js +17 -0
  4. package/dist/cjs/internal/vendor-metric-payloads.js +390 -0
  5. package/dist/cjs/iteration-observations.js +24 -8
  6. package/dist/cjs/local-report-input.js +21 -0
  7. package/dist/cjs/local.js +48 -63
  8. package/dist/cjs/report-history.js +421 -0
  9. package/dist/cjs/reporting-containment.js +242 -0
  10. package/dist/cjs/reporting-svg.js +116 -0
  11. package/dist/cjs/reporting.js +413 -136
  12. package/dist/cjs/runtime.js +237 -8
  13. package/dist/cjs/sinks.js +1337 -38
  14. package/dist/cjs/transports.js +1339 -151
  15. package/dist/esm/internal/prometheus-remote-write.js +31 -0
  16. package/dist/esm/internal/reporting-sink-http-error.js +13 -0
  17. package/dist/esm/internal/vendor-metric-payloads.js +382 -0
  18. package/dist/esm/iteration-observations.js +24 -8
  19. package/dist/esm/local-report-input.js +17 -0
  20. package/dist/esm/local.js +49 -64
  21. package/dist/esm/report-history.js +413 -0
  22. package/dist/esm/reporting-containment.js +238 -0
  23. package/dist/esm/reporting-svg.js +113 -0
  24. package/dist/esm/reporting.js +413 -136
  25. package/dist/esm/runtime.js +239 -10
  26. package/dist/esm/sinks.js +1334 -35
  27. package/dist/esm/transports.js +1335 -151
  28. package/dist/types/contracts.d.ts +1 -0
  29. package/dist/types/index.d.ts +1 -1
  30. package/dist/types/internal/prometheus-remote-write.d.ts +2 -0
  31. package/dist/types/internal/reporting-sink-http-error.d.ts +6 -0
  32. package/dist/types/internal/vendor-metric-payloads.d.ts +48 -0
  33. package/dist/types/local-report-input.d.ts +6 -0
  34. package/dist/types/local.d.ts +0 -6
  35. package/dist/types/report-history.d.ts +124 -0
  36. package/dist/types/reporting-containment.d.ts +2 -0
  37. package/dist/types/reporting-svg.d.ts +2 -0
  38. package/dist/types/reporting.d.ts +6 -3
  39. package/dist/types/runtime.d.ts +24 -0
  40. package/dist/types/sinks.d.ts +134 -17
  41. package/dist/types/transports.d.ts +2 -0
  42. package/package.json +9 -3
  43. package/dist/cjs/internal-build.js +0 -4
  44. package/dist/esm/internal-build.js +0 -1
  45. package/dist/types/internal-build.d.ts +0 -1
@@ -13,13 +13,18 @@ const cluster_js_1 = require("./cluster.js");
13
13
  const correlation_js_1 = require("./correlation.js");
14
14
  const transports_js_1 = require("./transports.js");
15
15
  const reporting_js_1 = require("./reporting.js");
16
+ const report_history_js_1 = require("./report-history.js");
17
+ const local_report_input_js_1 = require("./local-report-input.js");
16
18
  const sinks_js_1 = require("./sinks.js");
19
+ const reporting_sink_http_error_js_1 = require("./internal/reporting-sink-http-error.js");
20
+ const reporting_containment_js_1 = require("./reporting-containment.js");
17
21
  const load_engine_v2_js_1 = require("./load-engine-v2.js");
18
22
  const iteration_observations_js_1 = require("./iteration-observations.js");
19
23
  const iteration_observation_diagnostics_js_1 = require("./iteration-observation-diagnostics.js");
20
24
  const sink_retry_policy_js_1 = require("./sink-retry-policy.js");
21
25
  const LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS = Symbol("loadstrike.load-engine-v2.scheduler-distributions");
22
26
  const PORTAL_RUN_TOKEN_PROVIDER = Symbol.for("loadstrike.internal.portal-run-token-provider");
27
+ const REPORTING_INTERVAL_SECONDS = Symbol.for("loadstrike.internal.reporting-interval-seconds");
23
28
  exports.LoadStrikeNodeType = {
24
29
  SingleNode: "SingleNode",
25
30
  Coordinator: "Coordinator",
@@ -291,6 +296,28 @@ class MeasurementAccumulator {
291
296
  moreOrEq1200: left.moreOrEq1200 + right.moreOrEq1200
292
297
  }, allRequestCount, durationMs);
293
298
  }
299
+ /**
300
+ * Projects only the cumulative fields used by the private HTML-report
301
+ * history. Legacy distributions deliberately omit latency so this cadence
302
+ * path never scans their retained per-request arrays.
303
+ */
304
+ buildReportHistoryMeasurement() {
305
+ return projectNativeReportHistoryMeasurement(this.count, this.allBytes, this.useHistogram ? this.latencyHistogram : undefined);
306
+ }
307
+ /**
308
+ * Combines only bounded native latency state plus exact counters. It does
309
+ * not materialize status rows or any public measurement DTO.
310
+ */
311
+ buildCombinedReportHistoryMeasurement(other) {
312
+ const count = this.count + other.count;
313
+ const bytes = this.allBytes + other.allBytes;
314
+ if (!this.useHistogram || !other.useHistogram) {
315
+ return projectNativeReportHistoryMeasurement(count, bytes);
316
+ }
317
+ const latency = this.latencyHistogram.clone();
318
+ latency.merge(other.latencyHistogram);
319
+ return projectNativeReportHistoryMeasurement(count, bytes, latency);
320
+ }
294
321
  histogramSnapshot() {
295
322
  return {
296
323
  count: this.count,
@@ -304,6 +331,24 @@ class MeasurementAccumulator {
304
331
  };
305
332
  }
306
333
  }
334
+ function projectNativeReportHistoryMeasurement(count, bytes, latency) {
335
+ const measurement = {
336
+ count,
337
+ bytes,
338
+ approximate: latency?.maxRelativeError !== undefined
339
+ && latency.maxRelativeError > 0
340
+ };
341
+ if (!latency || latency.count === 0n) {
342
+ return measurement;
343
+ }
344
+ return {
345
+ ...measurement,
346
+ percent50Ms: Number(latency.percentile(0.5)) / 1000,
347
+ percent75Ms: Number(latency.percentile(0.75)) / 1000,
348
+ percent95Ms: Number(latency.percentile(0.95)) / 1000,
349
+ percent99Ms: Number(latency.percentile(0.99)) / 1000
350
+ };
351
+ }
307
352
  function buildHistogramMeasurement(snapshot, allRequestCount, durationMs) {
308
353
  const count = snapshot.count;
309
354
  const totalDurationMs = Math.max(durationMs, 0);
@@ -642,6 +687,21 @@ class ScenarioStatsAccumulator {
642
687
  step.record(reply, observedLatencyMs);
643
688
  return step.sortIndex;
644
689
  }
690
+ /**
691
+ * Captures the bounded, private history view without building scenario
692
+ * steps, status rows, plugins, aliases, or any other public result shape.
693
+ */
694
+ buildReportHistorySnapshot() {
695
+ const ok = this.ok.buildReportHistoryMeasurement();
696
+ const failed = this.fail.buildReportHistoryMeasurement();
697
+ return {
698
+ scenarioName: this.scenarioName,
699
+ sortIndex: this.sortIndex,
700
+ all: this.ok.buildCombinedReportHistoryMeasurement(this.fail),
701
+ ...(ok.count > 0 ? { ok } : {}),
702
+ ...(failed.count > 0 ? { failed } : {})
703
+ };
704
+ }
645
705
  /**
646
706
  * Builds the configured payload or helper object.
647
707
  * Use this when all builder inputs are ready to be materialized.
@@ -690,6 +750,15 @@ class ScenarioStatsAccumulator {
690
750
  return attachScenarioStatsAliases(scenario);
691
751
  }
692
752
  }
753
+ function isRetryableReportingSinkError(sink, error) {
754
+ if (!(sink instanceof sinks_js_1.PrometheusRemoteWriteReportingSink)) {
755
+ return true;
756
+ }
757
+ if (!(error instanceof reporting_sink_http_error_js_1.ReportingSinkHttpError)) {
758
+ return true;
759
+ }
760
+ return error.status === 429 || error.status < 400 || error.status >= 500;
761
+ }
693
762
  class LoadStrikeResponse {
694
763
  /**
695
764
  * Creates a successful reply.
@@ -3438,6 +3507,8 @@ class LoadStrikeRunner {
3438
3507
  return this.buildContext();
3439
3508
  }
3440
3509
  async run(args = []) {
3510
+ (0, reporting_containment_js_1.assertNoUnsupportedReportingSinkGraph)(this.options.reportingSinks ?? []);
3511
+ assertNoUnsupportedNativeTrackingScenarios(this.scenarios);
3441
3512
  if (this.contextConfigurators.length) {
3442
3513
  return new LoadStrikeRunner(this.scenarios, this.buildContext().toRunnerOptions(), [], this.internalOptions).run(args);
3443
3514
  }
@@ -3449,7 +3520,7 @@ class LoadStrikeRunner {
3449
3520
  const scenarioAccumulators = new Map();
3450
3521
  const scenarioDurationsMs = new Map();
3451
3522
  const stepStats = new Map();
3452
- const sinks = (this.options.reportingSinks ?? []).map((sink) => (0, sinks_js_1.cloneReportingSinkForRun)(sink));
3523
+ const sinks = (this.options.reportingSinks ?? []).map((sink) => (0, sinks_js_1.cloneReportingSinkForRun)(sink, this.options.infraConfig ?? {}));
3453
3524
  const sinkStates = sinks.map((sink, index) => ({
3454
3525
  sink,
3455
3526
  disabled: false,
@@ -3509,6 +3580,58 @@ class LoadStrikeRunner {
3509
3580
  licenseSession = await licenseClient.acquireLicenseLease(licensePayload);
3510
3581
  const loggerSetup = createLoggerSetup(this.options.loggerConfig, this.options.minimumLogLevel, this.options, testInfo, nodeInfo);
3511
3582
  const runLogger = loggerSetup.logger;
3583
+ const htmlReportHistoryRequested = (this.options.reportsEnabled ?? true)
3584
+ && normalizeReportFormats(this.options.reportFormats ?? ["html", "txt", "csv", "md"]).includes("html");
3585
+ const distributedReportHistory = isDistributedReportHistoryExecution(clusterMode, this.options);
3586
+ let localReportInput = htmlReportHistoryRequested
3587
+ && distributedReportHistory
3588
+ ? (0, local_report_input_js_1.distributedLocalReportInput)()
3589
+ : (0, local_report_input_js_1.emptyLocalReportInput)();
3590
+ let reportHistoryCollector;
3591
+ let reportHistoryLifecycle;
3592
+ if (htmlReportHistoryRequested && !distributedReportHistory) {
3593
+ const warnReportHistoryFailure = (category, error) => {
3594
+ const exceptionClasses = error === undefined
3595
+ ? ""
3596
+ : `; exception_classes=${(0, report_history_js_1.sanitizedExceptionClassChain)(error)}`;
3597
+ try {
3598
+ runLogger.warn(`LoadStrike report history unavailable: ${category}${exceptionClasses}.`);
3599
+ }
3600
+ catch {
3601
+ // Report-only diagnostics are best-effort.
3602
+ }
3603
+ };
3604
+ reportHistoryCollector = new report_history_js_1.ReportHistoryCollector({
3605
+ scenarioCount: selectedScenarios.length,
3606
+ onCaptureError: (error) => warnReportHistoryFailure("capture_failure", error)
3607
+ });
3608
+ if (!reportHistoryCollector.available) {
3609
+ warnReportHistoryFailure("budget_pressure");
3610
+ }
3611
+ const snapshotFactory = () => Array.from(scenarioAccumulators.values())
3612
+ .map((value) => value.buildReportHistorySnapshot())
3613
+ .sort((left, right) => (left.sortIndex ?? 0) - (right.sortIndex ?? 0)
3614
+ || left.scenarioName.localeCompare(right.scenarioName));
3615
+ const reportHistoryWorker = new report_history_js_1.ReportHistoryWorker({
3616
+ collector: reportHistoryCollector,
3617
+ cadenceSeconds: resolvedReportHistoryCadenceSeconds(this.options.reportingIntervalSeconds ?? 5),
3618
+ snapshotFactory,
3619
+ onFailure: (category, exceptionClasses) => {
3620
+ try {
3621
+ runLogger.warn(`LoadStrike report history unavailable: ${category}; exception_classes=${exceptionClasses}.`);
3622
+ }
3623
+ catch {
3624
+ // Report-only diagnostics are best-effort.
3625
+ }
3626
+ }
3627
+ });
3628
+ reportHistoryLifecycle = new report_history_js_1.ReportHistoryLifecycleCoordinator({
3629
+ scenarioCount: selectedScenarios.length,
3630
+ start: () => reportHistoryWorker.start(),
3631
+ stopAndFinalize: () => reportHistoryWorker.stopAndFinalize(),
3632
+ stopWithoutFinalizing: () => reportHistoryWorker.stopWithoutFinalizing()
3633
+ });
3634
+ }
3512
3635
  const scenarioStartInfos = selectedScenarios.map((scenario, index) => {
3513
3636
  const startInfo = {
3514
3637
  scenarioName: scenario.name,
@@ -3522,6 +3645,12 @@ class LoadStrikeRunner {
3522
3645
  testInfo,
3523
3646
  getNodeInfo: () => attachNodeInfoAliases({ ...nodeInfo })
3524
3647
  };
3648
+ Object.defineProperty(baseContext, REPORTING_INTERVAL_SECONDS, {
3649
+ value: this.options.reportingIntervalSeconds ?? 5,
3650
+ enumerable: false,
3651
+ configurable: false,
3652
+ writable: false
3653
+ });
3525
3654
  attachBaseContextAliases(baseContext);
3526
3655
  const sinkSession = {
3527
3656
  startedUtc: createdUtc,
@@ -3576,6 +3705,7 @@ class LoadStrikeRunner {
3576
3705
  name: state.name,
3577
3706
  iterationObservationPortalSink: Boolean(state.sink.iterationObservationPortalSink),
3578
3707
  iterationObservationShapeLimited: Boolean(state.sink.iterationObservationShapeLimited),
3708
+ retryableErrorClassifier: (error) => isRetryableReportingSinkError(state.sink, error),
3579
3709
  saveIterationBatch: resolveSinkSaveIterationBatch(state.sink),
3580
3710
  completeIterationObservationStream: resolveSinkCompleteIterationObservationStream(state.sink)
3581
3711
  }));
@@ -3618,7 +3748,7 @@ class LoadStrikeRunner {
3618
3748
  realtimeInFlight = false;
3619
3749
  }
3620
3750
  };
3621
- const reportingIntervalMs = Math.max(Math.trunc((this.options.reportingIntervalSeconds ?? 5) * 1000), 1);
3751
+ const reportingIntervalMs = boundedReportingIntervalMs(this.options.reportingIntervalSeconds ?? 5);
3622
3752
  const triggerRealtimeSnapshot = () => {
3623
3753
  if (realtimeCurrent) {
3624
3754
  return;
@@ -3705,6 +3835,7 @@ class LoadStrikeRunner {
3705
3835
  iterationObservationRunId,
3706
3836
  iterationObservationResultOwnerId,
3707
3837
  iterationObservationProcessGroup,
3838
+ reportHistoryLifecycle,
3708
3839
  executeScenarioInvocation: (targetScenario, context, operation) => this.executeScenarioInvocation(targetScenario, context, operation),
3709
3840
  invokeBeforeScenario: (runtimePolicies, scenarioName) => this.invokeBeforeScenario(runtimePolicies, scenarioName, policyErrors, runtimePolicyErrorMode),
3710
3841
  invokeAfterScenario: (runtimePolicies, scenarioName, stats) => this.invokeAfterScenario(runtimePolicies, scenarioName, stats, policyErrors, runtimePolicyErrorMode),
@@ -3792,9 +3923,14 @@ class LoadStrikeRunner {
3792
3923
  ...iterationObservationReporter.buildWarnings()
3793
3924
  ];
3794
3925
  result.pluginsData = mergePluginData(result.pluginsData, await this.collectPluginData(plugins, attachRunResultAliases(result), pluginLifecycleErrors));
3926
+ if (reportHistoryCollector) {
3927
+ localReportInput = {
3928
+ history: reportHistoryCollector.toProjection()
3929
+ };
3930
+ }
3795
3931
  const finalizedResult = attachRunResultAliases(result);
3796
3932
  finalizedResult.logFiles = mergeStringArrays(finalizedResult.logFiles, loggerSetup.logFiles);
3797
- finalizedResult.reportFiles = this.writeReports(finalizedResult);
3933
+ finalizedResult.reportFiles = this.writeReports(finalizedResult, localReportInput);
3798
3934
  finalizedResult.disabledSinks = sinkStates.filter((x) => x.disabled).map((x) => x.name);
3799
3935
  await this.emitRunResult(sinkStates, finalizedResult, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
3800
3936
  await this.stopSinks(sinkStates, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
@@ -3805,6 +3941,7 @@ class LoadStrikeRunner {
3805
3941
  return finalizedResult;
3806
3942
  }
3807
3943
  finally {
3944
+ reportHistoryLifecycle?.stopWithoutFinalizing();
3808
3945
  await stopRealtimeReporting();
3809
3946
  if (!iterationObservationsFinalized) {
3810
3947
  await iterationObservationReporter.sealAndDrain().catch(() => { });
@@ -4203,7 +4340,9 @@ class LoadStrikeRunner {
4203
4340
  return;
4204
4341
  }
4205
4342
  catch (error) {
4206
- const exhausted = attempts >= maximumAttempts;
4343
+ const retryable = isRetryableReportingSinkError(state.sink, error);
4344
+ const exhausted = attempts >= maximumAttempts || !retryable;
4345
+ const reportedMaximumAttempts = retryable ? maximumAttempts : attempts;
4207
4346
  const nextDelayMs = exhausted ? 0 : (0, sink_retry_policy_js_1.sinkRetryDelayMs)(backoffMs, attempts);
4208
4347
  (0, iteration_observation_diagnostics_js_1.logIterationObservationFailure)(logger, exhausted ? "error" : "warn", {
4209
4348
  sinkName: state.name,
@@ -4213,7 +4352,7 @@ class LoadStrikeRunner {
4213
4352
  resultOwnerId: "",
4214
4353
  observationCount: 0,
4215
4354
  attempt: attempts,
4216
- maximumAttempts,
4355
+ maximumAttempts: reportedMaximumAttempts,
4217
4356
  nextDelayMs
4218
4357
  }, error);
4219
4358
  if (exhausted) {
@@ -4282,7 +4421,7 @@ class LoadStrikeRunner {
4282
4421
  message: String(error ?? "runtime policy callback failed")
4283
4422
  });
4284
4423
  }
4285
- writeReports(result) {
4424
+ writeReports(result, localReportInput = (0, local_report_input_js_1.emptyLocalReportInput)()) {
4286
4425
  const reportsEnabled = this.options.reportsEnabled ?? true;
4287
4426
  if (!reportsEnabled) {
4288
4427
  return [];
@@ -4307,7 +4446,7 @@ class LoadStrikeRunner {
4307
4446
  (0, node_fs_1.writeFileSync)(path, (0, reporting_js_1.buildDotnetMarkdownReport)(nodeStats), "utf8");
4308
4447
  }
4309
4448
  else if (format === "html") {
4310
- (0, node_fs_1.writeFileSync)(path, (0, reporting_js_1.buildDotnetHtmlReport)(nodeStats), "utf8");
4449
+ (0, node_fs_1.writeFileSync)(path, (0, reporting_js_1.buildDotnetHtmlReport)(nodeStats, localReportInput), "utf8");
4311
4450
  }
4312
4451
  written.push(path);
4313
4452
  }
@@ -4341,6 +4480,19 @@ function hasPluginRows(value) {
4341
4480
  }
4342
4481
  return value.tables.some((table) => Array.isArray(table.rows) && table.rows.length > 0);
4343
4482
  }
4483
+ function boundedReportingIntervalMs(seconds) {
4484
+ if (!Number.isFinite(seconds) || seconds <= 0) {
4485
+ return 1;
4486
+ }
4487
+ const milliseconds = seconds * 1000;
4488
+ if (!Number.isFinite(milliseconds) || milliseconds >= 2147483647) {
4489
+ return 2147483647;
4490
+ }
4491
+ return Math.max(Math.trunc(milliseconds), 1);
4492
+ }
4493
+ function resolvedReportHistoryCadenceSeconds(seconds) {
4494
+ return boundedReportingIntervalMs(seconds) / 1000;
4495
+ }
4344
4496
  async function executeV2FixedArrivals(args) {
4345
4497
  const { rate, intervalNs, totalArrivals, budget, cancellationToken, shouldStopNow, nextInstanceInfo, runBombingInvocation, logger, scenarioName, deadlineOffsetsNs, tolerancesNs, ownedOrdinals, shardIndex = 0, shardCount = 1, telemetry, segment } = args;
4346
4498
  const segmentStartNs = process.hrtime.bigint();
@@ -4477,7 +4629,15 @@ function maxBigInt(left, right) {
4477
4629
  return left > right ? left : right;
4478
4630
  }
4479
4631
  async function executeScenarioRuntime(args) {
4480
- const { scenario, scenarioIndex, scenarioCount, options, logger, nodeInfo, testInfo, policies, restartIterationMaxAttempts, allRegisteredMetrics, scenarioRuntimes, stepRuntimes, scenarioAccumulators, scenarioDurationsMs, stopTestState, testAbortController, loadEngineV2Budget, loadEngineV2Telemetry, iterationObservationReporter, iterationObservationRunId = testInfo.sessionId, iterationObservationResultOwnerId = "", iterationObservationProcessGroup = 0, executeScenarioInvocation, invokeBeforeScenario, invokeAfterScenario, invokeBeforeStep, invokeAfterStep } = args;
4632
+ const { scenario, scenarioIndex, scenarioCount, options, logger, nodeInfo, testInfo, policies, restartIterationMaxAttempts, allRegisteredMetrics, scenarioRuntimes, stepRuntimes, scenarioAccumulators, scenarioDurationsMs, stopTestState, testAbortController, loadEngineV2Budget, loadEngineV2Telemetry, iterationObservationReporter, iterationObservationRunId = testInfo.sessionId, iterationObservationResultOwnerId = "", iterationObservationProcessGroup = 0, reportHistoryLifecycle, executeScenarioInvocation, invokeBeforeScenario, invokeAfterScenario, invokeBeforeStep, invokeAfterStep } = args;
4633
+ let reportHistoryExecutionEnded = false;
4634
+ const endReportHistoryExecution = async () => {
4635
+ if (reportHistoryExecutionEnded) {
4636
+ return;
4637
+ }
4638
+ reportHistoryExecutionEnded = true;
4639
+ await reportHistoryLifecycle?.scenarioExecutionEnded();
4640
+ };
4481
4641
  const scenarioStartedMs = Date.now();
4482
4642
  const scenarioContextData = {};
4483
4643
  const registeredMetrics = [];
@@ -5298,6 +5458,7 @@ async function executeScenarioRuntime(args) {
5298
5458
  })));
5299
5459
  await invokeBeforeScenario(policies, scenario.name);
5300
5460
  await runWarmUpAsync();
5461
+ reportHistoryLifecycle?.scenarioBombingStarted();
5301
5462
  accumulator.setCurrentOperation("Bombing");
5302
5463
  const simulations = scenario.getSimulations();
5303
5464
  if (!simulations.length) {
@@ -5331,6 +5492,7 @@ async function executeScenarioRuntime(args) {
5331
5492
  }
5332
5493
  }
5333
5494
  accumulator.setCurrentOperation(stopScenario || stopTestState.value ? "Stop" : "Complete");
5495
+ await endReportHistoryExecution();
5334
5496
  await invokeAfterScenario(policies, scenario.name, runtime);
5335
5497
  }
5336
5498
  catch (error) {
@@ -5347,6 +5509,7 @@ async function executeScenarioRuntime(args) {
5347
5509
  }
5348
5510
  }
5349
5511
  finally {
5512
+ await endReportHistoryExecution();
5350
5513
  scenarioDurationsMs.set(scenario.name, Math.max(Date.now() - scenarioStartedMs, 0));
5351
5514
  try {
5352
5515
  await scenario.invokeClean({
@@ -7280,6 +7443,11 @@ function resolveClusterExecutionMode(options) {
7280
7443
  }
7281
7444
  return "single";
7282
7445
  }
7446
+ function isDistributedReportHistoryExecution(clusterMode, options) {
7447
+ return clusterMode !== "single"
7448
+ || options.loadEngineV2SegmentLifecycleOverride !== undefined
7449
+ || Math.max(Math.trunc(options.clusterShardCount ?? 1), 1) > 1;
7450
+ }
7283
7451
  function buildEmptyNodeStats(args) {
7284
7452
  return attachNodeStatsAliases({
7285
7453
  startedUtc: args.startedUtc,
@@ -8751,6 +8919,7 @@ class ManagedScenarioTrackingRuntime {
8751
8919
  }
8752
8920
  async dispose() {
8753
8921
  this.shutdown = true;
8922
+ this.interruptEndpointAdapters();
8754
8923
  this.rejectOutstandingWaiters();
8755
8924
  await Promise.all([
8756
8925
  this.sourceLoop,
@@ -8816,6 +8985,7 @@ class ManagedScenarioTrackingRuntime {
8816
8985
  failureSeen = failureSeen || this.isFailedObservationOutcome(outcome);
8817
8986
  }
8818
8987
  this.shutdown = true;
8988
+ this.interruptEndpointAdapters();
8819
8989
  await Promise.all([
8820
8990
  this.sourceLoop,
8821
8991
  this.destinationLoop,
@@ -8832,6 +9002,20 @@ class ManagedScenarioTrackingRuntime {
8832
9002
  ? LoadStrikeResponse.fail("tracking_failures", "One or more observed source or destination events did not correlate successfully.", 0)
8833
9003
  : LoadStrikeResponse.ok("observed");
8834
9004
  }
9005
+ interruptEndpointAdapters() {
9006
+ try {
9007
+ this.sourceAdapter.interrupt?.();
9008
+ }
9009
+ catch {
9010
+ // Shutdown remains best-effort and still disposes every adapter below.
9011
+ }
9012
+ try {
9013
+ this.destinationAdapter?.interrupt?.();
9014
+ }
9015
+ catch {
9016
+ // Shutdown remains best-effort and still disposes every adapter below.
9017
+ }
9018
+ }
8835
9019
  async consumeSourceLoop() {
8836
9020
  while (!this.shutdown) {
8837
9021
  try {
@@ -9423,6 +9607,20 @@ function mapRuntimeTrackingEndpointSpec(spec, useLoadStrikeTraceIdHeader = false
9423
9607
  azureEventHubs: asTrackingRecord(pickTrackingValue(spec, "AzureEventHubs", "azureEventHubs")),
9424
9608
  sqs: asTrackingRecord(pickTrackingValue(spec, "Sqs", "sqs")),
9425
9609
  pushDiffusion: asTrackingRecord(pickTrackingValue(spec, "PushDiffusion", "pushDiffusion")),
9610
+ grpc: mapRuntimeEndpointProtocolOptions(spec, ["Grpc", "grpc"], [
9611
+ "Target", "target", "ServiceName", "serviceName", "MethodName", "methodName",
9612
+ "MethodType", "methodType", "Deadline", "deadline", "DeadlineSeconds", "deadlineSeconds",
9613
+ "DeadlineMs", "deadlineMs", "Metadata", "metadata", "Produce", "produce", "Consume", "consume",
9614
+ "ProduceAsync", "produceAsync", "ConsumeAsync", "consumeAsync", "ConnectionMetadata", "connectionMetadata",
9615
+ "NativeClient", "nativeClient"
9616
+ ]),
9617
+ webSocket: mapRuntimeEndpointProtocolOptions(spec, ["WebSocket", "webSocket"], [
9618
+ "Url", "url", "Subprotocols", "subprotocols", "ConnectTimeout", "connectTimeout",
9619
+ "ConnectTimeoutSeconds", "connectTimeoutSeconds", "ConnectTimeoutMs", "connectTimeoutMs",
9620
+ "CloseTimeout", "closeTimeout", "CloseTimeoutSeconds", "closeTimeoutSeconds", "CloseTimeoutMs", "closeTimeoutMs",
9621
+ "Produce", "produce", "Consume", "consume", "ProduceAsync", "produceAsync", "ConsumeAsync", "consumeAsync",
9622
+ "ConnectionMetadata", "connectionMetadata", "NativeClient", "nativeClient"
9623
+ ]),
9426
9624
  delegate: typeof delegateProduce === "function"
9427
9625
  || typeof delegateConsume === "function"
9428
9626
  || typeof delegateProduceAsync === "function"
@@ -9445,6 +9643,19 @@ function mapRuntimeTrackingEndpointSpec(spec, useLoadStrikeTraceIdHeader = false
9445
9643
  : undefined
9446
9644
  };
9447
9645
  }
9646
+ function mapRuntimeEndpointProtocolOptions(spec, nestedKeys, flatKeys) {
9647
+ const nested = asTrackingRecord(pickTrackingValue(spec, ...nestedKeys));
9648
+ if (Object.keys(nested).length > 0) {
9649
+ return nested;
9650
+ }
9651
+ const options = {};
9652
+ for (const key of flatKeys) {
9653
+ if (Object.prototype.hasOwnProperty.call(spec, key)) {
9654
+ options[key] = pickTrackingValue(spec, key);
9655
+ }
9656
+ }
9657
+ return options;
9658
+ }
9448
9659
  function normalizeRuntimeTrackingPayload(payload, endpoint, index) {
9449
9660
  const normalized = {
9450
9661
  headers: {
@@ -10208,6 +10419,20 @@ function assertNoDisableLicenseEnforcementOption(value, source) {
10208
10419
  }
10209
10420
  }
10210
10421
  }
10422
+ function assertNoUnsupportedNativeTrackingScenarios(scenarios) {
10423
+ for (const scenario of scenarios) {
10424
+ const tracking = scenario.getTrackingConfiguration();
10425
+ if (!tracking) {
10426
+ continue;
10427
+ }
10428
+ for (const field of ["Source", "Destination"]) {
10429
+ const endpoint = asTrackingRecord(pickTrackingValue(tracking, field, field.toLowerCase()));
10430
+ if (Object.keys(endpoint).length > 0) {
10431
+ (0, transports_js_1.validateNativeEndpointExecutionSupport)(endpoint);
10432
+ }
10433
+ }
10434
+ }
10435
+ }
10211
10436
  function normalizeRunContextCollectionShapes(values) {
10212
10437
  assertNoDisableLicenseEnforcementOption(values, "LoadStrikeContext");
10213
10438
  const normalized = {
@@ -11111,6 +11336,7 @@ function buildGroupedCorrelationRows(rows) {
11111
11336
  LatencyMinMs: latencySamples.length ? formatOptionalLatency(latencySamples[0]) : "",
11112
11337
  LatencyMeanMs: latencySamples.length ? formatOptionalLatency(latencySamples.reduce((sum, value) => sum + value, 0) / latencySamples.length) : "",
11113
11338
  LatencyP50Ms: latencySamples.length ? formatOptionalLatency(percentile(latencySamples, 0.5)) : "",
11339
+ LatencyP75Ms: latencySamples.length ? formatOptionalLatency(percentile(latencySamples, 0.75)) : "",
11114
11340
  LatencyP80Ms: latencySamples.length ? formatOptionalLatency(percentile(latencySamples, 0.8)) : "",
11115
11341
  LatencyP85Ms: latencySamples.length ? formatOptionalLatency(percentile(latencySamples, 0.85)) : "",
11116
11342
  LatencyP90Ms: latencySamples.length ? formatOptionalLatency(percentile(latencySamples, 0.9)) : "",
@@ -11323,6 +11549,8 @@ exports.__loadstrikeTestExports = {
11323
11549
  buildThresholdCheckExpression,
11324
11550
  buildTrackingLeaseKey,
11325
11551
  buildTrackingRunNamespace,
11552
+ boundedReportingIntervalMs,
11553
+ resolvedReportHistoryCadenceSeconds,
11326
11554
  clusterNodeResultToNodeStats,
11327
11555
  combineAbortSignals,
11328
11556
  computeScenarioRequestCount,
@@ -11342,6 +11570,7 @@ exports.__loadstrikeTestExports = {
11342
11570
  formatUtcReportTimestamp,
11343
11571
  hasPluginRows,
11344
11572
  inferRuntimeLegacyHttpResponseSource,
11573
+ isDistributedReportHistoryExecution,
11345
11574
  isComparisonFailed,
11346
11575
  loadJsonObject,
11347
11576
  logLevelOrder,