@loadstrike/loadstrike-sdk 1.0.23201 → 1.0.23601
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/runtime.js +53 -12
- package/dist/cjs/sinks.js +85 -7
- package/dist/esm/runtime.js +54 -13
- package/dist/esm/sinks.js +85 -8
- package/dist/types/runtime.d.ts +3 -0
- package/dist/types/sinks.d.ts +9 -2
- package/package.json +1 -1
package/dist/cjs/runtime.js
CHANGED
|
@@ -2487,7 +2487,7 @@ class LoadStrikeRunner {
|
|
|
2487
2487
|
const scenarioAccumulators = new Map();
|
|
2488
2488
|
const scenarioDurationsMs = new Map();
|
|
2489
2489
|
const stepStats = new Map();
|
|
2490
|
-
const sinks = this.options.reportingSinks ?? [];
|
|
2490
|
+
const sinks = (this.options.reportingSinks ?? []).map((sink) => (0, sinks_js_1.cloneReportingSinkForRun)(sink));
|
|
2491
2491
|
const sinkStates = sinks.map((sink, index) => ({
|
|
2492
2492
|
sink,
|
|
2493
2493
|
disabled: false,
|
|
@@ -2521,6 +2521,7 @@ class LoadStrikeRunner {
|
|
|
2521
2521
|
let sinksStopped = false;
|
|
2522
2522
|
let realtimeTimer = null;
|
|
2523
2523
|
let realtimeInFlight = false;
|
|
2524
|
+
let realtimeCurrent = null;
|
|
2524
2525
|
let licenseClient = null;
|
|
2525
2526
|
let licensePayload = null;
|
|
2526
2527
|
let licenseSession = null;
|
|
@@ -2616,9 +2617,38 @@ class LoadStrikeRunner {
|
|
|
2616
2617
|
}
|
|
2617
2618
|
};
|
|
2618
2619
|
const reportingIntervalMs = Math.max(Math.trunc((this.options.reportingIntervalSeconds ?? 5) * 1000), 1);
|
|
2620
|
+
const triggerRealtimeSnapshot = () => {
|
|
2621
|
+
if (realtimeCurrent) {
|
|
2622
|
+
return;
|
|
2623
|
+
}
|
|
2624
|
+
const current = emitRealtimeSnapshot()
|
|
2625
|
+
.catch((error) => {
|
|
2626
|
+
sinkErrors.push({
|
|
2627
|
+
sinkName: "realtime",
|
|
2628
|
+
phase: "realtime",
|
|
2629
|
+
message: String(error ?? "realtime reporting failed"),
|
|
2630
|
+
attempts: 1
|
|
2631
|
+
});
|
|
2632
|
+
})
|
|
2633
|
+
.finally(() => {
|
|
2634
|
+
if (realtimeCurrent === current) {
|
|
2635
|
+
realtimeCurrent = null;
|
|
2636
|
+
}
|
|
2637
|
+
});
|
|
2638
|
+
realtimeCurrent = current;
|
|
2639
|
+
};
|
|
2640
|
+
const stopRealtimeReporting = async () => {
|
|
2641
|
+
if (realtimeTimer) {
|
|
2642
|
+
clearInterval(realtimeTimer);
|
|
2643
|
+
realtimeTimer = null;
|
|
2644
|
+
}
|
|
2645
|
+
if (realtimeCurrent) {
|
|
2646
|
+
await realtimeCurrent;
|
|
2647
|
+
}
|
|
2648
|
+
};
|
|
2619
2649
|
if (sinkStates.length > 0 || toBoolean(this.options.displayConsoleMetrics, true)) {
|
|
2620
2650
|
realtimeTimer = setInterval(() => {
|
|
2621
|
-
|
|
2651
|
+
triggerRealtimeSnapshot();
|
|
2622
2652
|
}, reportingIntervalMs);
|
|
2623
2653
|
if (typeof realtimeTimer.unref === "function") {
|
|
2624
2654
|
realtimeTimer.unref();
|
|
@@ -2706,6 +2736,7 @@ class LoadStrikeRunner {
|
|
|
2706
2736
|
failedCorrelationRows: buildDetailedFailedCorrelationRows()
|
|
2707
2737
|
};
|
|
2708
2738
|
}
|
|
2739
|
+
await stopRealtimeReporting();
|
|
2709
2740
|
result.pluginsData = mergePluginData(result.pluginsData, await this.collectPluginData(plugins, attachRunResultAliases(result), pluginLifecycleErrors));
|
|
2710
2741
|
const finalizedResult = attachRunResultAliases(result);
|
|
2711
2742
|
finalizedResult.logFiles = mergeStringArrays(finalizedResult.logFiles, loggerSetup.logFiles);
|
|
@@ -2720,9 +2751,7 @@ class LoadStrikeRunner {
|
|
|
2720
2751
|
return finalizedResult;
|
|
2721
2752
|
}
|
|
2722
2753
|
finally {
|
|
2723
|
-
|
|
2724
|
-
clearInterval(realtimeTimer);
|
|
2725
|
-
}
|
|
2754
|
+
await stopRealtimeReporting();
|
|
2726
2755
|
if (!sinksStopped) {
|
|
2727
2756
|
await this.stopSinks(sinkStates, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
|
|
2728
2757
|
}
|
|
@@ -3043,14 +3072,14 @@ class LoadStrikeRunner {
|
|
|
3043
3072
|
}
|
|
3044
3073
|
catch (error) {
|
|
3045
3074
|
if (attempts > retryCount) {
|
|
3075
|
+
sinkErrors.push({
|
|
3076
|
+
sinkName: state.name,
|
|
3077
|
+
phase,
|
|
3078
|
+
message: String(error ?? "sink action failed"),
|
|
3079
|
+
attempts
|
|
3080
|
+
});
|
|
3046
3081
|
if (disableOnFailure) {
|
|
3047
3082
|
state.disabled = true;
|
|
3048
|
-
sinkErrors.push({
|
|
3049
|
-
sinkName: state.name,
|
|
3050
|
-
phase,
|
|
3051
|
-
message: String(error ?? "sink action failed"),
|
|
3052
|
-
attempts
|
|
3053
|
-
});
|
|
3054
3083
|
}
|
|
3055
3084
|
return;
|
|
3056
3085
|
}
|
|
@@ -4112,7 +4141,8 @@ function attachSessionStartInfoAliases(session) {
|
|
|
4112
4141
|
ScenarioNames: "scenarioNames",
|
|
4113
4142
|
Scenarios: "scenarios",
|
|
4114
4143
|
RunToken: "runToken",
|
|
4115
|
-
PortalReportingIngestUrl: "portalReportingIngestUrl"
|
|
4144
|
+
PortalReportingIngestUrl: "portalReportingIngestUrl",
|
|
4145
|
+
PortalReportingRunId: "portalReportingRunId"
|
|
4116
4146
|
});
|
|
4117
4147
|
return session;
|
|
4118
4148
|
}
|
|
@@ -4122,10 +4152,21 @@ function attachPortalReportingSession(sinkSession, sessionInfo, licenseClient, l
|
|
|
4122
4152
|
return;
|
|
4123
4153
|
}
|
|
4124
4154
|
const ingestUrl = licenseClient.portalReportingIngestUrl();
|
|
4155
|
+
const runId = buildPortalReportingRunId(sessionInfo.testInfo.sessionId);
|
|
4125
4156
|
sinkSession.runToken = runToken;
|
|
4126
4157
|
sinkSession.portalReportingIngestUrl = ingestUrl;
|
|
4158
|
+
sinkSession.portalReportingRunId = runId;
|
|
4127
4159
|
sessionInfo.runToken = runToken;
|
|
4128
4160
|
sessionInfo.portalReportingIngestUrl = ingestUrl;
|
|
4161
|
+
sessionInfo.portalReportingRunId = runId;
|
|
4162
|
+
}
|
|
4163
|
+
function buildPortalReportingRunId(sessionId) {
|
|
4164
|
+
const sessionPart = String(sessionId ?? "")
|
|
4165
|
+
.replace(/[^A-Za-z0-9._-]+/g, "-")
|
|
4166
|
+
.replace(/^-+|-+$/g, "")
|
|
4167
|
+
.slice(0, 80);
|
|
4168
|
+
const uniquePart = generateRuntimeSessionId().slice(0, 16);
|
|
4169
|
+
return sessionPart ? `${sessionPart}-${uniquePart}` : uniquePart;
|
|
4129
4170
|
}
|
|
4130
4171
|
function attachScenarioInitContextAliases(context) {
|
|
4131
4172
|
context.nodeInfo = attachNodeInfoAliases(context.nodeInfo);
|
package/dist/cjs/sinks.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
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
|
+
exports.cloneReportingSinkForRun = cloneReportingSinkForRun;
|
|
4
5
|
const runtime_js_1 = require("./runtime.js");
|
|
5
6
|
const node_crypto_1 = require("node:crypto");
|
|
6
7
|
const pg_1 = require("pg");
|
|
@@ -395,10 +396,14 @@ class PortalReportingSink {
|
|
|
395
396
|
this.session = null;
|
|
396
397
|
this.runToken = "";
|
|
397
398
|
this.ingestUrl = "";
|
|
399
|
+
this.optionsInput = { ...options };
|
|
398
400
|
const source = asRecord(options);
|
|
399
401
|
this.timeoutMs = resolveTimeoutMs(optionNumber(source, "timeoutSeconds", "TimeoutSeconds"), optionNumber(source, "timeoutMs", "TimeoutMs"));
|
|
400
402
|
this.fetchImpl = pickRecordValue(source, "fetchImpl", "FetchImpl") ?? fetch;
|
|
401
403
|
}
|
|
404
|
+
cloneForRun() {
|
|
405
|
+
return new PortalReportingSink(this.optionsInput);
|
|
406
|
+
}
|
|
402
407
|
init(context, _infraConfig) {
|
|
403
408
|
this.baseContext = cloneBaseContext(context);
|
|
404
409
|
}
|
|
@@ -411,7 +416,9 @@ class PortalReportingSink {
|
|
|
411
416
|
if (!this.runToken || !this.ingestUrl) {
|
|
412
417
|
throw new Error("PortalReportingSink requires a managed portal reporting session.");
|
|
413
418
|
}
|
|
414
|
-
this.session = sinkSessionMetadataFromContext(this.getBaseContext(), session
|
|
419
|
+
this.session = sinkSessionMetadataFromContext(this.getBaseContext(), session, {
|
|
420
|
+
distinctRunIdFallback: true
|
|
421
|
+
});
|
|
415
422
|
}
|
|
416
423
|
Start(session) {
|
|
417
424
|
this.start(session);
|
|
@@ -460,7 +467,7 @@ class PortalReportingSink {
|
|
|
460
467
|
if (!events.length) {
|
|
461
468
|
return;
|
|
462
469
|
}
|
|
463
|
-
await postWithTimeout(this.fetchImpl, this.ingestUrl, {
|
|
470
|
+
const responseBody = await postWithTimeout(this.fetchImpl, this.ingestUrl, {
|
|
464
471
|
method: "POST",
|
|
465
472
|
headers: { "Content-Type": "application/json" },
|
|
466
473
|
body: JSON.stringify({
|
|
@@ -468,9 +475,16 @@ class PortalReportingSink {
|
|
|
468
475
|
events: events.map((event, index) => portalEventPayload(event, index))
|
|
469
476
|
})
|
|
470
477
|
}, this.timeoutMs, "PortalReportingSink");
|
|
478
|
+
validatePortalIngestResponse(responseBody);
|
|
471
479
|
}
|
|
472
480
|
}
|
|
473
481
|
exports.PortalReportingSink = PortalReportingSink;
|
|
482
|
+
function cloneReportingSinkForRun(sink) {
|
|
483
|
+
if (sink instanceof PortalReportingSink) {
|
|
484
|
+
return sink.cloneForRun();
|
|
485
|
+
}
|
|
486
|
+
return sink;
|
|
487
|
+
}
|
|
474
488
|
class InfluxDbReportingSink {
|
|
475
489
|
constructor(options = {}) {
|
|
476
490
|
this.sinkName = "influxdb";
|
|
@@ -1619,6 +1633,7 @@ function createReportingEvent(session, occurredUtc, eventType, scenarioName, ste
|
|
|
1619
1633
|
eventFields.completed_utc = occurredUtc.toISOString();
|
|
1620
1634
|
}
|
|
1621
1635
|
return {
|
|
1636
|
+
runId: session.runId,
|
|
1622
1637
|
eventType,
|
|
1623
1638
|
occurredUtc,
|
|
1624
1639
|
sessionId: session.sessionId,
|
|
@@ -1636,7 +1651,7 @@ function createReportingEvent(session, occurredUtc, eventType, scenarioName, ste
|
|
|
1636
1651
|
function portalEventPayload(event, index) {
|
|
1637
1652
|
return {
|
|
1638
1653
|
eventId: portalEventId(event, index),
|
|
1639
|
-
runId: event.
|
|
1654
|
+
runId: event.runId,
|
|
1640
1655
|
eventType: event.eventType,
|
|
1641
1656
|
occurredUtc: event.occurredUtc.toISOString(),
|
|
1642
1657
|
sessionId: event.sessionId,
|
|
@@ -1654,6 +1669,7 @@ function portalEventPayload(event, index) {
|
|
|
1654
1669
|
function portalEventId(event, index) {
|
|
1655
1670
|
const material = JSON.stringify({
|
|
1656
1671
|
sessionId: event.sessionId,
|
|
1672
|
+
runId: event.runId,
|
|
1657
1673
|
eventType: event.eventType,
|
|
1658
1674
|
occurredUtc: event.occurredUtc.toISOString(),
|
|
1659
1675
|
scenarioName: event.scenarioName ?? "",
|
|
@@ -2206,13 +2222,20 @@ function toOtelAnyValue(value) {
|
|
|
2206
2222
|
function toUnixSeconds(value) {
|
|
2207
2223
|
return value.getTime() / 1000;
|
|
2208
2224
|
}
|
|
2209
|
-
function sinkSessionMetadataFromContext(context, session) {
|
|
2225
|
+
function sinkSessionMetadataFromContext(context, session, options = {}) {
|
|
2210
2226
|
const nodeInfo = context.getNodeInfo?.() ?? context.nodeInfo;
|
|
2211
2227
|
const testInfo = context.testInfo;
|
|
2212
2228
|
const sessionStartedUtc = String(session?.startedUtc ?? session?.StartedUtc ?? "");
|
|
2213
2229
|
const contextStartedUtc = String(testInfo.createdUtc ?? testInfo.CreatedUtc ?? "");
|
|
2230
|
+
const explicitRunId = String(session?.portalReportingRunId ?? session?.PortalReportingRunId ?? "").trim();
|
|
2231
|
+
const sessionId = String(testInfo.sessionId ?? "");
|
|
2232
|
+
const fallbackRunId = options.distinctRunIdFallback
|
|
2233
|
+
? buildDistinctSinkRunId(sessionId)
|
|
2234
|
+
: sessionId;
|
|
2235
|
+
const runId = explicitRunId || fallbackRunId;
|
|
2214
2236
|
return {
|
|
2215
|
-
|
|
2237
|
+
runId: runId || sessionId,
|
|
2238
|
+
sessionId,
|
|
2216
2239
|
testSuite: String(testInfo.testSuite ?? ""),
|
|
2217
2240
|
testName: String(testInfo.testName ?? ""),
|
|
2218
2241
|
clusterId: String(testInfo.clusterId ?? ""),
|
|
@@ -2221,6 +2244,14 @@ function sinkSessionMetadataFromContext(context, session) {
|
|
|
2221
2244
|
startedUtc: sessionStartedUtc || contextStartedUtc || new Date().toISOString()
|
|
2222
2245
|
};
|
|
2223
2246
|
}
|
|
2247
|
+
function buildDistinctSinkRunId(sessionId) {
|
|
2248
|
+
const sessionPart = String(sessionId ?? "")
|
|
2249
|
+
.replace(/[^A-Za-z0-9._-]+/g, "-")
|
|
2250
|
+
.replace(/^-+|-+$/g, "")
|
|
2251
|
+
.slice(0, 80);
|
|
2252
|
+
const uniquePart = (0, node_crypto_1.randomBytes)(8).toString("hex");
|
|
2253
|
+
return sessionPart ? `${sessionPart}-${uniquePart}` : uniquePart;
|
|
2254
|
+
}
|
|
2224
2255
|
function readInfluxOptionsFromConfig(infraConfig, configurationSectionPath) {
|
|
2225
2256
|
const section = resolveConfigSection(infraConfig, configurationSectionPath);
|
|
2226
2257
|
return {
|
|
@@ -2691,7 +2722,8 @@ function cloneSessionStartInfo(session) {
|
|
|
2691
2722
|
scenarioNames: [...session.scenarioNames],
|
|
2692
2723
|
scenarios: session.scenarios.map((value) => ({ ...value })),
|
|
2693
2724
|
runToken: session.runToken,
|
|
2694
|
-
portalReportingIngestUrl: session.portalReportingIngestUrl
|
|
2725
|
+
portalReportingIngestUrl: session.portalReportingIngestUrl,
|
|
2726
|
+
portalReportingRunId: session.portalReportingRunId
|
|
2695
2727
|
};
|
|
2696
2728
|
}
|
|
2697
2729
|
function cloneNodeInfo(nodeInfo) {
|
|
@@ -2818,15 +2850,61 @@ async function postWithTimeout(fetchImpl, url, init, timeoutMs, sinkName) {
|
|
|
2818
2850
|
const timer = setTimeout(() => controller.abort(), Math.max(timeoutMs, 1));
|
|
2819
2851
|
try {
|
|
2820
2852
|
const response = await fetchImpl(url, { ...init, signal: controller.signal });
|
|
2853
|
+
const body = await readResponseBodyText(response);
|
|
2821
2854
|
if (!response.ok) {
|
|
2822
|
-
const body = await response.text();
|
|
2823
2855
|
throw new Error(`${sinkName} write failed with status ${response.status}: ${body}`);
|
|
2824
2856
|
}
|
|
2857
|
+
return body;
|
|
2825
2858
|
}
|
|
2826
2859
|
finally {
|
|
2827
2860
|
clearTimeout(timer);
|
|
2828
2861
|
}
|
|
2829
2862
|
}
|
|
2863
|
+
async function readResponseBodyText(response) {
|
|
2864
|
+
const candidate = response;
|
|
2865
|
+
if (typeof candidate.text !== "function") {
|
|
2866
|
+
return "";
|
|
2867
|
+
}
|
|
2868
|
+
return String(await candidate.text());
|
|
2869
|
+
}
|
|
2870
|
+
function validatePortalIngestResponse(responseBody) {
|
|
2871
|
+
const text = String(responseBody ?? "").trim();
|
|
2872
|
+
if (!text) {
|
|
2873
|
+
return;
|
|
2874
|
+
}
|
|
2875
|
+
let parsed;
|
|
2876
|
+
try {
|
|
2877
|
+
parsed = JSON.parse(text);
|
|
2878
|
+
}
|
|
2879
|
+
catch {
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2882
|
+
if (!isRecord(parsed)) {
|
|
2883
|
+
return;
|
|
2884
|
+
}
|
|
2885
|
+
const rejected = readPortalIngestCount(parsed, "rejectedCount", "RejectedCount", "rejected");
|
|
2886
|
+
if (rejected <= 0) {
|
|
2887
|
+
return;
|
|
2888
|
+
}
|
|
2889
|
+
const accepted = readPortalIngestCount(parsed, "acceptedCount", "AcceptedCount", "accepted");
|
|
2890
|
+
const duplicate = readPortalIngestCount(parsed, "duplicateCount", "DuplicateCount", "duplicate");
|
|
2891
|
+
throw new Error(`PortalReportingSink ingest rejected ${rejected} reporting event(s). Accepted ${accepted}, duplicate ${duplicate}.`);
|
|
2892
|
+
}
|
|
2893
|
+
function readPortalIngestCount(source, ...keys) {
|
|
2894
|
+
for (const key of keys) {
|
|
2895
|
+
const value = source[key];
|
|
2896
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
2897
|
+
return Math.max(Math.trunc(value), 0);
|
|
2898
|
+
}
|
|
2899
|
+
if (typeof value === "string") {
|
|
2900
|
+
const parsed = Number.parseInt(value, 10);
|
|
2901
|
+
if (Number.isFinite(parsed)) {
|
|
2902
|
+
return Math.max(parsed, 0);
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
return 0;
|
|
2907
|
+
}
|
|
2830
2908
|
exports.__loadstrikeTestExports = {
|
|
2831
2909
|
GrafanaLokiReportingSink,
|
|
2832
2910
|
InfluxDbReportingSink,
|
package/dist/esm/runtime.js
CHANGED
|
@@ -7,7 +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
|
+
import { PortalReportingSink, cloneReportingSinkForRun } from "./sinks.js";
|
|
11
11
|
export const LoadStrikeNodeType = {
|
|
12
12
|
SingleNode: "SingleNode",
|
|
13
13
|
Coordinator: "Coordinator",
|
|
@@ -2468,7 +2468,7 @@ export class LoadStrikeRunner {
|
|
|
2468
2468
|
const scenarioAccumulators = new Map();
|
|
2469
2469
|
const scenarioDurationsMs = new Map();
|
|
2470
2470
|
const stepStats = new Map();
|
|
2471
|
-
const sinks = this.options.reportingSinks ?? [];
|
|
2471
|
+
const sinks = (this.options.reportingSinks ?? []).map((sink) => cloneReportingSinkForRun(sink));
|
|
2472
2472
|
const sinkStates = sinks.map((sink, index) => ({
|
|
2473
2473
|
sink,
|
|
2474
2474
|
disabled: false,
|
|
@@ -2502,6 +2502,7 @@ export class LoadStrikeRunner {
|
|
|
2502
2502
|
let sinksStopped = false;
|
|
2503
2503
|
let realtimeTimer = null;
|
|
2504
2504
|
let realtimeInFlight = false;
|
|
2505
|
+
let realtimeCurrent = null;
|
|
2505
2506
|
let licenseClient = null;
|
|
2506
2507
|
let licensePayload = null;
|
|
2507
2508
|
let licenseSession = null;
|
|
@@ -2597,9 +2598,38 @@ export class LoadStrikeRunner {
|
|
|
2597
2598
|
}
|
|
2598
2599
|
};
|
|
2599
2600
|
const reportingIntervalMs = Math.max(Math.trunc((this.options.reportingIntervalSeconds ?? 5) * 1000), 1);
|
|
2601
|
+
const triggerRealtimeSnapshot = () => {
|
|
2602
|
+
if (realtimeCurrent) {
|
|
2603
|
+
return;
|
|
2604
|
+
}
|
|
2605
|
+
const current = emitRealtimeSnapshot()
|
|
2606
|
+
.catch((error) => {
|
|
2607
|
+
sinkErrors.push({
|
|
2608
|
+
sinkName: "realtime",
|
|
2609
|
+
phase: "realtime",
|
|
2610
|
+
message: String(error ?? "realtime reporting failed"),
|
|
2611
|
+
attempts: 1
|
|
2612
|
+
});
|
|
2613
|
+
})
|
|
2614
|
+
.finally(() => {
|
|
2615
|
+
if (realtimeCurrent === current) {
|
|
2616
|
+
realtimeCurrent = null;
|
|
2617
|
+
}
|
|
2618
|
+
});
|
|
2619
|
+
realtimeCurrent = current;
|
|
2620
|
+
};
|
|
2621
|
+
const stopRealtimeReporting = async () => {
|
|
2622
|
+
if (realtimeTimer) {
|
|
2623
|
+
clearInterval(realtimeTimer);
|
|
2624
|
+
realtimeTimer = null;
|
|
2625
|
+
}
|
|
2626
|
+
if (realtimeCurrent) {
|
|
2627
|
+
await realtimeCurrent;
|
|
2628
|
+
}
|
|
2629
|
+
};
|
|
2600
2630
|
if (sinkStates.length > 0 || toBoolean(this.options.displayConsoleMetrics, true)) {
|
|
2601
2631
|
realtimeTimer = setInterval(() => {
|
|
2602
|
-
|
|
2632
|
+
triggerRealtimeSnapshot();
|
|
2603
2633
|
}, reportingIntervalMs);
|
|
2604
2634
|
if (typeof realtimeTimer.unref === "function") {
|
|
2605
2635
|
realtimeTimer.unref();
|
|
@@ -2687,6 +2717,7 @@ export class LoadStrikeRunner {
|
|
|
2687
2717
|
failedCorrelationRows: buildDetailedFailedCorrelationRows()
|
|
2688
2718
|
};
|
|
2689
2719
|
}
|
|
2720
|
+
await stopRealtimeReporting();
|
|
2690
2721
|
result.pluginsData = mergePluginData(result.pluginsData, await this.collectPluginData(plugins, attachRunResultAliases(result), pluginLifecycleErrors));
|
|
2691
2722
|
const finalizedResult = attachRunResultAliases(result);
|
|
2692
2723
|
finalizedResult.logFiles = mergeStringArrays(finalizedResult.logFiles, loggerSetup.logFiles);
|
|
@@ -2701,9 +2732,7 @@ export class LoadStrikeRunner {
|
|
|
2701
2732
|
return finalizedResult;
|
|
2702
2733
|
}
|
|
2703
2734
|
finally {
|
|
2704
|
-
|
|
2705
|
-
clearInterval(realtimeTimer);
|
|
2706
|
-
}
|
|
2735
|
+
await stopRealtimeReporting();
|
|
2707
2736
|
if (!sinksStopped) {
|
|
2708
2737
|
await this.stopSinks(sinkStates, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
|
|
2709
2738
|
}
|
|
@@ -3024,14 +3053,14 @@ export class LoadStrikeRunner {
|
|
|
3024
3053
|
}
|
|
3025
3054
|
catch (error) {
|
|
3026
3055
|
if (attempts > retryCount) {
|
|
3056
|
+
sinkErrors.push({
|
|
3057
|
+
sinkName: state.name,
|
|
3058
|
+
phase,
|
|
3059
|
+
message: String(error ?? "sink action failed"),
|
|
3060
|
+
attempts
|
|
3061
|
+
});
|
|
3027
3062
|
if (disableOnFailure) {
|
|
3028
3063
|
state.disabled = true;
|
|
3029
|
-
sinkErrors.push({
|
|
3030
|
-
sinkName: state.name,
|
|
3031
|
-
phase,
|
|
3032
|
-
message: String(error ?? "sink action failed"),
|
|
3033
|
-
attempts
|
|
3034
|
-
});
|
|
3035
3064
|
}
|
|
3036
3065
|
return;
|
|
3037
3066
|
}
|
|
@@ -4092,7 +4121,8 @@ function attachSessionStartInfoAliases(session) {
|
|
|
4092
4121
|
ScenarioNames: "scenarioNames",
|
|
4093
4122
|
Scenarios: "scenarios",
|
|
4094
4123
|
RunToken: "runToken",
|
|
4095
|
-
PortalReportingIngestUrl: "portalReportingIngestUrl"
|
|
4124
|
+
PortalReportingIngestUrl: "portalReportingIngestUrl",
|
|
4125
|
+
PortalReportingRunId: "portalReportingRunId"
|
|
4096
4126
|
});
|
|
4097
4127
|
return session;
|
|
4098
4128
|
}
|
|
@@ -4102,10 +4132,21 @@ function attachPortalReportingSession(sinkSession, sessionInfo, licenseClient, l
|
|
|
4102
4132
|
return;
|
|
4103
4133
|
}
|
|
4104
4134
|
const ingestUrl = licenseClient.portalReportingIngestUrl();
|
|
4135
|
+
const runId = buildPortalReportingRunId(sessionInfo.testInfo.sessionId);
|
|
4105
4136
|
sinkSession.runToken = runToken;
|
|
4106
4137
|
sinkSession.portalReportingIngestUrl = ingestUrl;
|
|
4138
|
+
sinkSession.portalReportingRunId = runId;
|
|
4107
4139
|
sessionInfo.runToken = runToken;
|
|
4108
4140
|
sessionInfo.portalReportingIngestUrl = ingestUrl;
|
|
4141
|
+
sessionInfo.portalReportingRunId = runId;
|
|
4142
|
+
}
|
|
4143
|
+
function buildPortalReportingRunId(sessionId) {
|
|
4144
|
+
const sessionPart = String(sessionId ?? "")
|
|
4145
|
+
.replace(/[^A-Za-z0-9._-]+/g, "-")
|
|
4146
|
+
.replace(/^-+|-+$/g, "")
|
|
4147
|
+
.slice(0, 80);
|
|
4148
|
+
const uniquePart = generateRuntimeSessionId().slice(0, 16);
|
|
4149
|
+
return sessionPart ? `${sessionPart}-${uniquePart}` : uniquePart;
|
|
4109
4150
|
}
|
|
4110
4151
|
function attachScenarioInitContextAliases(context) {
|
|
4111
4152
|
context.nodeInfo = attachNodeInfoAliases(context.nodeInfo);
|
package/dist/esm/sinks.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LoadStrikePluginData as LoadStrikePluginDataModel, LoadStrikePluginDataTable as LoadStrikePluginDataTableModel } from "./runtime.js";
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
2
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
3
3
|
import { Pool } from "pg";
|
|
4
4
|
const DEFAULT_INFLUX_CONFIGURATION_SECTION_PATH = "LoadStrike:ReportingSinks:InfluxDb";
|
|
5
5
|
const DEFAULT_GRAFANA_LOKI_CONFIGURATION_SECTION_PATH = "LoadStrike:ReportingSinks:GrafanaLoki";
|
|
@@ -383,10 +383,14 @@ export class PortalReportingSink {
|
|
|
383
383
|
this.session = null;
|
|
384
384
|
this.runToken = "";
|
|
385
385
|
this.ingestUrl = "";
|
|
386
|
+
this.optionsInput = { ...options };
|
|
386
387
|
const source = asRecord(options);
|
|
387
388
|
this.timeoutMs = resolveTimeoutMs(optionNumber(source, "timeoutSeconds", "TimeoutSeconds"), optionNumber(source, "timeoutMs", "TimeoutMs"));
|
|
388
389
|
this.fetchImpl = pickRecordValue(source, "fetchImpl", "FetchImpl") ?? fetch;
|
|
389
390
|
}
|
|
391
|
+
cloneForRun() {
|
|
392
|
+
return new PortalReportingSink(this.optionsInput);
|
|
393
|
+
}
|
|
390
394
|
init(context, _infraConfig) {
|
|
391
395
|
this.baseContext = cloneBaseContext(context);
|
|
392
396
|
}
|
|
@@ -399,7 +403,9 @@ export class PortalReportingSink {
|
|
|
399
403
|
if (!this.runToken || !this.ingestUrl) {
|
|
400
404
|
throw new Error("PortalReportingSink requires a managed portal reporting session.");
|
|
401
405
|
}
|
|
402
|
-
this.session = sinkSessionMetadataFromContext(this.getBaseContext(), session
|
|
406
|
+
this.session = sinkSessionMetadataFromContext(this.getBaseContext(), session, {
|
|
407
|
+
distinctRunIdFallback: true
|
|
408
|
+
});
|
|
403
409
|
}
|
|
404
410
|
Start(session) {
|
|
405
411
|
this.start(session);
|
|
@@ -448,7 +454,7 @@ export class PortalReportingSink {
|
|
|
448
454
|
if (!events.length) {
|
|
449
455
|
return;
|
|
450
456
|
}
|
|
451
|
-
await postWithTimeout(this.fetchImpl, this.ingestUrl, {
|
|
457
|
+
const responseBody = await postWithTimeout(this.fetchImpl, this.ingestUrl, {
|
|
452
458
|
method: "POST",
|
|
453
459
|
headers: { "Content-Type": "application/json" },
|
|
454
460
|
body: JSON.stringify({
|
|
@@ -456,8 +462,15 @@ export class PortalReportingSink {
|
|
|
456
462
|
events: events.map((event, index) => portalEventPayload(event, index))
|
|
457
463
|
})
|
|
458
464
|
}, this.timeoutMs, "PortalReportingSink");
|
|
465
|
+
validatePortalIngestResponse(responseBody);
|
|
459
466
|
}
|
|
460
467
|
}
|
|
468
|
+
export function cloneReportingSinkForRun(sink) {
|
|
469
|
+
if (sink instanceof PortalReportingSink) {
|
|
470
|
+
return sink.cloneForRun();
|
|
471
|
+
}
|
|
472
|
+
return sink;
|
|
473
|
+
}
|
|
461
474
|
export class InfluxDbReportingSink {
|
|
462
475
|
constructor(options = {}) {
|
|
463
476
|
this.sinkName = "influxdb";
|
|
@@ -1600,6 +1613,7 @@ function createReportingEvent(session, occurredUtc, eventType, scenarioName, ste
|
|
|
1600
1613
|
eventFields.completed_utc = occurredUtc.toISOString();
|
|
1601
1614
|
}
|
|
1602
1615
|
return {
|
|
1616
|
+
runId: session.runId,
|
|
1603
1617
|
eventType,
|
|
1604
1618
|
occurredUtc,
|
|
1605
1619
|
sessionId: session.sessionId,
|
|
@@ -1617,7 +1631,7 @@ function createReportingEvent(session, occurredUtc, eventType, scenarioName, ste
|
|
|
1617
1631
|
function portalEventPayload(event, index) {
|
|
1618
1632
|
return {
|
|
1619
1633
|
eventId: portalEventId(event, index),
|
|
1620
|
-
runId: event.
|
|
1634
|
+
runId: event.runId,
|
|
1621
1635
|
eventType: event.eventType,
|
|
1622
1636
|
occurredUtc: event.occurredUtc.toISOString(),
|
|
1623
1637
|
sessionId: event.sessionId,
|
|
@@ -1635,6 +1649,7 @@ function portalEventPayload(event, index) {
|
|
|
1635
1649
|
function portalEventId(event, index) {
|
|
1636
1650
|
const material = JSON.stringify({
|
|
1637
1651
|
sessionId: event.sessionId,
|
|
1652
|
+
runId: event.runId,
|
|
1638
1653
|
eventType: event.eventType,
|
|
1639
1654
|
occurredUtc: event.occurredUtc.toISOString(),
|
|
1640
1655
|
scenarioName: event.scenarioName ?? "",
|
|
@@ -2187,13 +2202,20 @@ function toOtelAnyValue(value) {
|
|
|
2187
2202
|
function toUnixSeconds(value) {
|
|
2188
2203
|
return value.getTime() / 1000;
|
|
2189
2204
|
}
|
|
2190
|
-
function sinkSessionMetadataFromContext(context, session) {
|
|
2205
|
+
function sinkSessionMetadataFromContext(context, session, options = {}) {
|
|
2191
2206
|
const nodeInfo = context.getNodeInfo?.() ?? context.nodeInfo;
|
|
2192
2207
|
const testInfo = context.testInfo;
|
|
2193
2208
|
const sessionStartedUtc = String(session?.startedUtc ?? session?.StartedUtc ?? "");
|
|
2194
2209
|
const contextStartedUtc = String(testInfo.createdUtc ?? testInfo.CreatedUtc ?? "");
|
|
2210
|
+
const explicitRunId = String(session?.portalReportingRunId ?? session?.PortalReportingRunId ?? "").trim();
|
|
2211
|
+
const sessionId = String(testInfo.sessionId ?? "");
|
|
2212
|
+
const fallbackRunId = options.distinctRunIdFallback
|
|
2213
|
+
? buildDistinctSinkRunId(sessionId)
|
|
2214
|
+
: sessionId;
|
|
2215
|
+
const runId = explicitRunId || fallbackRunId;
|
|
2195
2216
|
return {
|
|
2196
|
-
|
|
2217
|
+
runId: runId || sessionId,
|
|
2218
|
+
sessionId,
|
|
2197
2219
|
testSuite: String(testInfo.testSuite ?? ""),
|
|
2198
2220
|
testName: String(testInfo.testName ?? ""),
|
|
2199
2221
|
clusterId: String(testInfo.clusterId ?? ""),
|
|
@@ -2202,6 +2224,14 @@ function sinkSessionMetadataFromContext(context, session) {
|
|
|
2202
2224
|
startedUtc: sessionStartedUtc || contextStartedUtc || new Date().toISOString()
|
|
2203
2225
|
};
|
|
2204
2226
|
}
|
|
2227
|
+
function buildDistinctSinkRunId(sessionId) {
|
|
2228
|
+
const sessionPart = String(sessionId ?? "")
|
|
2229
|
+
.replace(/[^A-Za-z0-9._-]+/g, "-")
|
|
2230
|
+
.replace(/^-+|-+$/g, "")
|
|
2231
|
+
.slice(0, 80);
|
|
2232
|
+
const uniquePart = randomBytes(8).toString("hex");
|
|
2233
|
+
return sessionPart ? `${sessionPart}-${uniquePart}` : uniquePart;
|
|
2234
|
+
}
|
|
2205
2235
|
function readInfluxOptionsFromConfig(infraConfig, configurationSectionPath) {
|
|
2206
2236
|
const section = resolveConfigSection(infraConfig, configurationSectionPath);
|
|
2207
2237
|
return {
|
|
@@ -2672,7 +2702,8 @@ function cloneSessionStartInfo(session) {
|
|
|
2672
2702
|
scenarioNames: [...session.scenarioNames],
|
|
2673
2703
|
scenarios: session.scenarios.map((value) => ({ ...value })),
|
|
2674
2704
|
runToken: session.runToken,
|
|
2675
|
-
portalReportingIngestUrl: session.portalReportingIngestUrl
|
|
2705
|
+
portalReportingIngestUrl: session.portalReportingIngestUrl,
|
|
2706
|
+
portalReportingRunId: session.portalReportingRunId
|
|
2676
2707
|
};
|
|
2677
2708
|
}
|
|
2678
2709
|
function cloneNodeInfo(nodeInfo) {
|
|
@@ -2799,15 +2830,61 @@ async function postWithTimeout(fetchImpl, url, init, timeoutMs, sinkName) {
|
|
|
2799
2830
|
const timer = setTimeout(() => controller.abort(), Math.max(timeoutMs, 1));
|
|
2800
2831
|
try {
|
|
2801
2832
|
const response = await fetchImpl(url, { ...init, signal: controller.signal });
|
|
2833
|
+
const body = await readResponseBodyText(response);
|
|
2802
2834
|
if (!response.ok) {
|
|
2803
|
-
const body = await response.text();
|
|
2804
2835
|
throw new Error(`${sinkName} write failed with status ${response.status}: ${body}`);
|
|
2805
2836
|
}
|
|
2837
|
+
return body;
|
|
2806
2838
|
}
|
|
2807
2839
|
finally {
|
|
2808
2840
|
clearTimeout(timer);
|
|
2809
2841
|
}
|
|
2810
2842
|
}
|
|
2843
|
+
async function readResponseBodyText(response) {
|
|
2844
|
+
const candidate = response;
|
|
2845
|
+
if (typeof candidate.text !== "function") {
|
|
2846
|
+
return "";
|
|
2847
|
+
}
|
|
2848
|
+
return String(await candidate.text());
|
|
2849
|
+
}
|
|
2850
|
+
function validatePortalIngestResponse(responseBody) {
|
|
2851
|
+
const text = String(responseBody ?? "").trim();
|
|
2852
|
+
if (!text) {
|
|
2853
|
+
return;
|
|
2854
|
+
}
|
|
2855
|
+
let parsed;
|
|
2856
|
+
try {
|
|
2857
|
+
parsed = JSON.parse(text);
|
|
2858
|
+
}
|
|
2859
|
+
catch {
|
|
2860
|
+
return;
|
|
2861
|
+
}
|
|
2862
|
+
if (!isRecord(parsed)) {
|
|
2863
|
+
return;
|
|
2864
|
+
}
|
|
2865
|
+
const rejected = readPortalIngestCount(parsed, "rejectedCount", "RejectedCount", "rejected");
|
|
2866
|
+
if (rejected <= 0) {
|
|
2867
|
+
return;
|
|
2868
|
+
}
|
|
2869
|
+
const accepted = readPortalIngestCount(parsed, "acceptedCount", "AcceptedCount", "accepted");
|
|
2870
|
+
const duplicate = readPortalIngestCount(parsed, "duplicateCount", "DuplicateCount", "duplicate");
|
|
2871
|
+
throw new Error(`PortalReportingSink ingest rejected ${rejected} reporting event(s). Accepted ${accepted}, duplicate ${duplicate}.`);
|
|
2872
|
+
}
|
|
2873
|
+
function readPortalIngestCount(source, ...keys) {
|
|
2874
|
+
for (const key of keys) {
|
|
2875
|
+
const value = source[key];
|
|
2876
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
2877
|
+
return Math.max(Math.trunc(value), 0);
|
|
2878
|
+
}
|
|
2879
|
+
if (typeof value === "string") {
|
|
2880
|
+
const parsed = Number.parseInt(value, 10);
|
|
2881
|
+
if (Number.isFinite(parsed)) {
|
|
2882
|
+
return Math.max(parsed, 0);
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
return 0;
|
|
2887
|
+
}
|
|
2811
2888
|
export const __loadstrikeTestExports = {
|
|
2812
2889
|
GrafanaLokiReportingSink,
|
|
2813
2890
|
InfluxDbReportingSink,
|
package/dist/types/runtime.d.ts
CHANGED
|
@@ -560,6 +560,7 @@ export interface LoadStrikeSinkSession {
|
|
|
560
560
|
infraConfig?: Record<string, unknown>;
|
|
561
561
|
runToken?: string;
|
|
562
562
|
portalReportingIngestUrl?: string;
|
|
563
|
+
portalReportingRunId?: string;
|
|
563
564
|
}
|
|
564
565
|
export interface LoadStrikeBaseContext {
|
|
565
566
|
logger: LoadStrikeLogger;
|
|
@@ -577,11 +578,13 @@ export interface LoadStrikeSessionStartInfo extends LoadStrikeBaseContext {
|
|
|
577
578
|
scenarios: LoadStrikeScenarioStartInfo[];
|
|
578
579
|
runToken?: string;
|
|
579
580
|
portalReportingIngestUrl?: string;
|
|
581
|
+
portalReportingRunId?: string;
|
|
580
582
|
readonly StartedUtc?: string;
|
|
581
583
|
readonly ScenarioNames?: string[];
|
|
582
584
|
readonly Scenarios?: LoadStrikeScenarioStartInfo[];
|
|
583
585
|
readonly RunToken?: string;
|
|
584
586
|
readonly PortalReportingIngestUrl?: string;
|
|
587
|
+
readonly PortalReportingRunId?: string;
|
|
585
588
|
}
|
|
586
589
|
type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Omit<T, Keys> & {
|
|
587
590
|
[Key in Keys]-?: Required<Pick<T, Key>> & Partial<Pick<T, Exclude<Keys, Key>>>;
|
package/dist/types/sinks.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ interface LoadStrikeNodeStats {
|
|
|
24
24
|
}
|
|
25
25
|
type SinkFetch = (input: string, init?: RequestInit) => Promise<Response>;
|
|
26
26
|
interface ReportingSinkEvent {
|
|
27
|
+
runId: string;
|
|
27
28
|
eventType: string;
|
|
28
29
|
occurredUtc: Date;
|
|
29
30
|
sessionId: string;
|
|
@@ -38,6 +39,7 @@ interface ReportingSinkEvent {
|
|
|
38
39
|
fields: Record<string, unknown>;
|
|
39
40
|
}
|
|
40
41
|
interface SinkSessionMetadata {
|
|
42
|
+
runId: string;
|
|
41
43
|
sessionId: string;
|
|
42
44
|
testSuite: string;
|
|
43
45
|
testName: string;
|
|
@@ -448,6 +450,7 @@ export declare class PortalReportingSink implements LoadStrikeReportingSink {
|
|
|
448
450
|
readonly SinkName = "portal";
|
|
449
451
|
readonly licenseFeature = "extensions.reporting_sinks.portal";
|
|
450
452
|
readonly LicenseFeature = "extensions.reporting_sinks.portal";
|
|
453
|
+
private readonly optionsInput;
|
|
451
454
|
private readonly fetchImpl;
|
|
452
455
|
private readonly timeoutMs;
|
|
453
456
|
private baseContext;
|
|
@@ -455,6 +458,7 @@ export declare class PortalReportingSink implements LoadStrikeReportingSink {
|
|
|
455
458
|
private runToken;
|
|
456
459
|
private ingestUrl;
|
|
457
460
|
constructor(options?: PortalReportingSinkOptionsInput);
|
|
461
|
+
cloneForRun(): PortalReportingSink;
|
|
458
462
|
init(context: LoadStrikeBaseContext, _infraConfig: Record<string, unknown>): void;
|
|
459
463
|
Init(context: LoadStrikeBaseContext, infraConfig: Record<string, unknown>): void;
|
|
460
464
|
start(session: LoadStrikeSessionStartInfo): void;
|
|
@@ -472,6 +476,7 @@ export declare class PortalReportingSink implements LoadStrikeReportingSink {
|
|
|
472
476
|
private getSession;
|
|
473
477
|
private persistEvents;
|
|
474
478
|
}
|
|
479
|
+
export declare function cloneReportingSinkForRun(sink: LoadStrikeReportingSink): LoadStrikeReportingSink;
|
|
475
480
|
export declare class InfluxDbReportingSink implements LoadStrikeReportingSink {
|
|
476
481
|
readonly sinkName = "influxdb";
|
|
477
482
|
readonly SinkName = "influxdb";
|
|
@@ -642,7 +647,9 @@ declare function createFinalStatsEvents(session: SinkSessionMetadata, stats: Loa
|
|
|
642
647
|
declare function createRunResultEvents(session: SinkSessionMetadata, result: LoadStrikeRunResult): ReportingSinkEvent[];
|
|
643
648
|
declare function createReportingEvent(session: SinkSessionMetadata, occurredUtc: Date, eventType: string, scenarioName: string | null, stepName: string | null, tags: Record<string, string>, fields: Record<string, unknown>): ReportingSinkEvent;
|
|
644
649
|
declare function toOtelAnyValue(value: unknown): Record<string, unknown>;
|
|
645
|
-
declare function sinkSessionMetadataFromContext(context: LoadStrikeBaseContext, session?: LoadStrikeSessionStartInfo
|
|
650
|
+
declare function sinkSessionMetadataFromContext(context: LoadStrikeBaseContext, session?: LoadStrikeSessionStartInfo, options?: {
|
|
651
|
+
distinctRunIdFallback?: boolean;
|
|
652
|
+
}): SinkSessionMetadata;
|
|
646
653
|
declare function mergeInfluxOptions(target: InfluxDbResolvedOptions, source: Partial<InfluxDbResolvedOptions>): void;
|
|
647
654
|
declare function mergeGrafanaLokiOptions(target: GrafanaLokiResolvedOptions, source: Partial<GrafanaLokiResolvedOptions>): void;
|
|
648
655
|
declare function mergeTimescaleDbOptions(target: TimescaleDbResolvedOptions, source: Partial<TimescaleDbResolvedOptions>): void;
|
|
@@ -673,7 +680,7 @@ declare function cloneSessionStartInfo(session: LoadStrikeSessionStartInfo): Loa
|
|
|
673
680
|
declare function cloneMetricStats(metrics: LoadStrikeMetricStats): LoadStrikeMetricStats;
|
|
674
681
|
declare function cloneScenarioStats(value: LoadStrikeScenarioStats): LoadStrikeScenarioStats;
|
|
675
682
|
declare function cloneNodeStats(result: LoadStrikeNodeStats): LoadStrikeNodeStats;
|
|
676
|
-
declare function postWithTimeout(fetchImpl: SinkFetch, url: string, init: RequestInit, timeoutMs: number, sinkName: string): Promise<
|
|
683
|
+
declare function postWithTimeout(fetchImpl: SinkFetch, url: string, init: RequestInit, timeoutMs: number, sinkName: string): Promise<string>;
|
|
677
684
|
export declare const __loadstrikeTestExports: {
|
|
678
685
|
GrafanaLokiReportingSink: typeof GrafanaLokiReportingSink;
|
|
679
686
|
InfluxDbReportingSink: typeof InfluxDbReportingSink;
|
package/package.json
CHANGED