@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/README.md
CHANGED
|
@@ -55,7 +55,7 @@ const result = await LoadStrikeRunner
|
|
|
55
55
|
|
|
56
56
|
## Trace-To-Test Autopilot
|
|
57
57
|
|
|
58
|
-
Use `LoadStrikeAutopilot.generate(...)` to infer a starter plan from a captured artifact. Check `result.Readiness` and `result.ReadinessFailures` first; call `result.buildScenario()` only when it is `LoadStrikeAutopilotReadiness.Ready`, then execute the scenario through the normal runner with a valid `RunnerKey`.
|
|
58
|
+
Use `await LoadStrikeAutopilot.generate(...)` to infer a starter plan from a captured artifact. Set `Options.RunnerKey` so generation can validate the Trace-To-Test Autopilot entitlement. Check `result.Readiness` and `result.ReadinessFailures` first; call `result.buildScenario()` only when it is `LoadStrikeAutopilotReadiness.Ready`, then execute the scenario through the normal runner with a valid `RunnerKey`.
|
|
59
59
|
|
|
60
60
|
Use `SecretBindings` to map redaction locations such as `header:Authorization` or `body:$.client_secret` to environment variables, `TrackingSelector` when the selector cannot be inferred, and `EndpointBindings`, `AllowedReplayHosts`, or `BaseUrlRewrite` when a replay target must be bound. Secret values are resolved when the generated scenario runs; they are not written into the generated plan. Any gate satisfied by user setup is omitted from `ReadinessFailures`.
|
|
61
61
|
|
package/dist/cjs/autopilot.js
CHANGED
|
@@ -15,14 +15,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
15
15
|
};
|
|
16
16
|
var _LoadStrikeAutopilotResult_httpRequest;
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
exports.LoadStrikeAutopilot = exports.LoadStrikeAutopilotResult = void 0;
|
|
18
|
+
exports.__private = exports.LoadStrikeAutopilot = exports.LoadStrikeAutopilotResult = void 0;
|
|
19
19
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
20
20
|
const runtime_js_1 = require("./runtime.js");
|
|
21
|
+
const local_js_1 = require("./local.js");
|
|
21
22
|
const autopilot_contracts_js_1 = require("./autopilot-contracts.js");
|
|
22
23
|
const REDACTED = "[REDACTED]";
|
|
23
24
|
const ENV_MARKER_PREFIX = "${LOADSTRIKE_ENV:";
|
|
24
25
|
const ENV_MARKER_SUFFIX = "}";
|
|
25
26
|
const TRACE_TO_TEST_AUTOPILOT_FEATURE = "autopilot.trace_to_test";
|
|
27
|
+
let autopilotLicenseValidationBypassForTests = false;
|
|
26
28
|
const SECRET_KEYS = new Set([
|
|
27
29
|
"authorization",
|
|
28
30
|
"proxy_authorization",
|
|
@@ -95,9 +97,10 @@ class LoadStrikeAutopilotResult {
|
|
|
95
97
|
exports.LoadStrikeAutopilotResult = LoadStrikeAutopilotResult;
|
|
96
98
|
_LoadStrikeAutopilotResult_httpRequest = new WeakMap();
|
|
97
99
|
class LoadStrikeAutopilot {
|
|
98
|
-
static generate(request) {
|
|
100
|
+
static async generate(request) {
|
|
99
101
|
const artifact = loadAutopilotArtifact(request);
|
|
100
102
|
const options = request.Options ?? {};
|
|
103
|
+
await validateGenerationEntitlement(options);
|
|
101
104
|
const result = inferAutopilotResult(artifact, options);
|
|
102
105
|
if (options.IncludePreviewReport) {
|
|
103
106
|
result.PreviewReport = buildPreviewReport(result);
|
|
@@ -109,6 +112,57 @@ class LoadStrikeAutopilot {
|
|
|
109
112
|
}
|
|
110
113
|
}
|
|
111
114
|
exports.LoadStrikeAutopilot = LoadStrikeAutopilot;
|
|
115
|
+
exports.__private = {
|
|
116
|
+
setAutopilotLicenseValidationBypassForTests(value) {
|
|
117
|
+
autopilotLicenseValidationBypassForTests = value;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
async function validateGenerationEntitlement(options) {
|
|
121
|
+
if (autopilotLicenseValidationBypassForTests) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const licensePayload = {
|
|
125
|
+
Context: {
|
|
126
|
+
RunnerKey: options.RunnerKey ?? "",
|
|
127
|
+
LicenseValidationTimeoutSeconds: options.LicenseValidationTimeoutSeconds,
|
|
128
|
+
TestSuite: "loadstrike-autopilot",
|
|
129
|
+
TestName: options.ScenarioName?.trim() || "autopilot-generation"
|
|
130
|
+
},
|
|
131
|
+
Scenarios: [
|
|
132
|
+
{
|
|
133
|
+
Name: "autopilot-generation",
|
|
134
|
+
InternalLicenseFeatures: [TRACE_TO_TEST_AUTOPILOT_FEATURE]
|
|
135
|
+
}
|
|
136
|
+
],
|
|
137
|
+
RunArgs: []
|
|
138
|
+
};
|
|
139
|
+
const client = new local_js_1.LoadStrikeLocalClient({
|
|
140
|
+
licenseValidationTimeoutMs: normalizeLicenseValidationTimeoutMs(options.LicenseValidationTimeoutSeconds)
|
|
141
|
+
});
|
|
142
|
+
let session;
|
|
143
|
+
try {
|
|
144
|
+
session = await client.acquireLicenseLease(licensePayload);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
148
|
+
if (message.toLowerCase().includes("runner key is required")) {
|
|
149
|
+
throw new Error("Runner key is required for Trace-To-Test Autopilot generation. " +
|
|
150
|
+
"Set Options.RunnerKey before calling LoadStrikeAutopilot.generate(...).");
|
|
151
|
+
}
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
if (session) {
|
|
156
|
+
await client.releaseLicenseLease(session, licensePayload);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function normalizeLicenseValidationTimeoutMs(value) {
|
|
161
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
return Math.trunc(value * 1000);
|
|
165
|
+
}
|
|
112
166
|
function loadAutopilotArtifact(request) {
|
|
113
167
|
if (!request || typeof request.Kind !== "string" || !request.Kind.trim()) {
|
|
114
168
|
throw new Error("Autopilot kind must be provided.");
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.TimescaleDbReportingSinkOptions = exports.TimescaleDbReportingSink = exports.SplunkReportingSinkOptions = exports.SplunkReportingSink = exports.OtelCollectorReportingSinkOptions = exports.OtelCollectorReportingSink = void 0;
|
|
3
|
+
exports.InfluxDbReportingSink = exports.GrafanaLokiReportingSinkOptions = exports.GrafanaLokiReportingSink = exports.DatadogReportingSinkOptions = exports.DatadogReportingSink = exports.HttpAuthOptions = exports.HttpOAuth2ClientCredentialsOptions = exports.PushDiffusionEndpointDefinition = exports.DelegateStreamEndpointDefinition = exports.SqsEndpointDefinition = exports.AzureEventHubsEndpointDefinition = exports.RedisStreamsEndpointDefinition = exports.NatsEndpointDefinition = exports.RabbitMqEndpointDefinition = exports.KafkaSaslOptions = exports.KafkaEndpointDefinition = exports.HttpEndpointDefinition = exports.TrafficEndpointDefinition = exports.LOADSTRIKE_TRACE_ID_TRACKING_FIELD = exports.LOADSTRIKE_TRACE_ID_HEADER = exports.TrackingFieldSelector = exports.TrackingPayloadBuilder = exports.RedisCorrelationStore = exports.RedisCorrelationStoreOptions = exports.InMemoryCorrelationStore = exports.CrossPlatformTrackingRuntime = exports.CorrelationStoreConfiguration = exports.LoadStrikeThreshold = exports.LoadStrikeStep = exports.LoadStrikeGauge = exports.LoadStrikeCounter = exports.LoadStrikeMetric = exports.CrossPlatformTrackingConfiguration = exports.LoadStrikeSimulation = exports.LoadStrikeScenario = exports.LoadStrikeRunner = exports.LoadStrikeOperationType = exports.LoadStrikeScenarioOperation = exports.LoadStrikeLogLevel = exports.LoadStrikeResponse = exports.LoadStrikeReportFormat = exports.LoadStrikeNodeType = exports.LoadStrikePluginDataTable = exports.LoadStrikePluginData = exports.LoadStrikeContext = exports.ScenarioTrackingExtensions = exports.CrossPlatformScenarioConfigurator = exports.LoadStrikeAutopilotReadiness = exports.LoadStrikeAutopilotResult = exports.LoadStrikeAutopilot = void 0;
|
|
4
|
+
exports.TimescaleDbReportingSinkOptions = exports.TimescaleDbReportingSink = exports.SplunkReportingSinkOptions = exports.SplunkReportingSink = exports.OtelCollectorReportingSinkOptions = exports.OtelCollectorReportingSink = exports.InfluxDbReportingSinkOptions = void 0;
|
|
5
5
|
var autopilot_js_1 = require("./autopilot.js");
|
|
6
6
|
Object.defineProperty(exports, "LoadStrikeAutopilot", { enumerable: true, get: function () { return autopilot_js_1.LoadStrikeAutopilot; } });
|
|
7
7
|
Object.defineProperty(exports, "LoadStrikeAutopilotResult", { enumerable: true, get: function () { return autopilot_js_1.LoadStrikeAutopilotResult; } });
|
|
@@ -22,6 +22,7 @@ Object.defineProperty(exports, "LoadStrikeOperationType", { enumerable: true, ge
|
|
|
22
22
|
Object.defineProperty(exports, "LoadStrikeRunner", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeRunner; } });
|
|
23
23
|
Object.defineProperty(exports, "LoadStrikeScenario", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeScenario; } });
|
|
24
24
|
Object.defineProperty(exports, "LoadStrikeSimulation", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeSimulation; } });
|
|
25
|
+
Object.defineProperty(exports, "CrossPlatformTrackingConfiguration", { enumerable: true, get: function () { return runtime_js_1.CrossPlatformTrackingConfiguration; } });
|
|
25
26
|
Object.defineProperty(exports, "LoadStrikeMetric", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeMetric; } });
|
|
26
27
|
Object.defineProperty(exports, "LoadStrikeCounter", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeCounter; } });
|
|
27
28
|
Object.defineProperty(exports, "LoadStrikeGauge", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeGauge; } });
|
|
@@ -36,7 +37,6 @@ Object.defineProperty(exports, "RedisCorrelationStore", { enumerable: true, get:
|
|
|
36
37
|
Object.defineProperty(exports, "TrackingPayloadBuilder", { enumerable: true, get: function () { return correlation_js_1.TrackingPayloadBuilder; } });
|
|
37
38
|
Object.defineProperty(exports, "TrackingFieldSelector", { enumerable: true, get: function () { return correlation_js_1.TrackingFieldSelector; } });
|
|
38
39
|
var transports_js_1 = require("./transports.js");
|
|
39
|
-
Object.defineProperty(exports, "EndpointAdapterFactory", { enumerable: true, get: function () { return transports_js_1.EndpointAdapterFactory; } });
|
|
40
40
|
Object.defineProperty(exports, "LOADSTRIKE_TRACE_ID_HEADER", { enumerable: true, get: function () { return transports_js_1.LOADSTRIKE_TRACE_ID_HEADER; } });
|
|
41
41
|
Object.defineProperty(exports, "LOADSTRIKE_TRACE_ID_TRACKING_FIELD", { enumerable: true, get: function () { return transports_js_1.LOADSTRIKE_TRACE_ID_TRACKING_FIELD; } });
|
|
42
42
|
Object.defineProperty(exports, "TrafficEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.TrafficEndpointDefinition; } });
|
|
@@ -47,6 +47,7 @@ Object.defineProperty(exports, "RabbitMqEndpointDefinition", { enumerable: true,
|
|
|
47
47
|
Object.defineProperty(exports, "NatsEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.NatsEndpointDefinition; } });
|
|
48
48
|
Object.defineProperty(exports, "RedisStreamsEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.RedisStreamsEndpointDefinition; } });
|
|
49
49
|
Object.defineProperty(exports, "AzureEventHubsEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.AzureEventHubsEndpointDefinition; } });
|
|
50
|
+
Object.defineProperty(exports, "SqsEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.SqsEndpointDefinition; } });
|
|
50
51
|
Object.defineProperty(exports, "DelegateStreamEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.DelegateStreamEndpointDefinition; } });
|
|
51
52
|
Object.defineProperty(exports, "PushDiffusionEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.PushDiffusionEndpointDefinition; } });
|
|
52
53
|
Object.defineProperty(exports, "HttpOAuth2ClientCredentialsOptions", { enumerable: true, get: function () { return transports_js_1.HttpOAuth2ClientCredentialsOptions; } });
|
package/dist/cjs/local.js
CHANGED
|
@@ -44,7 +44,6 @@ const node_crypto_1 = require("node:crypto");
|
|
|
44
44
|
const correlation_js_1 = require("./correlation.js");
|
|
45
45
|
const transports_js_1 = require("./transports.js");
|
|
46
46
|
const DEFAULT_LICENSING_API_BASE_URL = "https://licensing.loadstrike.com";
|
|
47
|
-
const INTERNAL_BLACKBOX_LICENSING_API_BASE_URL_ENVIRONMENT_VARIABLE = "LOADSTRIKE_INTERNAL_BLACKBOX_API_BASE_URL";
|
|
48
47
|
let developmentLicensingApiBaseUrlOverride;
|
|
49
48
|
const BUILT_IN_WORKER_PLUGIN_NAMES = new Set([
|
|
50
49
|
"loadstrike failed responses",
|
|
@@ -66,7 +65,8 @@ const TRACKING_FEATURE_BY_KIND = {
|
|
|
66
65
|
pushdiffusion: "endpoint.push_diffusion",
|
|
67
66
|
delegatestream: "endpoint.delegate_stream",
|
|
68
67
|
nats: "endpoint.nats",
|
|
69
|
-
redisstreams: "endpoint.redis_streams"
|
|
68
|
+
redisstreams: "endpoint.redis_streams",
|
|
69
|
+
sqs: "endpoint.sqs"
|
|
70
70
|
};
|
|
71
71
|
const CI_ENVIRONMENT_VARIABLES = [
|
|
72
72
|
"GITHUB_ACTIONS",
|
|
@@ -506,25 +506,8 @@ function normalizeLicensingApiBaseUrl(value) {
|
|
|
506
506
|
const normalized = (value ?? "").trim();
|
|
507
507
|
return normalized || DEFAULT_LICENSING_API_BASE_URL;
|
|
508
508
|
}
|
|
509
|
-
function resolveInternalBlackboxLicensingApiBaseUrlOverride(value) {
|
|
510
|
-
const normalized = String(value ?? "").trim();
|
|
511
|
-
if (!normalized) {
|
|
512
|
-
return undefined;
|
|
513
|
-
}
|
|
514
|
-
try {
|
|
515
|
-
const parsed = new URL(normalized);
|
|
516
|
-
const host = parsed.hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
517
|
-
if (["http:", "https:"].includes(parsed.protocol) && ["127.0.0.1", "localhost", "::1"].includes(host)) {
|
|
518
|
-
return normalized.replace(/\/+$/, "");
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
catch {
|
|
522
|
-
return undefined;
|
|
523
|
-
}
|
|
524
|
-
return undefined;
|
|
525
|
-
}
|
|
526
509
|
function resolveLicensingApiBaseUrl() {
|
|
527
|
-
return normalizeLicensingApiBaseUrl(
|
|
510
|
+
return normalizeLicensingApiBaseUrl(developmentLicensingApiBaseUrlOverride);
|
|
528
511
|
}
|
|
529
512
|
function setDevelopmentLicensingApiBaseUrlOverride(value) {
|
|
530
513
|
developmentLicensingApiBaseUrlOverride = value;
|
|
@@ -563,6 +546,9 @@ function collectRequestedFeatures(request) {
|
|
|
563
546
|
if (countCustomWorkerPlugins(context) > 0) {
|
|
564
547
|
features.add("extensions.worker_plugins.custom");
|
|
565
548
|
}
|
|
549
|
+
if (asList(pickValue(context, "RuntimePolicies", "runtimePolicies")).length > 0) {
|
|
550
|
+
features.add("policy.runtime_controls");
|
|
551
|
+
}
|
|
566
552
|
const reportingSinks = asList(pickValue(context, "ReportingSinks", "reportingSinks"));
|
|
567
553
|
let hasCustomSink = false;
|
|
568
554
|
for (const sink of reportingSinks) {
|
|
@@ -858,159 +844,171 @@ async function evaluateScenarioOutcome(scenario, requestCount, context) {
|
|
|
858
844
|
validateTrackingConfiguration(tracking, sourceEndpoint, destinationEndpoint);
|
|
859
845
|
const sourceAdapter = transports_js_1.EndpointAdapterFactory.create(sourceEndpoint);
|
|
860
846
|
const destinationAdapter = destinationEndpoint ? transports_js_1.EndpointAdapterFactory.create(destinationEndpoint) : null;
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
let
|
|
910
|
-
let attempts = 0;
|
|
911
|
-
const maxAttempts = 1 + (restartOnFail ? restartMaxAttempts : 0);
|
|
912
|
-
while (!iterationComplete) {
|
|
913
|
-
attempts += 1;
|
|
847
|
+
let correlationStore = null;
|
|
848
|
+
try {
|
|
849
|
+
await sourceAdapter.initialize?.();
|
|
850
|
+
await destinationAdapter?.initialize?.();
|
|
851
|
+
const correlationTimeoutOverride = pickValue(tracking, "CorrelationTimeoutMs", "correlationTimeoutMs");
|
|
852
|
+
const correlationTimeoutSeconds = pickValue(tracking, "CorrelationTimeoutSeconds", "correlationTimeoutSeconds", "CorrelationTimeout");
|
|
853
|
+
const timeoutMs = correlationTimeoutOverride != null && String(correlationTimeoutOverride).trim() !== ""
|
|
854
|
+
? asInt(correlationTimeoutOverride)
|
|
855
|
+
: Math.trunc((correlationTimeoutSeconds == null || String(correlationTimeoutSeconds).trim() === ""
|
|
856
|
+
? 30
|
|
857
|
+
: asNumber(correlationTimeoutSeconds)) * 1000);
|
|
858
|
+
const timeoutCountsAsFailure = toBoolean(pickValue(tracking, "TimeoutCountsAsFailure", "timeoutCountsAsFailure"), true);
|
|
859
|
+
correlationStore = mapCorrelationStore(tracking, buildTrackingRunNamespace(stringOrDefault(pickValue(context, "SessionId", "sessionId"), "session"), stringOrDefault(pickValue(scenario, "Name", "name"), "scenario"), sourceEndpoint.name, destinationEndpoint?.name));
|
|
860
|
+
const timeoutSweepIntervalOverride = pickValue(tracking, "TimeoutSweepIntervalMs", "timeoutSweepIntervalMs");
|
|
861
|
+
const timeoutSweepIntervalSeconds = pickValue(tracking, "TimeoutSweepIntervalSeconds", "timeoutSweepIntervalSeconds");
|
|
862
|
+
const timeoutSweepIntervalMs = timeoutSweepIntervalOverride != null && String(timeoutSweepIntervalOverride).trim() !== ""
|
|
863
|
+
? asInt(timeoutSweepIntervalOverride)
|
|
864
|
+
: Math.trunc((timeoutSweepIntervalSeconds == null || String(timeoutSweepIntervalSeconds).trim() === ""
|
|
865
|
+
? 1
|
|
866
|
+
: asNumber(timeoutSweepIntervalSeconds)) * 1000);
|
|
867
|
+
const timeoutBatchSizeValue = pickValue(tracking, "TimeoutBatchSize", "timeoutBatchSize");
|
|
868
|
+
const timeoutBatchSize = timeoutBatchSizeValue == null || String(timeoutBatchSizeValue).trim() === ""
|
|
869
|
+
? 200
|
|
870
|
+
: asInt(timeoutBatchSizeValue);
|
|
871
|
+
const runtime = new correlation_js_1.CrossPlatformTrackingRuntime({
|
|
872
|
+
sourceTrackingField: sourceEndpoint.trackingField,
|
|
873
|
+
destinationTrackingField: destinationEndpoint?.trackingField,
|
|
874
|
+
destinationGatherByField: destinationEndpoint?.gatherByField,
|
|
875
|
+
trackingFieldValueCaseSensitive: toBoolean(pickValue(tracking, "TrackingFieldValueCaseSensitive", "trackingFieldValueCaseSensitive"), true),
|
|
876
|
+
gatherByFieldValueCaseSensitive: toBoolean(pickValue(tracking, "GatherByFieldValueCaseSensitive", "gatherByFieldValueCaseSensitive"), true),
|
|
877
|
+
correlationTimeoutMs: timeoutMs,
|
|
878
|
+
timeoutCountsAsFailure,
|
|
879
|
+
store: correlationStore ?? undefined
|
|
880
|
+
});
|
|
881
|
+
const runMode = stringOrDefault(pickValue(tracking, "RunMode", "runMode"), "GenerateAndCorrelate").trim().toLowerCase();
|
|
882
|
+
const sourceOnlyMode = !destinationEndpoint;
|
|
883
|
+
const restartOnFail = toBoolean(pickValue(scenario, "RestartIterationOnFail", "restartIterationOnFail"), false);
|
|
884
|
+
const restartMaxAttempts = Math.max(asInt(pickValue(context, "RestartIterationMaxAttempts", "restartIterationMaxAttempts")), 0);
|
|
885
|
+
const maxFailCount = Math.max(asInt(pickValue(scenario, "MaxFailCount", "maxFailCount")), 0);
|
|
886
|
+
const scenarioCompletionTimeoutSeconds = Math.max(asNumber(pickValue(context, "ScenarioCompletionTimeoutSeconds", "scenarioCompletionTimeoutSeconds")), 0);
|
|
887
|
+
const scenarioDeadlineMs = scenarioCompletionTimeoutSeconds > 0
|
|
888
|
+
? Date.now() + Math.trunc(scenarioCompletionTimeoutSeconds * 1000)
|
|
889
|
+
: Number.POSITIVE_INFINITY;
|
|
890
|
+
let okCount = 0;
|
|
891
|
+
let failCount = 0;
|
|
892
|
+
let nowMs = Date.now();
|
|
893
|
+
let processedIterations = 0;
|
|
894
|
+
let lastSweepAtMs = nowMs;
|
|
895
|
+
for (let i = 0; i < requestCount; i += 1) {
|
|
914
896
|
if (Date.now() > scenarioDeadlineMs) {
|
|
915
|
-
iterationComplete = true;
|
|
916
897
|
break;
|
|
917
898
|
}
|
|
918
|
-
let
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
899
|
+
let iterationComplete = false;
|
|
900
|
+
let attempts = 0;
|
|
901
|
+
const maxAttempts = 1 + (restartOnFail ? restartMaxAttempts : 0);
|
|
902
|
+
while (!iterationComplete) {
|
|
903
|
+
attempts += 1;
|
|
904
|
+
if (Date.now() > scenarioDeadlineMs) {
|
|
905
|
+
iterationComplete = true;
|
|
906
|
+
break;
|
|
907
|
+
}
|
|
908
|
+
let sourcePayload = runMode === "correlateexistingtraffic"
|
|
909
|
+
? await sourceAdapter.consume()
|
|
910
|
+
: await sourceAdapter.produce();
|
|
911
|
+
sourcePayload = normalizePayload(sourcePayload, sourceEndpoint, i);
|
|
912
|
+
const sourceTrackingId = sourceOnlyMode
|
|
913
|
+
? readTrackingId(sourcePayload, sourceEndpoint.trackingField)
|
|
914
|
+
: await runtime.onSourceProduced(sourcePayload, nowMs);
|
|
915
|
+
if (!sourceTrackingId) {
|
|
916
|
+
if (restartOnFail && attempts < maxAttempts) {
|
|
917
|
+
nowMs += 1;
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
failCount += 1;
|
|
921
|
+
iterationComplete = true;
|
|
927
922
|
nowMs += 1;
|
|
928
|
-
|
|
923
|
+
break;
|
|
929
924
|
}
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
}
|
|
935
|
-
if (sourceOnlyMode || !destinationAdapter || !destinationEndpoint) {
|
|
936
|
-
okCount += 1;
|
|
937
|
-
iterationComplete = true;
|
|
938
|
-
nowMs += 1;
|
|
939
|
-
break;
|
|
940
|
-
}
|
|
941
|
-
let destinationPayload = await destinationAdapter.consume();
|
|
942
|
-
destinationPayload = normalizePayload(destinationPayload, destinationEndpoint, i);
|
|
943
|
-
let matched = await runtime.onDestinationConsumed(destinationPayload, nowMs + 1);
|
|
944
|
-
const correlationDeadlineMs = nowMs + timeoutMs;
|
|
945
|
-
while (!matched && Date.now() <= scenarioDeadlineMs && nowMs < correlationDeadlineMs) {
|
|
946
|
-
const remainingTimeoutMs = correlationDeadlineMs - nowMs;
|
|
947
|
-
if (remainingTimeoutMs <= 0) {
|
|
925
|
+
if (sourceOnlyMode || !destinationAdapter || !destinationEndpoint) {
|
|
926
|
+
okCount += 1;
|
|
927
|
+
iterationComplete = true;
|
|
928
|
+
nowMs += 1;
|
|
948
929
|
break;
|
|
949
930
|
}
|
|
950
|
-
await
|
|
951
|
-
destinationPayload = normalizePayload(
|
|
952
|
-
matched = await runtime.onDestinationConsumed(destinationPayload, nowMs + 1);
|
|
931
|
+
let destinationPayload = await destinationAdapter.consume();
|
|
932
|
+
destinationPayload = normalizePayload(destinationPayload, destinationEndpoint, i);
|
|
933
|
+
let matched = await runtime.onDestinationConsumed(destinationPayload, nowMs + 1);
|
|
934
|
+
const correlationDeadlineMs = nowMs + timeoutMs;
|
|
935
|
+
while (!matched && Date.now() <= scenarioDeadlineMs && nowMs < correlationDeadlineMs) {
|
|
936
|
+
const remainingTimeoutMs = correlationDeadlineMs - nowMs;
|
|
937
|
+
if (remainingTimeoutMs <= 0) {
|
|
938
|
+
break;
|
|
939
|
+
}
|
|
940
|
+
await sleep(Math.min(destinationEndpoint.pollIntervalMs ?? 250, remainingTimeoutMs));
|
|
941
|
+
destinationPayload = normalizePayload(await destinationAdapter.consume(), destinationEndpoint, i);
|
|
942
|
+
matched = await runtime.onDestinationConsumed(destinationPayload, nowMs + 1);
|
|
943
|
+
if (matched) {
|
|
944
|
+
break;
|
|
945
|
+
}
|
|
946
|
+
nowMs += Math.max(destinationEndpoint.pollIntervalMs ?? 250, 1);
|
|
947
|
+
}
|
|
953
948
|
if (matched) {
|
|
954
|
-
|
|
949
|
+
okCount += 1;
|
|
950
|
+
iterationComplete = true;
|
|
955
951
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
okCount += 1;
|
|
960
|
-
iterationComplete = true;
|
|
961
|
-
}
|
|
962
|
-
else if (restartOnFail && attempts < maxAttempts) {
|
|
963
|
-
nowMs += 2;
|
|
964
|
-
continue;
|
|
965
|
-
}
|
|
966
|
-
else {
|
|
967
|
-
await runtime.sweepTimeouts(nowMs + timeoutMs + 1, timeoutBatchSize || undefined);
|
|
968
|
-
failCount += 1;
|
|
969
|
-
iterationComplete = true;
|
|
970
|
-
}
|
|
971
|
-
nowMs += 2;
|
|
972
|
-
}
|
|
973
|
-
processedIterations += 1;
|
|
974
|
-
if (timeoutSweepIntervalMs > 0 && nowMs - lastSweepAtMs >= timeoutSweepIntervalMs) {
|
|
975
|
-
const swept = await runtime.sweepTimeouts(nowMs, timeoutBatchSize || undefined);
|
|
976
|
-
if (swept > 0) {
|
|
977
|
-
if (timeoutCountsAsFailure) {
|
|
978
|
-
failCount += swept;
|
|
952
|
+
else if (restartOnFail && attempts < maxAttempts) {
|
|
953
|
+
nowMs += 2;
|
|
954
|
+
continue;
|
|
979
955
|
}
|
|
980
956
|
else {
|
|
981
|
-
|
|
957
|
+
await runtime.sweepTimeouts(nowMs + timeoutMs + 1, timeoutBatchSize || undefined);
|
|
958
|
+
failCount += 1;
|
|
959
|
+
iterationComplete = true;
|
|
982
960
|
}
|
|
961
|
+
nowMs += 2;
|
|
962
|
+
}
|
|
963
|
+
processedIterations += 1;
|
|
964
|
+
if (timeoutSweepIntervalMs > 0 && nowMs - lastSweepAtMs >= timeoutSweepIntervalMs) {
|
|
965
|
+
const swept = await runtime.sweepTimeouts(nowMs, timeoutBatchSize || undefined);
|
|
966
|
+
if (swept > 0) {
|
|
967
|
+
if (timeoutCountsAsFailure) {
|
|
968
|
+
failCount += swept;
|
|
969
|
+
}
|
|
970
|
+
else {
|
|
971
|
+
okCount += swept;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
lastSweepAtMs = nowMs;
|
|
975
|
+
}
|
|
976
|
+
if (maxFailCount > 0 && failCount >= maxFailCount) {
|
|
977
|
+
break;
|
|
983
978
|
}
|
|
984
|
-
lastSweepAtMs = nowMs;
|
|
985
|
-
}
|
|
986
|
-
if (maxFailCount > 0 && failCount >= maxFailCount) {
|
|
987
|
-
break;
|
|
988
979
|
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
980
|
+
let expired = 0;
|
|
981
|
+
let sweptChunk = 0;
|
|
982
|
+
do {
|
|
983
|
+
sweptChunk = await runtime.sweepTimeouts(nowMs + timeoutMs + 1, timeoutBatchSize || undefined);
|
|
984
|
+
expired += sweptChunk;
|
|
985
|
+
} while (timeoutBatchSize > 0 && sweptChunk >= timeoutBatchSize);
|
|
986
|
+
if (expired > 0) {
|
|
987
|
+
if (timeoutCountsAsFailure) {
|
|
988
|
+
failCount += expired;
|
|
989
|
+
}
|
|
990
|
+
else {
|
|
991
|
+
okCount += expired;
|
|
992
|
+
}
|
|
999
993
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
994
|
+
if (processedIterations < requestCount && Date.now() > scenarioDeadlineMs) {
|
|
995
|
+
const timedOutIterations = requestCount - processedIterations;
|
|
996
|
+
if (timeoutCountsAsFailure) {
|
|
997
|
+
failCount += timedOutIterations;
|
|
998
|
+
}
|
|
999
|
+
else {
|
|
1000
|
+
okCount += timedOutIterations;
|
|
1001
|
+
}
|
|
1002
1002
|
}
|
|
1003
|
+
return { requestCount: processedIterations, okCount, failCount };
|
|
1003
1004
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
okCount += timedOutIterations;
|
|
1011
|
-
}
|
|
1005
|
+
finally {
|
|
1006
|
+
await Promise.allSettled([
|
|
1007
|
+
sourceAdapter.dispose?.(),
|
|
1008
|
+
destinationAdapter?.dispose?.(),
|
|
1009
|
+
correlationStore?.close?.()
|
|
1010
|
+
].filter((value) => Boolean(value)));
|
|
1012
1011
|
}
|
|
1013
|
-
return { requestCount: processedIterations, okCount, failCount };
|
|
1014
1012
|
}
|
|
1015
1013
|
function validateTrackingConfiguration(tracking, sourceEndpoint, destinationEndpoint) {
|
|
1016
1014
|
if (sourceEndpoint.gatherByField?.trim()) {
|
|
@@ -1047,10 +1045,20 @@ function validateTrackingConfiguration(tracking, sourceEndpoint, destinationEndp
|
|
|
1047
1045
|
if (!metricPrefix.trim()) {
|
|
1048
1046
|
throw new Error("MetricPrefix must be provided.");
|
|
1049
1047
|
}
|
|
1048
|
+
const observationDurationOverride = pickValue(tracking, "ObservationDurationMs", "observationDurationMs");
|
|
1049
|
+
const observationDurationSeconds = pickValue(tracking, "ObservationDurationSeconds", "observationDurationSeconds", "ObservationDuration");
|
|
1050
|
+
const observationDurationMs = observationDurationOverride != null && String(observationDurationOverride).trim() !== ""
|
|
1051
|
+
? asNumber(observationDurationOverride)
|
|
1052
|
+
: (observationDurationSeconds == null || String(observationDurationSeconds).trim() === ""
|
|
1053
|
+
? 0
|
|
1054
|
+
: asNumber(observationDurationSeconds) * 1000);
|
|
1050
1055
|
validateRedisCorrelationStoreConfiguration(tracking);
|
|
1051
1056
|
const runModeRaw = stringOrDefault(pickValue(tracking, "RunMode", "runMode"), "GenerateAndCorrelate").trim().toLowerCase();
|
|
1052
1057
|
const runMode = runModeRaw === "sourceonly" ? "generateandcorrelate" : runModeRaw;
|
|
1053
1058
|
if (runMode === "generateandcorrelate") {
|
|
1059
|
+
if (observationDurationMs > 0) {
|
|
1060
|
+
throw new Error("ForDuration is only supported for CorrelateExistingTraffic.");
|
|
1061
|
+
}
|
|
1054
1062
|
if (sourceEndpoint.mode !== "Produce") {
|
|
1055
1063
|
throw new Error("Source endpoint mode must be Produce for GenerateAndCorrelate mode.");
|
|
1056
1064
|
}
|
|
@@ -1060,6 +1068,9 @@ function validateTrackingConfiguration(tracking, sourceEndpoint, destinationEndp
|
|
|
1060
1068
|
return;
|
|
1061
1069
|
}
|
|
1062
1070
|
if (runMode === "correlateexistingtraffic") {
|
|
1071
|
+
if (observationDurationMs <= 0) {
|
|
1072
|
+
throw new Error("CorrelateExistingTraffic requires ForDuration with a duration greater than zero.");
|
|
1073
|
+
}
|
|
1063
1074
|
if (!destinationEndpoint) {
|
|
1064
1075
|
throw new Error("Destination endpoint must be provided for CorrelateExistingTraffic mode.");
|
|
1065
1076
|
}
|