@loadstrike/loadstrike-sdk 1.0.31601 → 1.0.32601
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -2
- package/dist/cjs/internal/prometheus-remote-write.js +37 -0
- package/dist/cjs/internal/reporting-sink-http-error.js +17 -0
- package/dist/cjs/internal/vendor-metric-payloads.js +390 -0
- package/dist/cjs/iteration-observations.js +24 -8
- package/dist/cjs/local.js +48 -63
- package/dist/cjs/reporting-containment.js +242 -0
- package/dist/cjs/runtime.js +83 -3
- package/dist/cjs/sinks.js +1337 -38
- package/dist/cjs/transports.js +1339 -151
- package/dist/esm/internal/prometheus-remote-write.js +31 -0
- package/dist/esm/internal/reporting-sink-http-error.js +13 -0
- package/dist/esm/internal/vendor-metric-payloads.js +382 -0
- package/dist/esm/iteration-observations.js +24 -8
- package/dist/esm/local.js +49 -64
- package/dist/esm/reporting-containment.js +238 -0
- package/dist/esm/runtime.js +85 -5
- package/dist/esm/sinks.js +1334 -35
- package/dist/esm/transports.js +1335 -151
- package/dist/types/contracts.d.ts +1 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/internal/prometheus-remote-write.d.ts +2 -0
- package/dist/types/internal/reporting-sink-http-error.d.ts +6 -0
- package/dist/types/internal/vendor-metric-payloads.d.ts +48 -0
- package/dist/types/local.d.ts +0 -6
- package/dist/types/reporting-containment.d.ts +2 -0
- package/dist/types/runtime.d.ts +1 -0
- package/dist/types/sinks.d.ts +134 -17
- package/dist/types/transports.d.ts +2 -0
- package/package.json +9 -3
- package/dist/cjs/internal-build.js +0 -4
- package/dist/esm/internal-build.js +0 -1
- package/dist/types/internal-build.d.ts +0 -1
package/dist/esm/local.js
CHANGED
|
@@ -15,10 +15,9 @@ import * as fs from "node:fs";
|
|
|
15
15
|
import * as childProcess from "node:child_process";
|
|
16
16
|
import { createHash, createVerify, randomUUID } from "node:crypto";
|
|
17
17
|
import { CorrelationStoreConfiguration, CrossPlatformTrackingRuntime, RedisCorrelationStoreOptions, RedisCorrelationStore, TrackingFieldSelector } from "./correlation.js";
|
|
18
|
-
import { EndpointAdapterFactory, LOADSTRIKE_TRACE_ID_TRACKING_FIELD } from "./transports.js";
|
|
19
|
-
import {
|
|
18
|
+
import { EndpointAdapterFactory, LOADSTRIKE_TRACE_ID_TRACKING_FIELD, validateNativeEndpointExecutionSupport } from "./transports.js";
|
|
19
|
+
import { assertNoUnsupportedReportingInfraConfig, assertNoUnsupportedReportingSinkGraph } from "./reporting-containment.js";
|
|
20
20
|
const DEFAULT_LICENSING_API_BASE_URL = "https://licensing.loadstrike.com";
|
|
21
|
-
let developmentLicensingApiBaseUrlOverride;
|
|
22
21
|
const BUILT_IN_WORKER_PLUGIN_NAMES = new Set([
|
|
23
22
|
"loadstrike failed responses",
|
|
24
23
|
"loadstrike correlation"
|
|
@@ -72,7 +71,7 @@ export class LoadStrikeLocalClient {
|
|
|
72
71
|
_LoadStrikeLocalClient_signingKeyCache.set(this, new Map());
|
|
73
72
|
_LoadStrikeLocalClient_heartbeatDrains.set(this, new WeakMap());
|
|
74
73
|
assertNoDisableLicenseEnforcementOption(options, "LoadStrikeLocalClient");
|
|
75
|
-
__classPrivateFieldSet(this, _LoadStrikeLocalClient_licensingApiBaseUrl,
|
|
74
|
+
__classPrivateFieldSet(this, _LoadStrikeLocalClient_licensingApiBaseUrl, DEFAULT_LICENSING_API_BASE_URL, "f");
|
|
76
75
|
this.licenseValidationTimeoutMs = normalizeTimeoutMs(options.licenseValidationTimeoutMs);
|
|
77
76
|
}
|
|
78
77
|
portalReportingIngestUrl() {
|
|
@@ -80,6 +79,7 @@ export class LoadStrikeLocalClient {
|
|
|
80
79
|
}
|
|
81
80
|
async run(request) {
|
|
82
81
|
const sanitized = sanitizeRequest(request);
|
|
82
|
+
validateContainmentRequest(sanitized);
|
|
83
83
|
const licenseSession = await this.acquireLicenseLease(sanitized);
|
|
84
84
|
try {
|
|
85
85
|
const createdUtc = new Date();
|
|
@@ -534,66 +534,27 @@ function sanitizeRequest(request) {
|
|
|
534
534
|
RunArgs: runArgs
|
|
535
535
|
};
|
|
536
536
|
}
|
|
537
|
-
function
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
const normalized = normalizeLicensingApiBaseUrl(value);
|
|
548
|
-
if (!isLoopbackHttpBaseUrl(normalized)) {
|
|
549
|
-
throw new TypeError("Internal development licensing API overrides must use a loopback http URL.");
|
|
537
|
+
function validateContainmentRequest(request) {
|
|
538
|
+
const scenarios = Array.isArray(request.Scenarios) ? request.Scenarios : [];
|
|
539
|
+
for (const scenarioValue of scenarios) {
|
|
540
|
+
const scenario = asRecord(scenarioValue);
|
|
541
|
+
const tracking = asRecord(pickValue(scenario, "Tracking", "tracking"));
|
|
542
|
+
for (const field of ["Source", "Destination"]) {
|
|
543
|
+
const endpoint = asRecord(pickValue(tracking, field, field.toLowerCase()));
|
|
544
|
+
if (Object.keys(endpoint).length > 0) {
|
|
545
|
+
validateNativeEndpointExecutionSupport(endpoint);
|
|
546
|
+
}
|
|
550
547
|
}
|
|
551
|
-
developmentLicensingApiBaseUrlOverride = normalized;
|
|
552
|
-
return;
|
|
553
|
-
}
|
|
554
|
-
if (value != null) {
|
|
555
|
-
assertInternalTestHarnessLicensingOverride();
|
|
556
|
-
}
|
|
557
|
-
developmentLicensingApiBaseUrlOverride = undefined;
|
|
558
|
-
}
|
|
559
|
-
function assertInternalTestHarnessLicensingOverride() {
|
|
560
|
-
if (isInternalTestHarnessCall()) {
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
|
-
throw new TypeError("Internal development licensing API override is available only to LoadStrike SDK tests.");
|
|
564
|
-
}
|
|
565
|
-
function isInternalTestHarnessCall() {
|
|
566
|
-
const stack = String(new Error().stack ?? "").replace(/\\/g, "/").toLowerCase();
|
|
567
|
-
return stack.includes("/sdk/ts/tests/");
|
|
568
|
-
}
|
|
569
|
-
function isLoopbackHttpBaseUrl(value) {
|
|
570
|
-
let parsed;
|
|
571
|
-
try {
|
|
572
|
-
parsed = new URL(value);
|
|
573
|
-
}
|
|
574
|
-
catch {
|
|
575
|
-
return false;
|
|
576
|
-
}
|
|
577
|
-
if (parsed.protocol !== "http:") {
|
|
578
|
-
return false;
|
|
579
548
|
}
|
|
580
|
-
const
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
}
|
|
588
|
-
const envToken = String(process.env.LOADSTRIKE_INTERNAL_BLACKBOX_API_BASE_URL_TOKEN ?? "").trim();
|
|
589
|
-
if (!envToken || envToken !== compiledToken) {
|
|
590
|
-
return undefined;
|
|
591
|
-
}
|
|
592
|
-
const candidate = normalizeLicensingApiBaseUrl(process.env.LOADSTRIKE_INTERNAL_BLACKBOX_API_BASE_URL);
|
|
593
|
-
if (!isLoopbackHttpBaseUrl(candidate)) {
|
|
594
|
-
return undefined;
|
|
549
|
+
const context = asRecord(request.Context);
|
|
550
|
+
const reportingSinks = asList(pickValue(context, "ReportingSinks", "reportingSinks"));
|
|
551
|
+
for (const [key, value] of Object.entries(context)) {
|
|
552
|
+
const normalizedKey = key.trim().toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
553
|
+
if (normalizedKey === "infraconfig") {
|
|
554
|
+
assertNoUnsupportedReportingInfraConfig(value, reportingSinks);
|
|
555
|
+
}
|
|
595
556
|
}
|
|
596
|
-
|
|
557
|
+
assertNoUnsupportedReportingSinkGraph(reportingSinks);
|
|
597
558
|
}
|
|
598
559
|
function normalizeTimeoutMs(value) {
|
|
599
560
|
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
@@ -1301,6 +1262,20 @@ function mapEndpointSpec(spec, useLoadStrikeTraceIdHeader = false) {
|
|
|
1301
1262
|
redisStreams: asRecord(pickValue(spec, "RedisStreams", "redisStreams")),
|
|
1302
1263
|
azureEventHubs: asRecord(pickValue(spec, "AzureEventHubs", "azureEventHubs")),
|
|
1303
1264
|
pushDiffusion: asRecord(pickValue(spec, "PushDiffusion", "pushDiffusion")),
|
|
1265
|
+
grpc: mapEndpointProtocolOptions(spec, ["Grpc", "grpc"], [
|
|
1266
|
+
"Target", "target", "ServiceName", "serviceName", "MethodName", "methodName",
|
|
1267
|
+
"MethodType", "methodType", "Deadline", "deadline", "DeadlineSeconds", "deadlineSeconds",
|
|
1268
|
+
"DeadlineMs", "deadlineMs", "Metadata", "metadata", "Produce", "produce", "Consume", "consume",
|
|
1269
|
+
"ProduceAsync", "produceAsync", "ConsumeAsync", "consumeAsync", "ConnectionMetadata", "connectionMetadata",
|
|
1270
|
+
"NativeClient", "nativeClient"
|
|
1271
|
+
]),
|
|
1272
|
+
webSocket: mapEndpointProtocolOptions(spec, ["WebSocket", "webSocket"], [
|
|
1273
|
+
"Url", "url", "Subprotocols", "subprotocols", "ConnectTimeout", "connectTimeout",
|
|
1274
|
+
"ConnectTimeoutSeconds", "connectTimeoutSeconds", "ConnectTimeoutMs", "connectTimeoutMs",
|
|
1275
|
+
"CloseTimeout", "closeTimeout", "CloseTimeoutSeconds", "closeTimeoutSeconds", "CloseTimeoutMs", "closeTimeoutMs",
|
|
1276
|
+
"Produce", "produce", "Consume", "consume", "ProduceAsync", "produceAsync", "ConsumeAsync", "consumeAsync",
|
|
1277
|
+
"ConnectionMetadata", "connectionMetadata", "NativeClient", "nativeClient"
|
|
1278
|
+
]),
|
|
1304
1279
|
delegate: (typeof delegateProduce === "function"
|
|
1305
1280
|
|| typeof delegateConsume === "function"
|
|
1306
1281
|
|| typeof delegateProduceAsync === "function"
|
|
@@ -1323,6 +1298,19 @@ function mapEndpointSpec(spec, useLoadStrikeTraceIdHeader = false) {
|
|
|
1323
1298
|
: undefined
|
|
1324
1299
|
};
|
|
1325
1300
|
}
|
|
1301
|
+
function mapEndpointProtocolOptions(spec, nestedKeys, flatKeys) {
|
|
1302
|
+
const nested = asRecord(pickValue(spec, ...nestedKeys));
|
|
1303
|
+
if (Object.keys(nested).length > 0) {
|
|
1304
|
+
return nested;
|
|
1305
|
+
}
|
|
1306
|
+
const options = {};
|
|
1307
|
+
for (const key of flatKeys) {
|
|
1308
|
+
if (Object.prototype.hasOwnProperty.call(spec, key)) {
|
|
1309
|
+
options[key] = spec[key];
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
return options;
|
|
1313
|
+
}
|
|
1326
1314
|
function normalizePayload(payload, endpoint, index) {
|
|
1327
1315
|
const resolvedBody = payload?.body ?? endpoint.messagePayload;
|
|
1328
1316
|
const normalized = {
|
|
@@ -2279,8 +2267,6 @@ export const __loadstrikeTestExports = {
|
|
|
2279
2267
|
mapEnvironmentClassification,
|
|
2280
2268
|
mapEndpointSpec,
|
|
2281
2269
|
normalizeEndpointPayloadValue,
|
|
2282
|
-
normalizeLicensingApiBaseUrl,
|
|
2283
|
-
resolveLicensingApiBaseUrl,
|
|
2284
2270
|
normalizeNodeType,
|
|
2285
2271
|
normalizePayload,
|
|
2286
2272
|
normalizeStringArray,
|
|
@@ -2299,7 +2285,6 @@ export const __loadstrikeTestExports = {
|
|
|
2299
2285
|
sanitizeLooseJson,
|
|
2300
2286
|
sanitizeRequest,
|
|
2301
2287
|
setJsonPathValue,
|
|
2302
|
-
setDevelopmentLicensingApiBaseUrlOverride,
|
|
2303
2288
|
sleep,
|
|
2304
2289
|
stringOrDefault,
|
|
2305
2290
|
toBoolean,
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
const UNBOUND_EXPANDED_SECTIONS = [
|
|
2
|
+
["PrometheusRemoteWrite", "PrometheusRemoteWriteReportingSink"],
|
|
3
|
+
["CloudWatch", "CloudWatchReportingSink"],
|
|
4
|
+
["Dynatrace", "DynatraceReportingSink"],
|
|
5
|
+
["NewRelic", "NewRelicReportingSink"],
|
|
6
|
+
["Elasticsearch", "ElasticsearchReportingSink"],
|
|
7
|
+
["OpenSearch", "OpenSearchReportingSink"],
|
|
8
|
+
["Kafka", "KafkaReportingSink"],
|
|
9
|
+
["StatsD", "StatsDReportingSink"],
|
|
10
|
+
["DogStatsD", "DogStatsDReportingSink"],
|
|
11
|
+
["Netdata", "NetdataStatsDReportingSink"],
|
|
12
|
+
["Jsonl", "JsonlFileReportingSink"],
|
|
13
|
+
["Webhook", "GenericWebhookReportingSink"]
|
|
14
|
+
];
|
|
15
|
+
export function assertNoUnsupportedReportingInfraConfig(infraConfig, sinks = []) {
|
|
16
|
+
if (!isRecord(infraConfig)) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const selectedSections = selectedExpandedReportingSections(sinks);
|
|
20
|
+
for (const [sectionName, sinkType] of UNBOUND_EXPANDED_SECTIONS) {
|
|
21
|
+
if (selectedSections.has(sectionName)
|
|
22
|
+
&& hasNonEmptyReportingSectionAlias(infraConfig, sectionName)) {
|
|
23
|
+
throw new Error(`Configuration section 'LoadStrike:ReportingSinks:${sectionName}' is not bound automatically by the TypeScript SDK. `
|
|
24
|
+
+ `Construct ${sinkType} explicitly and pass its options directly.`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function selectedExpandedReportingSections(sinks) {
|
|
29
|
+
const selected = new Set();
|
|
30
|
+
const visited = new Set();
|
|
31
|
+
const inspect = (sink) => {
|
|
32
|
+
if (!isRecord(sink) || visited.has(sink)) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
visited.add(sink);
|
|
36
|
+
const identities = [
|
|
37
|
+
sink.licenseFeature,
|
|
38
|
+
sink.LicenseFeature,
|
|
39
|
+
sink.feature,
|
|
40
|
+
sink.Feature
|
|
41
|
+
];
|
|
42
|
+
if (!hasReportingSinkLifecycle(sink)) {
|
|
43
|
+
identities.push(sink.kind, sink.Kind);
|
|
44
|
+
}
|
|
45
|
+
const normalizedIdentities = identities
|
|
46
|
+
.map(normalizeExpandedSinkIdentity)
|
|
47
|
+
.filter(Boolean);
|
|
48
|
+
for (const identity of normalizedIdentities) {
|
|
49
|
+
const sectionName = EXPANDED_SECTION_BY_IDENTITY.get(identity);
|
|
50
|
+
if (sectionName) {
|
|
51
|
+
selected.add(sectionName);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const children = Array.isArray(sink.sinks)
|
|
55
|
+
? sink.sinks
|
|
56
|
+
: Array.isArray(sink.Sinks)
|
|
57
|
+
? sink.Sinks
|
|
58
|
+
: [];
|
|
59
|
+
for (const child of children) {
|
|
60
|
+
inspect(child);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
for (const sink of sinks) {
|
|
64
|
+
inspect(sink);
|
|
65
|
+
}
|
|
66
|
+
return selected;
|
|
67
|
+
}
|
|
68
|
+
const EXPANDED_SECTION_BY_IDENTITY = new Map([
|
|
69
|
+
["prometheusremotewrite", "PrometheusRemoteWrite"],
|
|
70
|
+
["cloudwatch", "CloudWatch"],
|
|
71
|
+
["dynatrace", "Dynatrace"],
|
|
72
|
+
["elasticsearch", "Elasticsearch"],
|
|
73
|
+
["opensearch", "OpenSearch"],
|
|
74
|
+
["newrelic", "NewRelic"],
|
|
75
|
+
["webhook", "Webhook"],
|
|
76
|
+
["genericwebhook", "Webhook"],
|
|
77
|
+
["kafka", "Kafka"],
|
|
78
|
+
["statsd", "StatsD"],
|
|
79
|
+
["dogstatsd", "DogStatsD"],
|
|
80
|
+
["netdata", "Netdata"],
|
|
81
|
+
["netdatastatsd", "Netdata"],
|
|
82
|
+
["jsonl", "Jsonl"],
|
|
83
|
+
["jsonlfile", "Jsonl"]
|
|
84
|
+
]);
|
|
85
|
+
function normalizeExpandedSinkIdentity(value) {
|
|
86
|
+
let normalized = normalizeIdentityToken(value);
|
|
87
|
+
const featurePrefix = "extensionsreportingsinks";
|
|
88
|
+
if (normalized.startsWith(featurePrefix)) {
|
|
89
|
+
normalized = normalized.slice(featurePrefix.length);
|
|
90
|
+
}
|
|
91
|
+
const classSuffix = "reportingsink";
|
|
92
|
+
if (normalized.endsWith(classSuffix)) {
|
|
93
|
+
normalized = normalized.slice(0, -classSuffix.length);
|
|
94
|
+
}
|
|
95
|
+
return normalized;
|
|
96
|
+
}
|
|
97
|
+
export function assertNoUnsupportedReportingSinkGraph(sinks) {
|
|
98
|
+
const visited = new Set();
|
|
99
|
+
const active = new Set();
|
|
100
|
+
const inspect = (sink) => {
|
|
101
|
+
if (!isRecord(sink)) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (active.has(sink)) {
|
|
105
|
+
throw new Error("Composite reporting sink graph cannot contain a cycle.");
|
|
106
|
+
}
|
|
107
|
+
if (visited.has(sink)) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
active.add(sink);
|
|
111
|
+
const identityTokens = [sink.sinkName, sink.SinkName, sink.kind, sink.Kind]
|
|
112
|
+
.map(normalizeIdentityToken)
|
|
113
|
+
.filter(Boolean);
|
|
114
|
+
const featureTokens = [sink.licenseFeature, sink.LicenseFeature, sink.feature, sink.Feature]
|
|
115
|
+
.map((value) => String(value ?? "").trim().toLowerCase())
|
|
116
|
+
.filter(Boolean);
|
|
117
|
+
const hasLifecycle = hasReportingSinkLifecycle(sink);
|
|
118
|
+
const displayName = directVendorDisplayName(hasLifecycle ? [] : identityTokens, hasLifecycle ? [] : featureTokens);
|
|
119
|
+
if (displayName) {
|
|
120
|
+
throw unsupportedDirectVendorReportingError(displayName);
|
|
121
|
+
}
|
|
122
|
+
if (identityTokens.includes("composite") || identityTokens.includes("compositereportingsink")) {
|
|
123
|
+
const children = Array.isArray(sink.sinks)
|
|
124
|
+
? sink.sinks
|
|
125
|
+
: Array.isArray(sink.Sinks)
|
|
126
|
+
? sink.Sinks
|
|
127
|
+
: [];
|
|
128
|
+
for (const child of children) {
|
|
129
|
+
inspect(child);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
active.delete(sink);
|
|
133
|
+
visited.add(sink);
|
|
134
|
+
};
|
|
135
|
+
for (const sink of sinks) {
|
|
136
|
+
inspect(sink);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function hasNonEmptyReportingSectionAlias(infraConfig, sectionName) {
|
|
140
|
+
const segments = ["LoadStrike", "ReportingSinks", sectionName];
|
|
141
|
+
const values = [
|
|
142
|
+
...readDirectValuesCaseInsensitive(infraConfig, segments.join(":")),
|
|
143
|
+
...readDirectValuesCaseInsensitive(infraConfig, segments.join(".")),
|
|
144
|
+
...readNestedValuesCaseInsensitive(infraConfig, segments)
|
|
145
|
+
];
|
|
146
|
+
return values.some(isNonEmptySection);
|
|
147
|
+
}
|
|
148
|
+
function readDirectValuesCaseInsensitive(record, key) {
|
|
149
|
+
const expected = key.toLowerCase();
|
|
150
|
+
return Object.entries(record)
|
|
151
|
+
.filter(([candidate]) => candidate.toLowerCase() === expected)
|
|
152
|
+
.map(([, value]) => value);
|
|
153
|
+
}
|
|
154
|
+
function readNestedValuesCaseInsensitive(record, segments) {
|
|
155
|
+
let current = [record];
|
|
156
|
+
for (const segment of segments) {
|
|
157
|
+
const next = [];
|
|
158
|
+
for (const candidate of current) {
|
|
159
|
+
if (isRecord(candidate)) {
|
|
160
|
+
next.push(...readDirectValuesCaseInsensitive(candidate, segment));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (next.length === 0) {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
current = next;
|
|
167
|
+
}
|
|
168
|
+
return current;
|
|
169
|
+
}
|
|
170
|
+
function isNonEmptySection(value) {
|
|
171
|
+
if (value == null) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
if (typeof value === "string") {
|
|
175
|
+
return value.trim().length > 0;
|
|
176
|
+
}
|
|
177
|
+
if (Array.isArray(value)) {
|
|
178
|
+
return value.length > 0;
|
|
179
|
+
}
|
|
180
|
+
if (isRecord(value)) {
|
|
181
|
+
return Object.keys(value).length > 0;
|
|
182
|
+
}
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
function directVendorDisplayName(identityTokens, featureTokens) {
|
|
186
|
+
if (identityTokens.includes("prometheusremotewrite")
|
|
187
|
+
|| identityTokens.includes("prometheusremotewritereportingsink")
|
|
188
|
+
|| featureTokens.includes("extensions.reporting_sinks.prometheus_remote_write")) {
|
|
189
|
+
return "Prometheus Remote Write";
|
|
190
|
+
}
|
|
191
|
+
if (identityTokens.includes("cloudwatch")
|
|
192
|
+
|| identityTokens.includes("cloudwatchreportingsink")
|
|
193
|
+
|| featureTokens.includes("extensions.reporting_sinks.cloudwatch")) {
|
|
194
|
+
return "CloudWatch";
|
|
195
|
+
}
|
|
196
|
+
if (identityTokens.includes("dynatrace")
|
|
197
|
+
|| identityTokens.includes("dynatracereportingsink")
|
|
198
|
+
|| featureTokens.includes("extensions.reporting_sinks.dynatrace")) {
|
|
199
|
+
return "Dynatrace";
|
|
200
|
+
}
|
|
201
|
+
if (identityTokens.includes("newrelic")
|
|
202
|
+
|| identityTokens.includes("newrelicreportingsink")
|
|
203
|
+
|| featureTokens.includes("extensions.reporting_sinks.new_relic")) {
|
|
204
|
+
return "New Relic";
|
|
205
|
+
}
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
function unsupportedDirectVendorReportingError(displayName) {
|
|
209
|
+
return new Error(`Direct ${displayName} reporting is not supported by the TypeScript SDK. `
|
|
210
|
+
+ "Use GenericWebhookReportingSink through a converting gateway or a custom LoadStrikeReportingSink.");
|
|
211
|
+
}
|
|
212
|
+
function normalizeIdentityToken(value) {
|
|
213
|
+
return String(value ?? "")
|
|
214
|
+
.trim()
|
|
215
|
+
.toLowerCase()
|
|
216
|
+
.replace(/[^a-z0-9]+/g, "");
|
|
217
|
+
}
|
|
218
|
+
function hasReportingSinkLifecycle(sink) {
|
|
219
|
+
return [
|
|
220
|
+
"init",
|
|
221
|
+
"Init",
|
|
222
|
+
"start",
|
|
223
|
+
"Start",
|
|
224
|
+
"saveRealtimeStats",
|
|
225
|
+
"SaveRealtimeStats",
|
|
226
|
+
"saveRealtimeMetrics",
|
|
227
|
+
"SaveRealtimeMetrics",
|
|
228
|
+
"saveRunResult",
|
|
229
|
+
"SaveRunResult",
|
|
230
|
+
"saveIterationBatch",
|
|
231
|
+
"SaveIterationBatch",
|
|
232
|
+
"stop",
|
|
233
|
+
"Stop"
|
|
234
|
+
].some((member) => typeof sink[member] === "function");
|
|
235
|
+
}
|
|
236
|
+
function isRecord(value) {
|
|
237
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
238
|
+
}
|
package/dist/esm/runtime.js
CHANGED
|
@@ -5,17 +5,20 @@ import { resolve } from "node:path";
|
|
|
5
5
|
import { LoadStrikeLocalClient } from "./local.js";
|
|
6
6
|
import { DistributedClusterAgent, DistributedClusterCoordinator, buildLoadEngineV2ReservedStepOtherIdentityKey, buildLoadEngineV2GlobalInvocationId, buildLoadEngineV2ScenarioIdentityKey, buildLoadEngineV2SchedulerIdentityKey, buildLoadEngineV2StatusIdentityKey, buildLoadEngineV2StepIdentityKey, resolveLoadEngineV2StepIdentityKey, buildLoadEngineV2Plan } from "./cluster.js";
|
|
7
7
|
import { CorrelationStoreConfiguration, CrossPlatformTrackingRuntime, RedisCorrelationStore, RedisCorrelationStoreOptions, TrackingFieldSelector } from "./correlation.js";
|
|
8
|
-
import { EndpointAdapterFactory, LOADSTRIKE_TRACE_ID_TRACKING_FIELD } from "./transports.js";
|
|
8
|
+
import { EndpointAdapterFactory, LOADSTRIKE_TRACE_ID_TRACKING_FIELD, validateNativeEndpointExecutionSupport } from "./transports.js";
|
|
9
9
|
import { buildDotnetCsvReport, buildDotnetHtmlReport, buildDotnetMarkdownReport, buildDotnetTxtReport } from "./reporting.js";
|
|
10
10
|
import { ReportHistoryCollector, ReportHistoryLifecycleCoordinator, ReportHistoryWorker, sanitizedExceptionClassChain } from "./report-history.js";
|
|
11
11
|
import { distributedLocalReportInput, emptyLocalReportInput } from "./local-report-input.js";
|
|
12
|
-
import { PortalReportingSink, cloneReportingSinkForRun } from "./sinks.js";
|
|
12
|
+
import { PortalReportingSink, PrometheusRemoteWriteReportingSink, cloneReportingSinkForRun } from "./sinks.js";
|
|
13
|
+
import { ReportingSinkHttpError } from "./internal/reporting-sink-http-error.js";
|
|
14
|
+
import { assertNoUnsupportedReportingSinkGraph } from "./reporting-containment.js";
|
|
13
15
|
import { LoadEngineV2ExecutionBudget, buildLoadEngineV2TrafficMixSeedId, LoadStrikeHistogramV1, parseLoadEngineV2HistogramArtifact, serializeLoadEngineV2HistogramArtifact, classifyLoadEngineV2Arrival, loadEngineV2FixedArrivalCount, loadEngineV2FixedDeadlineNs, loadEngineV2LatenessToleranceNs, loadEngineV2TrafficMixLaneUnitCount, loadEngineV2TrafficMixOwnedUnits, planRampingInjectionDeadlines, planRampingConstantDeadlines, planRandomInjectionDeadlines, fnv1a32 } from "./load-engine-v2.js";
|
|
14
16
|
import { DEFAULT_ITERATION_OBSERVATION_SETTINGS, IterationObservationReporter, createIterationObservation, createIterationStepObservation, utcNowNs, validateIterationObservationSettings } from "./iteration-observations.js";
|
|
15
17
|
import { logIterationObservationFailure, logIterationObservationRecovery } from "./iteration-observation-diagnostics.js";
|
|
16
18
|
import { normalizeSinkRetryBackoffMs, normalizeSinkRetryCount, sinkRetryDelayMs, waitForSinkRetryDelay } from "./sink-retry-policy.js";
|
|
17
19
|
const LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS = Symbol("loadstrike.load-engine-v2.scheduler-distributions");
|
|
18
20
|
const PORTAL_RUN_TOKEN_PROVIDER = Symbol.for("loadstrike.internal.portal-run-token-provider");
|
|
21
|
+
const REPORTING_INTERVAL_SECONDS = Symbol.for("loadstrike.internal.reporting-interval-seconds");
|
|
19
22
|
export const LoadStrikeNodeType = {
|
|
20
23
|
SingleNode: "SingleNode",
|
|
21
24
|
Coordinator: "Coordinator",
|
|
@@ -739,6 +742,15 @@ class ScenarioStatsAccumulator {
|
|
|
739
742
|
return attachScenarioStatsAliases(scenario);
|
|
740
743
|
}
|
|
741
744
|
}
|
|
745
|
+
function isRetryableReportingSinkError(sink, error) {
|
|
746
|
+
if (!(sink instanceof PrometheusRemoteWriteReportingSink)) {
|
|
747
|
+
return true;
|
|
748
|
+
}
|
|
749
|
+
if (!(error instanceof ReportingSinkHttpError)) {
|
|
750
|
+
return true;
|
|
751
|
+
}
|
|
752
|
+
return error.status === 429 || error.status < 400 || error.status >= 500;
|
|
753
|
+
}
|
|
742
754
|
export class LoadStrikeResponse {
|
|
743
755
|
/**
|
|
744
756
|
* Creates a successful reply.
|
|
@@ -3467,6 +3479,8 @@ export class LoadStrikeRunner {
|
|
|
3467
3479
|
return this.buildContext();
|
|
3468
3480
|
}
|
|
3469
3481
|
async run(args = []) {
|
|
3482
|
+
assertNoUnsupportedReportingSinkGraph(this.options.reportingSinks ?? []);
|
|
3483
|
+
assertNoUnsupportedNativeTrackingScenarios(this.scenarios);
|
|
3470
3484
|
if (this.contextConfigurators.length) {
|
|
3471
3485
|
return new LoadStrikeRunner(this.scenarios, this.buildContext().toRunnerOptions(), [], this.internalOptions).run(args);
|
|
3472
3486
|
}
|
|
@@ -3478,7 +3492,7 @@ export class LoadStrikeRunner {
|
|
|
3478
3492
|
const scenarioAccumulators = new Map();
|
|
3479
3493
|
const scenarioDurationsMs = new Map();
|
|
3480
3494
|
const stepStats = new Map();
|
|
3481
|
-
const sinks = (this.options.reportingSinks ?? []).map((sink) => cloneReportingSinkForRun(sink));
|
|
3495
|
+
const sinks = (this.options.reportingSinks ?? []).map((sink) => cloneReportingSinkForRun(sink, this.options.infraConfig ?? {}));
|
|
3482
3496
|
const sinkStates = sinks.map((sink, index) => ({
|
|
3483
3497
|
sink,
|
|
3484
3498
|
disabled: false,
|
|
@@ -3603,6 +3617,12 @@ export class LoadStrikeRunner {
|
|
|
3603
3617
|
testInfo,
|
|
3604
3618
|
getNodeInfo: () => attachNodeInfoAliases({ ...nodeInfo })
|
|
3605
3619
|
};
|
|
3620
|
+
Object.defineProperty(baseContext, REPORTING_INTERVAL_SECONDS, {
|
|
3621
|
+
value: this.options.reportingIntervalSeconds ?? 5,
|
|
3622
|
+
enumerable: false,
|
|
3623
|
+
configurable: false,
|
|
3624
|
+
writable: false
|
|
3625
|
+
});
|
|
3606
3626
|
attachBaseContextAliases(baseContext);
|
|
3607
3627
|
const sinkSession = {
|
|
3608
3628
|
startedUtc: createdUtc,
|
|
@@ -3657,6 +3677,7 @@ export class LoadStrikeRunner {
|
|
|
3657
3677
|
name: state.name,
|
|
3658
3678
|
iterationObservationPortalSink: Boolean(state.sink.iterationObservationPortalSink),
|
|
3659
3679
|
iterationObservationShapeLimited: Boolean(state.sink.iterationObservationShapeLimited),
|
|
3680
|
+
retryableErrorClassifier: (error) => isRetryableReportingSinkError(state.sink, error),
|
|
3660
3681
|
saveIterationBatch: resolveSinkSaveIterationBatch(state.sink),
|
|
3661
3682
|
completeIterationObservationStream: resolveSinkCompleteIterationObservationStream(state.sink)
|
|
3662
3683
|
}));
|
|
@@ -4291,7 +4312,9 @@ export class LoadStrikeRunner {
|
|
|
4291
4312
|
return;
|
|
4292
4313
|
}
|
|
4293
4314
|
catch (error) {
|
|
4294
|
-
const
|
|
4315
|
+
const retryable = isRetryableReportingSinkError(state.sink, error);
|
|
4316
|
+
const exhausted = attempts >= maximumAttempts || !retryable;
|
|
4317
|
+
const reportedMaximumAttempts = retryable ? maximumAttempts : attempts;
|
|
4295
4318
|
const nextDelayMs = exhausted ? 0 : sinkRetryDelayMs(backoffMs, attempts);
|
|
4296
4319
|
logIterationObservationFailure(logger, exhausted ? "error" : "warn", {
|
|
4297
4320
|
sinkName: state.name,
|
|
@@ -4301,7 +4324,7 @@ export class LoadStrikeRunner {
|
|
|
4301
4324
|
resultOwnerId: "",
|
|
4302
4325
|
observationCount: 0,
|
|
4303
4326
|
attempt: attempts,
|
|
4304
|
-
maximumAttempts,
|
|
4327
|
+
maximumAttempts: reportedMaximumAttempts,
|
|
4305
4328
|
nextDelayMs
|
|
4306
4329
|
}, error);
|
|
4307
4330
|
if (exhausted) {
|
|
@@ -8867,6 +8890,7 @@ class ManagedScenarioTrackingRuntime {
|
|
|
8867
8890
|
}
|
|
8868
8891
|
async dispose() {
|
|
8869
8892
|
this.shutdown = true;
|
|
8893
|
+
this.interruptEndpointAdapters();
|
|
8870
8894
|
this.rejectOutstandingWaiters();
|
|
8871
8895
|
await Promise.all([
|
|
8872
8896
|
this.sourceLoop,
|
|
@@ -8932,6 +8956,7 @@ class ManagedScenarioTrackingRuntime {
|
|
|
8932
8956
|
failureSeen = failureSeen || this.isFailedObservationOutcome(outcome);
|
|
8933
8957
|
}
|
|
8934
8958
|
this.shutdown = true;
|
|
8959
|
+
this.interruptEndpointAdapters();
|
|
8935
8960
|
await Promise.all([
|
|
8936
8961
|
this.sourceLoop,
|
|
8937
8962
|
this.destinationLoop,
|
|
@@ -8948,6 +8973,20 @@ class ManagedScenarioTrackingRuntime {
|
|
|
8948
8973
|
? LoadStrikeResponse.fail("tracking_failures", "One or more observed source or destination events did not correlate successfully.", 0)
|
|
8949
8974
|
: LoadStrikeResponse.ok("observed");
|
|
8950
8975
|
}
|
|
8976
|
+
interruptEndpointAdapters() {
|
|
8977
|
+
try {
|
|
8978
|
+
this.sourceAdapter.interrupt?.();
|
|
8979
|
+
}
|
|
8980
|
+
catch {
|
|
8981
|
+
// Shutdown remains best-effort and still disposes every adapter below.
|
|
8982
|
+
}
|
|
8983
|
+
try {
|
|
8984
|
+
this.destinationAdapter?.interrupt?.();
|
|
8985
|
+
}
|
|
8986
|
+
catch {
|
|
8987
|
+
// Shutdown remains best-effort and still disposes every adapter below.
|
|
8988
|
+
}
|
|
8989
|
+
}
|
|
8951
8990
|
async consumeSourceLoop() {
|
|
8952
8991
|
while (!this.shutdown) {
|
|
8953
8992
|
try {
|
|
@@ -9539,6 +9578,20 @@ function mapRuntimeTrackingEndpointSpec(spec, useLoadStrikeTraceIdHeader = false
|
|
|
9539
9578
|
azureEventHubs: asTrackingRecord(pickTrackingValue(spec, "AzureEventHubs", "azureEventHubs")),
|
|
9540
9579
|
sqs: asTrackingRecord(pickTrackingValue(spec, "Sqs", "sqs")),
|
|
9541
9580
|
pushDiffusion: asTrackingRecord(pickTrackingValue(spec, "PushDiffusion", "pushDiffusion")),
|
|
9581
|
+
grpc: mapRuntimeEndpointProtocolOptions(spec, ["Grpc", "grpc"], [
|
|
9582
|
+
"Target", "target", "ServiceName", "serviceName", "MethodName", "methodName",
|
|
9583
|
+
"MethodType", "methodType", "Deadline", "deadline", "DeadlineSeconds", "deadlineSeconds",
|
|
9584
|
+
"DeadlineMs", "deadlineMs", "Metadata", "metadata", "Produce", "produce", "Consume", "consume",
|
|
9585
|
+
"ProduceAsync", "produceAsync", "ConsumeAsync", "consumeAsync", "ConnectionMetadata", "connectionMetadata",
|
|
9586
|
+
"NativeClient", "nativeClient"
|
|
9587
|
+
]),
|
|
9588
|
+
webSocket: mapRuntimeEndpointProtocolOptions(spec, ["WebSocket", "webSocket"], [
|
|
9589
|
+
"Url", "url", "Subprotocols", "subprotocols", "ConnectTimeout", "connectTimeout",
|
|
9590
|
+
"ConnectTimeoutSeconds", "connectTimeoutSeconds", "ConnectTimeoutMs", "connectTimeoutMs",
|
|
9591
|
+
"CloseTimeout", "closeTimeout", "CloseTimeoutSeconds", "closeTimeoutSeconds", "CloseTimeoutMs", "closeTimeoutMs",
|
|
9592
|
+
"Produce", "produce", "Consume", "consume", "ProduceAsync", "produceAsync", "ConsumeAsync", "consumeAsync",
|
|
9593
|
+
"ConnectionMetadata", "connectionMetadata", "NativeClient", "nativeClient"
|
|
9594
|
+
]),
|
|
9542
9595
|
delegate: typeof delegateProduce === "function"
|
|
9543
9596
|
|| typeof delegateConsume === "function"
|
|
9544
9597
|
|| typeof delegateProduceAsync === "function"
|
|
@@ -9561,6 +9614,19 @@ function mapRuntimeTrackingEndpointSpec(spec, useLoadStrikeTraceIdHeader = false
|
|
|
9561
9614
|
: undefined
|
|
9562
9615
|
};
|
|
9563
9616
|
}
|
|
9617
|
+
function mapRuntimeEndpointProtocolOptions(spec, nestedKeys, flatKeys) {
|
|
9618
|
+
const nested = asTrackingRecord(pickTrackingValue(spec, ...nestedKeys));
|
|
9619
|
+
if (Object.keys(nested).length > 0) {
|
|
9620
|
+
return nested;
|
|
9621
|
+
}
|
|
9622
|
+
const options = {};
|
|
9623
|
+
for (const key of flatKeys) {
|
|
9624
|
+
if (Object.prototype.hasOwnProperty.call(spec, key)) {
|
|
9625
|
+
options[key] = pickTrackingValue(spec, key);
|
|
9626
|
+
}
|
|
9627
|
+
}
|
|
9628
|
+
return options;
|
|
9629
|
+
}
|
|
9564
9630
|
function normalizeRuntimeTrackingPayload(payload, endpoint, index) {
|
|
9565
9631
|
const normalized = {
|
|
9566
9632
|
headers: {
|
|
@@ -10324,6 +10390,20 @@ function assertNoDisableLicenseEnforcementOption(value, source) {
|
|
|
10324
10390
|
}
|
|
10325
10391
|
}
|
|
10326
10392
|
}
|
|
10393
|
+
function assertNoUnsupportedNativeTrackingScenarios(scenarios) {
|
|
10394
|
+
for (const scenario of scenarios) {
|
|
10395
|
+
const tracking = scenario.getTrackingConfiguration();
|
|
10396
|
+
if (!tracking) {
|
|
10397
|
+
continue;
|
|
10398
|
+
}
|
|
10399
|
+
for (const field of ["Source", "Destination"]) {
|
|
10400
|
+
const endpoint = asTrackingRecord(pickTrackingValue(tracking, field, field.toLowerCase()));
|
|
10401
|
+
if (Object.keys(endpoint).length > 0) {
|
|
10402
|
+
validateNativeEndpointExecutionSupport(endpoint);
|
|
10403
|
+
}
|
|
10404
|
+
}
|
|
10405
|
+
}
|
|
10406
|
+
}
|
|
10327
10407
|
function normalizeRunContextCollectionShapes(values) {
|
|
10328
10408
|
assertNoDisableLicenseEnforcementOption(values, "LoadStrikeContext");
|
|
10329
10409
|
const normalized = {
|