@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 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);
@@ -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);
@@ -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) {
@@ -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";
@@ -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>;
@@ -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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loadstrike/loadstrike-sdk",
3
- "version": "1.0.23001",
3
+ "version": "1.0.23401",
4
4
  "description": "TypeScript and JavaScript SDK for in-process load execution, traffic correlation, and reporting.",
5
5
  "keywords": [
6
6
  "load-testing",