@loadstrike/loadstrike-sdk 1.0.23001 → 1.0.23401
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/dist/cjs/index.js +2 -1
- package/dist/cjs/local.js +5 -1
- package/dist/cjs/runtime.js +64 -1
- package/dist/cjs/sinks.js +128 -19
- package/dist/esm/index.js +1 -1
- package/dist/esm/local.js +5 -1
- package/dist/esm/runtime.js +64 -1
- package/dist/esm/sinks.js +126 -18
- package/dist/types/index.d.ts +2 -2
- package/dist/types/local.d.ts +1 -0
- package/dist/types/runtime.d.ts +16 -0
- package/dist/types/sinks.d.ts +39 -4
- package/package.json +1 -1
package/dist/cjs/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
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;
|
|
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; } });
|
|
@@ -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
|
@@ -55,7 +55,8 @@ const SINK_FEATURE_BY_KIND = {
|
|
|
55
55
|
grafanaloki: "extensions.reporting_sinks.grafana_loki",
|
|
56
56
|
datadog: "extensions.reporting_sinks.datadog",
|
|
57
57
|
splunk: "extensions.reporting_sinks.splunk",
|
|
58
|
-
otelcollector: "extensions.reporting_sinks.otel_collector"
|
|
58
|
+
otelcollector: "extensions.reporting_sinks.otel_collector",
|
|
59
|
+
portal: "extensions.reporting_sinks.portal"
|
|
59
60
|
};
|
|
60
61
|
const TRACKING_FEATURE_BY_KIND = {
|
|
61
62
|
http: "endpoint.http",
|
|
@@ -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);
|
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,35 @@ 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",
|
|
4116
|
+
PortalReportingRunId: "portalReportingRunId"
|
|
4076
4117
|
});
|
|
4077
4118
|
return session;
|
|
4078
4119
|
}
|
|
4120
|
+
function attachPortalReportingSession(sinkSession, sessionInfo, licenseClient, licenseSession) {
|
|
4121
|
+
const runToken = stringValueOrDefault(licenseSession?.runToken, "").trim();
|
|
4122
|
+
if (!runToken || !licenseClient) {
|
|
4123
|
+
return;
|
|
4124
|
+
}
|
|
4125
|
+
const ingestUrl = licenseClient.portalReportingIngestUrl();
|
|
4126
|
+
const runId = buildPortalReportingRunId(sessionInfo.testInfo.sessionId);
|
|
4127
|
+
sinkSession.runToken = runToken;
|
|
4128
|
+
sinkSession.portalReportingIngestUrl = ingestUrl;
|
|
4129
|
+
sinkSession.portalReportingRunId = runId;
|
|
4130
|
+
sessionInfo.runToken = runToken;
|
|
4131
|
+
sessionInfo.portalReportingIngestUrl = ingestUrl;
|
|
4132
|
+
sessionInfo.portalReportingRunId = runId;
|
|
4133
|
+
}
|
|
4134
|
+
function buildPortalReportingRunId(sessionId) {
|
|
4135
|
+
const sessionPart = String(sessionId ?? "")
|
|
4136
|
+
.replace(/[^A-Za-z0-9._-]+/g, "-")
|
|
4137
|
+
.replace(/^-+|-+$/g, "")
|
|
4138
|
+
.slice(0, 80);
|
|
4139
|
+
const uniquePart = generateRuntimeSessionId().slice(0, 16);
|
|
4140
|
+
return sessionPart ? `${sessionPart}-${uniquePart}` : uniquePart;
|
|
4141
|
+
}
|
|
4079
4142
|
function attachScenarioInitContextAliases(context) {
|
|
4080
4143
|
context.nodeInfo = attachNodeInfoAliases(context.nodeInfo);
|
|
4081
4144
|
context.testInfo = attachTestInfoAliases(context.testInfo);
|
package/dist/cjs/sinks.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.__loadstrikeTestExports = exports.OtelCollectorReportingSink = exports.SplunkReportingSink = exports.DatadogReportingSink = exports.TimescaleDbReportingSink = exports.GrafanaLokiReportingSink = exports.InfluxDbReportingSink = exports.CompositeReportingSink = exports.ConsoleReportingSink = exports.MemoryReportingSink = exports.OtelCollectorReportingSinkOptions = exports.SplunkReportingSinkOptions = exports.DatadogReportingSinkOptions = exports.TimescaleDbReportingSinkOptions = exports.GrafanaLokiReportingSinkOptions = exports.InfluxDbReportingSinkOptions = void 0;
|
|
3
|
+
exports.__loadstrikeTestExports = exports.OtelCollectorReportingSink = exports.SplunkReportingSink = exports.DatadogReportingSink = exports.TimescaleDbReportingSink = exports.GrafanaLokiReportingSink = exports.InfluxDbReportingSink = exports.PortalReportingSink = exports.CompositeReportingSink = exports.ConsoleReportingSink = exports.MemoryReportingSink = exports.OtelCollectorReportingSinkOptions = exports.SplunkReportingSinkOptions = exports.DatadogReportingSinkOptions = exports.TimescaleDbReportingSinkOptions = exports.GrafanaLokiReportingSinkOptions = exports.InfluxDbReportingSinkOptions = void 0;
|
|
4
4
|
const runtime_js_1 = require("./runtime.js");
|
|
5
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
6
|
const pg_1 = require("pg");
|
|
6
7
|
const DEFAULT_INFLUX_CONFIGURATION_SECTION_PATH = "LoadStrike:ReportingSinks:InfluxDb";
|
|
7
8
|
const DEFAULT_GRAFANA_LOKI_CONFIGURATION_SECTION_PATH = "LoadStrike:ReportingSinks:GrafanaLoki";
|
|
@@ -384,6 +385,92 @@ class CompositeReportingSink {
|
|
|
384
385
|
}
|
|
385
386
|
}
|
|
386
387
|
exports.CompositeReportingSink = CompositeReportingSink;
|
|
388
|
+
class PortalReportingSink {
|
|
389
|
+
constructor(options = {}) {
|
|
390
|
+
this.sinkName = "portal";
|
|
391
|
+
this.SinkName = "portal";
|
|
392
|
+
this.licenseFeature = "extensions.reporting_sinks.portal";
|
|
393
|
+
this.LicenseFeature = "extensions.reporting_sinks.portal";
|
|
394
|
+
this.baseContext = null;
|
|
395
|
+
this.session = null;
|
|
396
|
+
this.runToken = "";
|
|
397
|
+
this.ingestUrl = "";
|
|
398
|
+
const source = asRecord(options);
|
|
399
|
+
this.timeoutMs = resolveTimeoutMs(optionNumber(source, "timeoutSeconds", "TimeoutSeconds"), optionNumber(source, "timeoutMs", "TimeoutMs"));
|
|
400
|
+
this.fetchImpl = pickRecordValue(source, "fetchImpl", "FetchImpl") ?? fetch;
|
|
401
|
+
}
|
|
402
|
+
init(context, _infraConfig) {
|
|
403
|
+
this.baseContext = cloneBaseContext(context);
|
|
404
|
+
}
|
|
405
|
+
Init(context, infraConfig) {
|
|
406
|
+
this.init(context, infraConfig);
|
|
407
|
+
}
|
|
408
|
+
start(session) {
|
|
409
|
+
this.runToken = String(session.runToken ?? session.RunToken ?? "").trim();
|
|
410
|
+
this.ingestUrl = String(session.portalReportingIngestUrl ?? session.PortalReportingIngestUrl ?? "").trim();
|
|
411
|
+
if (!this.runToken || !this.ingestUrl) {
|
|
412
|
+
throw new Error("PortalReportingSink requires a managed portal reporting session.");
|
|
413
|
+
}
|
|
414
|
+
this.session = sinkSessionMetadataFromContext(this.getBaseContext(), session);
|
|
415
|
+
}
|
|
416
|
+
Start(session) {
|
|
417
|
+
this.start(session);
|
|
418
|
+
}
|
|
419
|
+
async saveRealtimeStats(scenarioStats) {
|
|
420
|
+
await this.persistEvents(createRealtimeStatsEvents(this.getSession(), scenarioStats));
|
|
421
|
+
}
|
|
422
|
+
async SaveRealtimeStats(scenarioStats) {
|
|
423
|
+
await this.saveRealtimeStats(scenarioStats);
|
|
424
|
+
}
|
|
425
|
+
async saveRealtimeMetrics(metrics) {
|
|
426
|
+
await this.persistEvents(createRealtimeMetricEvents(this.getSession(), metrics));
|
|
427
|
+
}
|
|
428
|
+
async SaveRealtimeMetrics(metrics) {
|
|
429
|
+
await this.saveRealtimeMetrics(metrics);
|
|
430
|
+
}
|
|
431
|
+
async saveRunResult(result) {
|
|
432
|
+
await this.persistEvents(createRunResultEvents(this.getSession(), result));
|
|
433
|
+
}
|
|
434
|
+
async SaveRunResult(result) {
|
|
435
|
+
await this.saveRunResult(result);
|
|
436
|
+
}
|
|
437
|
+
stop() {
|
|
438
|
+
this.session = null;
|
|
439
|
+
}
|
|
440
|
+
Stop() {
|
|
441
|
+
this.stop();
|
|
442
|
+
}
|
|
443
|
+
Dispose() {
|
|
444
|
+
this.baseContext = null;
|
|
445
|
+
this.stop();
|
|
446
|
+
}
|
|
447
|
+
getBaseContext() {
|
|
448
|
+
if (!this.baseContext) {
|
|
449
|
+
throw new Error(`${this.sinkName} has not been initialized.`);
|
|
450
|
+
}
|
|
451
|
+
return this.baseContext;
|
|
452
|
+
}
|
|
453
|
+
getSession() {
|
|
454
|
+
if (!this.session) {
|
|
455
|
+
throw new Error(`${this.sinkName} has not been started.`);
|
|
456
|
+
}
|
|
457
|
+
return this.session;
|
|
458
|
+
}
|
|
459
|
+
async persistEvents(events) {
|
|
460
|
+
if (!events.length) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
await postWithTimeout(this.fetchImpl, this.ingestUrl, {
|
|
464
|
+
method: "POST",
|
|
465
|
+
headers: { "Content-Type": "application/json" },
|
|
466
|
+
body: JSON.stringify({
|
|
467
|
+
runToken: this.runToken,
|
|
468
|
+
events: events.map((event, index) => portalEventPayload(event, index))
|
|
469
|
+
})
|
|
470
|
+
}, this.timeoutMs, "PortalReportingSink");
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
exports.PortalReportingSink = PortalReportingSink;
|
|
387
474
|
class InfluxDbReportingSink {
|
|
388
475
|
constructor(options = {}) {
|
|
389
476
|
this.sinkName = "influxdb";
|
|
@@ -1532,6 +1619,7 @@ function createReportingEvent(session, occurredUtc, eventType, scenarioName, ste
|
|
|
1532
1619
|
eventFields.completed_utc = occurredUtc.toISOString();
|
|
1533
1620
|
}
|
|
1534
1621
|
return {
|
|
1622
|
+
runId: session.runId,
|
|
1535
1623
|
eventType,
|
|
1536
1624
|
occurredUtc,
|
|
1537
1625
|
sessionId: session.sessionId,
|
|
@@ -1546,6 +1634,38 @@ function createReportingEvent(session, occurredUtc, eventType, scenarioName, ste
|
|
|
1546
1634
|
fields: eventFields
|
|
1547
1635
|
};
|
|
1548
1636
|
}
|
|
1637
|
+
function portalEventPayload(event, index) {
|
|
1638
|
+
return {
|
|
1639
|
+
eventId: portalEventId(event, index),
|
|
1640
|
+
runId: event.runId,
|
|
1641
|
+
eventType: event.eventType,
|
|
1642
|
+
occurredUtc: event.occurredUtc.toISOString(),
|
|
1643
|
+
sessionId: event.sessionId,
|
|
1644
|
+
testSuite: event.testSuite,
|
|
1645
|
+
testName: event.testName,
|
|
1646
|
+
clusterId: event.clusterId,
|
|
1647
|
+
nodeType: event.nodeType,
|
|
1648
|
+
machineName: event.machineName,
|
|
1649
|
+
scenarioName: event.scenarioName,
|
|
1650
|
+
stepName: event.stepName,
|
|
1651
|
+
tags: { ...event.tags },
|
|
1652
|
+
fields: deepCloneRecord(event.fields)
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
function portalEventId(event, index) {
|
|
1656
|
+
const material = JSON.stringify({
|
|
1657
|
+
sessionId: event.sessionId,
|
|
1658
|
+
runId: event.runId,
|
|
1659
|
+
eventType: event.eventType,
|
|
1660
|
+
occurredUtc: event.occurredUtc.toISOString(),
|
|
1661
|
+
scenarioName: event.scenarioName ?? "",
|
|
1662
|
+
stepName: event.stepName ?? "",
|
|
1663
|
+
index,
|
|
1664
|
+
tags: event.tags,
|
|
1665
|
+
fields: event.fields
|
|
1666
|
+
});
|
|
1667
|
+
return `lsr_${(0, node_crypto_1.createHash)("sha256").update(material).digest("hex").slice(0, 40)}`;
|
|
1668
|
+
}
|
|
1549
1669
|
function addMeasurementFields(fields, prefix, measurement) {
|
|
1550
1670
|
const request = measurement?.request ?? { count: 0, percent: 0, rps: 0 };
|
|
1551
1671
|
const latency = measurement?.latency ?? {
|
|
@@ -2093,7 +2213,9 @@ function sinkSessionMetadataFromContext(context, session) {
|
|
|
2093
2213
|
const testInfo = context.testInfo;
|
|
2094
2214
|
const sessionStartedUtc = String(session?.startedUtc ?? session?.StartedUtc ?? "");
|
|
2095
2215
|
const contextStartedUtc = String(testInfo.createdUtc ?? testInfo.CreatedUtc ?? "");
|
|
2216
|
+
const runId = String(session?.portalReportingRunId ?? session?.PortalReportingRunId ?? testInfo.sessionId ?? "").trim();
|
|
2096
2217
|
return {
|
|
2218
|
+
runId: runId || String(testInfo.sessionId ?? ""),
|
|
2097
2219
|
sessionId: String(testInfo.sessionId ?? ""),
|
|
2098
2220
|
testSuite: String(testInfo.testSuite ?? ""),
|
|
2099
2221
|
testName: String(testInfo.testName ?? ""),
|
|
@@ -2571,7 +2693,10 @@ function cloneSessionStartInfo(session) {
|
|
|
2571
2693
|
...cloneBaseContext(session),
|
|
2572
2694
|
startedUtc: session.startedUtc,
|
|
2573
2695
|
scenarioNames: [...session.scenarioNames],
|
|
2574
|
-
scenarios: session.scenarios.map((value) => ({ ...value }))
|
|
2696
|
+
scenarios: session.scenarios.map((value) => ({ ...value })),
|
|
2697
|
+
runToken: session.runToken,
|
|
2698
|
+
portalReportingIngestUrl: session.portalReportingIngestUrl,
|
|
2699
|
+
portalReportingRunId: session.portalReportingRunId
|
|
2575
2700
|
};
|
|
2576
2701
|
}
|
|
2577
2702
|
function cloneNodeInfo(nodeInfo) {
|
|
@@ -2673,23 +2798,7 @@ function runResultToNodeStats(result) {
|
|
|
2673
2798
|
disabledSinks: [...(result.disabledSinks ?? [])],
|
|
2674
2799
|
sinkErrors: (result.sinkErrors ?? []).map((value) => ({ ...value })),
|
|
2675
2800
|
reportFiles: [...(result.reportFiles ?? [])],
|
|
2676
|
-
logFiles: [...(result.logFiles ?? [])]
|
|
2677
|
-
findScenarioStats: (scenarioName) => result.scenarioStats.find((value) => value.scenarioName === scenarioName),
|
|
2678
|
-
getScenarioStats: (scenarioName) => {
|
|
2679
|
-
const scenario = result.scenarioStats.find((value) => value.scenarioName === scenarioName);
|
|
2680
|
-
if (!scenario) {
|
|
2681
|
-
throw new Error(`Scenario stats not found: ${scenarioName}`);
|
|
2682
|
-
}
|
|
2683
|
-
return scenario;
|
|
2684
|
-
},
|
|
2685
|
-
FindScenarioStats: (scenarioName) => result.scenarioStats.find((value) => value.scenarioName === scenarioName),
|
|
2686
|
-
GetScenarioStats: (scenarioName) => {
|
|
2687
|
-
const scenario = result.scenarioStats.find((value) => value.scenarioName === scenarioName);
|
|
2688
|
-
if (!scenario) {
|
|
2689
|
-
throw new Error(`Scenario stats not found: ${scenarioName}`);
|
|
2690
|
-
}
|
|
2691
|
-
return scenario;
|
|
2692
|
-
}
|
|
2801
|
+
logFiles: [...(result.logFiles ?? [])]
|
|
2693
2802
|
};
|
|
2694
2803
|
}
|
|
2695
2804
|
function deepCloneRecord(value) {
|
package/dist/esm/index.js
CHANGED
|
@@ -3,4 +3,4 @@ export { LoadStrikeAutopilotReadiness } from "./autopilot-contracts.js";
|
|
|
3
3
|
export { CrossPlatformScenarioConfigurator, ScenarioTrackingExtensions, LoadStrikeContext, LoadStrikePluginData, LoadStrikePluginDataTable, LoadStrikeNodeType, LoadStrikeReportFormat, LoadStrikeResponse, LoadStrikeLogLevel, LoadStrikeScenarioOperation, LoadStrikeOperationType, LoadStrikeRunner, LoadStrikeScenario, LoadStrikeSimulation, CrossPlatformTrackingConfiguration, LoadStrikeMetric, LoadStrikeCounter, LoadStrikeGauge, LoadStrikeStep, LoadStrikeThreshold } from "./runtime.js";
|
|
4
4
|
export { CorrelationStoreConfiguration, CrossPlatformTrackingRuntime, InMemoryCorrelationStore, RedisCorrelationStoreOptions, RedisCorrelationStore, TrackingPayloadBuilder, TrackingFieldSelector } from "./correlation.js";
|
|
5
5
|
export { LOADSTRIKE_TRACE_ID_HEADER, LOADSTRIKE_TRACE_ID_TRACKING_FIELD, TrafficEndpointDefinition, HttpEndpointDefinition, KafkaEndpointDefinition, KafkaSaslOptions, RabbitMqEndpointDefinition, NatsEndpointDefinition, RedisStreamsEndpointDefinition, AzureEventHubsEndpointDefinition, SqsEndpointDefinition, DelegateStreamEndpointDefinition, PushDiffusionEndpointDefinition, HttpOAuth2ClientCredentialsOptions, HttpAuthOptions } from "./transports.js";
|
|
6
|
-
export { DatadogReportingSink, DatadogReportingSinkOptions, GrafanaLokiReportingSink, GrafanaLokiReportingSinkOptions, InfluxDbReportingSink, InfluxDbReportingSinkOptions, OtelCollectorReportingSink, OtelCollectorReportingSinkOptions, SplunkReportingSink, SplunkReportingSinkOptions, TimescaleDbReportingSink, TimescaleDbReportingSinkOptions } from "./sinks.js";
|
|
6
|
+
export { DatadogReportingSink, DatadogReportingSinkOptions, GrafanaLokiReportingSink, GrafanaLokiReportingSinkOptions, InfluxDbReportingSink, InfluxDbReportingSinkOptions, OtelCollectorReportingSink, OtelCollectorReportingSinkOptions, PortalReportingSink, SplunkReportingSink, SplunkReportingSinkOptions, TimescaleDbReportingSink, TimescaleDbReportingSinkOptions } from "./sinks.js";
|
package/dist/esm/local.js
CHANGED
|
@@ -16,7 +16,8 @@ const SINK_FEATURE_BY_KIND = {
|
|
|
16
16
|
grafanaloki: "extensions.reporting_sinks.grafana_loki",
|
|
17
17
|
datadog: "extensions.reporting_sinks.datadog",
|
|
18
18
|
splunk: "extensions.reporting_sinks.splunk",
|
|
19
|
-
otelcollector: "extensions.reporting_sinks.otel_collector"
|
|
19
|
+
otelcollector: "extensions.reporting_sinks.otel_collector",
|
|
20
|
+
portal: "extensions.reporting_sinks.portal"
|
|
20
21
|
};
|
|
21
22
|
const TRACKING_FEATURE_BY_KIND = {
|
|
22
23
|
http: "endpoint.http",
|
|
@@ -57,6 +58,9 @@ export class LoadStrikeLocalClient {
|
|
|
57
58
|
this.licensingApiBaseUrl = resolveLicensingApiBaseUrl();
|
|
58
59
|
this.licenseValidationTimeoutMs = normalizeTimeoutMs(options.licenseValidationTimeoutMs);
|
|
59
60
|
}
|
|
61
|
+
portalReportingIngestUrl() {
|
|
62
|
+
return `${this.licensingApiBaseUrl.replace(/\/+$/, "")}/api/v1/reporting/ingest`;
|
|
63
|
+
}
|
|
60
64
|
async run(request) {
|
|
61
65
|
const sanitized = sanitizeRequest(request);
|
|
62
66
|
const licenseSession = await this.acquireLicenseLease(sanitized);
|
package/dist/esm/runtime.js
CHANGED
|
@@ -7,6 +7,7 @@ import { DistributedClusterAgent, DistributedClusterCoordinator } from "./cluste
|
|
|
7
7
|
import { CorrelationStoreConfiguration, CrossPlatformTrackingRuntime, RedisCorrelationStore, RedisCorrelationStoreOptions, TrackingFieldSelector } from "./correlation.js";
|
|
8
8
|
import { EndpointAdapterFactory, LOADSTRIKE_TRACE_ID_TRACKING_FIELD } from "./transports.js";
|
|
9
9
|
import { buildDotnetCsvReport, buildDotnetHtmlReport, buildDotnetMarkdownReport, buildDotnetTxtReport } from "./reporting.js";
|
|
10
|
+
import { PortalReportingSink } from "./sinks.js";
|
|
10
11
|
export const LoadStrikeNodeType = {
|
|
11
12
|
SingleNode: "SingleNode",
|
|
12
13
|
Coordinator: "Coordinator",
|
|
@@ -1378,6 +1379,16 @@ export class LoadStrikeContext {
|
|
|
1378
1379
|
validateNamedReportingSinks(sinks);
|
|
1379
1380
|
return this.mergeValues({ ReportingSinks: sinks });
|
|
1380
1381
|
}
|
|
1382
|
+
withPortalReporting() {
|
|
1383
|
+
return this.WithPortalReporting();
|
|
1384
|
+
}
|
|
1385
|
+
WithPortalReporting() {
|
|
1386
|
+
const sinks = [...(this.values.ReportingSinks ?? [])];
|
|
1387
|
+
if (!sinks.some((sink, index) => resolveSinkName(sink, index).trim().toLowerCase() === "portal")) {
|
|
1388
|
+
sinks.push(new PortalReportingSink());
|
|
1389
|
+
}
|
|
1390
|
+
return this.mergeValues({ ReportingSinks: sinks });
|
|
1391
|
+
}
|
|
1381
1392
|
/**
|
|
1382
1393
|
* Registers runtime policies for the run.
|
|
1383
1394
|
* Use this when scenario selection or step execution should obey policy callbacks.
|
|
@@ -2118,6 +2129,9 @@ export class LoadStrikeRunner {
|
|
|
2118
2129
|
static WithReportingSinks(context, ...sinks) {
|
|
2119
2130
|
return context.WithReportingSinks(...sinks);
|
|
2120
2131
|
}
|
|
2132
|
+
static WithPortalReporting(context) {
|
|
2133
|
+
return context.WithPortalReporting();
|
|
2134
|
+
}
|
|
2121
2135
|
/**
|
|
2122
2136
|
* Registers runtime policies for the run.
|
|
2123
2137
|
* Use this when scenario selection or step execution should obey policy callbacks.
|
|
@@ -2346,6 +2360,29 @@ export class LoadStrikeRunner {
|
|
|
2346
2360
|
WithReportingInterval(intervalSeconds) {
|
|
2347
2361
|
return this.withReportingInterval(intervalSeconds);
|
|
2348
2362
|
}
|
|
2363
|
+
withReportingSinks(...sinks) {
|
|
2364
|
+
if (!sinks.length) {
|
|
2365
|
+
throw new Error("At least one reporting sink should be provided.");
|
|
2366
|
+
}
|
|
2367
|
+
if (sinks.some((sink) => sink == null)) {
|
|
2368
|
+
throw new Error("Reporting sink collection cannot contain null values.");
|
|
2369
|
+
}
|
|
2370
|
+
validateNamedReportingSinks(sinks);
|
|
2371
|
+
return this.configure({ reportingSinks: sinks });
|
|
2372
|
+
}
|
|
2373
|
+
WithReportingSinks(...sinks) {
|
|
2374
|
+
return this.withReportingSinks(...sinks);
|
|
2375
|
+
}
|
|
2376
|
+
withPortalReporting() {
|
|
2377
|
+
const sinks = [...(this.options.reportingSinks ?? [])];
|
|
2378
|
+
if (!sinks.some((sink, index) => resolveSinkName(sink, index).trim().toLowerCase() === "portal")) {
|
|
2379
|
+
sinks.push(new PortalReportingSink());
|
|
2380
|
+
}
|
|
2381
|
+
return this.configure({ reportingSinks: sinks });
|
|
2382
|
+
}
|
|
2383
|
+
WithPortalReporting() {
|
|
2384
|
+
return this.withPortalReporting();
|
|
2385
|
+
}
|
|
2349
2386
|
/**
|
|
2350
2387
|
* Sets the timeout for cluster command round-trips.
|
|
2351
2388
|
* Use this when distributed control messages need a tighter or looser deadline.
|
|
@@ -2519,6 +2556,7 @@ export class LoadStrikeRunner {
|
|
|
2519
2556
|
testInfo,
|
|
2520
2557
|
getNodeInfo: () => attachNodeInfoAliases({ ...nodeInfo })
|
|
2521
2558
|
};
|
|
2559
|
+
attachPortalReportingSession(sinkSession, sessionInfo, licenseClient, licenseSession);
|
|
2522
2560
|
attachSessionStartInfoAliases(sessionInfo);
|
|
2523
2561
|
nodeInfo.currentOperation = "Init";
|
|
2524
2562
|
for (const plugin of plugins) {
|
|
@@ -4052,10 +4090,35 @@ function attachSessionStartInfoAliases(session) {
|
|
|
4052
4090
|
attachAliasMap(session, {
|
|
4053
4091
|
StartedUtc: "startedUtc",
|
|
4054
4092
|
ScenarioNames: "scenarioNames",
|
|
4055
|
-
Scenarios: "scenarios"
|
|
4093
|
+
Scenarios: "scenarios",
|
|
4094
|
+
RunToken: "runToken",
|
|
4095
|
+
PortalReportingIngestUrl: "portalReportingIngestUrl",
|
|
4096
|
+
PortalReportingRunId: "portalReportingRunId"
|
|
4056
4097
|
});
|
|
4057
4098
|
return session;
|
|
4058
4099
|
}
|
|
4100
|
+
function attachPortalReportingSession(sinkSession, sessionInfo, licenseClient, licenseSession) {
|
|
4101
|
+
const runToken = stringValueOrDefault(licenseSession?.runToken, "").trim();
|
|
4102
|
+
if (!runToken || !licenseClient) {
|
|
4103
|
+
return;
|
|
4104
|
+
}
|
|
4105
|
+
const ingestUrl = licenseClient.portalReportingIngestUrl();
|
|
4106
|
+
const runId = buildPortalReportingRunId(sessionInfo.testInfo.sessionId);
|
|
4107
|
+
sinkSession.runToken = runToken;
|
|
4108
|
+
sinkSession.portalReportingIngestUrl = ingestUrl;
|
|
4109
|
+
sinkSession.portalReportingRunId = runId;
|
|
4110
|
+
sessionInfo.runToken = runToken;
|
|
4111
|
+
sessionInfo.portalReportingIngestUrl = ingestUrl;
|
|
4112
|
+
sessionInfo.portalReportingRunId = runId;
|
|
4113
|
+
}
|
|
4114
|
+
function buildPortalReportingRunId(sessionId) {
|
|
4115
|
+
const sessionPart = String(sessionId ?? "")
|
|
4116
|
+
.replace(/[^A-Za-z0-9._-]+/g, "-")
|
|
4117
|
+
.replace(/^-+|-+$/g, "")
|
|
4118
|
+
.slice(0, 80);
|
|
4119
|
+
const uniquePart = generateRuntimeSessionId().slice(0, 16);
|
|
4120
|
+
return sessionPart ? `${sessionPart}-${uniquePart}` : uniquePart;
|
|
4121
|
+
}
|
|
4059
4122
|
function attachScenarioInitContextAliases(context) {
|
|
4060
4123
|
context.nodeInfo = attachNodeInfoAliases(context.nodeInfo);
|
|
4061
4124
|
context.testInfo = attachTestInfoAliases(context.testInfo);
|
package/dist/esm/sinks.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LoadStrikePluginData as LoadStrikePluginDataModel, LoadStrikePluginDataTable as LoadStrikePluginDataTableModel } from "./runtime.js";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import { Pool } from "pg";
|
|
3
4
|
const DEFAULT_INFLUX_CONFIGURATION_SECTION_PATH = "LoadStrike:ReportingSinks:InfluxDb";
|
|
4
5
|
const DEFAULT_GRAFANA_LOKI_CONFIGURATION_SECTION_PATH = "LoadStrike:ReportingSinks:GrafanaLoki";
|
|
@@ -372,6 +373,91 @@ export class CompositeReportingSink {
|
|
|
372
373
|
await this.stop();
|
|
373
374
|
}
|
|
374
375
|
}
|
|
376
|
+
export class PortalReportingSink {
|
|
377
|
+
constructor(options = {}) {
|
|
378
|
+
this.sinkName = "portal";
|
|
379
|
+
this.SinkName = "portal";
|
|
380
|
+
this.licenseFeature = "extensions.reporting_sinks.portal";
|
|
381
|
+
this.LicenseFeature = "extensions.reporting_sinks.portal";
|
|
382
|
+
this.baseContext = null;
|
|
383
|
+
this.session = null;
|
|
384
|
+
this.runToken = "";
|
|
385
|
+
this.ingestUrl = "";
|
|
386
|
+
const source = asRecord(options);
|
|
387
|
+
this.timeoutMs = resolveTimeoutMs(optionNumber(source, "timeoutSeconds", "TimeoutSeconds"), optionNumber(source, "timeoutMs", "TimeoutMs"));
|
|
388
|
+
this.fetchImpl = pickRecordValue(source, "fetchImpl", "FetchImpl") ?? fetch;
|
|
389
|
+
}
|
|
390
|
+
init(context, _infraConfig) {
|
|
391
|
+
this.baseContext = cloneBaseContext(context);
|
|
392
|
+
}
|
|
393
|
+
Init(context, infraConfig) {
|
|
394
|
+
this.init(context, infraConfig);
|
|
395
|
+
}
|
|
396
|
+
start(session) {
|
|
397
|
+
this.runToken = String(session.runToken ?? session.RunToken ?? "").trim();
|
|
398
|
+
this.ingestUrl = String(session.portalReportingIngestUrl ?? session.PortalReportingIngestUrl ?? "").trim();
|
|
399
|
+
if (!this.runToken || !this.ingestUrl) {
|
|
400
|
+
throw new Error("PortalReportingSink requires a managed portal reporting session.");
|
|
401
|
+
}
|
|
402
|
+
this.session = sinkSessionMetadataFromContext(this.getBaseContext(), session);
|
|
403
|
+
}
|
|
404
|
+
Start(session) {
|
|
405
|
+
this.start(session);
|
|
406
|
+
}
|
|
407
|
+
async saveRealtimeStats(scenarioStats) {
|
|
408
|
+
await this.persistEvents(createRealtimeStatsEvents(this.getSession(), scenarioStats));
|
|
409
|
+
}
|
|
410
|
+
async SaveRealtimeStats(scenarioStats) {
|
|
411
|
+
await this.saveRealtimeStats(scenarioStats);
|
|
412
|
+
}
|
|
413
|
+
async saveRealtimeMetrics(metrics) {
|
|
414
|
+
await this.persistEvents(createRealtimeMetricEvents(this.getSession(), metrics));
|
|
415
|
+
}
|
|
416
|
+
async SaveRealtimeMetrics(metrics) {
|
|
417
|
+
await this.saveRealtimeMetrics(metrics);
|
|
418
|
+
}
|
|
419
|
+
async saveRunResult(result) {
|
|
420
|
+
await this.persistEvents(createRunResultEvents(this.getSession(), result));
|
|
421
|
+
}
|
|
422
|
+
async SaveRunResult(result) {
|
|
423
|
+
await this.saveRunResult(result);
|
|
424
|
+
}
|
|
425
|
+
stop() {
|
|
426
|
+
this.session = null;
|
|
427
|
+
}
|
|
428
|
+
Stop() {
|
|
429
|
+
this.stop();
|
|
430
|
+
}
|
|
431
|
+
Dispose() {
|
|
432
|
+
this.baseContext = null;
|
|
433
|
+
this.stop();
|
|
434
|
+
}
|
|
435
|
+
getBaseContext() {
|
|
436
|
+
if (!this.baseContext) {
|
|
437
|
+
throw new Error(`${this.sinkName} has not been initialized.`);
|
|
438
|
+
}
|
|
439
|
+
return this.baseContext;
|
|
440
|
+
}
|
|
441
|
+
getSession() {
|
|
442
|
+
if (!this.session) {
|
|
443
|
+
throw new Error(`${this.sinkName} has not been started.`);
|
|
444
|
+
}
|
|
445
|
+
return this.session;
|
|
446
|
+
}
|
|
447
|
+
async persistEvents(events) {
|
|
448
|
+
if (!events.length) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
await postWithTimeout(this.fetchImpl, this.ingestUrl, {
|
|
452
|
+
method: "POST",
|
|
453
|
+
headers: { "Content-Type": "application/json" },
|
|
454
|
+
body: JSON.stringify({
|
|
455
|
+
runToken: this.runToken,
|
|
456
|
+
events: events.map((event, index) => portalEventPayload(event, index))
|
|
457
|
+
})
|
|
458
|
+
}, this.timeoutMs, "PortalReportingSink");
|
|
459
|
+
}
|
|
460
|
+
}
|
|
375
461
|
export class InfluxDbReportingSink {
|
|
376
462
|
constructor(options = {}) {
|
|
377
463
|
this.sinkName = "influxdb";
|
|
@@ -1514,6 +1600,7 @@ function createReportingEvent(session, occurredUtc, eventType, scenarioName, ste
|
|
|
1514
1600
|
eventFields.completed_utc = occurredUtc.toISOString();
|
|
1515
1601
|
}
|
|
1516
1602
|
return {
|
|
1603
|
+
runId: session.runId,
|
|
1517
1604
|
eventType,
|
|
1518
1605
|
occurredUtc,
|
|
1519
1606
|
sessionId: session.sessionId,
|
|
@@ -1528,6 +1615,38 @@ function createReportingEvent(session, occurredUtc, eventType, scenarioName, ste
|
|
|
1528
1615
|
fields: eventFields
|
|
1529
1616
|
};
|
|
1530
1617
|
}
|
|
1618
|
+
function portalEventPayload(event, index) {
|
|
1619
|
+
return {
|
|
1620
|
+
eventId: portalEventId(event, index),
|
|
1621
|
+
runId: event.runId,
|
|
1622
|
+
eventType: event.eventType,
|
|
1623
|
+
occurredUtc: event.occurredUtc.toISOString(),
|
|
1624
|
+
sessionId: event.sessionId,
|
|
1625
|
+
testSuite: event.testSuite,
|
|
1626
|
+
testName: event.testName,
|
|
1627
|
+
clusterId: event.clusterId,
|
|
1628
|
+
nodeType: event.nodeType,
|
|
1629
|
+
machineName: event.machineName,
|
|
1630
|
+
scenarioName: event.scenarioName,
|
|
1631
|
+
stepName: event.stepName,
|
|
1632
|
+
tags: { ...event.tags },
|
|
1633
|
+
fields: deepCloneRecord(event.fields)
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
function portalEventId(event, index) {
|
|
1637
|
+
const material = JSON.stringify({
|
|
1638
|
+
sessionId: event.sessionId,
|
|
1639
|
+
runId: event.runId,
|
|
1640
|
+
eventType: event.eventType,
|
|
1641
|
+
occurredUtc: event.occurredUtc.toISOString(),
|
|
1642
|
+
scenarioName: event.scenarioName ?? "",
|
|
1643
|
+
stepName: event.stepName ?? "",
|
|
1644
|
+
index,
|
|
1645
|
+
tags: event.tags,
|
|
1646
|
+
fields: event.fields
|
|
1647
|
+
});
|
|
1648
|
+
return `lsr_${createHash("sha256").update(material).digest("hex").slice(0, 40)}`;
|
|
1649
|
+
}
|
|
1531
1650
|
function addMeasurementFields(fields, prefix, measurement) {
|
|
1532
1651
|
const request = measurement?.request ?? { count: 0, percent: 0, rps: 0 };
|
|
1533
1652
|
const latency = measurement?.latency ?? {
|
|
@@ -2075,7 +2194,9 @@ function sinkSessionMetadataFromContext(context, session) {
|
|
|
2075
2194
|
const testInfo = context.testInfo;
|
|
2076
2195
|
const sessionStartedUtc = String(session?.startedUtc ?? session?.StartedUtc ?? "");
|
|
2077
2196
|
const contextStartedUtc = String(testInfo.createdUtc ?? testInfo.CreatedUtc ?? "");
|
|
2197
|
+
const runId = String(session?.portalReportingRunId ?? session?.PortalReportingRunId ?? testInfo.sessionId ?? "").trim();
|
|
2078
2198
|
return {
|
|
2199
|
+
runId: runId || String(testInfo.sessionId ?? ""),
|
|
2079
2200
|
sessionId: String(testInfo.sessionId ?? ""),
|
|
2080
2201
|
testSuite: String(testInfo.testSuite ?? ""),
|
|
2081
2202
|
testName: String(testInfo.testName ?? ""),
|
|
@@ -2553,7 +2674,10 @@ function cloneSessionStartInfo(session) {
|
|
|
2553
2674
|
...cloneBaseContext(session),
|
|
2554
2675
|
startedUtc: session.startedUtc,
|
|
2555
2676
|
scenarioNames: [...session.scenarioNames],
|
|
2556
|
-
scenarios: session.scenarios.map((value) => ({ ...value }))
|
|
2677
|
+
scenarios: session.scenarios.map((value) => ({ ...value })),
|
|
2678
|
+
runToken: session.runToken,
|
|
2679
|
+
portalReportingIngestUrl: session.portalReportingIngestUrl,
|
|
2680
|
+
portalReportingRunId: session.portalReportingRunId
|
|
2557
2681
|
};
|
|
2558
2682
|
}
|
|
2559
2683
|
function cloneNodeInfo(nodeInfo) {
|
|
@@ -2655,23 +2779,7 @@ function runResultToNodeStats(result) {
|
|
|
2655
2779
|
disabledSinks: [...(result.disabledSinks ?? [])],
|
|
2656
2780
|
sinkErrors: (result.sinkErrors ?? []).map((value) => ({ ...value })),
|
|
2657
2781
|
reportFiles: [...(result.reportFiles ?? [])],
|
|
2658
|
-
logFiles: [...(result.logFiles ?? [])]
|
|
2659
|
-
findScenarioStats: (scenarioName) => result.scenarioStats.find((value) => value.scenarioName === scenarioName),
|
|
2660
|
-
getScenarioStats: (scenarioName) => {
|
|
2661
|
-
const scenario = result.scenarioStats.find((value) => value.scenarioName === scenarioName);
|
|
2662
|
-
if (!scenario) {
|
|
2663
|
-
throw new Error(`Scenario stats not found: ${scenarioName}`);
|
|
2664
|
-
}
|
|
2665
|
-
return scenario;
|
|
2666
|
-
},
|
|
2667
|
-
FindScenarioStats: (scenarioName) => result.scenarioStats.find((value) => value.scenarioName === scenarioName),
|
|
2668
|
-
GetScenarioStats: (scenarioName) => {
|
|
2669
|
-
const scenario = result.scenarioStats.find((value) => value.scenarioName === scenarioName);
|
|
2670
|
-
if (!scenario) {
|
|
2671
|
-
throw new Error(`Scenario stats not found: ${scenarioName}`);
|
|
2672
|
-
}
|
|
2673
|
-
return scenario;
|
|
2674
|
-
}
|
|
2782
|
+
logFiles: [...(result.logFiles ?? [])]
|
|
2675
2783
|
};
|
|
2676
2784
|
}
|
|
2677
2785
|
function deepCloneRecord(value) {
|
package/dist/types/index.d.ts
CHANGED
|
@@ -8,5 +8,5 @@ export { CorrelationStoreConfiguration, CrossPlatformTrackingRuntime, InMemoryCo
|
|
|
8
8
|
export type { CorrelationEntry, DestinationConsumeResult, GatheredRow, CorrelationStoreKind, CorrelationRuntimeOptions, CorrelationRuntimePlugin, CorrelationRuntimeStats, CorrelationStore, SourceProduceResult, TrackingFieldLocation, TrackingPayload } from "./correlation.js";
|
|
9
9
|
export { LOADSTRIKE_TRACE_ID_HEADER, LOADSTRIKE_TRACE_ID_TRACKING_FIELD, TrafficEndpointDefinition, HttpEndpointDefinition, KafkaEndpointDefinition, KafkaSaslOptions, RabbitMqEndpointDefinition, NatsEndpointDefinition, RedisStreamsEndpointDefinition, AzureEventHubsEndpointDefinition, SqsEndpointDefinition, DelegateStreamEndpointDefinition, PushDiffusionEndpointDefinition, HttpOAuth2ClientCredentialsOptions, HttpAuthOptions } from "./transports.js";
|
|
10
10
|
export type { EndpointAdapter, EndpointDefinition, EndpointDefinitionInput, EndpointKind, EndpointMode, DotNetDelegateEndpointOptions, DotNetEndpointDefinition, DotNetHttpAuthOptions, DotNetHttpEndpointOptions, DotNetHttpOAuth2ClientCredentialsOptions, HttpAuthMode, HttpAuthType, HttpRequestBodyType, HttpResponseSource, HttpTrackingPayloadSource, KafkaSaslMechanismType, KafkaSecurityProtocolType, HttpEndpointOptions, KafkaEndpointOptions, RabbitMqEndpointOptions, NatsEndpointOptions, RedisStreamsEndpointOptions, AzureEventHubsEndpointOptions, SqsEndpointOptions, PushDiffusionEndpointOptions, TrackingFieldSelectorInput, TrackingRunMode, TrafficEndpointKind, TrafficEndpointMode, ProducedMessageRequest, ProducedMessageResult, ConsumedMessage, DelegateConsumeAsync, DelegateConsumeStreamHandler, DelegateEndpointOptions } from "./transports.js";
|
|
11
|
-
export { DatadogReportingSink, DatadogReportingSinkOptions, GrafanaLokiReportingSink, GrafanaLokiReportingSinkOptions, InfluxDbReportingSink, InfluxDbReportingSinkOptions, OtelCollectorReportingSink, OtelCollectorReportingSinkOptions, SplunkReportingSink, SplunkReportingSinkOptions, TimescaleDbReportingSink, TimescaleDbReportingSinkOptions } from "./sinks.js";
|
|
12
|
-
export type { DatadogSinkOptions, GrafanaLokiSinkOptions, InfluxDbSinkOptions, OtelCollectorSinkOptions, SplunkSinkOptions, TimescaleDbSinkOptions } from "./sinks.js";
|
|
11
|
+
export { DatadogReportingSink, DatadogReportingSinkOptions, GrafanaLokiReportingSink, GrafanaLokiReportingSinkOptions, InfluxDbReportingSink, InfluxDbReportingSinkOptions, OtelCollectorReportingSink, OtelCollectorReportingSinkOptions, PortalReportingSink, SplunkReportingSink, SplunkReportingSinkOptions, TimescaleDbReportingSink, TimescaleDbReportingSinkOptions } from "./sinks.js";
|
|
12
|
+
export type { DatadogSinkOptions, GrafanaLokiSinkOptions, InfluxDbSinkOptions, OtelCollectorSinkOptions, PortalReportingSinkOptionsInput, SplunkSinkOptions, TimescaleDbSinkOptions } from "./sinks.js";
|
package/dist/types/local.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ export declare class LoadStrikeLocalClient {
|
|
|
29
29
|
* Use this when the surrounding wrapper type makes this operation the clearest way to express your intent.
|
|
30
30
|
*/
|
|
31
31
|
constructor(options?: LoadStrikeLocalClientOptions);
|
|
32
|
+
portalReportingIngestUrl(): string;
|
|
32
33
|
run(request: LoadStrikeRunRequest): Promise<LoadStrikeRunResponse>;
|
|
33
34
|
acquireLicenseLease(request: LoadStrikeRunRequest): Promise<LicenseValidationSession>;
|
|
34
35
|
releaseLicenseLease(session: LicenseValidationSession, request: LoadStrikeRunRequest): Promise<void>;
|
package/dist/types/runtime.d.ts
CHANGED
|
@@ -558,6 +558,9 @@ export interface LoadStrikeSinkSession {
|
|
|
558
558
|
configPath?: string;
|
|
559
559
|
infraConfigPath?: string;
|
|
560
560
|
infraConfig?: Record<string, unknown>;
|
|
561
|
+
runToken?: string;
|
|
562
|
+
portalReportingIngestUrl?: string;
|
|
563
|
+
portalReportingRunId?: string;
|
|
561
564
|
}
|
|
562
565
|
export interface LoadStrikeBaseContext {
|
|
563
566
|
logger: LoadStrikeLogger;
|
|
@@ -573,9 +576,15 @@ export interface LoadStrikeSessionStartInfo extends LoadStrikeBaseContext {
|
|
|
573
576
|
startedUtc: string;
|
|
574
577
|
scenarioNames: string[];
|
|
575
578
|
scenarios: LoadStrikeScenarioStartInfo[];
|
|
579
|
+
runToken?: string;
|
|
580
|
+
portalReportingIngestUrl?: string;
|
|
581
|
+
portalReportingRunId?: string;
|
|
576
582
|
readonly StartedUtc?: string;
|
|
577
583
|
readonly ScenarioNames?: string[];
|
|
578
584
|
readonly Scenarios?: LoadStrikeScenarioStartInfo[];
|
|
585
|
+
readonly RunToken?: string;
|
|
586
|
+
readonly PortalReportingIngestUrl?: string;
|
|
587
|
+
readonly PortalReportingRunId?: string;
|
|
579
588
|
}
|
|
580
589
|
type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Omit<T, Keys> & {
|
|
581
590
|
[Key in Keys]-?: Required<Pick<T, Key>> & Partial<Pick<T, Exclude<Keys, Key>>>;
|
|
@@ -1339,6 +1348,8 @@ export declare class LoadStrikeContext {
|
|
|
1339
1348
|
* Use this when run results must be pushed to external observability or storage systems.
|
|
1340
1349
|
*/
|
|
1341
1350
|
WithReportingSinks(...sinks: ILoadStrikeReportingSink[]): LoadStrikeContext;
|
|
1351
|
+
withPortalReporting(): LoadStrikeContext;
|
|
1352
|
+
WithPortalReporting(): LoadStrikeContext;
|
|
1342
1353
|
/**
|
|
1343
1354
|
* Registers runtime policies for the run.
|
|
1344
1355
|
* Use this when scenario selection or step execution should obey policy callbacks.
|
|
@@ -1790,6 +1801,7 @@ export declare class LoadStrikeRunner {
|
|
|
1790
1801
|
* Use this when run results must be pushed to external observability or storage systems.
|
|
1791
1802
|
*/
|
|
1792
1803
|
static WithReportingSinks(context: LoadStrikeContext, ...sinks: ILoadStrikeReportingSink[]): LoadStrikeContext;
|
|
1804
|
+
static WithPortalReporting(context: LoadStrikeContext): LoadStrikeContext;
|
|
1793
1805
|
/**
|
|
1794
1806
|
* Registers runtime policies for the run.
|
|
1795
1807
|
* Use this when scenario selection or step execution should obey policy callbacks.
|
|
@@ -1924,6 +1936,10 @@ export declare class LoadStrikeRunner {
|
|
|
1924
1936
|
* Use this when sinks or dashboards should receive updates at a controlled interval.
|
|
1925
1937
|
*/
|
|
1926
1938
|
WithReportingInterval(intervalSeconds: number): LoadStrikeRunner;
|
|
1939
|
+
withReportingSinks(...sinks: ILoadStrikeReportingSink[]): LoadStrikeRunner;
|
|
1940
|
+
WithReportingSinks(...sinks: ILoadStrikeReportingSink[]): LoadStrikeRunner;
|
|
1941
|
+
withPortalReporting(): LoadStrikeRunner;
|
|
1942
|
+
WithPortalReporting(): LoadStrikeRunner;
|
|
1927
1943
|
/**
|
|
1928
1944
|
* Sets the timeout for cluster command round-trips.
|
|
1929
1945
|
* Use this when distributed control messages need a tighter or looser deadline.
|
package/dist/types/sinks.d.ts
CHANGED
|
@@ -21,13 +21,10 @@ interface LoadStrikeNodeStats {
|
|
|
21
21
|
sinkErrors: LoadStrikeSinkError[];
|
|
22
22
|
reportFiles: string[];
|
|
23
23
|
logFiles: string[];
|
|
24
|
-
findScenarioStats: (scenarioName: string) => LoadStrikeScenarioStats | undefined;
|
|
25
|
-
getScenarioStats: (scenarioName: string) => LoadStrikeScenarioStats;
|
|
26
|
-
FindScenarioStats: (scenarioName: string) => LoadStrikeScenarioStats | undefined;
|
|
27
|
-
GetScenarioStats: (scenarioName: string) => LoadStrikeScenarioStats;
|
|
28
24
|
}
|
|
29
25
|
type SinkFetch = (input: string, init?: RequestInit) => Promise<Response>;
|
|
30
26
|
interface ReportingSinkEvent {
|
|
27
|
+
runId: string;
|
|
31
28
|
eventType: string;
|
|
32
29
|
occurredUtc: Date;
|
|
33
30
|
sessionId: string;
|
|
@@ -42,6 +39,7 @@ interface ReportingSinkEvent {
|
|
|
42
39
|
fields: Record<string, unknown>;
|
|
43
40
|
}
|
|
44
41
|
interface SinkSessionMetadata {
|
|
42
|
+
runId: string;
|
|
45
43
|
sessionId: string;
|
|
46
44
|
testSuite: string;
|
|
47
45
|
testName: string;
|
|
@@ -282,6 +280,14 @@ export interface OtelCollectorSinkOptionsInput {
|
|
|
282
280
|
FetchImpl?: SinkFetch;
|
|
283
281
|
fetchImpl?: SinkFetch;
|
|
284
282
|
}
|
|
283
|
+
export interface PortalReportingSinkOptionsInput {
|
|
284
|
+
TimeoutSeconds?: number;
|
|
285
|
+
timeoutSeconds?: number;
|
|
286
|
+
TimeoutMs?: number;
|
|
287
|
+
timeoutMs?: number;
|
|
288
|
+
FetchImpl?: SinkFetch;
|
|
289
|
+
fetchImpl?: SinkFetch;
|
|
290
|
+
}
|
|
285
291
|
export declare class InfluxDbReportingSinkOptions {
|
|
286
292
|
ConfigurationSectionPath: string;
|
|
287
293
|
BaseUrl: string;
|
|
@@ -439,6 +445,35 @@ export declare class CompositeReportingSink implements LoadStrikeReportingSink {
|
|
|
439
445
|
stop(): Promise<void>;
|
|
440
446
|
Stop(): Promise<void>;
|
|
441
447
|
}
|
|
448
|
+
export declare class PortalReportingSink implements LoadStrikeReportingSink {
|
|
449
|
+
readonly sinkName = "portal";
|
|
450
|
+
readonly SinkName = "portal";
|
|
451
|
+
readonly licenseFeature = "extensions.reporting_sinks.portal";
|
|
452
|
+
readonly LicenseFeature = "extensions.reporting_sinks.portal";
|
|
453
|
+
private readonly fetchImpl;
|
|
454
|
+
private readonly timeoutMs;
|
|
455
|
+
private baseContext;
|
|
456
|
+
private session;
|
|
457
|
+
private runToken;
|
|
458
|
+
private ingestUrl;
|
|
459
|
+
constructor(options?: PortalReportingSinkOptionsInput);
|
|
460
|
+
init(context: LoadStrikeBaseContext, _infraConfig: Record<string, unknown>): void;
|
|
461
|
+
Init(context: LoadStrikeBaseContext, infraConfig: Record<string, unknown>): void;
|
|
462
|
+
start(session: LoadStrikeSessionStartInfo): void;
|
|
463
|
+
Start(session: LoadStrikeSessionStartInfo): void;
|
|
464
|
+
saveRealtimeStats(scenarioStats: LoadStrikeScenarioStats[]): Promise<void>;
|
|
465
|
+
SaveRealtimeStats(scenarioStats: LoadStrikeScenarioStats[]): Promise<void>;
|
|
466
|
+
saveRealtimeMetrics(metrics: LoadStrikeMetricStats): Promise<void>;
|
|
467
|
+
SaveRealtimeMetrics(metrics: LoadStrikeMetricStats): Promise<void>;
|
|
468
|
+
saveRunResult(result: LoadStrikeRunResult): Promise<void>;
|
|
469
|
+
SaveRunResult(result: LoadStrikeRunResult): Promise<void>;
|
|
470
|
+
stop(): void;
|
|
471
|
+
Stop(): void;
|
|
472
|
+
Dispose(): void;
|
|
473
|
+
private getBaseContext;
|
|
474
|
+
private getSession;
|
|
475
|
+
private persistEvents;
|
|
476
|
+
}
|
|
442
477
|
export declare class InfluxDbReportingSink implements LoadStrikeReportingSink {
|
|
443
478
|
readonly sinkName = "influxdb";
|
|
444
479
|
readonly SinkName = "influxdb";
|
package/package.json
CHANGED