@loadstrike/loadstrike-sdk 1.0.10101 → 1.0.10801
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -8
- package/dist/cjs/cluster.js +18 -1
- package/dist/cjs/correlation.js +69 -11
- package/dist/cjs/index.js +2 -14
- package/dist/cjs/local.js +88 -18
- package/dist/cjs/reporting.js +19 -5
- package/dist/cjs/runtime.js +526 -264
- package/dist/cjs/sinks.js +200 -106
- package/dist/cjs/transports.js +104 -18
- package/dist/esm/cluster.js +17 -0
- package/dist/esm/correlation.js +68 -10
- package/dist/esm/index.js +2 -4
- package/dist/esm/local.js +86 -16
- package/dist/esm/reporting.js +18 -2
- package/dist/esm/runtime.js +527 -264
- package/dist/esm/sinks.js +199 -105
- package/dist/esm/transports.js +103 -17
- package/dist/types/cluster.d.ts +30 -0
- package/dist/types/contracts.d.ts +15 -15
- package/dist/types/correlation.d.ts +41 -1
- package/dist/types/index.d.ts +4 -8
- package/dist/types/local.d.ts +146 -2
- package/dist/types/reporting.d.ts +33 -0
- package/dist/types/runtime.d.ts +464 -77
- package/dist/types/sinks.d.ts +212 -21
- package/dist/types/transports.d.ts +142 -0
- package/package.json +8 -19
package/dist/esm/runtime.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { randomBytes } from "node:crypto";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import { LoadStrikeLocalClient } from "./local.js";
|
|
6
6
|
import { DistributedClusterAgent, DistributedClusterCoordinator } from "./cluster.js";
|
|
7
7
|
import { CorrelationStoreConfiguration, CrossPlatformTrackingRuntime, RedisCorrelationStore, RedisCorrelationStoreOptions, TrackingFieldSelector } from "./correlation.js";
|
|
8
8
|
import { EndpointAdapterFactory } from "./transports.js";
|
|
@@ -85,42 +85,6 @@ export class LoadStrikePluginData {
|
|
|
85
85
|
return this.tables;
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
|
-
export class LoadStrikeReportData {
|
|
89
|
-
constructor(scenarioStats = []) {
|
|
90
|
-
if (!Array.isArray(scenarioStats) || scenarioStats.some((value) => !value || typeof value !== "object")) {
|
|
91
|
-
throw new TypeError("Scenario stats cannot contain null values.");
|
|
92
|
-
}
|
|
93
|
-
this.scenarioStats = scenarioStats.map((value, index) => attachScenarioStatsAliases(normalizeScenarioStatsValue(value, index)));
|
|
94
|
-
}
|
|
95
|
-
static create(...scenarioStats) {
|
|
96
|
-
return new LoadStrikeReportData(scenarioStats);
|
|
97
|
-
}
|
|
98
|
-
static Create(...scenarioStats) {
|
|
99
|
-
return LoadStrikeReportData.create(...scenarioStats);
|
|
100
|
-
}
|
|
101
|
-
static fromRunResult(result) {
|
|
102
|
-
return new LoadStrikeReportData((result?.scenarioStats ?? result?.ScenarioStats ?? []));
|
|
103
|
-
}
|
|
104
|
-
findScenarioStats(scenarioName) {
|
|
105
|
-
return this.scenarioStats.find((value) => value.scenarioName === scenarioName);
|
|
106
|
-
}
|
|
107
|
-
getScenarioStats(scenarioName) {
|
|
108
|
-
const value = this.findScenarioStats(scenarioName);
|
|
109
|
-
if (!value) {
|
|
110
|
-
throw new Error(`Scenario stats not found: ${scenarioName}`);
|
|
111
|
-
}
|
|
112
|
-
return value;
|
|
113
|
-
}
|
|
114
|
-
FindScenarioStats(scenarioName) {
|
|
115
|
-
return this.findScenarioStats(scenarioName);
|
|
116
|
-
}
|
|
117
|
-
GetScenarioStats(scenarioName) {
|
|
118
|
-
return this.getScenarioStats(scenarioName);
|
|
119
|
-
}
|
|
120
|
-
get ScenarioStats() {
|
|
121
|
-
return this.scenarioStats;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
88
|
class MeasurementAccumulator {
|
|
125
89
|
constructor() {
|
|
126
90
|
this.count = 0;
|
|
@@ -731,28 +695,10 @@ export class LoadStrikeContext {
|
|
|
731
695
|
.configureContext(this);
|
|
732
696
|
return runner.run();
|
|
733
697
|
}
|
|
734
|
-
async runDetailed(...argsOrSingleArray) {
|
|
735
|
-
const args = normalizeRunArgsInput(argsOrSingleArray);
|
|
736
|
-
if (!this.registeredScenarios.length) {
|
|
737
|
-
throw new Error("At least one scenario must be added before running the context.");
|
|
738
|
-
}
|
|
739
|
-
if (args.length) {
|
|
740
|
-
this.mergeValues(extractContextOverridesFromArgs(args), args);
|
|
741
|
-
}
|
|
742
|
-
const runner = LoadStrikeRunner
|
|
743
|
-
.create()
|
|
744
|
-
.addScenarios(...this.registeredScenarios)
|
|
745
|
-
.configureContext(this);
|
|
746
|
-
return runner.runDetailed();
|
|
747
|
-
}
|
|
748
698
|
async Run(...argsOrSingleArray) {
|
|
749
699
|
const args = normalizeRunArgsInput(argsOrSingleArray);
|
|
750
700
|
return this.run(args);
|
|
751
701
|
}
|
|
752
|
-
async RunDetailed(...argsOrSingleArray) {
|
|
753
|
-
const args = normalizeRunArgsInput(argsOrSingleArray);
|
|
754
|
-
return this.runDetailed(args);
|
|
755
|
-
}
|
|
756
702
|
toRunnerOptions() {
|
|
757
703
|
const normalizedValues = normalizeRunContextCollectionShapes(this.values);
|
|
758
704
|
return {
|
|
@@ -766,7 +712,6 @@ export class LoadStrikeContext {
|
|
|
766
712
|
coordinatorTargetScenarios: normalizedValues.CoordinatorTargetScenarios,
|
|
767
713
|
natsServerUrl: normalizedValues.NatsServerUrl,
|
|
768
714
|
runnerKey: normalizedValues.RunnerKey,
|
|
769
|
-
licenseValidationServerUrl: normalizedValues.LicenseValidationServerUrl,
|
|
770
715
|
licenseValidationTimeoutSeconds: normalizedValues.LicenseValidationTimeoutSeconds,
|
|
771
716
|
configPath: normalizedValues.ConfigPath,
|
|
772
717
|
infraConfigPath: normalizedValues.InfraConfigPath,
|
|
@@ -779,8 +724,6 @@ export class LoadStrikeContext {
|
|
|
779
724
|
reportFileName: normalizedValues.ReportFileName,
|
|
780
725
|
reportFolderPath: normalizedValues.ReportFolderPath,
|
|
781
726
|
reportFormats: normalizedValues.ReportFormats,
|
|
782
|
-
reportFinalizer: normalizedValues.ReportFinalizer,
|
|
783
|
-
detailedReportFinalizer: normalizedValues.DetailedReportFinalizer,
|
|
784
727
|
reportingIntervalSeconds: normalizedValues.ReportingIntervalSeconds,
|
|
785
728
|
minimumLogLevel: normalizedValues.MinimumLogLevel,
|
|
786
729
|
loggerConfig: normalizedValues.LoggerConfig,
|
|
@@ -788,6 +731,7 @@ export class LoadStrikeContext {
|
|
|
788
731
|
sinkRetryCount: normalizedValues.SinkRetryCount,
|
|
789
732
|
sinkRetryBackoffMs: normalizedValues.SinkRetryBackoffMs,
|
|
790
733
|
runtimePolicies: normalizedValues.RuntimePolicies,
|
|
734
|
+
runtimePolicyErrorMode: normalizedRuntimePolicyErrorMode(normalizedValues.RuntimePolicyErrorMode),
|
|
791
735
|
scenarioCompletionTimeoutSeconds: normalizedValues.ScenarioCompletionTimeoutSeconds,
|
|
792
736
|
clusterCommandTimeoutSeconds: normalizedValues.ClusterCommandTimeoutSeconds,
|
|
793
737
|
restartIterationMaxAttempts: normalizedValues.RestartIterationMaxAttempts,
|
|
@@ -833,28 +777,6 @@ export class LoadStrikeContext {
|
|
|
833
777
|
ConfigureContext(context) {
|
|
834
778
|
return this.configureContext(context);
|
|
835
779
|
}
|
|
836
|
-
buildLicenseValidationPayload() {
|
|
837
|
-
const scenarios = this.registeredScenarios.map((scenario) => ({
|
|
838
|
-
Name: scenario.name,
|
|
839
|
-
MaxFailCount: scenario.getMaxFailCount(),
|
|
840
|
-
RestartIterationOnFail: scenario.shouldRestartIterationOnFail(),
|
|
841
|
-
WithoutWarmUp: scenario.isWithoutWarmUp(),
|
|
842
|
-
WarmUpDurationSeconds: scenario.getWarmUpDurationSeconds(),
|
|
843
|
-
Weight: scenario.getWeight(),
|
|
844
|
-
LoadSimulations: [...scenario.getSimulations()],
|
|
845
|
-
Thresholds: [...scenario.getThresholds()],
|
|
846
|
-
Tracking: scenario.getTrackingConfiguration() ?? {},
|
|
847
|
-
LicenseFeatures: scenario.getLicenseFeatures()
|
|
848
|
-
}));
|
|
849
|
-
return {
|
|
850
|
-
Context: this.toObject(),
|
|
851
|
-
Scenarios: scenarios,
|
|
852
|
-
RunArgs: [...this.runArgs]
|
|
853
|
-
};
|
|
854
|
-
}
|
|
855
|
-
BuildLicenseValidationPayload() {
|
|
856
|
-
return this.buildLicenseValidationPayload();
|
|
857
|
-
}
|
|
858
780
|
displayConsoleMetrics(enable) {
|
|
859
781
|
return this.DisplayConsoleMetrics(enable);
|
|
860
782
|
}
|
|
@@ -928,12 +850,6 @@ export class LoadStrikeContext {
|
|
|
928
850
|
WithRunnerKey(runnerKey) {
|
|
929
851
|
return this.mergeValues({ RunnerKey: requireNonEmpty(runnerKey, "Runner key must be provided.") });
|
|
930
852
|
}
|
|
931
|
-
withLicenseValidationServerUrl(serverUrl) {
|
|
932
|
-
return this.WithLicenseValidationServerUrl(serverUrl);
|
|
933
|
-
}
|
|
934
|
-
WithLicenseValidationServerUrl(serverUrl) {
|
|
935
|
-
return this.mergeValues({ LicenseValidationServerUrl: requireNonEmpty(serverUrl, "License validation server URL must be provided.") });
|
|
936
|
-
}
|
|
937
853
|
withLicenseValidationTimeout(timeoutSeconds) {
|
|
938
854
|
return this.WithLicenseValidationTimeout(timeoutSeconds);
|
|
939
855
|
}
|
|
@@ -986,24 +902,6 @@ export class LoadStrikeContext {
|
|
|
986
902
|
}
|
|
987
903
|
return this.mergeValues({ ReportFormats: normalized });
|
|
988
904
|
}
|
|
989
|
-
withReportFinalizer(handler) {
|
|
990
|
-
return this.WithReportFinalizer(handler);
|
|
991
|
-
}
|
|
992
|
-
WithReportFinalizer(handler) {
|
|
993
|
-
if (typeof handler !== "function") {
|
|
994
|
-
throw new TypeError("Report finalizer must be provided.");
|
|
995
|
-
}
|
|
996
|
-
return this.mergeValues({ ReportFinalizer: handler });
|
|
997
|
-
}
|
|
998
|
-
withDetailedReportFinalizer(handler) {
|
|
999
|
-
return this.WithDetailedReportFinalizer(handler);
|
|
1000
|
-
}
|
|
1001
|
-
WithDetailedReportFinalizer(handler) {
|
|
1002
|
-
if (typeof handler !== "function") {
|
|
1003
|
-
throw new TypeError("Detailed report finalizer must be provided.");
|
|
1004
|
-
}
|
|
1005
|
-
return this.mergeValues({ DetailedReportFinalizer: handler });
|
|
1006
|
-
}
|
|
1007
905
|
withReportingInterval(intervalSeconds) {
|
|
1008
906
|
return this.WithReportingInterval(intervalSeconds);
|
|
1009
907
|
}
|
|
@@ -1026,6 +924,26 @@ export class LoadStrikeContext {
|
|
|
1026
924
|
validateNamedReportingSinks(sinks);
|
|
1027
925
|
return this.mergeValues({ ReportingSinks: sinks });
|
|
1028
926
|
}
|
|
927
|
+
withRuntimePolicies(...policies) {
|
|
928
|
+
return this.WithRuntimePolicies(...policies);
|
|
929
|
+
}
|
|
930
|
+
WithRuntimePolicies(...policies) {
|
|
931
|
+
if (!policies.length) {
|
|
932
|
+
throw new Error("At least one runtime policy should be provided.");
|
|
933
|
+
}
|
|
934
|
+
if (policies.some((policy) => policy == null)) {
|
|
935
|
+
throw new Error("Runtime policy collection cannot contain null values.");
|
|
936
|
+
}
|
|
937
|
+
return this.mergeValues({ RuntimePolicies: policies });
|
|
938
|
+
}
|
|
939
|
+
withRuntimePolicyErrorMode(mode) {
|
|
940
|
+
return this.WithRuntimePolicyErrorMode(mode);
|
|
941
|
+
}
|
|
942
|
+
WithRuntimePolicyErrorMode(mode) {
|
|
943
|
+
return this.mergeValues({
|
|
944
|
+
RuntimePolicyErrorMode: normalizedRuntimePolicyErrorMode(mode)
|
|
945
|
+
});
|
|
946
|
+
}
|
|
1029
947
|
withWorkerPlugins(...plugins) {
|
|
1030
948
|
return this.WithWorkerPlugins(...plugins);
|
|
1031
949
|
}
|
|
@@ -1121,7 +1039,7 @@ export class LoadStrikeContext {
|
|
|
1121
1039
|
}
|
|
1122
1040
|
}
|
|
1123
1041
|
export class LoadStrikeScenario {
|
|
1124
|
-
constructor(name, runHandler, initHandler, cleanHandler, loadSimulations, thresholds, trackingConfiguration, maxFailCount, withoutWarmUpValue, warmUpDurationSeconds, weight, restartIterationOnFail
|
|
1042
|
+
constructor(name, runHandler, initHandler, cleanHandler, loadSimulations, thresholds, trackingConfiguration, maxFailCount, withoutWarmUpValue, warmUpDurationSeconds, weight, restartIterationOnFail) {
|
|
1125
1043
|
this.name = name;
|
|
1126
1044
|
this.runHandler = runHandler;
|
|
1127
1045
|
this.initHandler = initHandler;
|
|
@@ -1134,18 +1052,23 @@ export class LoadStrikeScenario {
|
|
|
1134
1052
|
this.warmUpDurationSeconds = warmUpDurationSeconds;
|
|
1135
1053
|
this.weight = weight;
|
|
1136
1054
|
this.restartIterationOnFail = restartIterationOnFail;
|
|
1137
|
-
this.licenseFeatures = [...licenseFeatures];
|
|
1138
1055
|
}
|
|
1139
1056
|
static create(name, runHandler) {
|
|
1140
1057
|
const scenarioName = requireNonEmpty(name, "Scenario name must be provided.");
|
|
1141
1058
|
if (typeof runHandler !== "function") {
|
|
1142
1059
|
throw new TypeError("Scenario run handler must be provided.");
|
|
1143
1060
|
}
|
|
1144
|
-
return new LoadStrikeScenario(scenarioName, runHandler, undefined, undefined, [], [], undefined, 0, false, 0, 1, false
|
|
1061
|
+
return new LoadStrikeScenario(scenarioName, runHandler, undefined, undefined, [], [], undefined, 0, false, 0, 1, false);
|
|
1145
1062
|
}
|
|
1146
1063
|
static Create(name, runHandler) {
|
|
1147
1064
|
return LoadStrikeScenario.create(name, runHandler);
|
|
1148
1065
|
}
|
|
1066
|
+
static createAsync(name, runHandler) {
|
|
1067
|
+
return LoadStrikeScenario.create(name, runHandler);
|
|
1068
|
+
}
|
|
1069
|
+
static CreateAsync(name, runHandler) {
|
|
1070
|
+
return LoadStrikeScenario.createAsync(name, runHandler);
|
|
1071
|
+
}
|
|
1149
1072
|
static empty(name) {
|
|
1150
1073
|
return LoadStrikeScenario.create(name, () => LoadStrikeResponse.ok());
|
|
1151
1074
|
}
|
|
@@ -1159,37 +1082,43 @@ export class LoadStrikeScenario {
|
|
|
1159
1082
|
if (typeof handler !== "function") {
|
|
1160
1083
|
throw new TypeError("Init handler must be provided.");
|
|
1161
1084
|
}
|
|
1162
|
-
return new LoadStrikeScenario(this.name, this.runHandler, handler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail
|
|
1085
|
+
return new LoadStrikeScenario(this.name, this.runHandler, handler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail);
|
|
1086
|
+
}
|
|
1087
|
+
withInitAsync(handler) {
|
|
1088
|
+
return this.withInit(handler);
|
|
1163
1089
|
}
|
|
1164
1090
|
withClean(handler) {
|
|
1165
1091
|
if (typeof handler !== "function") {
|
|
1166
1092
|
throw new TypeError("Clean handler must be provided.");
|
|
1167
1093
|
}
|
|
1168
|
-
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, handler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail
|
|
1094
|
+
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, handler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail);
|
|
1095
|
+
}
|
|
1096
|
+
withCleanAsync(handler) {
|
|
1097
|
+
return this.withClean(handler);
|
|
1169
1098
|
}
|
|
1170
1099
|
withMaxFailCount(maxFailCount) {
|
|
1171
1100
|
if (!Number.isFinite(maxFailCount)) {
|
|
1172
1101
|
throw new RangeError("maxFailCount should be a finite number.");
|
|
1173
1102
|
}
|
|
1174
|
-
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, Math.trunc(maxFailCount), this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail
|
|
1103
|
+
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, Math.trunc(maxFailCount), this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail);
|
|
1175
1104
|
}
|
|
1176
1105
|
withoutWarmUp() {
|
|
1177
|
-
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, true, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail
|
|
1106
|
+
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, true, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail);
|
|
1178
1107
|
}
|
|
1179
1108
|
withWarmUpDuration(durationSeconds) {
|
|
1180
1109
|
if (!Number.isFinite(durationSeconds)) {
|
|
1181
1110
|
throw new RangeError("Warmup duration should be a finite number.");
|
|
1182
1111
|
}
|
|
1183
|
-
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, durationSeconds, this.weight, this.restartIterationOnFail
|
|
1112
|
+
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, durationSeconds, this.weight, this.restartIterationOnFail);
|
|
1184
1113
|
}
|
|
1185
1114
|
withWeight(weight) {
|
|
1186
1115
|
if (!Number.isFinite(weight)) {
|
|
1187
1116
|
throw new RangeError("Weight should be a finite number.");
|
|
1188
1117
|
}
|
|
1189
|
-
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, Math.trunc(weight), this.restartIterationOnFail
|
|
1118
|
+
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, Math.trunc(weight), this.restartIterationOnFail);
|
|
1190
1119
|
}
|
|
1191
1120
|
withRestartIterationOnFail(shouldRestart) {
|
|
1192
|
-
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, Boolean(shouldRestart)
|
|
1121
|
+
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, Boolean(shouldRestart));
|
|
1193
1122
|
}
|
|
1194
1123
|
withCrossPlatformTracking(configuration) {
|
|
1195
1124
|
if (!configuration || typeof configuration !== "object" || Array.isArray(configuration)) {
|
|
@@ -1206,25 +1135,19 @@ export class LoadStrikeScenario {
|
|
|
1206
1135
|
? mapRuntimeTrackingEndpointSpec(destinationSpec)
|
|
1207
1136
|
: null;
|
|
1208
1137
|
validateRuntimeTrackingConfiguration(copied, sourceEndpoint, destinationEndpoint);
|
|
1209
|
-
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, copied, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail
|
|
1138
|
+
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, copied, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail);
|
|
1210
1139
|
}
|
|
1211
1140
|
withLoadSimulations(...simulations) {
|
|
1212
1141
|
if (!simulations.length) {
|
|
1213
1142
|
throw new Error("At least one load simulation should be provided.");
|
|
1214
1143
|
}
|
|
1215
|
-
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, simulations.map((simulation) => attachLoadSimulationProjection({ ...simulation })), this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail
|
|
1144
|
+
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, simulations.map((simulation) => attachLoadSimulationProjection({ ...simulation })), this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail);
|
|
1216
1145
|
}
|
|
1217
1146
|
withThresholds(...thresholds) {
|
|
1218
1147
|
if (!thresholds.length) {
|
|
1219
1148
|
throw new Error("At least one threshold should be provided.");
|
|
1220
1149
|
}
|
|
1221
|
-
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, thresholds.map((threshold) => ({ ...threshold })), this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail
|
|
1222
|
-
}
|
|
1223
|
-
withLicenseFeatures(...features) {
|
|
1224
|
-
const normalized = features
|
|
1225
|
-
.map((value) => String(value ?? "").trim())
|
|
1226
|
-
.filter((value) => value.length > 0);
|
|
1227
|
-
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, [...this.licenseFeatures, ...normalized]);
|
|
1150
|
+
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, thresholds.map((threshold) => ({ ...threshold })), this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail);
|
|
1228
1151
|
}
|
|
1229
1152
|
getSimulations() {
|
|
1230
1153
|
return this.loadSimulations.map((simulation) => attachLoadSimulationProjection({ ...simulation }));
|
|
@@ -1250,9 +1173,6 @@ export class LoadStrikeScenario {
|
|
|
1250
1173
|
shouldRestartIterationOnFail() {
|
|
1251
1174
|
return this.restartIterationOnFail;
|
|
1252
1175
|
}
|
|
1253
|
-
getLicenseFeatures() {
|
|
1254
|
-
return [...this.licenseFeatures];
|
|
1255
|
-
}
|
|
1256
1176
|
async invokeInit(context) {
|
|
1257
1177
|
if (this.initHandler) {
|
|
1258
1178
|
await this.initHandler(context);
|
|
@@ -1281,9 +1201,15 @@ export class LoadStrikeScenario {
|
|
|
1281
1201
|
WithInit(handler) {
|
|
1282
1202
|
return this.withInit(handler);
|
|
1283
1203
|
}
|
|
1204
|
+
WithInitAsync(handler) {
|
|
1205
|
+
return this.withInitAsync(handler);
|
|
1206
|
+
}
|
|
1284
1207
|
WithClean(handler) {
|
|
1285
1208
|
return this.withClean(handler);
|
|
1286
1209
|
}
|
|
1210
|
+
WithCleanAsync(handler) {
|
|
1211
|
+
return this.withCleanAsync(handler);
|
|
1212
|
+
}
|
|
1287
1213
|
WithLoadSimulations(...simulations) {
|
|
1288
1214
|
return this.withLoadSimulations(...simulations);
|
|
1289
1215
|
}
|
|
@@ -1296,9 +1222,6 @@ export class LoadStrikeScenario {
|
|
|
1296
1222
|
WithRestartIterationOnFail(shouldRestart) {
|
|
1297
1223
|
return this.withRestartIterationOnFail(shouldRestart);
|
|
1298
1224
|
}
|
|
1299
|
-
WithLicenseFeatures(...features) {
|
|
1300
|
-
return this.withLicenseFeatures(...features);
|
|
1301
|
-
}
|
|
1302
1225
|
WithThresholds(...thresholds) {
|
|
1303
1226
|
return this.withThresholds(...thresholds);
|
|
1304
1227
|
}
|
|
@@ -1361,9 +1284,6 @@ export class LoadStrikeRunner {
|
|
|
1361
1284
|
static WithRunnerKey(context, runnerKey) {
|
|
1362
1285
|
return context.WithRunnerKey(runnerKey);
|
|
1363
1286
|
}
|
|
1364
|
-
static WithLicenseValidationServerUrl(context, serverUrl) {
|
|
1365
|
-
return context.WithLicenseValidationServerUrl(serverUrl);
|
|
1366
|
-
}
|
|
1367
1287
|
static WithLicenseValidationTimeout(context, timeoutSeconds) {
|
|
1368
1288
|
return context.WithLicenseValidationTimeout(timeoutSeconds);
|
|
1369
1289
|
}
|
|
@@ -1385,12 +1305,6 @@ export class LoadStrikeRunner {
|
|
|
1385
1305
|
static WithReportFileName(context, reportFileName) {
|
|
1386
1306
|
return context.WithReportFileName(reportFileName);
|
|
1387
1307
|
}
|
|
1388
|
-
static WithReportFinalizer(context, handler) {
|
|
1389
|
-
return context.WithReportFinalizer(handler);
|
|
1390
|
-
}
|
|
1391
|
-
static WithDetailedReportFinalizer(context, handler) {
|
|
1392
|
-
return context.WithDetailedReportFinalizer(handler);
|
|
1393
|
-
}
|
|
1394
1308
|
static WithReportFolder(context, reportFolderPath) {
|
|
1395
1309
|
return context.WithReportFolder(reportFolderPath);
|
|
1396
1310
|
}
|
|
@@ -1403,6 +1317,12 @@ export class LoadStrikeRunner {
|
|
|
1403
1317
|
static WithReportingSinks(context, ...sinks) {
|
|
1404
1318
|
return context.WithReportingSinks(...sinks);
|
|
1405
1319
|
}
|
|
1320
|
+
static WithRuntimePolicies(context, ...policies) {
|
|
1321
|
+
return context.WithRuntimePolicies(...policies);
|
|
1322
|
+
}
|
|
1323
|
+
static WithRuntimePolicyErrorMode(context, mode) {
|
|
1324
|
+
return context.WithRuntimePolicyErrorMode(mode);
|
|
1325
|
+
}
|
|
1406
1326
|
static WithScenarioCompletionTimeout(context, timeoutSeconds) {
|
|
1407
1327
|
return context.WithScenarioCompletionTimeout(timeoutSeconds);
|
|
1408
1328
|
}
|
|
@@ -1563,15 +1483,11 @@ export class LoadStrikeRunner {
|
|
|
1563
1483
|
return this.buildContext();
|
|
1564
1484
|
}
|
|
1565
1485
|
async run(args = []) {
|
|
1566
|
-
const detailed = await this.runDetailed(args);
|
|
1567
|
-
return detailedToNodeStats(detailed);
|
|
1568
|
-
}
|
|
1569
|
-
async runDetailed(args = []) {
|
|
1570
1486
|
if (this.contextConfigurators.length) {
|
|
1571
|
-
return new LoadStrikeRunner(this.scenarios, this.buildContext().toRunnerOptions()).
|
|
1487
|
+
return new LoadStrikeRunner(this.scenarios, this.buildContext().toRunnerOptions()).run(args);
|
|
1572
1488
|
}
|
|
1573
1489
|
if (args.length) {
|
|
1574
|
-
return this.buildContext().
|
|
1490
|
+
return this.buildContext().run(args);
|
|
1575
1491
|
}
|
|
1576
1492
|
const started = new Date();
|
|
1577
1493
|
const scenarioStats = new Map();
|
|
@@ -1585,14 +1501,15 @@ export class LoadStrikeRunner {
|
|
|
1585
1501
|
name: resolveSinkName(sink, index)
|
|
1586
1502
|
}));
|
|
1587
1503
|
const sinkErrors = [];
|
|
1504
|
+
const policyErrors = [];
|
|
1588
1505
|
const sinkRetryCount = Math.max(this.options.sinkRetryCount ?? 2, 0);
|
|
1589
1506
|
const sinkRetryBackoffMs = Math.max(this.options.sinkRetryBackoffMs ?? 25, 0);
|
|
1590
1507
|
const policies = this.options.runtimePolicies ?? [];
|
|
1508
|
+
const runtimePolicyErrorMode = normalizedRuntimePolicyErrorMode(this.options.runtimePolicyErrorMode);
|
|
1591
1509
|
const plugins = this.options.reportingSinks === undefined && this.options.workerPlugins === undefined &&
|
|
1592
1510
|
this.options.runtimePolicies === undefined && this.options.customSettings === undefined &&
|
|
1593
1511
|
this.options.globalCustomSettings === undefined && this.options.configPath === undefined &&
|
|
1594
1512
|
this.options.infraConfigPath === undefined && this.options.infraConfig === undefined &&
|
|
1595
|
-
this.options.reportFinalizer === undefined && this.options.detailedReportFinalizer === undefined &&
|
|
1596
1513
|
this.options.loggerConfig === undefined && this.options.minimumLogLevel === undefined &&
|
|
1597
1514
|
this.options.sinkRetryCount === undefined && this.options.sinkRetryBackoffMs === undefined &&
|
|
1598
1515
|
this.options.reportsEnabled === false && this.options.displayConsoleMetrics === false &&
|
|
@@ -1607,7 +1524,6 @@ export class LoadStrikeRunner {
|
|
|
1607
1524
|
const createdUtc = started.toISOString();
|
|
1608
1525
|
const testInfo = buildTestInfo(this.options, createdUtc);
|
|
1609
1526
|
const nodeInfo = buildNodeInfo(this.options);
|
|
1610
|
-
const runLogger = createLogger(this.options.loggerConfig, this.options.minimumLogLevel);
|
|
1611
1527
|
let pluginsStopped = false;
|
|
1612
1528
|
let sinksStopped = false;
|
|
1613
1529
|
let realtimeTimer = null;
|
|
@@ -1617,18 +1533,18 @@ export class LoadStrikeRunner {
|
|
|
1617
1533
|
let licenseSession = null;
|
|
1618
1534
|
const clusterMode = resolveClusterExecutionMode(this.options);
|
|
1619
1535
|
const selectedScenarios = clusterMode === "local-coordinator" || clusterMode === "nats-coordinator"
|
|
1620
|
-
? await this.filterScenariosWithPolicies(this.scenarios, policies)
|
|
1621
|
-
: await this.selectScenarios(policies);
|
|
1622
|
-
|
|
1623
|
-
licensePayload = this.buildLicenseValidationPayload();
|
|
1536
|
+
? await this.filterScenariosWithPolicies(this.scenarios, policies, policyErrors, runtimePolicyErrorMode)
|
|
1537
|
+
: await this.selectScenarios(policies, policyErrors, runtimePolicyErrorMode);
|
|
1538
|
+
licensePayload = buildLicenseValidationPayload(this.options, this.scenarios);
|
|
1624
1539
|
licenseClient = new LoadStrikeLocalClient({
|
|
1625
|
-
licensingApiBaseUrl: licenseValidationServerUrl,
|
|
1626
1540
|
licenseValidationTimeoutMs: Math.max((this.options.licenseValidationTimeoutSeconds ?? 10) * 1000, 1)
|
|
1627
1541
|
});
|
|
1628
1542
|
licenseSession = await licenseClient.acquireLicenseLease(licensePayload);
|
|
1629
1543
|
if (clusterMode === "nats-agent") {
|
|
1630
1544
|
return this.runAgentWithNats(createdUtc, testInfo, nodeInfo);
|
|
1631
1545
|
}
|
|
1546
|
+
const loggerSetup = createLoggerSetup(this.options.loggerConfig, this.options.minimumLogLevel, this.options, testInfo, nodeInfo);
|
|
1547
|
+
const runLogger = loggerSetup.logger;
|
|
1632
1548
|
const scenarioStartInfos = selectedScenarios.map((scenario, index) => {
|
|
1633
1549
|
const startInfo = {
|
|
1634
1550
|
scenarioName: scenario.name,
|
|
@@ -1695,7 +1611,7 @@ export class LoadStrikeRunner {
|
|
|
1695
1611
|
const okCount = snapshot.reduce((sum, value) => sum + value.allOkCount, 0);
|
|
1696
1612
|
const failCount = snapshot.reduce((sum, value) => sum + value.allFailCount, 0);
|
|
1697
1613
|
const stamp = new Date().toTimeString().slice(0, 8);
|
|
1698
|
-
|
|
1614
|
+
console.log(`[${stamp}] requests=${requestCount} ok=${okCount} fail=${failCount}`);
|
|
1699
1615
|
}
|
|
1700
1616
|
}
|
|
1701
1617
|
finally {
|
|
@@ -1703,7 +1619,7 @@ export class LoadStrikeRunner {
|
|
|
1703
1619
|
}
|
|
1704
1620
|
};
|
|
1705
1621
|
const reportingIntervalMs = Math.max(Math.trunc((this.options.reportingIntervalSeconds ?? 5) * 1000), 1);
|
|
1706
|
-
if (sinkStates.length > 0) {
|
|
1622
|
+
if (sinkStates.length > 0 || toBoolean(this.options.displayConsoleMetrics, true)) {
|
|
1707
1623
|
realtimeTimer = setInterval(() => {
|
|
1708
1624
|
void emitRealtimeSnapshot();
|
|
1709
1625
|
}, reportingIntervalMs);
|
|
@@ -1718,12 +1634,12 @@ export class LoadStrikeRunner {
|
|
|
1718
1634
|
if (clusterMode === "local-coordinator") {
|
|
1719
1635
|
const aggregated = await this.runCoordinatorWithLocalAgents(selectedScenarios, testInfo, nodeInfo);
|
|
1720
1636
|
metricStats = aggregated.metrics;
|
|
1721
|
-
result = toDetailedRunResultFromNodeStats(aggregated, started.toISOString(), sinkErrors);
|
|
1637
|
+
result = toDetailedRunResultFromNodeStats(aggregated, started.toISOString(), sinkErrors, policyErrors);
|
|
1722
1638
|
}
|
|
1723
1639
|
else if (clusterMode === "nats-coordinator") {
|
|
1724
1640
|
const aggregated = await this.runCoordinatorWithNats(selectedScenarios, testInfo, nodeInfo);
|
|
1725
1641
|
metricStats = aggregated.metrics;
|
|
1726
|
-
result = toDetailedRunResultFromNodeStats(aggregated, started.toISOString(), sinkErrors);
|
|
1642
|
+
result = toDetailedRunResultFromNodeStats(aggregated, started.toISOString(), sinkErrors, policyErrors);
|
|
1727
1643
|
}
|
|
1728
1644
|
else {
|
|
1729
1645
|
const testAbortController = new AbortController();
|
|
@@ -1746,21 +1662,15 @@ export class LoadStrikeRunner {
|
|
|
1746
1662
|
stopTestState,
|
|
1747
1663
|
testAbortController,
|
|
1748
1664
|
executeScenarioInvocation: (targetScenario, context, operation) => this.executeScenarioInvocation(targetScenario, context, operation),
|
|
1749
|
-
invokeBeforeScenario: (runtimePolicies, scenarioName) => this.invokeBeforeScenario(runtimePolicies, scenarioName),
|
|
1750
|
-
invokeAfterScenario: (runtimePolicies, scenarioName, stats) => this.invokeAfterScenario(runtimePolicies, scenarioName, stats)
|
|
1665
|
+
invokeBeforeScenario: (runtimePolicies, scenarioName) => this.invokeBeforeScenario(runtimePolicies, scenarioName, policyErrors, runtimePolicyErrorMode),
|
|
1666
|
+
invokeAfterScenario: (runtimePolicies, scenarioName, stats) => this.invokeAfterScenario(runtimePolicies, scenarioName, stats, policyErrors, runtimePolicyErrorMode),
|
|
1667
|
+
invokeBeforeStep: (runtimePolicies, scenarioName, stepName) => this.invokeBeforeStep(runtimePolicies, scenarioName, stepName, policyErrors, runtimePolicyErrorMode),
|
|
1668
|
+
invokeAfterStep: (runtimePolicies, scenarioName, stepName, reply) => this.invokeAfterStep(runtimePolicies, scenarioName, stepName, reply, policyErrors, runtimePolicyErrorMode)
|
|
1751
1669
|
})));
|
|
1752
1670
|
nodeInfo.currentOperation = stopTestState.value ? "Stop" : "Complete";
|
|
1753
|
-
|
|
1671
|
+
const scenarioStatList = Array.from(scenarioAccumulators.values())
|
|
1754
1672
|
.map((value) => value.build(scenarioDurationsMs.get(value.scenarioName) ?? 0))
|
|
1755
1673
|
.sort((left, right) => left.sortIndex - right.sortIndex);
|
|
1756
|
-
const reportFinalizer = this.options.reportFinalizer;
|
|
1757
|
-
if (typeof reportFinalizer === "function") {
|
|
1758
|
-
const finalized = reportFinalizer(LoadStrikeReportData.create(...scenarioStatList));
|
|
1759
|
-
if (!(finalized instanceof LoadStrikeReportData)) {
|
|
1760
|
-
throw new TypeError("Report finalizer must return LoadStrikeReportData.");
|
|
1761
|
-
}
|
|
1762
|
-
scenarioStatList = [...finalized.scenarioStats];
|
|
1763
|
-
}
|
|
1764
1674
|
const metricValues = collectMetricValues(allRegisteredMetrics);
|
|
1765
1675
|
metricStats = collectMetricStats(allRegisteredMetrics, Date.now() - started.getTime());
|
|
1766
1676
|
const metricsByName = metricValues.reduce((accumulator, metric) => {
|
|
@@ -1774,35 +1684,42 @@ export class LoadStrikeRunner {
|
|
|
1774
1684
|
result = {
|
|
1775
1685
|
startedUtc: started.toISOString(),
|
|
1776
1686
|
completedUtc: new Date().toISOString(),
|
|
1687
|
+
allBytes: builtScenarioStats.reduce((sum, x) => sum + x.allBytes, 0),
|
|
1777
1688
|
allRequestCount: scenarioStatList.reduce((sum, x) => sum + x.allRequestCount, 0),
|
|
1778
1689
|
allOkCount: scenarioStatList.reduce((sum, x) => sum + x.allOkCount, 0),
|
|
1779
1690
|
allFailCount: scenarioStatList.reduce((sum, x) => sum + x.allFailCount, 0),
|
|
1780
1691
|
failedThresholds: thresholdEvaluation.failedCount,
|
|
1692
|
+
durationMs: Math.max(Date.now() - started.getTime(), 0),
|
|
1781
1693
|
nodeInfo,
|
|
1782
1694
|
testInfo,
|
|
1695
|
+
thresholds: thresholdEvaluation.results.map((value) => ({ ...value })),
|
|
1783
1696
|
thresholdResults: thresholdEvaluation.results,
|
|
1697
|
+
metricStats,
|
|
1784
1698
|
metrics: metricValues,
|
|
1785
1699
|
scenarioStats: builtScenarioStats,
|
|
1786
1700
|
stepStats: builtStepStats,
|
|
1701
|
+
scenarioDurationsMs: Object.fromEntries(scenarioDurationsMs.entries()),
|
|
1787
1702
|
pluginsData: [],
|
|
1788
1703
|
disabledSinks: [],
|
|
1789
1704
|
sinkErrors,
|
|
1790
|
-
|
|
1705
|
+
policyErrors,
|
|
1706
|
+
reportFiles: [],
|
|
1707
|
+
logFiles: [...loggerSetup.logFiles],
|
|
1708
|
+
correlationRows: buildDetailedCorrelationRows(),
|
|
1709
|
+
failedCorrelationRows: buildDetailedFailedCorrelationRows()
|
|
1791
1710
|
};
|
|
1792
1711
|
}
|
|
1793
|
-
result.pluginsData = mergePluginData(result.pluginsData, await this.collectPluginData(plugins,
|
|
1794
|
-
const
|
|
1795
|
-
|
|
1796
|
-
const finalizedResult = attachRunResultAliases(typeof detailedReportFinalizer === "function"
|
|
1797
|
-
? requireDetailedReportFinalizerResult(detailedReportFinalizer(detailedResult))
|
|
1798
|
-
: detailedResult);
|
|
1799
|
-
await this.emitFinalStats(sinkStates, detailedToNodeStats(finalizedResult, metricStats), sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
|
|
1712
|
+
result.pluginsData = mergePluginData(result.pluginsData, await this.collectPluginData(plugins, attachRunResultAliases(result), pluginLifecycleErrors));
|
|
1713
|
+
const finalizedResult = attachRunResultAliases(result);
|
|
1714
|
+
finalizedResult.logFiles = mergeStringArrays(finalizedResult.logFiles, loggerSetup.logFiles);
|
|
1800
1715
|
finalizedResult.reportFiles = this.writeReports(finalizedResult);
|
|
1801
1716
|
finalizedResult.disabledSinks = sinkStates.filter((x) => x.disabled).map((x) => x.name);
|
|
1802
1717
|
await this.emitRunResult(sinkStates, finalizedResult, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
|
|
1803
1718
|
await this.stopSinks(sinkStates, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
|
|
1804
1719
|
sinksStopped = true;
|
|
1805
1720
|
finalizedResult.disabledSinks = sinkStates.filter((x) => x.disabled).map((x) => x.name);
|
|
1721
|
+
finalizedResult.sinkErrors = sinkErrors
|
|
1722
|
+
.map((value) => attachSinkErrorAliases(normalizeSinkErrorValue(value)));
|
|
1806
1723
|
return finalizedResult;
|
|
1807
1724
|
}
|
|
1808
1725
|
finally {
|
|
@@ -1813,7 +1730,7 @@ export class LoadStrikeRunner {
|
|
|
1813
1730
|
await this.stopSinks(sinkStates, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
|
|
1814
1731
|
}
|
|
1815
1732
|
if (!pluginsStopped) {
|
|
1816
|
-
await this.stopPlugins(plugins, pluginLifecycleErrors);
|
|
1733
|
+
await this.stopPlugins(plugins, pluginLifecycleErrors, runLogger);
|
|
1817
1734
|
}
|
|
1818
1735
|
if (licenseClient && licenseSession && licensePayload) {
|
|
1819
1736
|
await licenseClient.releaseLicenseLease(licenseSession, licensePayload);
|
|
@@ -1823,10 +1740,7 @@ export class LoadStrikeRunner {
|
|
|
1823
1740
|
async Run(args = []) {
|
|
1824
1741
|
return this.run(args);
|
|
1825
1742
|
}
|
|
1826
|
-
async
|
|
1827
|
-
return this.runDetailed(args);
|
|
1828
|
-
}
|
|
1829
|
-
async filterScenariosWithPolicies(scenarios, policies) {
|
|
1743
|
+
async filterScenariosWithPolicies(scenarios, policies, policyErrors, mode) {
|
|
1830
1744
|
if (!policies.length) {
|
|
1831
1745
|
return [...scenarios];
|
|
1832
1746
|
}
|
|
@@ -1835,7 +1749,7 @@ export class LoadStrikeRunner {
|
|
|
1835
1749
|
let allowed = true;
|
|
1836
1750
|
for (const policy of policies) {
|
|
1837
1751
|
if (policy.shouldRunScenario) {
|
|
1838
|
-
const shouldRun = await policy.shouldRunScenario(scenario.name);
|
|
1752
|
+
const shouldRun = await this.invokePolicyCallback(policy, "shouldRunScenario", scenario.name, "", policyErrors, mode, () => policy.shouldRunScenario(scenario.name), true);
|
|
1839
1753
|
if (!shouldRun) {
|
|
1840
1754
|
allowed = false;
|
|
1841
1755
|
break;
|
|
@@ -1868,6 +1782,7 @@ export class LoadStrikeRunner {
|
|
|
1868
1782
|
localDevClusterEnabled: false,
|
|
1869
1783
|
nodeType,
|
|
1870
1784
|
natsServerUrl: undefined,
|
|
1785
|
+
reportFolderPath: resolve(this.options.reportFolderPath ?? "./reports", sanitizeReportFileName(machineName)),
|
|
1871
1786
|
targetScenarios,
|
|
1872
1787
|
agentTargetScenarios: targetScenarios,
|
|
1873
1788
|
coordinatorTargetScenarios: [],
|
|
@@ -1876,14 +1791,16 @@ export class LoadStrikeRunner {
|
|
|
1876
1791
|
reportingSinks: includeWorkerExtensions ? this.options.reportingSinks : [],
|
|
1877
1792
|
workerPlugins: includeWorkerExtensions ? this.options.workerPlugins : []
|
|
1878
1793
|
});
|
|
1879
|
-
const
|
|
1794
|
+
const childResult = await childRunner.run();
|
|
1795
|
+
const childStats = detailedToNodeStats(childResult);
|
|
1880
1796
|
return {
|
|
1881
1797
|
...childStats,
|
|
1882
1798
|
nodeInfo: {
|
|
1883
1799
|
...childStats.nodeInfo,
|
|
1884
1800
|
machineName,
|
|
1885
1801
|
nodeType
|
|
1886
|
-
}
|
|
1802
|
+
},
|
|
1803
|
+
logFiles: [...(childResult.logFiles ?? [])]
|
|
1887
1804
|
};
|
|
1888
1805
|
}
|
|
1889
1806
|
async runCoordinatorWithLocalAgents(scenarios, testInfo, nodeInfo) {
|
|
@@ -1951,7 +1868,7 @@ export class LoadStrikeRunner {
|
|
|
1951
1868
|
};
|
|
1952
1869
|
});
|
|
1953
1870
|
if (handled && handledStats) {
|
|
1954
|
-
return toDetailedRunResultFromNodeStats(handledStats, startedUtc, []);
|
|
1871
|
+
return toDetailedRunResultFromNodeStats(handledStats, startedUtc, [], []);
|
|
1955
1872
|
}
|
|
1956
1873
|
await sleep(5);
|
|
1957
1874
|
}
|
|
@@ -1961,15 +1878,17 @@ export class LoadStrikeRunner {
|
|
|
1961
1878
|
await agent.dispose().catch(() => { });
|
|
1962
1879
|
}
|
|
1963
1880
|
}
|
|
1964
|
-
async stopPlugins(plugins, pluginLifecycleErrors) {
|
|
1965
|
-
void pluginLifecycleErrors;
|
|
1881
|
+
async stopPlugins(plugins, pluginLifecycleErrors, logger) {
|
|
1966
1882
|
for (const plugin of plugins) {
|
|
1883
|
+
const pluginName = normalizePluginName(resolveWorkerPluginName(plugin));
|
|
1967
1884
|
const stop = resolveWorkerPluginStop(plugin);
|
|
1968
1885
|
if (stop) {
|
|
1969
1886
|
try {
|
|
1970
1887
|
await stop();
|
|
1971
1888
|
}
|
|
1972
|
-
catch {
|
|
1889
|
+
catch (error) {
|
|
1890
|
+
recordPluginLifecycleError(pluginLifecycleErrors, pluginName, "stop", error);
|
|
1891
|
+
logger?.warn?.(`Worker plugin ${pluginName} stop failed during shutdown: ${String(error ?? "unknown error")}`);
|
|
1973
1892
|
}
|
|
1974
1893
|
}
|
|
1975
1894
|
const dispose = resolveWorkerPluginDispose(plugin);
|
|
@@ -1977,7 +1896,9 @@ export class LoadStrikeRunner {
|
|
|
1977
1896
|
try {
|
|
1978
1897
|
await dispose();
|
|
1979
1898
|
}
|
|
1980
|
-
catch {
|
|
1899
|
+
catch (error) {
|
|
1900
|
+
recordPluginLifecycleError(pluginLifecycleErrors, pluginName, "dispose", error);
|
|
1901
|
+
logger?.warn?.(`Worker plugin ${pluginName} dispose failed during shutdown: ${String(error ?? "unknown error")}`);
|
|
1981
1902
|
}
|
|
1982
1903
|
}
|
|
1983
1904
|
}
|
|
@@ -1989,7 +1910,7 @@ export class LoadStrikeRunner {
|
|
|
1989
1910
|
}
|
|
1990
1911
|
return executeTrackedScenarioInvocation(tracking, context, () => scenario.execute(context), operation);
|
|
1991
1912
|
}
|
|
1992
|
-
async selectScenarios(policies) {
|
|
1913
|
+
async selectScenarios(policies, policyErrors, mode) {
|
|
1993
1914
|
const scenarioMap = new Map(this.scenarios.map((x) => [x.name, x]));
|
|
1994
1915
|
const nodeType = (this.options.nodeType ?? "SingleNode").toLowerCase();
|
|
1995
1916
|
let selectedNames = this.options.targetScenarios;
|
|
@@ -2012,7 +1933,7 @@ export class LoadStrikeRunner {
|
|
|
2012
1933
|
let allowed = true;
|
|
2013
1934
|
for (const policy of policies) {
|
|
2014
1935
|
if (policy.shouldRunScenario) {
|
|
2015
|
-
const shouldRun = await policy.shouldRunScenario(scenario.name);
|
|
1936
|
+
const shouldRun = await this.invokePolicyCallback(policy, "shouldRunScenario", scenario.name, "", policyErrors, mode, () => policy.shouldRunScenario(scenario.name), true);
|
|
2016
1937
|
if (!shouldRun) {
|
|
2017
1938
|
allowed = false;
|
|
2018
1939
|
break;
|
|
@@ -2025,9 +1946,6 @@ export class LoadStrikeRunner {
|
|
|
2025
1946
|
}
|
|
2026
1947
|
return filtered;
|
|
2027
1948
|
}
|
|
2028
|
-
buildLicenseValidationPayload() {
|
|
2029
|
-
return buildLicenseValidationPayload(this.options, this.scenarios);
|
|
2030
|
-
}
|
|
2031
1949
|
async initializeSinks(sinkStates, context, infraConfig, retryCount, retryBackoffMs, sinkErrors) {
|
|
2032
1950
|
for (const state of sinkStates) {
|
|
2033
1951
|
await this.invokeSinkAction(state, "init", retryCount, retryBackoffMs, sinkErrors, false, true, async () => {
|
|
@@ -2062,19 +1980,10 @@ export class LoadStrikeRunner {
|
|
|
2062
1980
|
});
|
|
2063
1981
|
}
|
|
2064
1982
|
}
|
|
2065
|
-
async emitFinalStats(sinkStates, result, retryCount, retryBackoffMs, sinkErrors) {
|
|
2066
|
-
for (const state of sinkStates) {
|
|
2067
|
-
await this.invokeSinkAction(state, "final", retryCount, retryBackoffMs, sinkErrors, false, false, async () => {
|
|
2068
|
-
const saveFinalStats = resolveSinkSaveFinalStats(state.sink);
|
|
2069
|
-
if (saveFinalStats) {
|
|
2070
|
-
await saveFinalStats(result);
|
|
2071
|
-
}
|
|
2072
|
-
});
|
|
2073
|
-
}
|
|
2074
|
-
}
|
|
2075
1983
|
async stopSinks(sinkStates, retryCount, retryBackoffMs, sinkErrors) {
|
|
1984
|
+
const shutdownRetryCount = 0;
|
|
2076
1985
|
for (const state of sinkStates) {
|
|
2077
|
-
await this.invokeSinkAction(state, "stop",
|
|
1986
|
+
await this.invokeSinkAction(state, "stop", shutdownRetryCount, retryBackoffMs, sinkErrors, true, true, async () => {
|
|
2078
1987
|
const stop = resolveSinkStop(state.sink);
|
|
2079
1988
|
if (stop) {
|
|
2080
1989
|
await stop();
|
|
@@ -2082,11 +1991,9 @@ export class LoadStrikeRunner {
|
|
|
2082
1991
|
});
|
|
2083
1992
|
const dispose = resolveSinkDispose(state.sink);
|
|
2084
1993
|
if (dispose) {
|
|
2085
|
-
|
|
1994
|
+
await this.invokeSinkAction(state, "dispose", shutdownRetryCount, retryBackoffMs, sinkErrors, true, true, async () => {
|
|
2086
1995
|
await dispose();
|
|
2087
|
-
}
|
|
2088
|
-
catch {
|
|
2089
|
-
}
|
|
1996
|
+
});
|
|
2090
1997
|
}
|
|
2091
1998
|
}
|
|
2092
1999
|
}
|
|
@@ -2130,20 +2037,56 @@ export class LoadStrikeRunner {
|
|
|
2130
2037
|
}
|
|
2131
2038
|
}
|
|
2132
2039
|
}
|
|
2133
|
-
async invokeBeforeScenario(policies, scenarioName) {
|
|
2040
|
+
async invokeBeforeScenario(policies, scenarioName, policyErrors, mode) {
|
|
2134
2041
|
for (const policy of policies) {
|
|
2135
2042
|
if (policy.beforeScenario) {
|
|
2136
|
-
await policy.beforeScenario(scenarioName);
|
|
2043
|
+
await this.invokePolicyCallback(policy, "beforeScenario", scenarioName, "", policyErrors, mode, () => policy.beforeScenario(scenarioName));
|
|
2137
2044
|
}
|
|
2138
2045
|
}
|
|
2139
2046
|
}
|
|
2140
|
-
async invokeAfterScenario(policies, scenarioName, stats) {
|
|
2047
|
+
async invokeAfterScenario(policies, scenarioName, stats, policyErrors, mode) {
|
|
2141
2048
|
for (const policy of policies) {
|
|
2142
2049
|
if (policy.afterScenario) {
|
|
2143
|
-
await policy.afterScenario(scenarioName, stats);
|
|
2050
|
+
await this.invokePolicyCallback(policy, "afterScenario", scenarioName, "", policyErrors, mode, () => policy.afterScenario(scenarioName, stats));
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
async invokeBeforeStep(policies, scenarioName, stepName, policyErrors, mode) {
|
|
2055
|
+
for (const policy of policies) {
|
|
2056
|
+
if (policy.beforeStep) {
|
|
2057
|
+
await this.invokePolicyCallback(policy, "beforeStep", scenarioName, stepName, policyErrors, mode, () => policy.beforeStep(scenarioName, stepName));
|
|
2144
2058
|
}
|
|
2145
2059
|
}
|
|
2146
2060
|
}
|
|
2061
|
+
async invokeAfterStep(policies, scenarioName, stepName, reply, policyErrors, mode) {
|
|
2062
|
+
for (const policy of policies) {
|
|
2063
|
+
if (policy.afterStep) {
|
|
2064
|
+
await this.invokePolicyCallback(policy, "afterStep", scenarioName, stepName, policyErrors, mode, () => policy.afterStep(scenarioName, stepName, reply));
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
async invokePolicyCallback(policy, callbackName, scenarioName, stepName, policyErrors, mode, callback, continueFallback) {
|
|
2069
|
+
try {
|
|
2070
|
+
return await callback();
|
|
2071
|
+
}
|
|
2072
|
+
catch (error) {
|
|
2073
|
+
const entry = this.buildRuntimePolicyError(policy, callbackName, scenarioName, stepName, error);
|
|
2074
|
+
policyErrors.push(entry);
|
|
2075
|
+
if (mode === "fail") {
|
|
2076
|
+
throw new RuntimePolicyCallbackError(`Runtime policy '${entry.policyName}' failed during ${callbackName}: ${entry.message}`);
|
|
2077
|
+
}
|
|
2078
|
+
return continueFallback;
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
buildRuntimePolicyError(policy, callbackName, scenarioName, stepName, error) {
|
|
2082
|
+
return attachRuntimePolicyErrorAliases({
|
|
2083
|
+
policyName: resolveRuntimePolicyName(policy),
|
|
2084
|
+
callbackName,
|
|
2085
|
+
scenarioName,
|
|
2086
|
+
stepName,
|
|
2087
|
+
message: String(error ?? "runtime policy callback failed")
|
|
2088
|
+
});
|
|
2089
|
+
}
|
|
2147
2090
|
writeReports(result) {
|
|
2148
2091
|
const reportsEnabled = this.options.reportsEnabled ?? true;
|
|
2149
2092
|
if (!reportsEnabled) {
|
|
@@ -2152,7 +2095,7 @@ export class LoadStrikeRunner {
|
|
|
2152
2095
|
const reportFolder = resolve(this.options.reportFolderPath ?? "./reports");
|
|
2153
2096
|
const reportFile = sanitizeReportFileName(this.options.reportFileName?.trim()
|
|
2154
2097
|
? this.options.reportFileName
|
|
2155
|
-
: `${result.testInfo.testSuite}_${result.testInfo.testName}_${
|
|
2098
|
+
: `${result.testInfo.testSuite}_${result.testInfo.testName}_${resolveDefaultReportTimestamp(result)}`);
|
|
2156
2099
|
const reportFormats = normalizeReportFormats(this.options.reportFormats ?? ["html", "txt", "csv", "md"]);
|
|
2157
2100
|
mkdirSync(reportFolder, { recursive: true });
|
|
2158
2101
|
const nodeStats = detailedToNodeStats(result);
|
|
@@ -2203,7 +2146,7 @@ function hasPluginRows(value) {
|
|
|
2203
2146
|
return value.tables.some((table) => Array.isArray(table.rows) && table.rows.length > 0);
|
|
2204
2147
|
}
|
|
2205
2148
|
async function executeScenarioRuntime(args) {
|
|
2206
|
-
const { scenario, scenarioIndex, scenarioCount, options, logger, nodeInfo, testInfo, policies, restartIterationMaxAttempts, allRegisteredMetrics, scenarioRuntimes, stepRuntimes, scenarioAccumulators, scenarioDurationsMs, stopTestState, testAbortController, executeScenarioInvocation, invokeBeforeScenario, invokeAfterScenario } = args;
|
|
2149
|
+
const { scenario, scenarioIndex, scenarioCount, options, logger, nodeInfo, testInfo, policies, restartIterationMaxAttempts, allRegisteredMetrics, scenarioRuntimes, stepRuntimes, scenarioAccumulators, scenarioDurationsMs, stopTestState, testAbortController, executeScenarioInvocation, invokeBeforeScenario, invokeAfterScenario, invokeBeforeStep, invokeAfterStep } = args;
|
|
2207
2150
|
const scenarioStartedMs = Date.now();
|
|
2208
2151
|
const scenarioContextData = {};
|
|
2209
2152
|
const registeredMetrics = [];
|
|
@@ -2316,20 +2259,8 @@ async function executeScenarioRuntime(args) {
|
|
|
2316
2259
|
},
|
|
2317
2260
|
shouldStopScenario: () => stopScenario || scenarioCancellationToken.aborted,
|
|
2318
2261
|
shouldStopTest: () => stopTestState.value || scenarioCancellationToken.aborted,
|
|
2319
|
-
invokeBeforeStep: async (stepName) =>
|
|
2320
|
-
|
|
2321
|
-
if (policy.beforeStep) {
|
|
2322
|
-
await policy.beforeStep(scenario.name, stepName);
|
|
2323
|
-
}
|
|
2324
|
-
}
|
|
2325
|
-
},
|
|
2326
|
-
invokeAfterStep: async (stepName, reply) => {
|
|
2327
|
-
for (const policy of policies) {
|
|
2328
|
-
if (policy.afterStep) {
|
|
2329
|
-
await policy.afterStep(scenario.name, stepName, reply);
|
|
2330
|
-
}
|
|
2331
|
-
}
|
|
2332
|
-
}
|
|
2262
|
+
invokeBeforeStep: async (stepName) => invokeBeforeStep(policies, scenario.name, stepName),
|
|
2263
|
+
invokeAfterStep: async (stepName, reply) => invokeAfterStep(policies, scenario.name, stepName, reply)
|
|
2333
2264
|
};
|
|
2334
2265
|
attachScenarioContextAliases(context);
|
|
2335
2266
|
const startedAt = Date.now();
|
|
@@ -2568,6 +2499,10 @@ async function executeScenarioRuntime(args) {
|
|
|
2568
2499
|
await invokeAfterScenario(policies, scenario.name, runtime);
|
|
2569
2500
|
}
|
|
2570
2501
|
catch (error) {
|
|
2502
|
+
if (error instanceof RuntimePolicyCallbackError) {
|
|
2503
|
+
accumulator.setCurrentOperation("Error");
|
|
2504
|
+
throw error;
|
|
2505
|
+
}
|
|
2571
2506
|
if (scenarioCancellationToken.aborted || testAbortController.signal.aborted) {
|
|
2572
2507
|
accumulator.setCurrentOperation("Stop");
|
|
2573
2508
|
}
|
|
@@ -2965,6 +2900,16 @@ function normalizeSinkErrorValue(value) {
|
|
|
2965
2900
|
attempts: pickAliasNumber(source, "attempts", "Attempts")
|
|
2966
2901
|
};
|
|
2967
2902
|
}
|
|
2903
|
+
function normalizeRuntimePolicyErrorValue(value) {
|
|
2904
|
+
const source = asAliasRecord(value);
|
|
2905
|
+
return {
|
|
2906
|
+
policyName: pickAliasString(source, "policyName", "PolicyName"),
|
|
2907
|
+
callbackName: pickAliasString(source, "callbackName", "CallbackName"),
|
|
2908
|
+
scenarioName: pickAliasString(source, "scenarioName", "ScenarioName"),
|
|
2909
|
+
stepName: pickAliasString(source, "stepName", "StepName"),
|
|
2910
|
+
message: pickAliasString(source, "message", "Message")
|
|
2911
|
+
};
|
|
2912
|
+
}
|
|
2968
2913
|
function normalizeMetricValueProjection(value) {
|
|
2969
2914
|
const source = asAliasRecord(value);
|
|
2970
2915
|
const scenarioName = pickAliasString(source, "scenarioName", "ScenarioName");
|
|
@@ -3307,6 +3252,16 @@ function attachSinkErrorAliases(error) {
|
|
|
3307
3252
|
Attempts: "attempts"
|
|
3308
3253
|
});
|
|
3309
3254
|
}
|
|
3255
|
+
function attachRuntimePolicyErrorAliases(error) {
|
|
3256
|
+
const projected = normalizeRuntimePolicyErrorValue(error);
|
|
3257
|
+
return attachAliasMap(projected, {
|
|
3258
|
+
PolicyName: "policyName",
|
|
3259
|
+
CallbackName: "callbackName",
|
|
3260
|
+
ScenarioName: "scenarioName",
|
|
3261
|
+
StepName: "stepName",
|
|
3262
|
+
Message: "message"
|
|
3263
|
+
});
|
|
3264
|
+
}
|
|
3310
3265
|
function attachMetricStatsAliases(stats) {
|
|
3311
3266
|
const projected = normalizeMetricStatsValue(stats);
|
|
3312
3267
|
projected.counters = projected.counters.map((value) => attachCounterStatsAliases(value));
|
|
@@ -3465,7 +3420,8 @@ function attachNodeStatsAliases(stats) {
|
|
|
3465
3420
|
PluginsData: "pluginsData",
|
|
3466
3421
|
DisabledSinks: "disabledSinks",
|
|
3467
3422
|
SinkErrors: "sinkErrors",
|
|
3468
|
-
ReportFiles: "reportFiles"
|
|
3423
|
+
ReportFiles: "reportFiles",
|
|
3424
|
+
LogFiles: "logFiles"
|
|
3469
3425
|
});
|
|
3470
3426
|
defineAliasProperty(projected, "StartedUtc", () => parseAliasDate(stats.startedUtc));
|
|
3471
3427
|
defineAliasProperty(projected, "CompletedUtc", () => parseAliasDate(stats.completedUtc));
|
|
@@ -3476,17 +3432,30 @@ function attachRunResultAliases(result) {
|
|
|
3476
3432
|
const source = asAliasRecord(result);
|
|
3477
3433
|
const scenarioStats = pickAliasArray(source, "scenarioStats", "ScenarioStats")
|
|
3478
3434
|
.map((value, index) => attachScenarioStatsAliases(normalizeScenarioStatsValue(value, index)));
|
|
3435
|
+
const findScenarioStats = (scenarioName) => scenarioStats.find((value) => value.scenarioName === scenarioName);
|
|
3436
|
+
const getScenarioStats = (scenarioName) => {
|
|
3437
|
+
const value = findScenarioStats(scenarioName);
|
|
3438
|
+
if (!value) {
|
|
3439
|
+
throw new Error(`Scenario stats not found: ${scenarioName}`);
|
|
3440
|
+
}
|
|
3441
|
+
return value;
|
|
3442
|
+
};
|
|
3479
3443
|
const projected = {
|
|
3480
3444
|
startedUtc: pickAliasDateToken(source, "startedUtc", "StartedUtc"),
|
|
3481
3445
|
completedUtc: pickAliasDateToken(source, "completedUtc", "CompletedUtc"),
|
|
3446
|
+
allBytes: pickAliasNumber(source, "allBytes", "AllBytes"),
|
|
3482
3447
|
allRequestCount: pickAliasNumber(source, "allRequestCount", "AllRequestCount"),
|
|
3483
3448
|
allOkCount: pickAliasNumber(source, "allOkCount", "AllOkCount"),
|
|
3484
3449
|
allFailCount: pickAliasNumber(source, "allFailCount", "AllFailCount"),
|
|
3485
3450
|
failedThresholds: pickAliasNumber(source, "failedThresholds", "FailedThresholds"),
|
|
3451
|
+
durationMs: pickAliasNumber(source, "durationMs", "DurationMs", "Duration"),
|
|
3486
3452
|
nodeInfo: attachNodeInfoAliases(pickAliasValue(source, "nodeInfo", "NodeInfo")),
|
|
3487
3453
|
testInfo: attachTestInfoAliases(pickAliasValue(source, "testInfo", "TestInfo")),
|
|
3454
|
+
thresholds: pickAliasArray(source, "thresholds", "Thresholds")
|
|
3455
|
+
.map((value) => attachThresholdResultAliases(normalizeThresholdResultValue(value))),
|
|
3488
3456
|
thresholdResults: pickAliasArray(source, "thresholdResults", "ThresholdResults")
|
|
3489
3457
|
.map((value) => attachThresholdResultAliases(normalizeThresholdResultValue(value))),
|
|
3458
|
+
metricStats: attachMetricStatsAliases(normalizeMetricStatsValue(pickAliasValue(source, "metricStats", "MetricStats"), pickAliasNumber(source, "durationMs", "DurationMs", "Duration"))),
|
|
3490
3459
|
metrics: pickAliasArray(source, "metrics", "Metrics")
|
|
3491
3460
|
.map((value) => attachMetricValueAliases(normalizeMetricValueProjection(value))),
|
|
3492
3461
|
scenarioStats,
|
|
@@ -3496,37 +3465,53 @@ function attachRunResultAliases(result) {
|
|
|
3496
3465
|
: scenarioStats.flatMap((value) => value.stepStats)),
|
|
3497
3466
|
pluginsData: pickAliasArray(source, "pluginsData", "PluginsData")
|
|
3498
3467
|
.map((value) => normalizePluginData(pickAliasString(asAliasRecord(value), "pluginName", "PluginName"), value)),
|
|
3468
|
+
scenarioDurationsMs: pickAliasValue(source, "scenarioDurationsMs", "ScenarioDurationsMs") ?? {},
|
|
3499
3469
|
disabledSinks: normalizeAliasStringArray(pickAliasValue(source, "disabledSinks", "DisabledSinks")),
|
|
3500
3470
|
sinkErrors: pickAliasArray(source, "sinkErrors", "SinkErrors")
|
|
3501
3471
|
.map((value) => attachSinkErrorAliases(normalizeSinkErrorValue(value))),
|
|
3502
|
-
|
|
3472
|
+
policyErrors: pickAliasArray(source, "policyErrors", "PolicyErrors")
|
|
3473
|
+
.map((value) => attachRuntimePolicyErrorAliases(normalizeRuntimePolicyErrorValue(value))),
|
|
3474
|
+
reportFiles: normalizeAliasStringArray(pickAliasValue(source, "reportFiles", "ReportFiles")),
|
|
3475
|
+
logFiles: normalizeAliasStringArray(pickAliasValue(source, "logFiles", "LogFiles")),
|
|
3476
|
+
correlationRows: pickAliasArray(source, "correlationRows", "CorrelationRows")
|
|
3477
|
+
.map((value) => ({ ...asAliasRecord(value) })),
|
|
3478
|
+
failedCorrelationRows: pickAliasArray(source, "failedCorrelationRows", "FailedCorrelationRows")
|
|
3479
|
+
.map((value) => ({ ...asAliasRecord(value) })),
|
|
3480
|
+
findScenarioStats,
|
|
3481
|
+
getScenarioStats,
|
|
3482
|
+
FindScenarioStats: findScenarioStats,
|
|
3483
|
+
GetScenarioStats: getScenarioStats
|
|
3503
3484
|
};
|
|
3504
3485
|
attachAliasMap(projected, {
|
|
3486
|
+
AllBytes: "allBytes",
|
|
3505
3487
|
AllRequestCount: "allRequestCount",
|
|
3506
3488
|
AllOkCount: "allOkCount",
|
|
3507
3489
|
AllFailCount: "allFailCount",
|
|
3508
3490
|
FailedThresholds: "failedThresholds",
|
|
3491
|
+
DurationMs: "durationMs",
|
|
3509
3492
|
NodeInfo: "nodeInfo",
|
|
3510
3493
|
TestInfo: "testInfo",
|
|
3494
|
+
Thresholds: "thresholds",
|
|
3511
3495
|
ThresholdResults: "thresholdResults",
|
|
3496
|
+
MetricStats: "metricStats",
|
|
3512
3497
|
Metrics: "metrics",
|
|
3513
3498
|
ScenarioStats: "scenarioStats",
|
|
3514
3499
|
StepStats: "stepStats",
|
|
3500
|
+
ScenarioDurationsMs: "scenarioDurationsMs",
|
|
3515
3501
|
PluginsData: "pluginsData",
|
|
3516
3502
|
DisabledSinks: "disabledSinks",
|
|
3517
3503
|
SinkErrors: "sinkErrors",
|
|
3518
|
-
|
|
3504
|
+
PolicyErrors: "policyErrors",
|
|
3505
|
+
ReportFiles: "reportFiles",
|
|
3506
|
+
LogFiles: "logFiles",
|
|
3507
|
+
CorrelationRows: "correlationRows",
|
|
3508
|
+
FailedCorrelationRows: "failedCorrelationRows"
|
|
3519
3509
|
});
|
|
3520
3510
|
defineAliasProperty(projected, "StartedUtc", () => parseAliasDate(projected.startedUtc));
|
|
3521
3511
|
defineAliasProperty(projected, "CompletedUtc", () => parseAliasDate(projected.completedUtc));
|
|
3512
|
+
defineAliasProperty(projected, "Duration", () => projected.durationMs);
|
|
3522
3513
|
return projected;
|
|
3523
3514
|
}
|
|
3524
|
-
function requireDetailedReportFinalizerResult(result) {
|
|
3525
|
-
if (!result || typeof result !== "object" || Array.isArray(result)) {
|
|
3526
|
-
throw new TypeError("Detailed report finalizer must return LoadStrikeRunResult.");
|
|
3527
|
-
}
|
|
3528
|
-
return result;
|
|
3529
|
-
}
|
|
3530
3515
|
function resolveResponseCustomLatency(customLatencyMs, usesOverloadDefaults) {
|
|
3531
3516
|
if (!Number.isFinite(customLatencyMs)) {
|
|
3532
3517
|
return usesOverloadDefaults ? -1 : 0;
|
|
@@ -4124,9 +4109,10 @@ function detailedToNodeStats(result, metricStats) {
|
|
|
4124
4109
|
scenarioStats,
|
|
4125
4110
|
stepStats,
|
|
4126
4111
|
pluginsData,
|
|
4127
|
-
disabledSinks: [...result.disabledSinks],
|
|
4128
|
-
sinkErrors: result.sinkErrors.map((sinkError) => ({ ...sinkError })),
|
|
4129
|
-
reportFiles: [...result.reportFiles],
|
|
4112
|
+
disabledSinks: [...(result.disabledSinks ?? [])],
|
|
4113
|
+
sinkErrors: (result.sinkErrors ?? []).map((sinkError) => ({ ...sinkError })),
|
|
4114
|
+
reportFiles: [...(result.reportFiles ?? [])],
|
|
4115
|
+
logFiles: [...(result.logFiles ?? [])],
|
|
4130
4116
|
findScenarioStats: (scenarioName) => scenarioStats.find((scenario) => scenario.scenarioName === scenarioName),
|
|
4131
4117
|
getScenarioStats: (scenarioName) => {
|
|
4132
4118
|
const value = scenarioStats.find((scenario) => scenario.scenarioName === scenarioName);
|
|
@@ -4193,6 +4179,7 @@ function buildEmptyNodeStats(args) {
|
|
|
4193
4179
|
disabledSinks: [],
|
|
4194
4180
|
sinkErrors: [],
|
|
4195
4181
|
reportFiles: [],
|
|
4182
|
+
logFiles: [],
|
|
4196
4183
|
findScenarioStats: (scenarioName) => undefined,
|
|
4197
4184
|
getScenarioStats: (scenarioName) => {
|
|
4198
4185
|
throw new Error(`Scenario stats not found: ${scenarioName}`);
|
|
@@ -4240,27 +4227,37 @@ function nodeStatsToClusterPayload(result) {
|
|
|
4240
4227
|
thresholds: result.thresholds,
|
|
4241
4228
|
pluginsData: result.pluginsData,
|
|
4242
4229
|
nodeInfo: result.nodeInfo,
|
|
4243
|
-
testInfo: result.testInfo
|
|
4230
|
+
testInfo: result.testInfo,
|
|
4231
|
+
logFiles: [...(result.logFiles ?? [])]
|
|
4244
4232
|
};
|
|
4245
4233
|
}
|
|
4246
|
-
function toDetailedRunResultFromNodeStats(result, startedUtc, sinkErrors) {
|
|
4234
|
+
function toDetailedRunResultFromNodeStats(result, startedUtc, sinkErrors, policyErrors = []) {
|
|
4247
4235
|
return attachRunResultAliases({
|
|
4248
4236
|
startedUtc,
|
|
4249
4237
|
completedUtc: result.completedUtc,
|
|
4238
|
+
allBytes: result.allBytes,
|
|
4250
4239
|
allRequestCount: result.allRequestCount,
|
|
4251
4240
|
allOkCount: result.allOkCount,
|
|
4252
4241
|
allFailCount: result.allFailCount,
|
|
4253
4242
|
failedThresholds: result.failedThresholds,
|
|
4243
|
+
durationMs: result.durationMs,
|
|
4254
4244
|
nodeInfo: result.nodeInfo,
|
|
4255
4245
|
testInfo: result.testInfo,
|
|
4246
|
+
thresholds: result.thresholds.map((value) => ({ ...value })),
|
|
4256
4247
|
thresholdResults: result.thresholds.map((value) => ({ ...value })),
|
|
4248
|
+
metricStats: result.metrics,
|
|
4257
4249
|
metrics: flattenMetricValues(result.metrics),
|
|
4258
4250
|
scenarioStats: result.scenarioStats,
|
|
4259
4251
|
stepStats: result.stepStats,
|
|
4252
|
+
scenarioDurationsMs: Object.fromEntries(result.scenarioStats.map((value) => [value.scenarioName, value.durationMs])),
|
|
4260
4253
|
pluginsData: result.pluginsData.map((value) => normalizePluginData(value.pluginName, value)),
|
|
4261
4254
|
disabledSinks: [...result.disabledSinks],
|
|
4262
4255
|
sinkErrors: [...sinkErrors, ...result.sinkErrors],
|
|
4263
|
-
|
|
4256
|
+
policyErrors: policyErrors.map((value) => attachRuntimePolicyErrorAliases({ ...value })),
|
|
4257
|
+
reportFiles: [...result.reportFiles],
|
|
4258
|
+
logFiles: [...(result.logFiles ?? [])],
|
|
4259
|
+
correlationRows: buildDetailedCorrelationRows(),
|
|
4260
|
+
failedCorrelationRows: buildDetailedFailedCorrelationRows()
|
|
4264
4261
|
});
|
|
4265
4262
|
}
|
|
4266
4263
|
function flattenMetricValues(metricStats) {
|
|
@@ -4353,6 +4350,7 @@ function clusterNodeResultToNodeStats(result, testInfo, fallbackNodeInfo) {
|
|
|
4353
4350
|
disabledSinks: [],
|
|
4354
4351
|
sinkErrors: [],
|
|
4355
4352
|
reportFiles: [],
|
|
4353
|
+
logFiles: normalizeAliasStringArray(result.stats.logFiles),
|
|
4356
4354
|
findScenarioStats: (scenarioName) => scenarioStats.find((value) => value.scenarioName === scenarioName),
|
|
4357
4355
|
getScenarioStats: (scenarioName) => {
|
|
4358
4356
|
const value = scenarioStats.find((scenario) => scenario.scenarioName === scenarioName);
|
|
@@ -4399,6 +4397,7 @@ function aggregateNodeStats(testInfo, coordinatorNodeInfo, nodes) {
|
|
|
4399
4397
|
disabledSinks: [],
|
|
4400
4398
|
sinkErrors: [],
|
|
4401
4399
|
reportFiles: [],
|
|
4400
|
+
logFiles: mergeStringArrays(...nodes.map((value) => value.logFiles ?? [])),
|
|
4402
4401
|
findScenarioStats: (scenarioName) => scenarioStats.find((value) => value.scenarioName === scenarioName),
|
|
4403
4402
|
getScenarioStats: (scenarioName) => {
|
|
4404
4403
|
const value = scenarioStats.find((scenario) => scenario.scenarioName === scenarioName);
|
|
@@ -4791,6 +4790,8 @@ class ManagedScenarioTrackingRuntime {
|
|
|
4791
4790
|
this.timeoutSweepIntervalMs = Math.trunc(pickTrackingNumber(tracking, ["TimeoutSweepIntervalMs", "timeoutSweepIntervalMs"], pickTrackingNumber(tracking, ["TimeoutSweepIntervalSeconds", "timeoutSweepIntervalSeconds"], 1) * 1000));
|
|
4792
4791
|
this.timeoutBatchSize = Math.trunc(pickTrackingNumber(tracking, ["TimeoutBatchSize", "timeoutBatchSize"], 200));
|
|
4793
4792
|
this.timeoutCountsAsFailure = pickTrackingBoolean(tracking, "TimeoutCountsAsFailure", "timeoutCountsAsFailure", true);
|
|
4793
|
+
const trackingFieldValueCaseSensitive = pickTrackingBoolean(tracking, "TrackingFieldValueCaseSensitive", "trackingFieldValueCaseSensitive", true);
|
|
4794
|
+
const gatherByFieldValueCaseSensitive = pickTrackingBoolean(tracking, "GatherByFieldValueCaseSensitive", "gatherByFieldValueCaseSensitive", true);
|
|
4794
4795
|
this.sourceAdapter = EndpointAdapterFactory.create(this.sourceEndpoint);
|
|
4795
4796
|
this.destinationAdapter = this.destinationEndpoint ? EndpointAdapterFactory.create(this.destinationEndpoint) : null;
|
|
4796
4797
|
this.correlationStore = mapRuntimeCorrelationStore(tracking, runNamespace);
|
|
@@ -4798,18 +4799,20 @@ class ManagedScenarioTrackingRuntime {
|
|
|
4798
4799
|
sourceTrackingField: this.sourceEndpoint.trackingField,
|
|
4799
4800
|
destinationTrackingField: this.destinationEndpoint?.trackingField,
|
|
4800
4801
|
destinationGatherByField: this.destinationEndpoint?.gatherByField,
|
|
4802
|
+
trackingFieldValueCaseSensitive,
|
|
4803
|
+
gatherByFieldValueCaseSensitive,
|
|
4801
4804
|
correlationTimeoutMs: this.correlationTimeoutMs,
|
|
4802
4805
|
timeoutCountsAsFailure: this.timeoutCountsAsFailure,
|
|
4803
4806
|
store: this.correlationStore ?? undefined,
|
|
4804
4807
|
plugins: [
|
|
4805
4808
|
{
|
|
4806
|
-
onMatched: async (trackingId, _source, destination, latencyMs) => {
|
|
4809
|
+
onMatched: async (trackingId, _source, destination, latencyMs, gatherByValue) => {
|
|
4807
4810
|
const eventId = this.shiftPendingValue(this.pendingEventIds, trackingId);
|
|
4808
4811
|
const sourceTimestampUtc = this.shiftPendingValue(this.pendingSourceTimestamps, trackingId);
|
|
4809
4812
|
const destinationTimestampUtc = new Date().toISOString();
|
|
4810
|
-
const
|
|
4813
|
+
const observedGatherByValue = gatherByValue ?? (this.destinationEndpoint?.gatherByField
|
|
4811
4814
|
? readRuntimeTrackingId(destination, this.destinationEndpoint.gatherByField)
|
|
4812
|
-
: undefined;
|
|
4815
|
+
: undefined);
|
|
4813
4816
|
addCorrelationRow({
|
|
4814
4817
|
occurredUtc: new Date().toISOString(),
|
|
4815
4818
|
scenarioName: this.scenarioName,
|
|
@@ -4825,7 +4828,7 @@ class ManagedScenarioTrackingRuntime {
|
|
|
4825
4828
|
isSuccess: true,
|
|
4826
4829
|
isFailure: false,
|
|
4827
4830
|
gatherByField: this.gatherByFieldExpression,
|
|
4828
|
-
gatherByValue:
|
|
4831
|
+
gatherByValue: observedGatherByValue ?? undefined
|
|
4829
4832
|
});
|
|
4830
4833
|
this.resolveTrackingWaiter(trackingId, {
|
|
4831
4834
|
status: "matched",
|
|
@@ -5596,10 +5599,10 @@ function normalizeRuntimeObservedTrackingPayload(payload, endpoint) {
|
|
|
5596
5599
|
function readRuntimeTrackingId(payload, selector) {
|
|
5597
5600
|
const normalized = selector.trim().toLowerCase();
|
|
5598
5601
|
if (normalized.startsWith("header:")) {
|
|
5599
|
-
const headerName = selector.slice("header:".length).trim()
|
|
5602
|
+
const headerName = selector.slice("header:".length).trim();
|
|
5600
5603
|
for (const [key, value] of Object.entries(payload.headers ?? {})) {
|
|
5601
5604
|
const resolved = String(value ?? "").trim();
|
|
5602
|
-
if (key
|
|
5605
|
+
if (key === headerName && resolved) {
|
|
5603
5606
|
return resolved;
|
|
5604
5607
|
}
|
|
5605
5608
|
}
|
|
@@ -5875,6 +5878,49 @@ function createLogger(loggerConfig, minimumLogLevel) {
|
|
|
5875
5878
|
};
|
|
5876
5879
|
}
|
|
5877
5880
|
}
|
|
5881
|
+
return wrapLoggerWithMinimumLevel(baseLogger, minimumLogLevel);
|
|
5882
|
+
}
|
|
5883
|
+
function createLoggerSetup(loggerConfig, minimumLogLevel, options, testInfo, nodeInfo) {
|
|
5884
|
+
if (typeof loggerConfig === "function") {
|
|
5885
|
+
return {
|
|
5886
|
+
logger: createLogger(loggerConfig, minimumLogLevel),
|
|
5887
|
+
logFiles: []
|
|
5888
|
+
};
|
|
5889
|
+
}
|
|
5890
|
+
const logFilePath = resolveDefaultLogFilePath(options, testInfo, nodeInfo);
|
|
5891
|
+
mkdirSync(resolve(options.reportFolderPath ?? "./reports"), { recursive: true });
|
|
5892
|
+
writeFileSync(logFilePath, "", "utf8");
|
|
5893
|
+
return {
|
|
5894
|
+
logger: wrapLoggerWithMinimumLevel(createDefaultLogger(logFilePath), minimumLogLevel),
|
|
5895
|
+
logFiles: [logFilePath]
|
|
5896
|
+
};
|
|
5897
|
+
}
|
|
5898
|
+
function createDefaultLogger(logFilePath) {
|
|
5899
|
+
const write = (level, message) => {
|
|
5900
|
+
const line = formatDefaultLoggerLine(level, message);
|
|
5901
|
+
const output = `${line}\n`;
|
|
5902
|
+
if (level === "debug") {
|
|
5903
|
+
console.debug(line);
|
|
5904
|
+
}
|
|
5905
|
+
else if (level === "info") {
|
|
5906
|
+
console.info(line);
|
|
5907
|
+
}
|
|
5908
|
+
else if (level === "warn") {
|
|
5909
|
+
console.warn(line);
|
|
5910
|
+
}
|
|
5911
|
+
else {
|
|
5912
|
+
console.error(line);
|
|
5913
|
+
}
|
|
5914
|
+
appendFileSync(logFilePath, output, "utf8");
|
|
5915
|
+
};
|
|
5916
|
+
return {
|
|
5917
|
+
debug: (message) => write("debug", message),
|
|
5918
|
+
info: (message) => write("info", message),
|
|
5919
|
+
warn: (message) => write("warn", message),
|
|
5920
|
+
error: (message) => write("error", message)
|
|
5921
|
+
};
|
|
5922
|
+
}
|
|
5923
|
+
function wrapLoggerWithMinimumLevel(baseLogger, minimumLogLevel) {
|
|
5878
5924
|
const threshold = logLevelOrder(minimumLogLevel);
|
|
5879
5925
|
return {
|
|
5880
5926
|
debug: (message) => {
|
|
@@ -5899,6 +5945,16 @@ function createLogger(loggerConfig, minimumLogLevel) {
|
|
|
5899
5945
|
}
|
|
5900
5946
|
};
|
|
5901
5947
|
}
|
|
5948
|
+
function formatDefaultLoggerLine(level, message) {
|
|
5949
|
+
const code = level === "debug"
|
|
5950
|
+
? "DBG"
|
|
5951
|
+
: level === "info"
|
|
5952
|
+
? "INF"
|
|
5953
|
+
: level === "warn"
|
|
5954
|
+
? "WRN"
|
|
5955
|
+
: "ERR";
|
|
5956
|
+
return `${new Date().toISOString()} [${code}] ${message}`;
|
|
5957
|
+
}
|
|
5902
5958
|
function logLevelOrder(level) {
|
|
5903
5959
|
const normalized = String(level ?? "").trim().toLowerCase();
|
|
5904
5960
|
if (!normalized) {
|
|
@@ -5930,11 +5986,41 @@ function formatUtcReportTimestamp(value) {
|
|
|
5930
5986
|
const second = String(value.getUTCSeconds()).padStart(2, "0");
|
|
5931
5987
|
return `${year}${month}${day}_${hour}${minute}${second}`;
|
|
5932
5988
|
}
|
|
5989
|
+
function resolveDefaultReportTimestamp(result) {
|
|
5990
|
+
const createdUtc = result.testInfo?.createdUtc ?? result.startedUtc;
|
|
5991
|
+
const parsed = createdUtc ? new Date(createdUtc) : undefined;
|
|
5992
|
+
return parsed && !Number.isNaN(parsed.getTime())
|
|
5993
|
+
? formatUtcReportTimestamp(parsed)
|
|
5994
|
+
: formatUtcReportTimestamp(new Date());
|
|
5995
|
+
}
|
|
5996
|
+
function resolveDefaultLogFilePath(options, testInfo, nodeInfo) {
|
|
5997
|
+
const reportFolder = resolve(options.reportFolderPath ?? "./reports");
|
|
5998
|
+
const parsedCreatedUtc = testInfo.createdUtc ? new Date(testInfo.createdUtc) : undefined;
|
|
5999
|
+
const timestamp = parsedCreatedUtc && !Number.isNaN(parsedCreatedUtc.getTime())
|
|
6000
|
+
? formatUtcReportTimestamp(parsedCreatedUtc)
|
|
6001
|
+
: formatUtcReportTimestamp(new Date());
|
|
6002
|
+
const suffixParts = [];
|
|
6003
|
+
if (nodeInfo.nodeType !== "SingleNode") {
|
|
6004
|
+
suffixParts.push(nodeInfo.nodeType === "Coordinator" ? "coordinator" : "agent");
|
|
6005
|
+
}
|
|
6006
|
+
const localMachineName = os.hostname().trim().toLowerCase();
|
|
6007
|
+
if (nodeInfo.machineName.trim() &&
|
|
6008
|
+
(nodeInfo.nodeType !== "SingleNode" || nodeInfo.machineName.trim().toLowerCase() !== localMachineName)) {
|
|
6009
|
+
suffixParts.push(nodeInfo.machineName);
|
|
6010
|
+
}
|
|
6011
|
+
const suffix = suffixParts.length
|
|
6012
|
+
? `-${suffixParts.map((value) => sanitizeReportFileName(value)).join("-")}`
|
|
6013
|
+
: "";
|
|
6014
|
+
return resolve(reportFolder, sanitizeReportFileName(`loadstrike-log-${timestamp}${suffix}.txt`));
|
|
6015
|
+
}
|
|
5933
6016
|
function sanitizeReportFileName(value) {
|
|
5934
6017
|
const normalized = String(value ?? "");
|
|
5935
6018
|
return normalized
|
|
5936
6019
|
.replace(/[<>:"/\\|?*\u0000-\u001F]/g, "_") || "loadstrike-run";
|
|
5937
6020
|
}
|
|
6021
|
+
function mergeStringArrays(...values) {
|
|
6022
|
+
return Array.from(new Set(values.flatMap((value) => (value ?? []).filter((entry) => entry.trim().length > 0))));
|
|
6023
|
+
}
|
|
5938
6024
|
function loadJsonObject(path) {
|
|
5939
6025
|
try {
|
|
5940
6026
|
const raw = readFileSync(resolve(path), "utf8");
|
|
@@ -6171,6 +6257,16 @@ function normalizeRunnerOptionCollectionShapes(options) {
|
|
|
6171
6257
|
validateNamedWorkerPlugins(normalized.workerPlugins ?? []);
|
|
6172
6258
|
return normalized;
|
|
6173
6259
|
}
|
|
6260
|
+
function normalizedRuntimePolicyErrorMode(value) {
|
|
6261
|
+
const normalized = String(value ?? "fail").trim().toLowerCase();
|
|
6262
|
+
if (normalized === "fail") {
|
|
6263
|
+
return "fail";
|
|
6264
|
+
}
|
|
6265
|
+
if (normalized === "continue") {
|
|
6266
|
+
return "continue";
|
|
6267
|
+
}
|
|
6268
|
+
throw new Error("Runtime policy error mode must be either Fail or Continue.");
|
|
6269
|
+
}
|
|
6174
6270
|
function extractContextOverridesFromConfig(config) {
|
|
6175
6271
|
const rootConfig = asRecord(config);
|
|
6176
6272
|
const loadStrikeSection = asRecord(tryReadConfigValue(rootConfig, "LoadStrike"));
|
|
@@ -6224,6 +6320,7 @@ function extractContextOverridesFromConfig(config) {
|
|
|
6224
6320
|
setString("AgentGroup", "AgentGroup", "LoadStrike:AgentGroup");
|
|
6225
6321
|
setString("NatsServerUrl", "NatsServerUrl", "LoadStrike:NatsServerUrl");
|
|
6226
6322
|
setString("RunnerKey", "RunnerKey", "LoadStrike:RunnerKey");
|
|
6323
|
+
setString("RuntimePolicyErrorMode", "RuntimePolicyErrorMode", "LoadStrike:RuntimePolicyErrorMode");
|
|
6227
6324
|
const nodeType = pick("NodeType", "LoadStrike:NodeType");
|
|
6228
6325
|
if (nodeType != null) {
|
|
6229
6326
|
const parsed = tryParseNodeTypeToken(nodeType);
|
|
@@ -6238,7 +6335,6 @@ function extractContextOverridesFromConfig(config) {
|
|
|
6238
6335
|
patch.MinimumLogLevel = parsed;
|
|
6239
6336
|
}
|
|
6240
6337
|
}
|
|
6241
|
-
setString("LicenseValidationServerUrl", "LicenseValidationServerUrl", "LoadStrike:LicenseValidation:ServerUrl", "LicenseValidation:ServerUrl");
|
|
6242
6338
|
const agentsCount = toInt(pick("AgentsCount", "LoadStrike:AgentsCount"));
|
|
6243
6339
|
if (agentsCount > 0) {
|
|
6244
6340
|
patch.AgentsCount = agentsCount;
|
|
@@ -6373,7 +6469,6 @@ function toRunContext(options) {
|
|
|
6373
6469
|
CoordinatorTargetScenarios: normalized.coordinatorTargetScenarios,
|
|
6374
6470
|
NatsServerUrl: normalized.natsServerUrl,
|
|
6375
6471
|
RunnerKey: normalized.runnerKey,
|
|
6376
|
-
LicenseValidationServerUrl: normalized.licenseValidationServerUrl,
|
|
6377
6472
|
LicenseValidationTimeoutSeconds: normalized.licenseValidationTimeoutSeconds,
|
|
6378
6473
|
ConfigPath: normalized.configPath,
|
|
6379
6474
|
InfraConfigPath: normalized.infraConfigPath,
|
|
@@ -6386,8 +6481,6 @@ function toRunContext(options) {
|
|
|
6386
6481
|
ReportFileName: normalized.reportFileName,
|
|
6387
6482
|
ReportFolderPath: normalized.reportFolderPath,
|
|
6388
6483
|
ReportFormats: normalized.reportFormats,
|
|
6389
|
-
ReportFinalizer: normalized.reportFinalizer,
|
|
6390
|
-
DetailedReportFinalizer: normalized.detailedReportFinalizer,
|
|
6391
6484
|
ReportingIntervalSeconds: normalized.reportingIntervalSeconds,
|
|
6392
6485
|
MinimumLogLevel: normalized.minimumLogLevel,
|
|
6393
6486
|
LoggerConfig: normalized.loggerConfig,
|
|
@@ -6395,6 +6488,7 @@ function toRunContext(options) {
|
|
|
6395
6488
|
SinkRetryCount: normalized.sinkRetryCount,
|
|
6396
6489
|
SinkRetryBackoffMs: normalized.sinkRetryBackoffMs,
|
|
6397
6490
|
RuntimePolicies: normalized.runtimePolicies,
|
|
6491
|
+
RuntimePolicyErrorMode: normalized.runtimePolicyErrorMode,
|
|
6398
6492
|
ScenarioCompletionTimeoutSeconds: normalized.scenarioCompletionTimeoutSeconds,
|
|
6399
6493
|
ClusterCommandTimeoutSeconds: normalized.clusterCommandTimeoutSeconds,
|
|
6400
6494
|
RestartIterationMaxAttempts: normalized.restartIterationMaxAttempts,
|
|
@@ -6403,6 +6497,26 @@ function toRunContext(options) {
|
|
|
6403
6497
|
GlobalCustomSettings: normalized.globalCustomSettings
|
|
6404
6498
|
};
|
|
6405
6499
|
}
|
|
6500
|
+
class RuntimePolicyCallbackError extends Error {
|
|
6501
|
+
constructor(message) {
|
|
6502
|
+
super(message);
|
|
6503
|
+
this.name = "RuntimePolicyCallbackError";
|
|
6504
|
+
}
|
|
6505
|
+
}
|
|
6506
|
+
function resolveRuntimePolicyName(policy) {
|
|
6507
|
+
const namedPolicy = policy.policyName ?? policy.PolicyName;
|
|
6508
|
+
if (typeof namedPolicy === "string" && namedPolicy.trim()) {
|
|
6509
|
+
return namedPolicy;
|
|
6510
|
+
}
|
|
6511
|
+
const constructorValue = Reflect.get(policy, "constructor");
|
|
6512
|
+
const constructorName = constructorValue && typeof constructorValue === "object"
|
|
6513
|
+
? Reflect.get(constructorValue, "name")
|
|
6514
|
+
: typeof constructorValue === "function"
|
|
6515
|
+
? Reflect.get(constructorValue, "name")
|
|
6516
|
+
: "";
|
|
6517
|
+
const fallback = typeof constructorName === "string" ? constructorName.trim() : "";
|
|
6518
|
+
return fallback || "runtime-policy";
|
|
6519
|
+
}
|
|
6406
6520
|
function buildLicenseValidationPayload(options, scenarios) {
|
|
6407
6521
|
const context = {
|
|
6408
6522
|
...toRunContext(options)
|
|
@@ -6416,8 +6530,7 @@ function buildLicenseValidationPayload(options, scenarios) {
|
|
|
6416
6530
|
Weight: scenario.getWeight(),
|
|
6417
6531
|
LoadSimulations: [...scenario.getSimulations()],
|
|
6418
6532
|
Thresholds: [...scenario.getThresholds()],
|
|
6419
|
-
Tracking: scenario.getTrackingConfiguration() ?? {}
|
|
6420
|
-
LicenseFeatures: scenario.getLicenseFeatures()
|
|
6533
|
+
Tracking: scenario.getTrackingConfiguration() ?? {}
|
|
6421
6534
|
}));
|
|
6422
6535
|
return {
|
|
6423
6536
|
Context: context,
|
|
@@ -6466,7 +6579,6 @@ function looksLikeRunContext(value) {
|
|
|
6466
6579
|
"ClusterId",
|
|
6467
6580
|
"CoordinatorTargetScenarios",
|
|
6468
6581
|
"RunnerKey",
|
|
6469
|
-
"LicenseValidationServerUrl",
|
|
6470
6582
|
"LicenseValidationTimeoutSeconds",
|
|
6471
6583
|
"MinimumLogLevel",
|
|
6472
6584
|
"LoggerConfig",
|
|
@@ -6476,8 +6588,6 @@ function looksLikeRunContext(value) {
|
|
|
6476
6588
|
"ReportFileName",
|
|
6477
6589
|
"ReportFolderPath",
|
|
6478
6590
|
"ReportFormats",
|
|
6479
|
-
"ReportFinalizer",
|
|
6480
|
-
"DetailedReportFinalizer",
|
|
6481
6591
|
"ReportingIntervalSeconds",
|
|
6482
6592
|
"ReportingSinks",
|
|
6483
6593
|
"SinkRetryCount",
|
|
@@ -6614,12 +6724,6 @@ function resolveSinkSaveRealtimeMetrics(sink) {
|
|
|
6614
6724
|
? method.bind(sink)
|
|
6615
6725
|
: undefined;
|
|
6616
6726
|
}
|
|
6617
|
-
function resolveSinkSaveFinalStats(sink) {
|
|
6618
|
-
const method = sink.saveFinalStats ?? sink.SaveFinalStats;
|
|
6619
|
-
return typeof method === "function"
|
|
6620
|
-
? method.bind(sink)
|
|
6621
|
-
: undefined;
|
|
6622
|
-
}
|
|
6623
6727
|
function resolveSinkSaveRunResult(sink) {
|
|
6624
6728
|
const method = sink.saveRunResult ?? sink.SaveRunResult;
|
|
6625
6729
|
return typeof method === "function"
|
|
@@ -6658,6 +6762,46 @@ function addCorrelationRow(row) {
|
|
|
6658
6762
|
correlationRows.splice(0, correlationRows.length - MAX_CORRELATION_ROWS);
|
|
6659
6763
|
}
|
|
6660
6764
|
}
|
|
6765
|
+
function buildDetailedFailedCorrelationRows() {
|
|
6766
|
+
return [...failedResponseRows]
|
|
6767
|
+
.reverse()
|
|
6768
|
+
.map((row) => ({
|
|
6769
|
+
OccurredUtc: row.occurredUtc,
|
|
6770
|
+
Scenario: row.scenarioName,
|
|
6771
|
+
Source: row.sourceEndpoint,
|
|
6772
|
+
Destination: row.destinationEndpoint,
|
|
6773
|
+
RunMode: row.runMode,
|
|
6774
|
+
StatusCode: row.statusCode,
|
|
6775
|
+
TrackingId: row.trackingId ?? "",
|
|
6776
|
+
EventId: row.eventId ?? "",
|
|
6777
|
+
SourceTimestampUtc: row.sourceTimestampUtc ?? "",
|
|
6778
|
+
DestinationTimestampUtc: row.destinationTimestampUtc ?? "",
|
|
6779
|
+
LatencyMs: formatOptionalLatency(row.latencyMs),
|
|
6780
|
+
Message: row.message ?? ""
|
|
6781
|
+
}));
|
|
6782
|
+
}
|
|
6783
|
+
function buildDetailedCorrelationRows() {
|
|
6784
|
+
return [...correlationRows]
|
|
6785
|
+
.reverse()
|
|
6786
|
+
.map((row) => ({
|
|
6787
|
+
OccurredUtc: row.occurredUtc,
|
|
6788
|
+
Scenario: row.scenarioName,
|
|
6789
|
+
Source: row.sourceEndpoint,
|
|
6790
|
+
Destination: row.destinationEndpoint,
|
|
6791
|
+
RunMode: row.runMode,
|
|
6792
|
+
StatusCode: row.statusCode,
|
|
6793
|
+
IsSuccess: row.isSuccess,
|
|
6794
|
+
IsFailure: row.isFailure,
|
|
6795
|
+
GatherByField: row.gatherByField ?? "",
|
|
6796
|
+
GatherByValue: row.gatherByValue ?? "",
|
|
6797
|
+
TrackingId: row.trackingId ?? "",
|
|
6798
|
+
EventId: row.eventId ?? "",
|
|
6799
|
+
SourceTimestampUtc: row.sourceTimestampUtc ?? "",
|
|
6800
|
+
DestinationTimestampUtc: row.destinationTimestampUtc ?? "",
|
|
6801
|
+
LatencyMs: formatOptionalLatency(row.latencyMs),
|
|
6802
|
+
Message: row.message ?? ""
|
|
6803
|
+
}));
|
|
6804
|
+
}
|
|
6661
6805
|
function resolveWorkerPlugins(customPlugins) {
|
|
6662
6806
|
const registry = new Map();
|
|
6663
6807
|
for (const plugin of createBuiltInWorkerPlugins()) {
|
|
@@ -6990,3 +7134,122 @@ function readConfiguredSinkName(sink) {
|
|
|
6990
7134
|
async function sleep(ms) {
|
|
6991
7135
|
await new Promise((resolve) => setTimeout(resolve, Math.max(ms, 0)));
|
|
6992
7136
|
}
|
|
7137
|
+
export const __loadstrikeTestExports = {
|
|
7138
|
+
LoadStrikeContext,
|
|
7139
|
+
LoadStrikeCounter,
|
|
7140
|
+
LoadStrikeGauge,
|
|
7141
|
+
LoadStrikeMetric,
|
|
7142
|
+
LoadStrikeResponse,
|
|
7143
|
+
LoadStrikeRunner,
|
|
7144
|
+
LoadStrikeScenario,
|
|
7145
|
+
LoadStrikeSimulation,
|
|
7146
|
+
LoadStrikeStep,
|
|
7147
|
+
ManagedScenarioTrackingRuntime,
|
|
7148
|
+
ScenarioStatsAccumulator,
|
|
7149
|
+
StepStatsAccumulator,
|
|
7150
|
+
TrackingFieldSelector,
|
|
7151
|
+
addCorrelationRow,
|
|
7152
|
+
addFailedResponseRow,
|
|
7153
|
+
aggregateNodeStats,
|
|
7154
|
+
asRecord,
|
|
7155
|
+
assertNoDisableLicenseEnforcementOption,
|
|
7156
|
+
buildEmptyNodeStats,
|
|
7157
|
+
buildGroupedCorrelationRows,
|
|
7158
|
+
buildMeasurementPlaceholder,
|
|
7159
|
+
buildRichHtmlReport,
|
|
7160
|
+
buildThresholdCheckExpression,
|
|
7161
|
+
buildTrackingLeaseKey,
|
|
7162
|
+
buildTrackingRunNamespace,
|
|
7163
|
+
clusterNodeResultToNodeStats,
|
|
7164
|
+
combineAbortSignals,
|
|
7165
|
+
computeScenarioRequestCount,
|
|
7166
|
+
computeWarmUpIterations,
|
|
7167
|
+
createBuiltInWorkerPlugins,
|
|
7168
|
+
createLogger,
|
|
7169
|
+
createRuntimeRandom,
|
|
7170
|
+
delayWithAbort,
|
|
7171
|
+
detailedToNodeStats,
|
|
7172
|
+
evaluateThresholdsForScenarios,
|
|
7173
|
+
executeTrackedScenarioInvocation,
|
|
7174
|
+
extractContextOverridesFromArgs,
|
|
7175
|
+
extractContextOverridesFromConfig,
|
|
7176
|
+
findCaseInsensitiveKey,
|
|
7177
|
+
formatOptionalLatency,
|
|
7178
|
+
formatUtcReportTimestamp,
|
|
7179
|
+
hasPluginRows,
|
|
7180
|
+
inferRuntimeLegacyHttpResponseSource,
|
|
7181
|
+
isComparisonFailed,
|
|
7182
|
+
loadJsonObject,
|
|
7183
|
+
logLevelOrder,
|
|
7184
|
+
looksLikeRunContext,
|
|
7185
|
+
mapRuntimeCorrelationStore,
|
|
7186
|
+
mapRuntimeTrackingEndpointSpec,
|
|
7187
|
+
mergeDefinedRecord,
|
|
7188
|
+
normalizeCliOverrideKey,
|
|
7189
|
+
normalizeMetricValue,
|
|
7190
|
+
normalizeOptionalReportFormats,
|
|
7191
|
+
normalizeOptionalStringArray,
|
|
7192
|
+
normalizeReplyArguments,
|
|
7193
|
+
normalizePluginData,
|
|
7194
|
+
normalizeReportFormats,
|
|
7195
|
+
normalizeRunArgsInput,
|
|
7196
|
+
normalizeRunContextCollectionShapes,
|
|
7197
|
+
normalizeRunnerOptionCollectionShapes,
|
|
7198
|
+
normalizeRuntimeHttpTrackingPayloadSource,
|
|
7199
|
+
normalizeRuntimeTrackingPayload,
|
|
7200
|
+
normalizeStringArray,
|
|
7201
|
+
normalizeFailureArguments,
|
|
7202
|
+
normalizeTrackingRunMode,
|
|
7203
|
+
normalizeThresholdScope,
|
|
7204
|
+
parseAliasDate,
|
|
7205
|
+
parseMinimumLogLevelToken,
|
|
7206
|
+
parseNodeTypeToken,
|
|
7207
|
+
parseStrictBooleanToken,
|
|
7208
|
+
percentile,
|
|
7209
|
+
pickOptionalTrackingSelectorString,
|
|
7210
|
+
pickTrackingNumber,
|
|
7211
|
+
produceOrConsumeTrackingPayload,
|
|
7212
|
+
readConfiguredSinkName,
|
|
7213
|
+
readRuntimeRedisCorrelationStoreOptions,
|
|
7214
|
+
readRuntimeTrackingId,
|
|
7215
|
+
recordPluginLifecycleError,
|
|
7216
|
+
requireNonEmpty,
|
|
7217
|
+
attachScenarioStatsAliases,
|
|
7218
|
+
attachNodeStatsAliases,
|
|
7219
|
+
attachRunResultAliases,
|
|
7220
|
+
resolveClusterExecutionMode,
|
|
7221
|
+
resolveThresholdActualValue,
|
|
7222
|
+
resetCrossPlatformReportRegistries,
|
|
7223
|
+
resolveSinkDispose,
|
|
7224
|
+
resolveSinkInit,
|
|
7225
|
+
resolveSinkName,
|
|
7226
|
+
resolveSinkSaveRealtimeMetrics,
|
|
7227
|
+
resolveSinkSaveRealtimeStats,
|
|
7228
|
+
resolveSinkSaveRunResult,
|
|
7229
|
+
resolveSinkStart,
|
|
7230
|
+
resolveSinkStop,
|
|
7231
|
+
resolveWorkerPlugins,
|
|
7232
|
+
runtimeParseBodyAsObject,
|
|
7233
|
+
sanitizeReportFileName,
|
|
7234
|
+
sanitizeTrackingNamespacePart,
|
|
7235
|
+
setRuntimeJsonPathValue,
|
|
7236
|
+
stripCaseInsensitivePrefix,
|
|
7237
|
+
toBoolean,
|
|
7238
|
+
toDetailedRunResultFromNodeStats,
|
|
7239
|
+
toInt,
|
|
7240
|
+
toNullableInt,
|
|
7241
|
+
toNumber,
|
|
7242
|
+
toRunContext,
|
|
7243
|
+
toTrackingStringMap,
|
|
7244
|
+
tryParseDotnetDurationMs,
|
|
7245
|
+
tryParseMinimumLogLevelToken,
|
|
7246
|
+
tryParseNodeTypeToken,
|
|
7247
|
+
tryReadConfigValue,
|
|
7248
|
+
validateNamedReportingSinks,
|
|
7249
|
+
validateRegisteredScenarios,
|
|
7250
|
+
validateRuntimeRedisCorrelationStoreConfiguration,
|
|
7251
|
+
validateRuntimeTrackingConfiguration,
|
|
7252
|
+
validateScenarioNames,
|
|
7253
|
+
waitForTrackingOutcome,
|
|
7254
|
+
withLifecycleErrors
|
|
7255
|
+
};
|