@loadstrike/loadstrike-sdk 1.0.22601 → 1.0.23001
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 +1 -1
- package/dist/cjs/autopilot.js +56 -2
- package/dist/cjs/index.js +4 -3
- package/dist/cjs/local.js +166 -155
- package/dist/cjs/runtime.js +127 -3
- package/dist/cjs/transports.js +295 -1
- package/dist/esm/autopilot.js +55 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/local.js +166 -155
- package/dist/esm/runtime.js +126 -2
- package/dist/esm/transports.js +294 -0
- package/dist/types/autopilot-contracts.d.ts +2 -0
- package/dist/types/autopilot.d.ts +5 -2
- package/dist/types/contracts.d.ts +14 -0
- package/dist/types/index.d.ts +5 -5
- package/dist/types/runtime.d.ts +9 -0
- package/dist/types/transports.d.ts +97 -2
- package/package.json +2 -7
package/dist/esm/autopilot.js
CHANGED
|
@@ -12,11 +12,13 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
12
12
|
var _LoadStrikeAutopilotResult_httpRequest;
|
|
13
13
|
import fs from "node:fs";
|
|
14
14
|
import { LoadStrikeResponse, LoadStrikeScenario, LoadStrikeSimulation, LoadStrikeThreshold } from "./runtime.js";
|
|
15
|
+
import { LoadStrikeLocalClient } from "./local.js";
|
|
15
16
|
import { LoadStrikeAutopilotReadiness } from "./autopilot-contracts.js";
|
|
16
17
|
const REDACTED = "[REDACTED]";
|
|
17
18
|
const ENV_MARKER_PREFIX = "${LOADSTRIKE_ENV:";
|
|
18
19
|
const ENV_MARKER_SUFFIX = "}";
|
|
19
20
|
const TRACE_TO_TEST_AUTOPILOT_FEATURE = "autopilot.trace_to_test";
|
|
21
|
+
let autopilotLicenseValidationBypassForTests = false;
|
|
20
22
|
const SECRET_KEYS = new Set([
|
|
21
23
|
"authorization",
|
|
22
24
|
"proxy_authorization",
|
|
@@ -88,9 +90,10 @@ export class LoadStrikeAutopilotResult {
|
|
|
88
90
|
}
|
|
89
91
|
_LoadStrikeAutopilotResult_httpRequest = new WeakMap();
|
|
90
92
|
export class LoadStrikeAutopilot {
|
|
91
|
-
static generate(request) {
|
|
93
|
+
static async generate(request) {
|
|
92
94
|
const artifact = loadAutopilotArtifact(request);
|
|
93
95
|
const options = request.Options ?? {};
|
|
96
|
+
await validateGenerationEntitlement(options);
|
|
94
97
|
const result = inferAutopilotResult(artifact, options);
|
|
95
98
|
if (options.IncludePreviewReport) {
|
|
96
99
|
result.PreviewReport = buildPreviewReport(result);
|
|
@@ -101,6 +104,57 @@ export class LoadStrikeAutopilot {
|
|
|
101
104
|
return LoadStrikeAutopilot.generate(request);
|
|
102
105
|
}
|
|
103
106
|
}
|
|
107
|
+
export const __private = {
|
|
108
|
+
setAutopilotLicenseValidationBypassForTests(value) {
|
|
109
|
+
autopilotLicenseValidationBypassForTests = value;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
async function validateGenerationEntitlement(options) {
|
|
113
|
+
if (autopilotLicenseValidationBypassForTests) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const licensePayload = {
|
|
117
|
+
Context: {
|
|
118
|
+
RunnerKey: options.RunnerKey ?? "",
|
|
119
|
+
LicenseValidationTimeoutSeconds: options.LicenseValidationTimeoutSeconds,
|
|
120
|
+
TestSuite: "loadstrike-autopilot",
|
|
121
|
+
TestName: options.ScenarioName?.trim() || "autopilot-generation"
|
|
122
|
+
},
|
|
123
|
+
Scenarios: [
|
|
124
|
+
{
|
|
125
|
+
Name: "autopilot-generation",
|
|
126
|
+
InternalLicenseFeatures: [TRACE_TO_TEST_AUTOPILOT_FEATURE]
|
|
127
|
+
}
|
|
128
|
+
],
|
|
129
|
+
RunArgs: []
|
|
130
|
+
};
|
|
131
|
+
const client = new LoadStrikeLocalClient({
|
|
132
|
+
licenseValidationTimeoutMs: normalizeLicenseValidationTimeoutMs(options.LicenseValidationTimeoutSeconds)
|
|
133
|
+
});
|
|
134
|
+
let session;
|
|
135
|
+
try {
|
|
136
|
+
session = await client.acquireLicenseLease(licensePayload);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
140
|
+
if (message.toLowerCase().includes("runner key is required")) {
|
|
141
|
+
throw new Error("Runner key is required for Trace-To-Test Autopilot generation. " +
|
|
142
|
+
"Set Options.RunnerKey before calling LoadStrikeAutopilot.generate(...).");
|
|
143
|
+
}
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
if (session) {
|
|
148
|
+
await client.releaseLicenseLease(session, licensePayload);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function normalizeLicenseValidationTimeoutMs(value) {
|
|
153
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
return Math.trunc(value * 1000);
|
|
157
|
+
}
|
|
104
158
|
function loadAutopilotArtifact(request) {
|
|
105
159
|
if (!request || typeof request.Kind !== "string" || !request.Kind.trim()) {
|
|
106
160
|
throw new Error("Autopilot kind must be provided.");
|
package/dist/esm/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { LoadStrikeAutopilot, LoadStrikeAutopilotResult } from "./autopilot.js";
|
|
2
2
|
export { LoadStrikeAutopilotReadiness } from "./autopilot-contracts.js";
|
|
3
|
-
export { CrossPlatformScenarioConfigurator, ScenarioTrackingExtensions, LoadStrikeContext, LoadStrikePluginData, LoadStrikePluginDataTable, LoadStrikeNodeType, LoadStrikeReportFormat, LoadStrikeResponse, LoadStrikeLogLevel, LoadStrikeScenarioOperation, LoadStrikeOperationType, LoadStrikeRunner, LoadStrikeScenario, LoadStrikeSimulation, LoadStrikeMetric, LoadStrikeCounter, LoadStrikeGauge, LoadStrikeStep, LoadStrikeThreshold } from "./runtime.js";
|
|
3
|
+
export { CrossPlatformScenarioConfigurator, ScenarioTrackingExtensions, LoadStrikeContext, LoadStrikePluginData, LoadStrikePluginDataTable, LoadStrikeNodeType, LoadStrikeReportFormat, LoadStrikeResponse, LoadStrikeLogLevel, LoadStrikeScenarioOperation, LoadStrikeOperationType, LoadStrikeRunner, LoadStrikeScenario, LoadStrikeSimulation, CrossPlatformTrackingConfiguration, LoadStrikeMetric, LoadStrikeCounter, LoadStrikeGauge, LoadStrikeStep, LoadStrikeThreshold } from "./runtime.js";
|
|
4
4
|
export { CorrelationStoreConfiguration, CrossPlatformTrackingRuntime, InMemoryCorrelationStore, RedisCorrelationStoreOptions, RedisCorrelationStore, TrackingPayloadBuilder, TrackingFieldSelector } from "./correlation.js";
|
|
5
|
-
export {
|
|
5
|
+
export { LOADSTRIKE_TRACE_ID_HEADER, LOADSTRIKE_TRACE_ID_TRACKING_FIELD, TrafficEndpointDefinition, HttpEndpointDefinition, KafkaEndpointDefinition, KafkaSaslOptions, RabbitMqEndpointDefinition, NatsEndpointDefinition, RedisStreamsEndpointDefinition, AzureEventHubsEndpointDefinition, SqsEndpointDefinition, DelegateStreamEndpointDefinition, PushDiffusionEndpointDefinition, HttpOAuth2ClientCredentialsOptions, HttpAuthOptions } from "./transports.js";
|
|
6
6
|
export { DatadogReportingSink, DatadogReportingSinkOptions, GrafanaLokiReportingSink, GrafanaLokiReportingSinkOptions, InfluxDbReportingSink, InfluxDbReportingSinkOptions, OtelCollectorReportingSink, OtelCollectorReportingSinkOptions, SplunkReportingSink, SplunkReportingSinkOptions, TimescaleDbReportingSink, TimescaleDbReportingSinkOptions } from "./sinks.js";
|
package/dist/esm/local.js
CHANGED
|
@@ -5,7 +5,6 @@ import { createHash, createVerify, randomUUID } from "node:crypto";
|
|
|
5
5
|
import { CorrelationStoreConfiguration, CrossPlatformTrackingRuntime, RedisCorrelationStoreOptions, RedisCorrelationStore, TrackingFieldSelector } from "./correlation.js";
|
|
6
6
|
import { EndpointAdapterFactory, LOADSTRIKE_TRACE_ID_TRACKING_FIELD } from "./transports.js";
|
|
7
7
|
const DEFAULT_LICENSING_API_BASE_URL = "https://licensing.loadstrike.com";
|
|
8
|
-
const INTERNAL_BLACKBOX_LICENSING_API_BASE_URL_ENVIRONMENT_VARIABLE = "LOADSTRIKE_INTERNAL_BLACKBOX_API_BASE_URL";
|
|
9
8
|
let developmentLicensingApiBaseUrlOverride;
|
|
10
9
|
const BUILT_IN_WORKER_PLUGIN_NAMES = new Set([
|
|
11
10
|
"loadstrike failed responses",
|
|
@@ -27,7 +26,8 @@ const TRACKING_FEATURE_BY_KIND = {
|
|
|
27
26
|
pushdiffusion: "endpoint.push_diffusion",
|
|
28
27
|
delegatestream: "endpoint.delegate_stream",
|
|
29
28
|
nats: "endpoint.nats",
|
|
30
|
-
redisstreams: "endpoint.redis_streams"
|
|
29
|
+
redisstreams: "endpoint.redis_streams",
|
|
30
|
+
sqs: "endpoint.sqs"
|
|
31
31
|
};
|
|
32
32
|
const CI_ENVIRONMENT_VARIABLES = [
|
|
33
33
|
"GITHUB_ACTIONS",
|
|
@@ -466,25 +466,8 @@ function normalizeLicensingApiBaseUrl(value) {
|
|
|
466
466
|
const normalized = (value ?? "").trim();
|
|
467
467
|
return normalized || DEFAULT_LICENSING_API_BASE_URL;
|
|
468
468
|
}
|
|
469
|
-
function resolveInternalBlackboxLicensingApiBaseUrlOverride(value) {
|
|
470
|
-
const normalized = String(value ?? "").trim();
|
|
471
|
-
if (!normalized) {
|
|
472
|
-
return undefined;
|
|
473
|
-
}
|
|
474
|
-
try {
|
|
475
|
-
const parsed = new URL(normalized);
|
|
476
|
-
const host = parsed.hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
477
|
-
if (["http:", "https:"].includes(parsed.protocol) && ["127.0.0.1", "localhost", "::1"].includes(host)) {
|
|
478
|
-
return normalized.replace(/\/+$/, "");
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
catch {
|
|
482
|
-
return undefined;
|
|
483
|
-
}
|
|
484
|
-
return undefined;
|
|
485
|
-
}
|
|
486
469
|
function resolveLicensingApiBaseUrl() {
|
|
487
|
-
return normalizeLicensingApiBaseUrl(
|
|
470
|
+
return normalizeLicensingApiBaseUrl(developmentLicensingApiBaseUrlOverride);
|
|
488
471
|
}
|
|
489
472
|
function setDevelopmentLicensingApiBaseUrlOverride(value) {
|
|
490
473
|
developmentLicensingApiBaseUrlOverride = value;
|
|
@@ -523,6 +506,9 @@ function collectRequestedFeatures(request) {
|
|
|
523
506
|
if (countCustomWorkerPlugins(context) > 0) {
|
|
524
507
|
features.add("extensions.worker_plugins.custom");
|
|
525
508
|
}
|
|
509
|
+
if (asList(pickValue(context, "RuntimePolicies", "runtimePolicies")).length > 0) {
|
|
510
|
+
features.add("policy.runtime_controls");
|
|
511
|
+
}
|
|
526
512
|
const reportingSinks = asList(pickValue(context, "ReportingSinks", "reportingSinks"));
|
|
527
513
|
let hasCustomSink = false;
|
|
528
514
|
for (const sink of reportingSinks) {
|
|
@@ -818,159 +804,171 @@ async function evaluateScenarioOutcome(scenario, requestCount, context) {
|
|
|
818
804
|
validateTrackingConfiguration(tracking, sourceEndpoint, destinationEndpoint);
|
|
819
805
|
const sourceAdapter = EndpointAdapterFactory.create(sourceEndpoint);
|
|
820
806
|
const destinationAdapter = destinationEndpoint ? EndpointAdapterFactory.create(destinationEndpoint) : null;
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
let
|
|
870
|
-
let attempts = 0;
|
|
871
|
-
const maxAttempts = 1 + (restartOnFail ? restartMaxAttempts : 0);
|
|
872
|
-
while (!iterationComplete) {
|
|
873
|
-
attempts += 1;
|
|
807
|
+
let correlationStore = null;
|
|
808
|
+
try {
|
|
809
|
+
await sourceAdapter.initialize?.();
|
|
810
|
+
await destinationAdapter?.initialize?.();
|
|
811
|
+
const correlationTimeoutOverride = pickValue(tracking, "CorrelationTimeoutMs", "correlationTimeoutMs");
|
|
812
|
+
const correlationTimeoutSeconds = pickValue(tracking, "CorrelationTimeoutSeconds", "correlationTimeoutSeconds", "CorrelationTimeout");
|
|
813
|
+
const timeoutMs = correlationTimeoutOverride != null && String(correlationTimeoutOverride).trim() !== ""
|
|
814
|
+
? asInt(correlationTimeoutOverride)
|
|
815
|
+
: Math.trunc((correlationTimeoutSeconds == null || String(correlationTimeoutSeconds).trim() === ""
|
|
816
|
+
? 30
|
|
817
|
+
: asNumber(correlationTimeoutSeconds)) * 1000);
|
|
818
|
+
const timeoutCountsAsFailure = toBoolean(pickValue(tracking, "TimeoutCountsAsFailure", "timeoutCountsAsFailure"), true);
|
|
819
|
+
correlationStore = mapCorrelationStore(tracking, buildTrackingRunNamespace(stringOrDefault(pickValue(context, "SessionId", "sessionId"), "session"), stringOrDefault(pickValue(scenario, "Name", "name"), "scenario"), sourceEndpoint.name, destinationEndpoint?.name));
|
|
820
|
+
const timeoutSweepIntervalOverride = pickValue(tracking, "TimeoutSweepIntervalMs", "timeoutSweepIntervalMs");
|
|
821
|
+
const timeoutSweepIntervalSeconds = pickValue(tracking, "TimeoutSweepIntervalSeconds", "timeoutSweepIntervalSeconds");
|
|
822
|
+
const timeoutSweepIntervalMs = timeoutSweepIntervalOverride != null && String(timeoutSweepIntervalOverride).trim() !== ""
|
|
823
|
+
? asInt(timeoutSweepIntervalOverride)
|
|
824
|
+
: Math.trunc((timeoutSweepIntervalSeconds == null || String(timeoutSweepIntervalSeconds).trim() === ""
|
|
825
|
+
? 1
|
|
826
|
+
: asNumber(timeoutSweepIntervalSeconds)) * 1000);
|
|
827
|
+
const timeoutBatchSizeValue = pickValue(tracking, "TimeoutBatchSize", "timeoutBatchSize");
|
|
828
|
+
const timeoutBatchSize = timeoutBatchSizeValue == null || String(timeoutBatchSizeValue).trim() === ""
|
|
829
|
+
? 200
|
|
830
|
+
: asInt(timeoutBatchSizeValue);
|
|
831
|
+
const runtime = new CrossPlatformTrackingRuntime({
|
|
832
|
+
sourceTrackingField: sourceEndpoint.trackingField,
|
|
833
|
+
destinationTrackingField: destinationEndpoint?.trackingField,
|
|
834
|
+
destinationGatherByField: destinationEndpoint?.gatherByField,
|
|
835
|
+
trackingFieldValueCaseSensitive: toBoolean(pickValue(tracking, "TrackingFieldValueCaseSensitive", "trackingFieldValueCaseSensitive"), true),
|
|
836
|
+
gatherByFieldValueCaseSensitive: toBoolean(pickValue(tracking, "GatherByFieldValueCaseSensitive", "gatherByFieldValueCaseSensitive"), true),
|
|
837
|
+
correlationTimeoutMs: timeoutMs,
|
|
838
|
+
timeoutCountsAsFailure,
|
|
839
|
+
store: correlationStore ?? undefined
|
|
840
|
+
});
|
|
841
|
+
const runMode = stringOrDefault(pickValue(tracking, "RunMode", "runMode"), "GenerateAndCorrelate").trim().toLowerCase();
|
|
842
|
+
const sourceOnlyMode = !destinationEndpoint;
|
|
843
|
+
const restartOnFail = toBoolean(pickValue(scenario, "RestartIterationOnFail", "restartIterationOnFail"), false);
|
|
844
|
+
const restartMaxAttempts = Math.max(asInt(pickValue(context, "RestartIterationMaxAttempts", "restartIterationMaxAttempts")), 0);
|
|
845
|
+
const maxFailCount = Math.max(asInt(pickValue(scenario, "MaxFailCount", "maxFailCount")), 0);
|
|
846
|
+
const scenarioCompletionTimeoutSeconds = Math.max(asNumber(pickValue(context, "ScenarioCompletionTimeoutSeconds", "scenarioCompletionTimeoutSeconds")), 0);
|
|
847
|
+
const scenarioDeadlineMs = scenarioCompletionTimeoutSeconds > 0
|
|
848
|
+
? Date.now() + Math.trunc(scenarioCompletionTimeoutSeconds * 1000)
|
|
849
|
+
: Number.POSITIVE_INFINITY;
|
|
850
|
+
let okCount = 0;
|
|
851
|
+
let failCount = 0;
|
|
852
|
+
let nowMs = Date.now();
|
|
853
|
+
let processedIterations = 0;
|
|
854
|
+
let lastSweepAtMs = nowMs;
|
|
855
|
+
for (let i = 0; i < requestCount; i += 1) {
|
|
874
856
|
if (Date.now() > scenarioDeadlineMs) {
|
|
875
|
-
iterationComplete = true;
|
|
876
857
|
break;
|
|
877
858
|
}
|
|
878
|
-
let
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
859
|
+
let iterationComplete = false;
|
|
860
|
+
let attempts = 0;
|
|
861
|
+
const maxAttempts = 1 + (restartOnFail ? restartMaxAttempts : 0);
|
|
862
|
+
while (!iterationComplete) {
|
|
863
|
+
attempts += 1;
|
|
864
|
+
if (Date.now() > scenarioDeadlineMs) {
|
|
865
|
+
iterationComplete = true;
|
|
866
|
+
break;
|
|
867
|
+
}
|
|
868
|
+
let sourcePayload = runMode === "correlateexistingtraffic"
|
|
869
|
+
? await sourceAdapter.consume()
|
|
870
|
+
: await sourceAdapter.produce();
|
|
871
|
+
sourcePayload = normalizePayload(sourcePayload, sourceEndpoint, i);
|
|
872
|
+
const sourceTrackingId = sourceOnlyMode
|
|
873
|
+
? readTrackingId(sourcePayload, sourceEndpoint.trackingField)
|
|
874
|
+
: await runtime.onSourceProduced(sourcePayload, nowMs);
|
|
875
|
+
if (!sourceTrackingId) {
|
|
876
|
+
if (restartOnFail && attempts < maxAttempts) {
|
|
877
|
+
nowMs += 1;
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
failCount += 1;
|
|
881
|
+
iterationComplete = true;
|
|
887
882
|
nowMs += 1;
|
|
888
|
-
|
|
883
|
+
break;
|
|
889
884
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
}
|
|
895
|
-
if (sourceOnlyMode || !destinationAdapter || !destinationEndpoint) {
|
|
896
|
-
okCount += 1;
|
|
897
|
-
iterationComplete = true;
|
|
898
|
-
nowMs += 1;
|
|
899
|
-
break;
|
|
900
|
-
}
|
|
901
|
-
let destinationPayload = await destinationAdapter.consume();
|
|
902
|
-
destinationPayload = normalizePayload(destinationPayload, destinationEndpoint, i);
|
|
903
|
-
let matched = await runtime.onDestinationConsumed(destinationPayload, nowMs + 1);
|
|
904
|
-
const correlationDeadlineMs = nowMs + timeoutMs;
|
|
905
|
-
while (!matched && Date.now() <= scenarioDeadlineMs && nowMs < correlationDeadlineMs) {
|
|
906
|
-
const remainingTimeoutMs = correlationDeadlineMs - nowMs;
|
|
907
|
-
if (remainingTimeoutMs <= 0) {
|
|
885
|
+
if (sourceOnlyMode || !destinationAdapter || !destinationEndpoint) {
|
|
886
|
+
okCount += 1;
|
|
887
|
+
iterationComplete = true;
|
|
888
|
+
nowMs += 1;
|
|
908
889
|
break;
|
|
909
890
|
}
|
|
910
|
-
await
|
|
911
|
-
destinationPayload = normalizePayload(
|
|
912
|
-
matched = await runtime.onDestinationConsumed(destinationPayload, nowMs + 1);
|
|
891
|
+
let destinationPayload = await destinationAdapter.consume();
|
|
892
|
+
destinationPayload = normalizePayload(destinationPayload, destinationEndpoint, i);
|
|
893
|
+
let matched = await runtime.onDestinationConsumed(destinationPayload, nowMs + 1);
|
|
894
|
+
const correlationDeadlineMs = nowMs + timeoutMs;
|
|
895
|
+
while (!matched && Date.now() <= scenarioDeadlineMs && nowMs < correlationDeadlineMs) {
|
|
896
|
+
const remainingTimeoutMs = correlationDeadlineMs - nowMs;
|
|
897
|
+
if (remainingTimeoutMs <= 0) {
|
|
898
|
+
break;
|
|
899
|
+
}
|
|
900
|
+
await sleep(Math.min(destinationEndpoint.pollIntervalMs ?? 250, remainingTimeoutMs));
|
|
901
|
+
destinationPayload = normalizePayload(await destinationAdapter.consume(), destinationEndpoint, i);
|
|
902
|
+
matched = await runtime.onDestinationConsumed(destinationPayload, nowMs + 1);
|
|
903
|
+
if (matched) {
|
|
904
|
+
break;
|
|
905
|
+
}
|
|
906
|
+
nowMs += Math.max(destinationEndpoint.pollIntervalMs ?? 250, 1);
|
|
907
|
+
}
|
|
913
908
|
if (matched) {
|
|
914
|
-
|
|
909
|
+
okCount += 1;
|
|
910
|
+
iterationComplete = true;
|
|
915
911
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
okCount += 1;
|
|
920
|
-
iterationComplete = true;
|
|
921
|
-
}
|
|
922
|
-
else if (restartOnFail && attempts < maxAttempts) {
|
|
923
|
-
nowMs += 2;
|
|
924
|
-
continue;
|
|
925
|
-
}
|
|
926
|
-
else {
|
|
927
|
-
await runtime.sweepTimeouts(nowMs + timeoutMs + 1, timeoutBatchSize || undefined);
|
|
928
|
-
failCount += 1;
|
|
929
|
-
iterationComplete = true;
|
|
930
|
-
}
|
|
931
|
-
nowMs += 2;
|
|
932
|
-
}
|
|
933
|
-
processedIterations += 1;
|
|
934
|
-
if (timeoutSweepIntervalMs > 0 && nowMs - lastSweepAtMs >= timeoutSweepIntervalMs) {
|
|
935
|
-
const swept = await runtime.sweepTimeouts(nowMs, timeoutBatchSize || undefined);
|
|
936
|
-
if (swept > 0) {
|
|
937
|
-
if (timeoutCountsAsFailure) {
|
|
938
|
-
failCount += swept;
|
|
912
|
+
else if (restartOnFail && attempts < maxAttempts) {
|
|
913
|
+
nowMs += 2;
|
|
914
|
+
continue;
|
|
939
915
|
}
|
|
940
916
|
else {
|
|
941
|
-
|
|
917
|
+
await runtime.sweepTimeouts(nowMs + timeoutMs + 1, timeoutBatchSize || undefined);
|
|
918
|
+
failCount += 1;
|
|
919
|
+
iterationComplete = true;
|
|
942
920
|
}
|
|
921
|
+
nowMs += 2;
|
|
922
|
+
}
|
|
923
|
+
processedIterations += 1;
|
|
924
|
+
if (timeoutSweepIntervalMs > 0 && nowMs - lastSweepAtMs >= timeoutSweepIntervalMs) {
|
|
925
|
+
const swept = await runtime.sweepTimeouts(nowMs, timeoutBatchSize || undefined);
|
|
926
|
+
if (swept > 0) {
|
|
927
|
+
if (timeoutCountsAsFailure) {
|
|
928
|
+
failCount += swept;
|
|
929
|
+
}
|
|
930
|
+
else {
|
|
931
|
+
okCount += swept;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
lastSweepAtMs = nowMs;
|
|
935
|
+
}
|
|
936
|
+
if (maxFailCount > 0 && failCount >= maxFailCount) {
|
|
937
|
+
break;
|
|
943
938
|
}
|
|
944
|
-
lastSweepAtMs = nowMs;
|
|
945
|
-
}
|
|
946
|
-
if (maxFailCount > 0 && failCount >= maxFailCount) {
|
|
947
|
-
break;
|
|
948
939
|
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
940
|
+
let expired = 0;
|
|
941
|
+
let sweptChunk = 0;
|
|
942
|
+
do {
|
|
943
|
+
sweptChunk = await runtime.sweepTimeouts(nowMs + timeoutMs + 1, timeoutBatchSize || undefined);
|
|
944
|
+
expired += sweptChunk;
|
|
945
|
+
} while (timeoutBatchSize > 0 && sweptChunk >= timeoutBatchSize);
|
|
946
|
+
if (expired > 0) {
|
|
947
|
+
if (timeoutCountsAsFailure) {
|
|
948
|
+
failCount += expired;
|
|
949
|
+
}
|
|
950
|
+
else {
|
|
951
|
+
okCount += expired;
|
|
952
|
+
}
|
|
959
953
|
}
|
|
960
|
-
|
|
961
|
-
|
|
954
|
+
if (processedIterations < requestCount && Date.now() > scenarioDeadlineMs) {
|
|
955
|
+
const timedOutIterations = requestCount - processedIterations;
|
|
956
|
+
if (timeoutCountsAsFailure) {
|
|
957
|
+
failCount += timedOutIterations;
|
|
958
|
+
}
|
|
959
|
+
else {
|
|
960
|
+
okCount += timedOutIterations;
|
|
961
|
+
}
|
|
962
962
|
}
|
|
963
|
+
return { requestCount: processedIterations, okCount, failCount };
|
|
963
964
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
okCount += timedOutIterations;
|
|
971
|
-
}
|
|
965
|
+
finally {
|
|
966
|
+
await Promise.allSettled([
|
|
967
|
+
sourceAdapter.dispose?.(),
|
|
968
|
+
destinationAdapter?.dispose?.(),
|
|
969
|
+
correlationStore?.close?.()
|
|
970
|
+
].filter((value) => Boolean(value)));
|
|
972
971
|
}
|
|
973
|
-
return { requestCount: processedIterations, okCount, failCount };
|
|
974
972
|
}
|
|
975
973
|
function validateTrackingConfiguration(tracking, sourceEndpoint, destinationEndpoint) {
|
|
976
974
|
if (sourceEndpoint.gatherByField?.trim()) {
|
|
@@ -1007,10 +1005,20 @@ function validateTrackingConfiguration(tracking, sourceEndpoint, destinationEndp
|
|
|
1007
1005
|
if (!metricPrefix.trim()) {
|
|
1008
1006
|
throw new Error("MetricPrefix must be provided.");
|
|
1009
1007
|
}
|
|
1008
|
+
const observationDurationOverride = pickValue(tracking, "ObservationDurationMs", "observationDurationMs");
|
|
1009
|
+
const observationDurationSeconds = pickValue(tracking, "ObservationDurationSeconds", "observationDurationSeconds", "ObservationDuration");
|
|
1010
|
+
const observationDurationMs = observationDurationOverride != null && String(observationDurationOverride).trim() !== ""
|
|
1011
|
+
? asNumber(observationDurationOverride)
|
|
1012
|
+
: (observationDurationSeconds == null || String(observationDurationSeconds).trim() === ""
|
|
1013
|
+
? 0
|
|
1014
|
+
: asNumber(observationDurationSeconds) * 1000);
|
|
1010
1015
|
validateRedisCorrelationStoreConfiguration(tracking);
|
|
1011
1016
|
const runModeRaw = stringOrDefault(pickValue(tracking, "RunMode", "runMode"), "GenerateAndCorrelate").trim().toLowerCase();
|
|
1012
1017
|
const runMode = runModeRaw === "sourceonly" ? "generateandcorrelate" : runModeRaw;
|
|
1013
1018
|
if (runMode === "generateandcorrelate") {
|
|
1019
|
+
if (observationDurationMs > 0) {
|
|
1020
|
+
throw new Error("ForDuration is only supported for CorrelateExistingTraffic.");
|
|
1021
|
+
}
|
|
1014
1022
|
if (sourceEndpoint.mode !== "Produce") {
|
|
1015
1023
|
throw new Error("Source endpoint mode must be Produce for GenerateAndCorrelate mode.");
|
|
1016
1024
|
}
|
|
@@ -1020,6 +1028,9 @@ function validateTrackingConfiguration(tracking, sourceEndpoint, destinationEndp
|
|
|
1020
1028
|
return;
|
|
1021
1029
|
}
|
|
1022
1030
|
if (runMode === "correlateexistingtraffic") {
|
|
1031
|
+
if (observationDurationMs <= 0) {
|
|
1032
|
+
throw new Error("CorrelateExistingTraffic requires ForDuration with a duration greater than zero.");
|
|
1033
|
+
}
|
|
1023
1034
|
if (!destinationEndpoint) {
|
|
1024
1035
|
throw new Error("Destination endpoint must be provided for CorrelateExistingTraffic mode.");
|
|
1025
1036
|
}
|