@loadstrike/loadstrike-sdk 1.0.30401 → 1.0.31601
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/README.md +6 -0
- package/dist/cjs/iteration-observation-diagnostics.js +513 -0
- package/dist/cjs/iteration-observations.js +195 -22
- package/dist/cjs/local-report-input.js +21 -0
- package/dist/cjs/local.js +55 -10
- package/dist/cjs/report-history.js +421 -0
- package/dist/cjs/reporting-svg.js +116 -0
- package/dist/cjs/reporting.js +436 -140
- package/dist/cjs/runtime.js +313 -85
- package/dist/cjs/sink-retry-policy.js +52 -0
- package/dist/cjs/sinks.js +112 -9
- package/dist/cjs/transports.js +78 -25
- package/dist/esm/iteration-observation-diagnostics.js +508 -0
- package/dist/esm/iteration-observations.js +195 -22
- package/dist/esm/local-report-input.js +17 -0
- package/dist/esm/local.js +55 -10
- package/dist/esm/report-history.js +413 -0
- package/dist/esm/reporting-svg.js +113 -0
- package/dist/esm/reporting.js +436 -140
- package/dist/esm/runtime.js +313 -85
- package/dist/esm/sink-retry-policy.js +44 -0
- package/dist/esm/sinks.js +112 -9
- package/dist/esm/transports.js +78 -25
- package/dist/types/iteration-observation-diagnostics.d.ts +21 -0
- package/dist/types/iteration-observations.d.ts +5 -0
- package/dist/types/local-report-input.d.ts +6 -0
- package/dist/types/report-history.d.ts +124 -0
- package/dist/types/reporting-svg.d.ts +2 -0
- package/dist/types/reporting.d.ts +6 -3
- package/dist/types/runtime.d.ts +25 -0
- package/dist/types/sink-retry-policy.d.ts +9 -0
- package/dist/types/sinks.d.ts +6 -0
- package/dist/types/transports.d.ts +4 -0
- package/package.json +2 -2
package/dist/esm/runtime.js
CHANGED
|
@@ -7,9 +7,13 @@ import { DistributedClusterAgent, DistributedClusterCoordinator, buildLoadEngine
|
|
|
7
7
|
import { CorrelationStoreConfiguration, CrossPlatformTrackingRuntime, RedisCorrelationStore, RedisCorrelationStoreOptions, TrackingFieldSelector } from "./correlation.js";
|
|
8
8
|
import { EndpointAdapterFactory, LOADSTRIKE_TRACE_ID_TRACKING_FIELD } from "./transports.js";
|
|
9
9
|
import { buildDotnetCsvReport, buildDotnetHtmlReport, buildDotnetMarkdownReport, buildDotnetTxtReport } from "./reporting.js";
|
|
10
|
+
import { ReportHistoryCollector, ReportHistoryLifecycleCoordinator, ReportHistoryWorker, sanitizedExceptionClassChain } from "./report-history.js";
|
|
11
|
+
import { distributedLocalReportInput, emptyLocalReportInput } from "./local-report-input.js";
|
|
10
12
|
import { PortalReportingSink, cloneReportingSinkForRun } from "./sinks.js";
|
|
11
13
|
import { LoadEngineV2ExecutionBudget, buildLoadEngineV2TrafficMixSeedId, LoadStrikeHistogramV1, parseLoadEngineV2HistogramArtifact, serializeLoadEngineV2HistogramArtifact, classifyLoadEngineV2Arrival, loadEngineV2FixedArrivalCount, loadEngineV2FixedDeadlineNs, loadEngineV2LatenessToleranceNs, loadEngineV2TrafficMixLaneUnitCount, loadEngineV2TrafficMixOwnedUnits, planRampingInjectionDeadlines, planRampingConstantDeadlines, planRandomInjectionDeadlines, fnv1a32 } from "./load-engine-v2.js";
|
|
12
14
|
import { DEFAULT_ITERATION_OBSERVATION_SETTINGS, IterationObservationReporter, createIterationObservation, createIterationStepObservation, utcNowNs, validateIterationObservationSettings } from "./iteration-observations.js";
|
|
15
|
+
import { logIterationObservationFailure, logIterationObservationRecovery } from "./iteration-observation-diagnostics.js";
|
|
16
|
+
import { normalizeSinkRetryBackoffMs, normalizeSinkRetryCount, sinkRetryDelayMs, waitForSinkRetryDelay } from "./sink-retry-policy.js";
|
|
13
17
|
const LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS = Symbol("loadstrike.load-engine-v2.scheduler-distributions");
|
|
14
18
|
const PORTAL_RUN_TOKEN_PROVIDER = Symbol.for("loadstrike.internal.portal-run-token-provider");
|
|
15
19
|
export const LoadStrikeNodeType = {
|
|
@@ -281,6 +285,28 @@ class MeasurementAccumulator {
|
|
|
281
285
|
moreOrEq1200: left.moreOrEq1200 + right.moreOrEq1200
|
|
282
286
|
}, allRequestCount, durationMs);
|
|
283
287
|
}
|
|
288
|
+
/**
|
|
289
|
+
* Projects only the cumulative fields used by the private HTML-report
|
|
290
|
+
* history. Legacy distributions deliberately omit latency so this cadence
|
|
291
|
+
* path never scans their retained per-request arrays.
|
|
292
|
+
*/
|
|
293
|
+
buildReportHistoryMeasurement() {
|
|
294
|
+
return projectNativeReportHistoryMeasurement(this.count, this.allBytes, this.useHistogram ? this.latencyHistogram : undefined);
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Combines only bounded native latency state plus exact counters. It does
|
|
298
|
+
* not materialize status rows or any public measurement DTO.
|
|
299
|
+
*/
|
|
300
|
+
buildCombinedReportHistoryMeasurement(other) {
|
|
301
|
+
const count = this.count + other.count;
|
|
302
|
+
const bytes = this.allBytes + other.allBytes;
|
|
303
|
+
if (!this.useHistogram || !other.useHistogram) {
|
|
304
|
+
return projectNativeReportHistoryMeasurement(count, bytes);
|
|
305
|
+
}
|
|
306
|
+
const latency = this.latencyHistogram.clone();
|
|
307
|
+
latency.merge(other.latencyHistogram);
|
|
308
|
+
return projectNativeReportHistoryMeasurement(count, bytes, latency);
|
|
309
|
+
}
|
|
284
310
|
histogramSnapshot() {
|
|
285
311
|
return {
|
|
286
312
|
count: this.count,
|
|
@@ -294,6 +320,24 @@ class MeasurementAccumulator {
|
|
|
294
320
|
};
|
|
295
321
|
}
|
|
296
322
|
}
|
|
323
|
+
function projectNativeReportHistoryMeasurement(count, bytes, latency) {
|
|
324
|
+
const measurement = {
|
|
325
|
+
count,
|
|
326
|
+
bytes,
|
|
327
|
+
approximate: latency?.maxRelativeError !== undefined
|
|
328
|
+
&& latency.maxRelativeError > 0
|
|
329
|
+
};
|
|
330
|
+
if (!latency || latency.count === 0n) {
|
|
331
|
+
return measurement;
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
...measurement,
|
|
335
|
+
percent50Ms: Number(latency.percentile(0.5)) / 1000,
|
|
336
|
+
percent75Ms: Number(latency.percentile(0.75)) / 1000,
|
|
337
|
+
percent95Ms: Number(latency.percentile(0.95)) / 1000,
|
|
338
|
+
percent99Ms: Number(latency.percentile(0.99)) / 1000
|
|
339
|
+
};
|
|
340
|
+
}
|
|
297
341
|
function buildHistogramMeasurement(snapshot, allRequestCount, durationMs) {
|
|
298
342
|
const count = snapshot.count;
|
|
299
343
|
const totalDurationMs = Math.max(durationMs, 0);
|
|
@@ -632,6 +676,21 @@ class ScenarioStatsAccumulator {
|
|
|
632
676
|
step.record(reply, observedLatencyMs);
|
|
633
677
|
return step.sortIndex;
|
|
634
678
|
}
|
|
679
|
+
/**
|
|
680
|
+
* Captures the bounded, private history view without building scenario
|
|
681
|
+
* steps, status rows, plugins, aliases, or any other public result shape.
|
|
682
|
+
*/
|
|
683
|
+
buildReportHistorySnapshot() {
|
|
684
|
+
const ok = this.ok.buildReportHistoryMeasurement();
|
|
685
|
+
const failed = this.fail.buildReportHistoryMeasurement();
|
|
686
|
+
return {
|
|
687
|
+
scenarioName: this.scenarioName,
|
|
688
|
+
sortIndex: this.sortIndex,
|
|
689
|
+
all: this.ok.buildCombinedReportHistoryMeasurement(this.fail),
|
|
690
|
+
...(ok.count > 0 ? { ok } : {}),
|
|
691
|
+
...(failed.count > 0 ? { failed } : {})
|
|
692
|
+
};
|
|
693
|
+
}
|
|
635
694
|
/**
|
|
636
695
|
* Builds the configured payload or helper object.
|
|
637
696
|
* Use this when all builder inputs are ready to be materialized.
|
|
@@ -3427,8 +3486,8 @@ export class LoadStrikeRunner {
|
|
|
3427
3486
|
}));
|
|
3428
3487
|
const sinkErrors = [];
|
|
3429
3488
|
const policyErrors = [];
|
|
3430
|
-
const sinkRetryCount =
|
|
3431
|
-
const sinkRetryBackoffMs =
|
|
3489
|
+
const sinkRetryCount = normalizeSinkRetryCount(this.options.sinkRetryCount);
|
|
3490
|
+
const sinkRetryBackoffMs = normalizeSinkRetryBackoffMs(this.options.sinkRetryBackoffMs);
|
|
3432
3491
|
const policies = this.options.runtimePolicies ?? [];
|
|
3433
3492
|
const runtimePolicyErrorMode = normalizedRuntimePolicyErrorMode(this.options.runtimePolicyErrorMode);
|
|
3434
3493
|
const plugins = this.options.reportingSinks === undefined && this.options.workerPlugins === undefined &&
|
|
@@ -3479,6 +3538,58 @@ export class LoadStrikeRunner {
|
|
|
3479
3538
|
licenseSession = await licenseClient.acquireLicenseLease(licensePayload);
|
|
3480
3539
|
const loggerSetup = createLoggerSetup(this.options.loggerConfig, this.options.minimumLogLevel, this.options, testInfo, nodeInfo);
|
|
3481
3540
|
const runLogger = loggerSetup.logger;
|
|
3541
|
+
const htmlReportHistoryRequested = (this.options.reportsEnabled ?? true)
|
|
3542
|
+
&& normalizeReportFormats(this.options.reportFormats ?? ["html", "txt", "csv", "md"]).includes("html");
|
|
3543
|
+
const distributedReportHistory = isDistributedReportHistoryExecution(clusterMode, this.options);
|
|
3544
|
+
let localReportInput = htmlReportHistoryRequested
|
|
3545
|
+
&& distributedReportHistory
|
|
3546
|
+
? distributedLocalReportInput()
|
|
3547
|
+
: emptyLocalReportInput();
|
|
3548
|
+
let reportHistoryCollector;
|
|
3549
|
+
let reportHistoryLifecycle;
|
|
3550
|
+
if (htmlReportHistoryRequested && !distributedReportHistory) {
|
|
3551
|
+
const warnReportHistoryFailure = (category, error) => {
|
|
3552
|
+
const exceptionClasses = error === undefined
|
|
3553
|
+
? ""
|
|
3554
|
+
: `; exception_classes=${sanitizedExceptionClassChain(error)}`;
|
|
3555
|
+
try {
|
|
3556
|
+
runLogger.warn(`LoadStrike report history unavailable: ${category}${exceptionClasses}.`);
|
|
3557
|
+
}
|
|
3558
|
+
catch {
|
|
3559
|
+
// Report-only diagnostics are best-effort.
|
|
3560
|
+
}
|
|
3561
|
+
};
|
|
3562
|
+
reportHistoryCollector = new ReportHistoryCollector({
|
|
3563
|
+
scenarioCount: selectedScenarios.length,
|
|
3564
|
+
onCaptureError: (error) => warnReportHistoryFailure("capture_failure", error)
|
|
3565
|
+
});
|
|
3566
|
+
if (!reportHistoryCollector.available) {
|
|
3567
|
+
warnReportHistoryFailure("budget_pressure");
|
|
3568
|
+
}
|
|
3569
|
+
const snapshotFactory = () => Array.from(scenarioAccumulators.values())
|
|
3570
|
+
.map((value) => value.buildReportHistorySnapshot())
|
|
3571
|
+
.sort((left, right) => (left.sortIndex ?? 0) - (right.sortIndex ?? 0)
|
|
3572
|
+
|| left.scenarioName.localeCompare(right.scenarioName));
|
|
3573
|
+
const reportHistoryWorker = new ReportHistoryWorker({
|
|
3574
|
+
collector: reportHistoryCollector,
|
|
3575
|
+
cadenceSeconds: resolvedReportHistoryCadenceSeconds(this.options.reportingIntervalSeconds ?? 5),
|
|
3576
|
+
snapshotFactory,
|
|
3577
|
+
onFailure: (category, exceptionClasses) => {
|
|
3578
|
+
try {
|
|
3579
|
+
runLogger.warn(`LoadStrike report history unavailable: ${category}; exception_classes=${exceptionClasses}.`);
|
|
3580
|
+
}
|
|
3581
|
+
catch {
|
|
3582
|
+
// Report-only diagnostics are best-effort.
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
});
|
|
3586
|
+
reportHistoryLifecycle = new ReportHistoryLifecycleCoordinator({
|
|
3587
|
+
scenarioCount: selectedScenarios.length,
|
|
3588
|
+
start: () => reportHistoryWorker.start(),
|
|
3589
|
+
stopAndFinalize: () => reportHistoryWorker.stopAndFinalize(),
|
|
3590
|
+
stopWithoutFinalizing: () => reportHistoryWorker.stopWithoutFinalizing()
|
|
3591
|
+
});
|
|
3592
|
+
}
|
|
3482
3593
|
const scenarioStartInfos = selectedScenarios.map((scenario, index) => {
|
|
3483
3594
|
const startInfo = {
|
|
3484
3595
|
scenarioName: scenario.name,
|
|
@@ -3522,14 +3633,14 @@ export class LoadStrikeRunner {
|
|
|
3522
3633
|
await init(baseContext, this.options.infraConfig ?? {});
|
|
3523
3634
|
}
|
|
3524
3635
|
}
|
|
3525
|
-
await this.initializeSinks(sinkStates, baseContext, this.options.infraConfig ?? {}, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
|
|
3636
|
+
await this.initializeSinks(sinkStates, baseContext, this.options.infraConfig ?? {}, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
|
|
3526
3637
|
for (const plugin of plugins) {
|
|
3527
3638
|
const start = resolveWorkerPluginStart(plugin);
|
|
3528
3639
|
if (start) {
|
|
3529
3640
|
await start(sessionInfo);
|
|
3530
3641
|
}
|
|
3531
3642
|
}
|
|
3532
|
-
await this.startSinks(sinkStates, sessionInfo, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
|
|
3643
|
+
await this.startSinks(sinkStates, sessionInfo, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
|
|
3533
3644
|
const iterationObservationRunId = String(this.internalOptions.iterationObservationRunId
|
|
3534
3645
|
?? sessionInfo.portalReportingRunId
|
|
3535
3646
|
?? sessionInfo.PortalReportingRunId
|
|
@@ -3560,7 +3671,10 @@ export class LoadStrikeRunner {
|
|
|
3560
3671
|
expectedResultOwnerCount64: iterationObservationExpectedResultOwnerCount64,
|
|
3561
3672
|
processGroup: iterationObservationProcessGroup,
|
|
3562
3673
|
settings: resolveIterationObservationSettings(this.options),
|
|
3563
|
-
sinks: reporterIterationObservationSinks
|
|
3674
|
+
sinks: reporterIterationObservationSinks,
|
|
3675
|
+
sinkRetryCount,
|
|
3676
|
+
sinkRetryBackoffMs,
|
|
3677
|
+
logger: runLogger
|
|
3564
3678
|
});
|
|
3565
3679
|
const emitRealtimeSnapshot = async () => {
|
|
3566
3680
|
if (realtimeInFlight) {
|
|
@@ -3572,7 +3686,7 @@ export class LoadStrikeRunner {
|
|
|
3572
3686
|
.map((value) => value.build(scenarioDurationsMs.get(value.scenarioName) ?? Math.max(Date.now() - started.getTime(), 0)))
|
|
3573
3687
|
.sort((left, right) => left.sortIndex - right.sortIndex);
|
|
3574
3688
|
const metricsSnapshot = collectMetricStats(allRegisteredMetrics, Date.now() - started.getTime());
|
|
3575
|
-
await this.emitRealtimeStats(sinkStates, snapshot, metricsSnapshot, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
|
|
3689
|
+
await this.emitRealtimeStats(sinkStates, snapshot, metricsSnapshot, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
|
|
3576
3690
|
if (toBoolean(this.options.displayConsoleMetrics, true)) {
|
|
3577
3691
|
const requestCount = snapshot.reduce((sum, value) => sum + value.allRequestCount, 0);
|
|
3578
3692
|
const okCount = snapshot.reduce((sum, value) => sum + value.allOkCount, 0);
|
|
@@ -3585,7 +3699,7 @@ export class LoadStrikeRunner {
|
|
|
3585
3699
|
realtimeInFlight = false;
|
|
3586
3700
|
}
|
|
3587
3701
|
};
|
|
3588
|
-
const reportingIntervalMs =
|
|
3702
|
+
const reportingIntervalMs = boundedReportingIntervalMs(this.options.reportingIntervalSeconds ?? 5);
|
|
3589
3703
|
const triggerRealtimeSnapshot = () => {
|
|
3590
3704
|
if (realtimeCurrent) {
|
|
3591
3705
|
return;
|
|
@@ -3672,6 +3786,7 @@ export class LoadStrikeRunner {
|
|
|
3672
3786
|
iterationObservationRunId,
|
|
3673
3787
|
iterationObservationResultOwnerId,
|
|
3674
3788
|
iterationObservationProcessGroup,
|
|
3789
|
+
reportHistoryLifecycle,
|
|
3675
3790
|
executeScenarioInvocation: (targetScenario, context, operation) => this.executeScenarioInvocation(targetScenario, context, operation),
|
|
3676
3791
|
invokeBeforeScenario: (runtimePolicies, scenarioName) => this.invokeBeforeScenario(runtimePolicies, scenarioName, policyErrors, runtimePolicyErrorMode),
|
|
3677
3792
|
invokeAfterScenario: (runtimePolicies, scenarioName, stats) => this.invokeAfterScenario(runtimePolicies, scenarioName, stats, policyErrors, runtimePolicyErrorMode),
|
|
@@ -3759,12 +3874,17 @@ export class LoadStrikeRunner {
|
|
|
3759
3874
|
...iterationObservationReporter.buildWarnings()
|
|
3760
3875
|
];
|
|
3761
3876
|
result.pluginsData = mergePluginData(result.pluginsData, await this.collectPluginData(plugins, attachRunResultAliases(result), pluginLifecycleErrors));
|
|
3877
|
+
if (reportHistoryCollector) {
|
|
3878
|
+
localReportInput = {
|
|
3879
|
+
history: reportHistoryCollector.toProjection()
|
|
3880
|
+
};
|
|
3881
|
+
}
|
|
3762
3882
|
const finalizedResult = attachRunResultAliases(result);
|
|
3763
3883
|
finalizedResult.logFiles = mergeStringArrays(finalizedResult.logFiles, loggerSetup.logFiles);
|
|
3764
|
-
finalizedResult.reportFiles = this.writeReports(finalizedResult);
|
|
3884
|
+
finalizedResult.reportFiles = this.writeReports(finalizedResult, localReportInput);
|
|
3765
3885
|
finalizedResult.disabledSinks = sinkStates.filter((x) => x.disabled).map((x) => x.name);
|
|
3766
|
-
await this.emitRunResult(sinkStates, finalizedResult, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
|
|
3767
|
-
await this.stopSinks(sinkStates, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
|
|
3886
|
+
await this.emitRunResult(sinkStates, finalizedResult, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
|
|
3887
|
+
await this.stopSinks(sinkStates, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
|
|
3768
3888
|
sinksStopped = true;
|
|
3769
3889
|
finalizedResult.disabledSinks = sinkStates.filter((x) => x.disabled).map((x) => x.name);
|
|
3770
3890
|
finalizedResult.sinkErrors = sinkErrors
|
|
@@ -3772,12 +3892,13 @@ export class LoadStrikeRunner {
|
|
|
3772
3892
|
return finalizedResult;
|
|
3773
3893
|
}
|
|
3774
3894
|
finally {
|
|
3895
|
+
reportHistoryLifecycle?.stopWithoutFinalizing();
|
|
3775
3896
|
await stopRealtimeReporting();
|
|
3776
3897
|
if (!iterationObservationsFinalized) {
|
|
3777
3898
|
await iterationObservationReporter.sealAndDrain().catch(() => { });
|
|
3778
3899
|
}
|
|
3779
3900
|
if (!sinksStopped) {
|
|
3780
|
-
await this.stopSinks(sinkStates, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
|
|
3901
|
+
await this.stopSinks(sinkStates, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
|
|
3781
3902
|
}
|
|
3782
3903
|
if (!pluginsStopped) {
|
|
3783
3904
|
await this.stopPlugins(plugins, pluginLifecycleErrors, runLogger);
|
|
@@ -4085,9 +4206,9 @@ export class LoadStrikeRunner {
|
|
|
4085
4206
|
}
|
|
4086
4207
|
return filtered;
|
|
4087
4208
|
}
|
|
4088
|
-
async initializeSinks(sinkStates, context, infraConfig, retryCount, retryBackoffMs, sinkErrors) {
|
|
4209
|
+
async initializeSinks(sinkStates, context, infraConfig, retryCount, retryBackoffMs, sinkErrors, runId, logger) {
|
|
4089
4210
|
for (const state of sinkStates) {
|
|
4090
|
-
await this.invokeSinkAction(state, "init", retryCount, retryBackoffMs, sinkErrors, false, true, async () => {
|
|
4211
|
+
await this.invokeSinkAction(state, "init", retryCount, retryBackoffMs, sinkErrors, runId, logger, false, true, async () => {
|
|
4091
4212
|
const init = resolveSinkInit(state.sink);
|
|
4092
4213
|
if (init) {
|
|
4093
4214
|
await init(context, infraConfig);
|
|
@@ -4095,9 +4216,9 @@ export class LoadStrikeRunner {
|
|
|
4095
4216
|
});
|
|
4096
4217
|
}
|
|
4097
4218
|
}
|
|
4098
|
-
async startSinks(sinkStates, session, retryCount, retryBackoffMs, sinkErrors) {
|
|
4219
|
+
async startSinks(sinkStates, session, retryCount, retryBackoffMs, sinkErrors, runId, logger) {
|
|
4099
4220
|
for (const state of sinkStates) {
|
|
4100
|
-
await this.invokeSinkAction(state, "start", retryCount, retryBackoffMs, sinkErrors, false, true, async () => {
|
|
4221
|
+
await this.invokeSinkAction(state, "start", retryCount, retryBackoffMs, sinkErrors, runId, logger, false, true, async () => {
|
|
4101
4222
|
const start = resolveSinkStart(state.sink);
|
|
4102
4223
|
if (start) {
|
|
4103
4224
|
await start(session);
|
|
@@ -4105,9 +4226,9 @@ export class LoadStrikeRunner {
|
|
|
4105
4226
|
});
|
|
4106
4227
|
}
|
|
4107
4228
|
}
|
|
4108
|
-
async emitRealtimeStats(sinkStates, scenarioStats, metrics, retryCount, retryBackoffMs, sinkErrors) {
|
|
4229
|
+
async emitRealtimeStats(sinkStates, scenarioStats, metrics, retryCount, retryBackoffMs, sinkErrors, runId, logger) {
|
|
4109
4230
|
for (const state of sinkStates) {
|
|
4110
|
-
await this.invokeSinkAction(state, "realtime", retryCount, retryBackoffMs, sinkErrors, false, true, async () => {
|
|
4231
|
+
await this.invokeSinkAction(state, "realtime", retryCount, retryBackoffMs, sinkErrors, runId, logger, false, true, async () => {
|
|
4111
4232
|
const saveRealtimeStats = resolveSinkSaveRealtimeStats(state.sink);
|
|
4112
4233
|
if (saveRealtimeStats) {
|
|
4113
4234
|
await saveRealtimeStats(scenarioStats);
|
|
@@ -4119,10 +4240,9 @@ export class LoadStrikeRunner {
|
|
|
4119
4240
|
});
|
|
4120
4241
|
}
|
|
4121
4242
|
}
|
|
4122
|
-
async stopSinks(sinkStates, retryCount, retryBackoffMs, sinkErrors) {
|
|
4123
|
-
const shutdownRetryCount = 0;
|
|
4243
|
+
async stopSinks(sinkStates, retryCount, retryBackoffMs, sinkErrors, runId, logger) {
|
|
4124
4244
|
for (const state of sinkStates) {
|
|
4125
|
-
await this.invokeSinkAction(state, "stop",
|
|
4245
|
+
await this.invokeSinkAction(state, "stop", retryCount, retryBackoffMs, sinkErrors, runId, logger, true, true, async () => {
|
|
4126
4246
|
const stop = resolveSinkStop(state.sink);
|
|
4127
4247
|
if (stop) {
|
|
4128
4248
|
await stop();
|
|
@@ -4130,15 +4250,15 @@ export class LoadStrikeRunner {
|
|
|
4130
4250
|
});
|
|
4131
4251
|
const dispose = resolveSinkDispose(state.sink);
|
|
4132
4252
|
if (dispose) {
|
|
4133
|
-
await this.invokeSinkAction(state, "dispose",
|
|
4253
|
+
await this.invokeSinkAction(state, "dispose", retryCount, retryBackoffMs, sinkErrors, runId, logger, true, true, async () => {
|
|
4134
4254
|
await dispose();
|
|
4135
4255
|
});
|
|
4136
4256
|
}
|
|
4137
4257
|
}
|
|
4138
4258
|
}
|
|
4139
|
-
async emitRunResult(sinkStates, result, retryCount, retryBackoffMs, sinkErrors) {
|
|
4259
|
+
async emitRunResult(sinkStates, result, retryCount, retryBackoffMs, sinkErrors, runId, logger) {
|
|
4140
4260
|
for (const state of sinkStates) {
|
|
4141
|
-
await this.invokeSinkAction(state, "run-result", retryCount, retryBackoffMs, sinkErrors, false, false, async () => {
|
|
4261
|
+
await this.invokeSinkAction(state, "run-result", retryCount, retryBackoffMs, sinkErrors, runId, logger, false, false, async () => {
|
|
4142
4262
|
const saveRunResult = resolveSinkSaveRunResult(state.sink);
|
|
4143
4263
|
if (saveRunResult) {
|
|
4144
4264
|
await saveRunResult(result);
|
|
@@ -4146,23 +4266,49 @@ export class LoadStrikeRunner {
|
|
|
4146
4266
|
});
|
|
4147
4267
|
}
|
|
4148
4268
|
}
|
|
4149
|
-
async invokeSinkAction(state, phase, retryCount, retryBackoffMs, sinkErrors, ignoreDisabled, disableOnFailure, action) {
|
|
4269
|
+
async invokeSinkAction(state, phase, retryCount, retryBackoffMs, sinkErrors, runId, logger, ignoreDisabled, disableOnFailure, action) {
|
|
4150
4270
|
if (state.disabled && !ignoreDisabled) {
|
|
4151
4271
|
return;
|
|
4152
4272
|
}
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4273
|
+
const maximumAttempts = normalizeSinkRetryCount(retryCount) + 1;
|
|
4274
|
+
const backoffMs = normalizeSinkRetryBackoffMs(retryBackoffMs);
|
|
4275
|
+
for (let attempts = 1; attempts <= maximumAttempts; attempts += 1) {
|
|
4156
4276
|
try {
|
|
4157
4277
|
await action();
|
|
4278
|
+
if (attempts > 1) {
|
|
4279
|
+
logIterationObservationRecovery(logger, {
|
|
4280
|
+
sinkName: state.name,
|
|
4281
|
+
operation: "reporting-sink-action",
|
|
4282
|
+
phase,
|
|
4283
|
+
runId,
|
|
4284
|
+
resultOwnerId: "",
|
|
4285
|
+
observationCount: 0,
|
|
4286
|
+
attempt: attempts,
|
|
4287
|
+
maximumAttempts,
|
|
4288
|
+
nextDelayMs: 0
|
|
4289
|
+
});
|
|
4290
|
+
}
|
|
4158
4291
|
return;
|
|
4159
4292
|
}
|
|
4160
4293
|
catch (error) {
|
|
4161
|
-
|
|
4294
|
+
const exhausted = attempts >= maximumAttempts;
|
|
4295
|
+
const nextDelayMs = exhausted ? 0 : sinkRetryDelayMs(backoffMs, attempts);
|
|
4296
|
+
logIterationObservationFailure(logger, exhausted ? "error" : "warn", {
|
|
4297
|
+
sinkName: state.name,
|
|
4298
|
+
operation: "reporting-sink-action",
|
|
4299
|
+
phase,
|
|
4300
|
+
runId,
|
|
4301
|
+
resultOwnerId: "",
|
|
4302
|
+
observationCount: 0,
|
|
4303
|
+
attempt: attempts,
|
|
4304
|
+
maximumAttempts,
|
|
4305
|
+
nextDelayMs
|
|
4306
|
+
}, error);
|
|
4307
|
+
if (exhausted) {
|
|
4162
4308
|
sinkErrors.push({
|
|
4163
4309
|
sinkName: state.name,
|
|
4164
4310
|
phase,
|
|
4165
|
-
message:
|
|
4311
|
+
message: "The reporting sink action failed after retries.",
|
|
4166
4312
|
attempts
|
|
4167
4313
|
});
|
|
4168
4314
|
if (disableOnFailure) {
|
|
@@ -4170,9 +4316,7 @@ export class LoadStrikeRunner {
|
|
|
4170
4316
|
}
|
|
4171
4317
|
return;
|
|
4172
4318
|
}
|
|
4173
|
-
|
|
4174
|
-
await sleep(retryBackoffMs * attempts);
|
|
4175
|
-
}
|
|
4319
|
+
await waitForSinkRetryDelay(nextDelayMs);
|
|
4176
4320
|
}
|
|
4177
4321
|
}
|
|
4178
4322
|
}
|
|
@@ -4226,7 +4370,7 @@ export class LoadStrikeRunner {
|
|
|
4226
4370
|
message: String(error ?? "runtime policy callback failed")
|
|
4227
4371
|
});
|
|
4228
4372
|
}
|
|
4229
|
-
writeReports(result) {
|
|
4373
|
+
writeReports(result, localReportInput = emptyLocalReportInput()) {
|
|
4230
4374
|
const reportsEnabled = this.options.reportsEnabled ?? true;
|
|
4231
4375
|
if (!reportsEnabled) {
|
|
4232
4376
|
return [];
|
|
@@ -4251,7 +4395,7 @@ export class LoadStrikeRunner {
|
|
|
4251
4395
|
writeFileSync(path, buildDotnetMarkdownReport(nodeStats), "utf8");
|
|
4252
4396
|
}
|
|
4253
4397
|
else if (format === "html") {
|
|
4254
|
-
writeFileSync(path, buildDotnetHtmlReport(nodeStats), "utf8");
|
|
4398
|
+
writeFileSync(path, buildDotnetHtmlReport(nodeStats, localReportInput), "utf8");
|
|
4255
4399
|
}
|
|
4256
4400
|
written.push(path);
|
|
4257
4401
|
}
|
|
@@ -4284,6 +4428,19 @@ function hasPluginRows(value) {
|
|
|
4284
4428
|
}
|
|
4285
4429
|
return value.tables.some((table) => Array.isArray(table.rows) && table.rows.length > 0);
|
|
4286
4430
|
}
|
|
4431
|
+
function boundedReportingIntervalMs(seconds) {
|
|
4432
|
+
if (!Number.isFinite(seconds) || seconds <= 0) {
|
|
4433
|
+
return 1;
|
|
4434
|
+
}
|
|
4435
|
+
const milliseconds = seconds * 1000;
|
|
4436
|
+
if (!Number.isFinite(milliseconds) || milliseconds >= 2147483647) {
|
|
4437
|
+
return 2147483647;
|
|
4438
|
+
}
|
|
4439
|
+
return Math.max(Math.trunc(milliseconds), 1);
|
|
4440
|
+
}
|
|
4441
|
+
function resolvedReportHistoryCadenceSeconds(seconds) {
|
|
4442
|
+
return boundedReportingIntervalMs(seconds) / 1000;
|
|
4443
|
+
}
|
|
4287
4444
|
async function executeV2FixedArrivals(args) {
|
|
4288
4445
|
const { rate, intervalNs, totalArrivals, budget, cancellationToken, shouldStopNow, nextInstanceInfo, runBombingInvocation, logger, scenarioName, deadlineOffsetsNs, tolerancesNs, ownedOrdinals, shardIndex = 0, shardCount = 1, telemetry, segment } = args;
|
|
4289
4446
|
const segmentStartNs = process.hrtime.bigint();
|
|
@@ -4420,7 +4577,15 @@ function maxBigInt(left, right) {
|
|
|
4420
4577
|
return left > right ? left : right;
|
|
4421
4578
|
}
|
|
4422
4579
|
async function executeScenarioRuntime(args) {
|
|
4423
|
-
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;
|
|
4580
|
+
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;
|
|
4581
|
+
let reportHistoryExecutionEnded = false;
|
|
4582
|
+
const endReportHistoryExecution = async () => {
|
|
4583
|
+
if (reportHistoryExecutionEnded) {
|
|
4584
|
+
return;
|
|
4585
|
+
}
|
|
4586
|
+
reportHistoryExecutionEnded = true;
|
|
4587
|
+
await reportHistoryLifecycle?.scenarioExecutionEnded();
|
|
4588
|
+
};
|
|
4424
4589
|
const scenarioStartedMs = Date.now();
|
|
4425
4590
|
const scenarioContextData = {};
|
|
4426
4591
|
const registeredMetrics = [];
|
|
@@ -4581,7 +4746,18 @@ async function executeScenarioRuntime(args) {
|
|
|
4581
4746
|
attachScenarioContextAliases(context);
|
|
4582
4747
|
const startedUtcNs = utcNowNs();
|
|
4583
4748
|
const startedAtNs = process.hrtime.bigint();
|
|
4584
|
-
|
|
4749
|
+
let policyFailure;
|
|
4750
|
+
let reply;
|
|
4751
|
+
try {
|
|
4752
|
+
reply = await executeScenarioInvocation(scenario, context, operation);
|
|
4753
|
+
}
|
|
4754
|
+
catch (error) {
|
|
4755
|
+
if (!(error instanceof RuntimePolicyCallbackError)) {
|
|
4756
|
+
throw error;
|
|
4757
|
+
}
|
|
4758
|
+
policyFailure = error;
|
|
4759
|
+
reply = LoadStrikeResponse.fail("runtime_policy_error", "", 0);
|
|
4760
|
+
}
|
|
4585
4761
|
const observedLatencyNs = maxBigInt(process.hrtime.bigint() - startedAtNs, 0n);
|
|
4586
4762
|
const completedUtcNs = startedUtcNs + observedLatencyNs;
|
|
4587
4763
|
const observedLatencyMs = Number(observedLatencyNs) / 1000000;
|
|
@@ -4596,7 +4772,8 @@ async function executeScenarioRuntime(args) {
|
|
|
4596
4772
|
observedLatencyUs: observedLatencyNs / 1000n,
|
|
4597
4773
|
reportedLatencyUs: normalizeRawObservationLatencyMicroseconds(resolveRecordedLatency(reply.customLatencyMs, observedLatencyMs)),
|
|
4598
4774
|
steps: attemptSteps,
|
|
4599
|
-
recordedSteps
|
|
4775
|
+
recordedSteps,
|
|
4776
|
+
...(policyFailure ? { policyFailure } : {})
|
|
4600
4777
|
};
|
|
4601
4778
|
};
|
|
4602
4779
|
const captureAttemptObservation = (operation, globalOrdinal, attemptIndex, isFinalAttempt, attempt, simulationIndex, simulationKind, iterationId, globalSecondaryOrdinal = 0n) => {
|
|
@@ -4645,22 +4822,18 @@ async function executeScenarioRuntime(args) {
|
|
|
4645
4822
|
const maxAttempts = 1 + (scenario.shouldRestartIterationOnFail() ? restartIterationMaxAttempts : 0);
|
|
4646
4823
|
while (attempts < maxAttempts && !shouldStopNow()) {
|
|
4647
4824
|
attempts += 1;
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
attempt
|
|
4651
|
-
}
|
|
4652
|
-
catch (error) {
|
|
4653
|
-
if (error instanceof RuntimePolicyCallbackError) {
|
|
4654
|
-
stopScenario = true;
|
|
4655
|
-
scenarioAbortController.abort(error);
|
|
4656
|
-
}
|
|
4657
|
-
throw error;
|
|
4658
|
-
}
|
|
4659
|
-
const shouldRetry = !attempt.reply.isSuccess
|
|
4825
|
+
const attempt = await runSingleInvocation("Bombing", instanceData, instanceNumber, instanceId, false);
|
|
4826
|
+
const shouldRetry = !attempt.policyFailure
|
|
4827
|
+
&& !attempt.reply.isSuccess
|
|
4660
4828
|
&& scenario.shouldRestartIterationOnFail()
|
|
4661
4829
|
&& attempts < maxAttempts
|
|
4662
4830
|
&& !shouldStopNow();
|
|
4663
4831
|
captureAttemptObservation("Bombing", globalOrdinal, attempts - 1, !shouldRetry, attempt, simulationIndex, simulationKind, iterationId, explicitSecondaryOrdinal);
|
|
4832
|
+
if (attempt.policyFailure) {
|
|
4833
|
+
stopScenario = true;
|
|
4834
|
+
scenarioAbortController.abort(attempt.policyFailure);
|
|
4835
|
+
throw attempt.policyFailure;
|
|
4836
|
+
}
|
|
4664
4837
|
if (!shouldRetry) {
|
|
4665
4838
|
for (const step of attempt.recordedSteps) {
|
|
4666
4839
|
recordStepReply(step.stepName, step.reply, step.observedLatencyMs, step.sortIndex);
|
|
@@ -4687,6 +4860,11 @@ async function executeScenarioRuntime(args) {
|
|
|
4687
4860
|
const globalOrdinal = nextObservationOrdinal();
|
|
4688
4861
|
const attempt = await runSingleInvocation("WarmUp", instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId, false);
|
|
4689
4862
|
captureAttemptObservation("WarmUp", globalOrdinal, 0, true, attempt, -1, "SingleInvocation");
|
|
4863
|
+
if (attempt.policyFailure) {
|
|
4864
|
+
stopScenario = true;
|
|
4865
|
+
scenarioAbortController.abort(attempt.policyFailure);
|
|
4866
|
+
throw attempt.policyFailure;
|
|
4867
|
+
}
|
|
4690
4868
|
}
|
|
4691
4869
|
};
|
|
4692
4870
|
const executeV2TimedConstant = async (copies, durationNs, ramping, runSimulationInvocation, segment) => {
|
|
@@ -5228,6 +5406,7 @@ async function executeScenarioRuntime(args) {
|
|
|
5228
5406
|
})));
|
|
5229
5407
|
await invokeBeforeScenario(policies, scenario.name);
|
|
5230
5408
|
await runWarmUpAsync();
|
|
5409
|
+
reportHistoryLifecycle?.scenarioBombingStarted();
|
|
5231
5410
|
accumulator.setCurrentOperation("Bombing");
|
|
5232
5411
|
const simulations = scenario.getSimulations();
|
|
5233
5412
|
if (!simulations.length) {
|
|
@@ -5261,6 +5440,7 @@ async function executeScenarioRuntime(args) {
|
|
|
5261
5440
|
}
|
|
5262
5441
|
}
|
|
5263
5442
|
accumulator.setCurrentOperation(stopScenario || stopTestState.value ? "Stop" : "Complete");
|
|
5443
|
+
await endReportHistoryExecution();
|
|
5264
5444
|
await invokeAfterScenario(policies, scenario.name, runtime);
|
|
5265
5445
|
}
|
|
5266
5446
|
catch (error) {
|
|
@@ -5277,6 +5457,7 @@ async function executeScenarioRuntime(args) {
|
|
|
5277
5457
|
}
|
|
5278
5458
|
}
|
|
5279
5459
|
finally {
|
|
5460
|
+
await endReportHistoryExecution();
|
|
5280
5461
|
scenarioDurationsMs.set(scenario.name, Math.max(Date.now() - scenarioStartedMs, 0));
|
|
5281
5462
|
try {
|
|
5282
5463
|
await scenario.invokeClean({
|
|
@@ -5357,10 +5538,17 @@ async function waitForScenarioTasks(tasks, scenarioName, timeoutSeconds, logger,
|
|
|
5357
5538
|
throwPolicyFailure(await settled);
|
|
5358
5539
|
return;
|
|
5359
5540
|
}
|
|
5360
|
-
const
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5541
|
+
const timeoutController = new AbortController();
|
|
5542
|
+
let completed;
|
|
5543
|
+
try {
|
|
5544
|
+
completed = await Promise.race([
|
|
5545
|
+
settled.then(() => true),
|
|
5546
|
+
delayWithAbort(Math.trunc(timeoutSeconds * 1000), combineAbortSignals(signal, timeoutController.signal)).then(() => false)
|
|
5547
|
+
]);
|
|
5548
|
+
}
|
|
5549
|
+
finally {
|
|
5550
|
+
timeoutController.abort();
|
|
5551
|
+
}
|
|
5364
5552
|
if (!completed) {
|
|
5365
5553
|
if (signal.reason instanceof RuntimePolicyCallbackError) {
|
|
5366
5554
|
throw signal.reason;
|
|
@@ -7203,6 +7391,11 @@ function resolveClusterExecutionMode(options) {
|
|
|
7203
7391
|
}
|
|
7204
7392
|
return "single";
|
|
7205
7393
|
}
|
|
7394
|
+
function isDistributedReportHistoryExecution(clusterMode, options) {
|
|
7395
|
+
return clusterMode !== "single"
|
|
7396
|
+
|| options.loadEngineV2SegmentLifecycleOverride !== undefined
|
|
7397
|
+
|| Math.max(Math.trunc(options.clusterShardCount ?? 1), 1) > 1;
|
|
7398
|
+
}
|
|
7206
7399
|
function buildEmptyNodeStats(args) {
|
|
7207
7400
|
return attachNodeStatsAliases({
|
|
7208
7401
|
startedUtc: args.startedUtc,
|
|
@@ -9422,11 +9615,23 @@ function readRuntimeTrackingId(payload, selector) {
|
|
|
9422
9615
|
return null;
|
|
9423
9616
|
}
|
|
9424
9617
|
let current = body;
|
|
9425
|
-
|
|
9618
|
+
const path = selector.slice("json:".length).trim().replace(/^\$\./, "");
|
|
9619
|
+
let segments;
|
|
9620
|
+
try {
|
|
9621
|
+
segments = runtimeSafeJsonPathSegments(path);
|
|
9622
|
+
}
|
|
9623
|
+
catch {
|
|
9624
|
+
return null;
|
|
9625
|
+
}
|
|
9626
|
+
for (const segment of segments) {
|
|
9426
9627
|
if (!current || typeof current !== "object" || Array.isArray(current)) {
|
|
9427
9628
|
return null;
|
|
9428
9629
|
}
|
|
9429
|
-
|
|
9630
|
+
const record = current;
|
|
9631
|
+
if (!Object.prototype.hasOwnProperty.call(record, segment)) {
|
|
9632
|
+
return null;
|
|
9633
|
+
}
|
|
9634
|
+
current = runtimeReadOwnJsonProperty(record, segment);
|
|
9430
9635
|
}
|
|
9431
9636
|
return current == null ? null : String(current);
|
|
9432
9637
|
}
|
|
@@ -9450,23 +9655,57 @@ function runtimeParseBodyAsObject(body) {
|
|
|
9450
9655
|
}
|
|
9451
9656
|
}
|
|
9452
9657
|
function setRuntimeJsonPathValue(body, path, value) {
|
|
9453
|
-
const target =
|
|
9454
|
-
|
|
9455
|
-
: {};
|
|
9456
|
-
const segments = path.split(".").filter(Boolean);
|
|
9658
|
+
const target = runtimeCloneJsonRecord(body);
|
|
9659
|
+
const segments = runtimeSafeJsonPathSegments(path);
|
|
9457
9660
|
if (!segments.length) {
|
|
9458
9661
|
return target;
|
|
9459
9662
|
}
|
|
9460
9663
|
let current = target;
|
|
9461
9664
|
for (let i = 0; i < segments.length - 1; i += 1) {
|
|
9462
9665
|
const segment = segments[i];
|
|
9463
|
-
const next = current
|
|
9666
|
+
const next = runtimeReadOwnJsonProperty(current, segment);
|
|
9667
|
+
let child;
|
|
9464
9668
|
if (!next || typeof next !== "object" || Array.isArray(next)) {
|
|
9465
|
-
|
|
9669
|
+
child = {};
|
|
9466
9670
|
}
|
|
9467
|
-
|
|
9671
|
+
else {
|
|
9672
|
+
child = runtimeCloneJsonRecord(next);
|
|
9673
|
+
}
|
|
9674
|
+
runtimeDefineJsonProperty(current, segment, child);
|
|
9675
|
+
current = child;
|
|
9676
|
+
}
|
|
9677
|
+
runtimeDefineJsonProperty(current, segments[segments.length - 1], value);
|
|
9678
|
+
return target;
|
|
9679
|
+
}
|
|
9680
|
+
const FORBIDDEN_RUNTIME_JSON_PATH_SEGMENTS = new Set(["__proto__", "prototype", "constructor"]);
|
|
9681
|
+
function runtimeSafeJsonPathSegments(path) {
|
|
9682
|
+
const segments = path.split(".").filter(Boolean);
|
|
9683
|
+
const forbidden = segments.find((segment) => FORBIDDEN_RUNTIME_JSON_PATH_SEGMENTS.has(segment));
|
|
9684
|
+
if (forbidden) {
|
|
9685
|
+
throw new Error(`Tracking selector contains forbidden JSON path segment '${forbidden}'.`);
|
|
9686
|
+
}
|
|
9687
|
+
return segments;
|
|
9688
|
+
}
|
|
9689
|
+
function runtimeDefineJsonProperty(target, key, value) {
|
|
9690
|
+
Object.defineProperty(target, key, {
|
|
9691
|
+
configurable: true,
|
|
9692
|
+
enumerable: true,
|
|
9693
|
+
value,
|
|
9694
|
+
writable: true
|
|
9695
|
+
});
|
|
9696
|
+
}
|
|
9697
|
+
function runtimeReadOwnJsonProperty(target, key) {
|
|
9698
|
+
const descriptor = Object.getOwnPropertyDescriptor(target, key);
|
|
9699
|
+
return descriptor && "value" in descriptor ? descriptor.value : undefined;
|
|
9700
|
+
}
|
|
9701
|
+
function runtimeCloneJsonRecord(value) {
|
|
9702
|
+
const target = {};
|
|
9703
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
9704
|
+
return target;
|
|
9705
|
+
}
|
|
9706
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
9707
|
+
runtimeDefineJsonProperty(target, key, entry);
|
|
9468
9708
|
}
|
|
9469
|
-
current[segments[segments.length - 1]] = value;
|
|
9470
9709
|
return target;
|
|
9471
9710
|
}
|
|
9472
9711
|
function asTrackingRecord(value) {
|
|
@@ -9476,8 +9715,8 @@ function asTrackingRecord(value) {
|
|
|
9476
9715
|
}
|
|
9477
9716
|
function pickTrackingValue(source, ...keys) {
|
|
9478
9717
|
for (const key of keys) {
|
|
9479
|
-
if (key
|
|
9480
|
-
return source
|
|
9718
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
9719
|
+
return runtimeReadOwnJsonProperty(source, key);
|
|
9481
9720
|
}
|
|
9482
9721
|
}
|
|
9483
9722
|
return undefined;
|
|
@@ -9731,26 +9970,10 @@ function createDefaultLogger(logFilePath) {
|
|
|
9731
9970
|
function wrapLoggerWithMinimumLevel(baseLogger, minimumLogLevel) {
|
|
9732
9971
|
const threshold = logLevelOrder(minimumLogLevel);
|
|
9733
9972
|
return {
|
|
9734
|
-
debug: (message) =>
|
|
9735
|
-
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
},
|
|
9739
|
-
info: (message) => {
|
|
9740
|
-
if (threshold <= 1) {
|
|
9741
|
-
baseLogger.info(message);
|
|
9742
|
-
}
|
|
9743
|
-
},
|
|
9744
|
-
warn: (message) => {
|
|
9745
|
-
if (threshold <= 2) {
|
|
9746
|
-
baseLogger.warn(message);
|
|
9747
|
-
}
|
|
9748
|
-
},
|
|
9749
|
-
error: (message) => {
|
|
9750
|
-
if (threshold <= 3) {
|
|
9751
|
-
baseLogger.error(message);
|
|
9752
|
-
}
|
|
9753
|
-
}
|
|
9973
|
+
debug: (message) => threshold <= 0 ? baseLogger.debug(message) : undefined,
|
|
9974
|
+
info: (message) => threshold <= 1 ? baseLogger.info(message) : undefined,
|
|
9975
|
+
warn: (message) => threshold <= 2 ? baseLogger.warn(message) : undefined,
|
|
9976
|
+
error: (message) => threshold <= 3 ? baseLogger.error(message) : undefined
|
|
9754
9977
|
};
|
|
9755
9978
|
}
|
|
9756
9979
|
function formatDefaultLoggerLine(level, message) {
|
|
@@ -11004,6 +11227,7 @@ function buildGroupedCorrelationRows(rows) {
|
|
|
11004
11227
|
LatencyMinMs: latencySamples.length ? formatOptionalLatency(latencySamples[0]) : "",
|
|
11005
11228
|
LatencyMeanMs: latencySamples.length ? formatOptionalLatency(latencySamples.reduce((sum, value) => sum + value, 0) / latencySamples.length) : "",
|
|
11006
11229
|
LatencyP50Ms: latencySamples.length ? formatOptionalLatency(percentile(latencySamples, 0.5)) : "",
|
|
11230
|
+
LatencyP75Ms: latencySamples.length ? formatOptionalLatency(percentile(latencySamples, 0.75)) : "",
|
|
11007
11231
|
LatencyP80Ms: latencySamples.length ? formatOptionalLatency(percentile(latencySamples, 0.8)) : "",
|
|
11008
11232
|
LatencyP85Ms: latencySamples.length ? formatOptionalLatency(percentile(latencySamples, 0.85)) : "",
|
|
11009
11233
|
LatencyP90Ms: latencySamples.length ? formatOptionalLatency(percentile(latencySamples, 0.9)) : "",
|
|
@@ -11216,6 +11440,8 @@ export const __loadstrikeTestExports = {
|
|
|
11216
11440
|
buildThresholdCheckExpression,
|
|
11217
11441
|
buildTrackingLeaseKey,
|
|
11218
11442
|
buildTrackingRunNamespace,
|
|
11443
|
+
boundedReportingIntervalMs,
|
|
11444
|
+
resolvedReportHistoryCadenceSeconds,
|
|
11219
11445
|
clusterNodeResultToNodeStats,
|
|
11220
11446
|
combineAbortSignals,
|
|
11221
11447
|
computeScenarioRequestCount,
|
|
@@ -11235,6 +11461,7 @@ export const __loadstrikeTestExports = {
|
|
|
11235
11461
|
formatUtcReportTimestamp,
|
|
11236
11462
|
hasPluginRows,
|
|
11237
11463
|
inferRuntimeLegacyHttpResponseSource,
|
|
11464
|
+
isDistributedReportHistoryExecution,
|
|
11238
11465
|
isComparisonFailed,
|
|
11239
11466
|
loadJsonObject,
|
|
11240
11467
|
logLevelOrder,
|
|
@@ -11264,6 +11491,7 @@ export const __loadstrikeTestExports = {
|
|
|
11264
11491
|
parseStrictBooleanToken,
|
|
11265
11492
|
percentile,
|
|
11266
11493
|
pickOptionalTrackingSelectorString,
|
|
11494
|
+
pickTrackingValue,
|
|
11267
11495
|
pickTrackingNumber,
|
|
11268
11496
|
produceOrConsumeTrackingPayload,
|
|
11269
11497
|
readConfiguredSinkName,
|