@loadstrike/loadstrike-sdk 1.0.22801 → 1.0.23201
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 +158 -156
- package/dist/cjs/runtime.js +53 -1
- package/dist/cjs/sinks.js +123 -19
- 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 +158 -156
- package/dist/esm/runtime.js +53 -1
- package/dist/esm/sinks.js +121 -18
- 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 +13 -0
- package/dist/types/index.d.ts +5 -5
- package/dist/types/local.d.ts +1 -0
- package/dist/types/runtime.d.ts +13 -0
- package/dist/types/sinks.d.ts +37 -4
- 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.InfluxDbReportingSink = exports.GrafanaLokiReportingSinkOptions = exports.GrafanaLokiReportingSink = exports.DatadogReportingSinkOptions = exports.DatadogReportingSink = exports.HttpAuthOptions = exports.HttpOAuth2ClientCredentialsOptions = exports.PushDiffusionEndpointDefinition = exports.DelegateStreamEndpointDefinition = 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.
|
|
4
|
-
exports.TimescaleDbReportingSinkOptions = exports.TimescaleDbReportingSink = exports.SplunkReportingSinkOptions = exports.SplunkReportingSink = exports.OtelCollectorReportingSinkOptions = exports.OtelCollectorReportingSink = exports.InfluxDbReportingSinkOptions = 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.PortalReportingSink = 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; } });
|
|
@@ -37,7 +37,6 @@ Object.defineProperty(exports, "RedisCorrelationStore", { enumerable: true, get:
|
|
|
37
37
|
Object.defineProperty(exports, "TrackingPayloadBuilder", { enumerable: true, get: function () { return correlation_js_1.TrackingPayloadBuilder; } });
|
|
38
38
|
Object.defineProperty(exports, "TrackingFieldSelector", { enumerable: true, get: function () { return correlation_js_1.TrackingFieldSelector; } });
|
|
39
39
|
var transports_js_1 = require("./transports.js");
|
|
40
|
-
Object.defineProperty(exports, "EndpointAdapterFactory", { enumerable: true, get: function () { return transports_js_1.EndpointAdapterFactory; } });
|
|
41
40
|
Object.defineProperty(exports, "LOADSTRIKE_TRACE_ID_HEADER", { enumerable: true, get: function () { return transports_js_1.LOADSTRIKE_TRACE_ID_HEADER; } });
|
|
42
41
|
Object.defineProperty(exports, "LOADSTRIKE_TRACE_ID_TRACKING_FIELD", { enumerable: true, get: function () { return transports_js_1.LOADSTRIKE_TRACE_ID_TRACKING_FIELD; } });
|
|
43
42
|
Object.defineProperty(exports, "TrafficEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.TrafficEndpointDefinition; } });
|
|
@@ -48,6 +47,7 @@ Object.defineProperty(exports, "RabbitMqEndpointDefinition", { enumerable: true,
|
|
|
48
47
|
Object.defineProperty(exports, "NatsEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.NatsEndpointDefinition; } });
|
|
49
48
|
Object.defineProperty(exports, "RedisStreamsEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.RedisStreamsEndpointDefinition; } });
|
|
50
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; } });
|
|
51
51
|
Object.defineProperty(exports, "DelegateStreamEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.DelegateStreamEndpointDefinition; } });
|
|
52
52
|
Object.defineProperty(exports, "PushDiffusionEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.PushDiffusionEndpointDefinition; } });
|
|
53
53
|
Object.defineProperty(exports, "HttpOAuth2ClientCredentialsOptions", { enumerable: true, get: function () { return transports_js_1.HttpOAuth2ClientCredentialsOptions; } });
|
|
@@ -61,6 +61,7 @@ Object.defineProperty(exports, "InfluxDbReportingSink", { enumerable: true, get:
|
|
|
61
61
|
Object.defineProperty(exports, "InfluxDbReportingSinkOptions", { enumerable: true, get: function () { return sinks_js_1.InfluxDbReportingSinkOptions; } });
|
|
62
62
|
Object.defineProperty(exports, "OtelCollectorReportingSink", { enumerable: true, get: function () { return sinks_js_1.OtelCollectorReportingSink; } });
|
|
63
63
|
Object.defineProperty(exports, "OtelCollectorReportingSinkOptions", { enumerable: true, get: function () { return sinks_js_1.OtelCollectorReportingSinkOptions; } });
|
|
64
|
+
Object.defineProperty(exports, "PortalReportingSink", { enumerable: true, get: function () { return sinks_js_1.PortalReportingSink; } });
|
|
64
65
|
Object.defineProperty(exports, "SplunkReportingSink", { enumerable: true, get: function () { return sinks_js_1.SplunkReportingSink; } });
|
|
65
66
|
Object.defineProperty(exports, "SplunkReportingSinkOptions", { enumerable: true, get: function () { return sinks_js_1.SplunkReportingSinkOptions; } });
|
|
66
67
|
Object.defineProperty(exports, "TimescaleDbReportingSink", { enumerable: true, get: function () { return sinks_js_1.TimescaleDbReportingSink; } });
|
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",
|
|
@@ -56,7 +55,8 @@ const SINK_FEATURE_BY_KIND = {
|
|
|
56
55
|
grafanaloki: "extensions.reporting_sinks.grafana_loki",
|
|
57
56
|
datadog: "extensions.reporting_sinks.datadog",
|
|
58
57
|
splunk: "extensions.reporting_sinks.splunk",
|
|
59
|
-
otelcollector: "extensions.reporting_sinks.otel_collector"
|
|
58
|
+
otelcollector: "extensions.reporting_sinks.otel_collector",
|
|
59
|
+
portal: "extensions.reporting_sinks.portal"
|
|
60
60
|
};
|
|
61
61
|
const TRACKING_FEATURE_BY_KIND = {
|
|
62
62
|
http: "endpoint.http",
|
|
@@ -66,7 +66,8 @@ const TRACKING_FEATURE_BY_KIND = {
|
|
|
66
66
|
pushdiffusion: "endpoint.push_diffusion",
|
|
67
67
|
delegatestream: "endpoint.delegate_stream",
|
|
68
68
|
nats: "endpoint.nats",
|
|
69
|
-
redisstreams: "endpoint.redis_streams"
|
|
69
|
+
redisstreams: "endpoint.redis_streams",
|
|
70
|
+
sqs: "endpoint.sqs"
|
|
70
71
|
};
|
|
71
72
|
const CI_ENVIRONMENT_VARIABLES = [
|
|
72
73
|
"GITHUB_ACTIONS",
|
|
@@ -96,6 +97,9 @@ class LoadStrikeLocalClient {
|
|
|
96
97
|
this.licensingApiBaseUrl = resolveLicensingApiBaseUrl();
|
|
97
98
|
this.licenseValidationTimeoutMs = normalizeTimeoutMs(options.licenseValidationTimeoutMs);
|
|
98
99
|
}
|
|
100
|
+
portalReportingIngestUrl() {
|
|
101
|
+
return `${this.licensingApiBaseUrl.replace(/\/+$/, "")}/api/v1/reporting/ingest`;
|
|
102
|
+
}
|
|
99
103
|
async run(request) {
|
|
100
104
|
const sanitized = sanitizeRequest(request);
|
|
101
105
|
const licenseSession = await this.acquireLicenseLease(sanitized);
|
|
@@ -506,25 +510,8 @@ function normalizeLicensingApiBaseUrl(value) {
|
|
|
506
510
|
const normalized = (value ?? "").trim();
|
|
507
511
|
return normalized || DEFAULT_LICENSING_API_BASE_URL;
|
|
508
512
|
}
|
|
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
513
|
function resolveLicensingApiBaseUrl() {
|
|
527
|
-
return normalizeLicensingApiBaseUrl(
|
|
514
|
+
return normalizeLicensingApiBaseUrl(developmentLicensingApiBaseUrlOverride);
|
|
528
515
|
}
|
|
529
516
|
function setDevelopmentLicensingApiBaseUrlOverride(value) {
|
|
530
517
|
developmentLicensingApiBaseUrlOverride = value;
|
|
@@ -563,6 +550,9 @@ function collectRequestedFeatures(request) {
|
|
|
563
550
|
if (countCustomWorkerPlugins(context) > 0) {
|
|
564
551
|
features.add("extensions.worker_plugins.custom");
|
|
565
552
|
}
|
|
553
|
+
if (asList(pickValue(context, "RuntimePolicies", "runtimePolicies")).length > 0) {
|
|
554
|
+
features.add("policy.runtime_controls");
|
|
555
|
+
}
|
|
566
556
|
const reportingSinks = asList(pickValue(context, "ReportingSinks", "reportingSinks"));
|
|
567
557
|
let hasCustomSink = false;
|
|
568
558
|
for (const sink of reportingSinks) {
|
|
@@ -858,159 +848,171 @@ async function evaluateScenarioOutcome(scenario, requestCount, context) {
|
|
|
858
848
|
validateTrackingConfiguration(tracking, sourceEndpoint, destinationEndpoint);
|
|
859
849
|
const sourceAdapter = transports_js_1.EndpointAdapterFactory.create(sourceEndpoint);
|
|
860
850
|
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;
|
|
851
|
+
let correlationStore = null;
|
|
852
|
+
try {
|
|
853
|
+
await sourceAdapter.initialize?.();
|
|
854
|
+
await destinationAdapter?.initialize?.();
|
|
855
|
+
const correlationTimeoutOverride = pickValue(tracking, "CorrelationTimeoutMs", "correlationTimeoutMs");
|
|
856
|
+
const correlationTimeoutSeconds = pickValue(tracking, "CorrelationTimeoutSeconds", "correlationTimeoutSeconds", "CorrelationTimeout");
|
|
857
|
+
const timeoutMs = correlationTimeoutOverride != null && String(correlationTimeoutOverride).trim() !== ""
|
|
858
|
+
? asInt(correlationTimeoutOverride)
|
|
859
|
+
: Math.trunc((correlationTimeoutSeconds == null || String(correlationTimeoutSeconds).trim() === ""
|
|
860
|
+
? 30
|
|
861
|
+
: asNumber(correlationTimeoutSeconds)) * 1000);
|
|
862
|
+
const timeoutCountsAsFailure = toBoolean(pickValue(tracking, "TimeoutCountsAsFailure", "timeoutCountsAsFailure"), true);
|
|
863
|
+
correlationStore = mapCorrelationStore(tracking, buildTrackingRunNamespace(stringOrDefault(pickValue(context, "SessionId", "sessionId"), "session"), stringOrDefault(pickValue(scenario, "Name", "name"), "scenario"), sourceEndpoint.name, destinationEndpoint?.name));
|
|
864
|
+
const timeoutSweepIntervalOverride = pickValue(tracking, "TimeoutSweepIntervalMs", "timeoutSweepIntervalMs");
|
|
865
|
+
const timeoutSweepIntervalSeconds = pickValue(tracking, "TimeoutSweepIntervalSeconds", "timeoutSweepIntervalSeconds");
|
|
866
|
+
const timeoutSweepIntervalMs = timeoutSweepIntervalOverride != null && String(timeoutSweepIntervalOverride).trim() !== ""
|
|
867
|
+
? asInt(timeoutSweepIntervalOverride)
|
|
868
|
+
: Math.trunc((timeoutSweepIntervalSeconds == null || String(timeoutSweepIntervalSeconds).trim() === ""
|
|
869
|
+
? 1
|
|
870
|
+
: asNumber(timeoutSweepIntervalSeconds)) * 1000);
|
|
871
|
+
const timeoutBatchSizeValue = pickValue(tracking, "TimeoutBatchSize", "timeoutBatchSize");
|
|
872
|
+
const timeoutBatchSize = timeoutBatchSizeValue == null || String(timeoutBatchSizeValue).trim() === ""
|
|
873
|
+
? 200
|
|
874
|
+
: asInt(timeoutBatchSizeValue);
|
|
875
|
+
const runtime = new correlation_js_1.CrossPlatformTrackingRuntime({
|
|
876
|
+
sourceTrackingField: sourceEndpoint.trackingField,
|
|
877
|
+
destinationTrackingField: destinationEndpoint?.trackingField,
|
|
878
|
+
destinationGatherByField: destinationEndpoint?.gatherByField,
|
|
879
|
+
trackingFieldValueCaseSensitive: toBoolean(pickValue(tracking, "TrackingFieldValueCaseSensitive", "trackingFieldValueCaseSensitive"), true),
|
|
880
|
+
gatherByFieldValueCaseSensitive: toBoolean(pickValue(tracking, "GatherByFieldValueCaseSensitive", "gatherByFieldValueCaseSensitive"), true),
|
|
881
|
+
correlationTimeoutMs: timeoutMs,
|
|
882
|
+
timeoutCountsAsFailure,
|
|
883
|
+
store: correlationStore ?? undefined
|
|
884
|
+
});
|
|
885
|
+
const runMode = stringOrDefault(pickValue(tracking, "RunMode", "runMode"), "GenerateAndCorrelate").trim().toLowerCase();
|
|
886
|
+
const sourceOnlyMode = !destinationEndpoint;
|
|
887
|
+
const restartOnFail = toBoolean(pickValue(scenario, "RestartIterationOnFail", "restartIterationOnFail"), false);
|
|
888
|
+
const restartMaxAttempts = Math.max(asInt(pickValue(context, "RestartIterationMaxAttempts", "restartIterationMaxAttempts")), 0);
|
|
889
|
+
const maxFailCount = Math.max(asInt(pickValue(scenario, "MaxFailCount", "maxFailCount")), 0);
|
|
890
|
+
const scenarioCompletionTimeoutSeconds = Math.max(asNumber(pickValue(context, "ScenarioCompletionTimeoutSeconds", "scenarioCompletionTimeoutSeconds")), 0);
|
|
891
|
+
const scenarioDeadlineMs = scenarioCompletionTimeoutSeconds > 0
|
|
892
|
+
? Date.now() + Math.trunc(scenarioCompletionTimeoutSeconds * 1000)
|
|
893
|
+
: Number.POSITIVE_INFINITY;
|
|
894
|
+
let okCount = 0;
|
|
895
|
+
let failCount = 0;
|
|
896
|
+
let nowMs = Date.now();
|
|
897
|
+
let processedIterations = 0;
|
|
898
|
+
let lastSweepAtMs = nowMs;
|
|
899
|
+
for (let i = 0; i < requestCount; i += 1) {
|
|
914
900
|
if (Date.now() > scenarioDeadlineMs) {
|
|
915
|
-
iterationComplete = true;
|
|
916
901
|
break;
|
|
917
902
|
}
|
|
918
|
-
let
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
903
|
+
let iterationComplete = false;
|
|
904
|
+
let attempts = 0;
|
|
905
|
+
const maxAttempts = 1 + (restartOnFail ? restartMaxAttempts : 0);
|
|
906
|
+
while (!iterationComplete) {
|
|
907
|
+
attempts += 1;
|
|
908
|
+
if (Date.now() > scenarioDeadlineMs) {
|
|
909
|
+
iterationComplete = true;
|
|
910
|
+
break;
|
|
911
|
+
}
|
|
912
|
+
let sourcePayload = runMode === "correlateexistingtraffic"
|
|
913
|
+
? await sourceAdapter.consume()
|
|
914
|
+
: await sourceAdapter.produce();
|
|
915
|
+
sourcePayload = normalizePayload(sourcePayload, sourceEndpoint, i);
|
|
916
|
+
const sourceTrackingId = sourceOnlyMode
|
|
917
|
+
? readTrackingId(sourcePayload, sourceEndpoint.trackingField)
|
|
918
|
+
: await runtime.onSourceProduced(sourcePayload, nowMs);
|
|
919
|
+
if (!sourceTrackingId) {
|
|
920
|
+
if (restartOnFail && attempts < maxAttempts) {
|
|
921
|
+
nowMs += 1;
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
failCount += 1;
|
|
925
|
+
iterationComplete = true;
|
|
927
926
|
nowMs += 1;
|
|
928
|
-
|
|
927
|
+
break;
|
|
929
928
|
}
|
|
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) {
|
|
929
|
+
if (sourceOnlyMode || !destinationAdapter || !destinationEndpoint) {
|
|
930
|
+
okCount += 1;
|
|
931
|
+
iterationComplete = true;
|
|
932
|
+
nowMs += 1;
|
|
948
933
|
break;
|
|
949
934
|
}
|
|
950
|
-
await
|
|
951
|
-
destinationPayload = normalizePayload(
|
|
952
|
-
matched = await runtime.onDestinationConsumed(destinationPayload, nowMs + 1);
|
|
935
|
+
let destinationPayload = await destinationAdapter.consume();
|
|
936
|
+
destinationPayload = normalizePayload(destinationPayload, destinationEndpoint, i);
|
|
937
|
+
let matched = await runtime.onDestinationConsumed(destinationPayload, nowMs + 1);
|
|
938
|
+
const correlationDeadlineMs = nowMs + timeoutMs;
|
|
939
|
+
while (!matched && Date.now() <= scenarioDeadlineMs && nowMs < correlationDeadlineMs) {
|
|
940
|
+
const remainingTimeoutMs = correlationDeadlineMs - nowMs;
|
|
941
|
+
if (remainingTimeoutMs <= 0) {
|
|
942
|
+
break;
|
|
943
|
+
}
|
|
944
|
+
await sleep(Math.min(destinationEndpoint.pollIntervalMs ?? 250, remainingTimeoutMs));
|
|
945
|
+
destinationPayload = normalizePayload(await destinationAdapter.consume(), destinationEndpoint, i);
|
|
946
|
+
matched = await runtime.onDestinationConsumed(destinationPayload, nowMs + 1);
|
|
947
|
+
if (matched) {
|
|
948
|
+
break;
|
|
949
|
+
}
|
|
950
|
+
nowMs += Math.max(destinationEndpoint.pollIntervalMs ?? 250, 1);
|
|
951
|
+
}
|
|
953
952
|
if (matched) {
|
|
954
|
-
|
|
953
|
+
okCount += 1;
|
|
954
|
+
iterationComplete = true;
|
|
955
955
|
}
|
|
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;
|
|
956
|
+
else if (restartOnFail && attempts < maxAttempts) {
|
|
957
|
+
nowMs += 2;
|
|
958
|
+
continue;
|
|
979
959
|
}
|
|
980
960
|
else {
|
|
981
|
-
|
|
961
|
+
await runtime.sweepTimeouts(nowMs + timeoutMs + 1, timeoutBatchSize || undefined);
|
|
962
|
+
failCount += 1;
|
|
963
|
+
iterationComplete = true;
|
|
964
|
+
}
|
|
965
|
+
nowMs += 2;
|
|
966
|
+
}
|
|
967
|
+
processedIterations += 1;
|
|
968
|
+
if (timeoutSweepIntervalMs > 0 && nowMs - lastSweepAtMs >= timeoutSweepIntervalMs) {
|
|
969
|
+
const swept = await runtime.sweepTimeouts(nowMs, timeoutBatchSize || undefined);
|
|
970
|
+
if (swept > 0) {
|
|
971
|
+
if (timeoutCountsAsFailure) {
|
|
972
|
+
failCount += swept;
|
|
973
|
+
}
|
|
974
|
+
else {
|
|
975
|
+
okCount += swept;
|
|
976
|
+
}
|
|
982
977
|
}
|
|
978
|
+
lastSweepAtMs = nowMs;
|
|
979
|
+
}
|
|
980
|
+
if (maxFailCount > 0 && failCount >= maxFailCount) {
|
|
981
|
+
break;
|
|
983
982
|
}
|
|
984
|
-
lastSweepAtMs = nowMs;
|
|
985
|
-
}
|
|
986
|
-
if (maxFailCount > 0 && failCount >= maxFailCount) {
|
|
987
|
-
break;
|
|
988
983
|
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
984
|
+
let expired = 0;
|
|
985
|
+
let sweptChunk = 0;
|
|
986
|
+
do {
|
|
987
|
+
sweptChunk = await runtime.sweepTimeouts(nowMs + timeoutMs + 1, timeoutBatchSize || undefined);
|
|
988
|
+
expired += sweptChunk;
|
|
989
|
+
} while (timeoutBatchSize > 0 && sweptChunk >= timeoutBatchSize);
|
|
990
|
+
if (expired > 0) {
|
|
991
|
+
if (timeoutCountsAsFailure) {
|
|
992
|
+
failCount += expired;
|
|
993
|
+
}
|
|
994
|
+
else {
|
|
995
|
+
okCount += expired;
|
|
996
|
+
}
|
|
999
997
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
998
|
+
if (processedIterations < requestCount && Date.now() > scenarioDeadlineMs) {
|
|
999
|
+
const timedOutIterations = requestCount - processedIterations;
|
|
1000
|
+
if (timeoutCountsAsFailure) {
|
|
1001
|
+
failCount += timedOutIterations;
|
|
1002
|
+
}
|
|
1003
|
+
else {
|
|
1004
|
+
okCount += timedOutIterations;
|
|
1005
|
+
}
|
|
1002
1006
|
}
|
|
1007
|
+
return { requestCount: processedIterations, okCount, failCount };
|
|
1003
1008
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
okCount += timedOutIterations;
|
|
1011
|
-
}
|
|
1009
|
+
finally {
|
|
1010
|
+
await Promise.allSettled([
|
|
1011
|
+
sourceAdapter.dispose?.(),
|
|
1012
|
+
destinationAdapter?.dispose?.(),
|
|
1013
|
+
correlationStore?.close?.()
|
|
1014
|
+
].filter((value) => Boolean(value)));
|
|
1012
1015
|
}
|
|
1013
|
-
return { requestCount: processedIterations, okCount, failCount };
|
|
1014
1016
|
}
|
|
1015
1017
|
function validateTrackingConfiguration(tracking, sourceEndpoint, destinationEndpoint) {
|
|
1016
1018
|
if (sourceEndpoint.gatherByField?.trim()) {
|
package/dist/cjs/runtime.js
CHANGED
|
@@ -13,6 +13,7 @@ const cluster_js_1 = require("./cluster.js");
|
|
|
13
13
|
const correlation_js_1 = require("./correlation.js");
|
|
14
14
|
const transports_js_1 = require("./transports.js");
|
|
15
15
|
const reporting_js_1 = require("./reporting.js");
|
|
16
|
+
const sinks_js_1 = require("./sinks.js");
|
|
16
17
|
exports.LoadStrikeNodeType = {
|
|
17
18
|
SingleNode: "SingleNode",
|
|
18
19
|
Coordinator: "Coordinator",
|
|
@@ -1395,6 +1396,16 @@ class LoadStrikeContext {
|
|
|
1395
1396
|
validateNamedReportingSinks(sinks);
|
|
1396
1397
|
return this.mergeValues({ ReportingSinks: sinks });
|
|
1397
1398
|
}
|
|
1399
|
+
withPortalReporting() {
|
|
1400
|
+
return this.WithPortalReporting();
|
|
1401
|
+
}
|
|
1402
|
+
WithPortalReporting() {
|
|
1403
|
+
const sinks = [...(this.values.ReportingSinks ?? [])];
|
|
1404
|
+
if (!sinks.some((sink, index) => resolveSinkName(sink, index).trim().toLowerCase() === "portal")) {
|
|
1405
|
+
sinks.push(new sinks_js_1.PortalReportingSink());
|
|
1406
|
+
}
|
|
1407
|
+
return this.mergeValues({ ReportingSinks: sinks });
|
|
1408
|
+
}
|
|
1398
1409
|
/**
|
|
1399
1410
|
* Registers runtime policies for the run.
|
|
1400
1411
|
* Use this when scenario selection or step execution should obey policy callbacks.
|
|
@@ -2137,6 +2148,9 @@ class LoadStrikeRunner {
|
|
|
2137
2148
|
static WithReportingSinks(context, ...sinks) {
|
|
2138
2149
|
return context.WithReportingSinks(...sinks);
|
|
2139
2150
|
}
|
|
2151
|
+
static WithPortalReporting(context) {
|
|
2152
|
+
return context.WithPortalReporting();
|
|
2153
|
+
}
|
|
2140
2154
|
/**
|
|
2141
2155
|
* Registers runtime policies for the run.
|
|
2142
2156
|
* Use this when scenario selection or step execution should obey policy callbacks.
|
|
@@ -2365,6 +2379,29 @@ class LoadStrikeRunner {
|
|
|
2365
2379
|
WithReportingInterval(intervalSeconds) {
|
|
2366
2380
|
return this.withReportingInterval(intervalSeconds);
|
|
2367
2381
|
}
|
|
2382
|
+
withReportingSinks(...sinks) {
|
|
2383
|
+
if (!sinks.length) {
|
|
2384
|
+
throw new Error("At least one reporting sink should be provided.");
|
|
2385
|
+
}
|
|
2386
|
+
if (sinks.some((sink) => sink == null)) {
|
|
2387
|
+
throw new Error("Reporting sink collection cannot contain null values.");
|
|
2388
|
+
}
|
|
2389
|
+
validateNamedReportingSinks(sinks);
|
|
2390
|
+
return this.configure({ reportingSinks: sinks });
|
|
2391
|
+
}
|
|
2392
|
+
WithReportingSinks(...sinks) {
|
|
2393
|
+
return this.withReportingSinks(...sinks);
|
|
2394
|
+
}
|
|
2395
|
+
withPortalReporting() {
|
|
2396
|
+
const sinks = [...(this.options.reportingSinks ?? [])];
|
|
2397
|
+
if (!sinks.some((sink, index) => resolveSinkName(sink, index).trim().toLowerCase() === "portal")) {
|
|
2398
|
+
sinks.push(new sinks_js_1.PortalReportingSink());
|
|
2399
|
+
}
|
|
2400
|
+
return this.configure({ reportingSinks: sinks });
|
|
2401
|
+
}
|
|
2402
|
+
WithPortalReporting() {
|
|
2403
|
+
return this.withPortalReporting();
|
|
2404
|
+
}
|
|
2368
2405
|
/**
|
|
2369
2406
|
* Sets the timeout for cluster command round-trips.
|
|
2370
2407
|
* Use this when distributed control messages need a tighter or looser deadline.
|
|
@@ -2538,6 +2575,7 @@ class LoadStrikeRunner {
|
|
|
2538
2575
|
testInfo,
|
|
2539
2576
|
getNodeInfo: () => attachNodeInfoAliases({ ...nodeInfo })
|
|
2540
2577
|
};
|
|
2578
|
+
attachPortalReportingSession(sinkSession, sessionInfo, licenseClient, licenseSession);
|
|
2541
2579
|
attachSessionStartInfoAliases(sessionInfo);
|
|
2542
2580
|
nodeInfo.currentOperation = "Init";
|
|
2543
2581
|
for (const plugin of plugins) {
|
|
@@ -4072,10 +4110,23 @@ function attachSessionStartInfoAliases(session) {
|
|
|
4072
4110
|
attachAliasMap(session, {
|
|
4073
4111
|
StartedUtc: "startedUtc",
|
|
4074
4112
|
ScenarioNames: "scenarioNames",
|
|
4075
|
-
Scenarios: "scenarios"
|
|
4113
|
+
Scenarios: "scenarios",
|
|
4114
|
+
RunToken: "runToken",
|
|
4115
|
+
PortalReportingIngestUrl: "portalReportingIngestUrl"
|
|
4076
4116
|
});
|
|
4077
4117
|
return session;
|
|
4078
4118
|
}
|
|
4119
|
+
function attachPortalReportingSession(sinkSession, sessionInfo, licenseClient, licenseSession) {
|
|
4120
|
+
const runToken = stringValueOrDefault(licenseSession?.runToken, "").trim();
|
|
4121
|
+
if (!runToken || !licenseClient) {
|
|
4122
|
+
return;
|
|
4123
|
+
}
|
|
4124
|
+
const ingestUrl = licenseClient.portalReportingIngestUrl();
|
|
4125
|
+
sinkSession.runToken = runToken;
|
|
4126
|
+
sinkSession.portalReportingIngestUrl = ingestUrl;
|
|
4127
|
+
sessionInfo.runToken = runToken;
|
|
4128
|
+
sessionInfo.portalReportingIngestUrl = ingestUrl;
|
|
4129
|
+
}
|
|
4079
4130
|
function attachScenarioInitContextAliases(context) {
|
|
4080
4131
|
context.nodeInfo = attachNodeInfoAliases(context.nodeInfo);
|
|
4081
4132
|
context.testInfo = attachTestInfoAliases(context.testInfo);
|
|
@@ -6626,6 +6677,7 @@ function mapRuntimeTrackingEndpointSpec(spec, useLoadStrikeTraceIdHeader = false
|
|
|
6626
6677
|
nats: asTrackingRecord(pickTrackingValue(spec, "Nats", "nats")),
|
|
6627
6678
|
redisStreams: asTrackingRecord(pickTrackingValue(spec, "RedisStreams", "redisStreams")),
|
|
6628
6679
|
azureEventHubs: asTrackingRecord(pickTrackingValue(spec, "AzureEventHubs", "azureEventHubs")),
|
|
6680
|
+
sqs: asTrackingRecord(pickTrackingValue(spec, "Sqs", "sqs")),
|
|
6629
6681
|
pushDiffusion: asTrackingRecord(pickTrackingValue(spec, "PushDiffusion", "pushDiffusion")),
|
|
6630
6682
|
delegate: typeof delegateProduce === "function"
|
|
6631
6683
|
|| typeof delegateConsume === "function"
|