@testsmith/perfornium 0.6.4 → 0.6.6

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.
Files changed (194) hide show
  1. package/dist/cli/cli.js +16 -1
  2. package/dist/cli/commands/distributed.js +2 -2
  3. package/dist/cli/commands/report.js +2 -2
  4. package/dist/cli/commands/run.js +2 -0
  5. package/dist/config/parser.js +2 -2
  6. package/dist/config/types/global-config.d.ts +82 -2
  7. package/dist/config/types/scenario-config.d.ts +2 -2
  8. package/dist/config/types/step-types.d.ts +1 -1
  9. package/dist/core/data/data-manager.d.ts +70 -0
  10. package/dist/core/data/data-manager.js +186 -0
  11. package/dist/core/data/data-provider.d.ts +85 -0
  12. package/dist/core/data/data-provider.js +468 -0
  13. package/dist/core/data/index.d.ts +8 -0
  14. package/dist/core/data/index.js +13 -0
  15. package/dist/core/execution/check-evaluator.d.ts +10 -0
  16. package/dist/core/execution/check-evaluator.js +79 -0
  17. package/dist/core/execution/data-extractor.d.ts +6 -0
  18. package/dist/core/execution/data-extractor.js +70 -0
  19. package/dist/core/execution/index.d.ts +3 -0
  20. package/dist/core/execution/index.js +9 -0
  21. package/dist/core/execution/json-payload-processor.d.ts +7 -0
  22. package/dist/core/execution/json-payload-processor.js +140 -0
  23. package/dist/core/factories/index.d.ts +2 -0
  24. package/dist/core/factories/index.js +7 -0
  25. package/dist/core/factories/output-handler-factory.d.ts +10 -0
  26. package/dist/core/factories/output-handler-factory.js +91 -0
  27. package/dist/core/factories/protocol-handler-factory.d.ts +12 -0
  28. package/dist/core/factories/protocol-handler-factory.js +96 -0
  29. package/dist/core/index.d.ts +3 -2
  30. package/dist/core/index.js +8 -3
  31. package/dist/core/reporting/dashboard-reporter.d.ts +17 -0
  32. package/dist/core/reporting/dashboard-reporter.js +127 -0
  33. package/dist/core/reporting/index.d.ts +1 -0
  34. package/dist/core/reporting/index.js +5 -0
  35. package/dist/core/step-executor.d.ts +6 -20
  36. package/dist/core/step-executor.js +72 -366
  37. package/dist/core/strategies/index.d.ts +2 -0
  38. package/dist/core/strategies/index.js +7 -0
  39. package/dist/core/strategies/scenario-selector.d.ts +13 -0
  40. package/dist/core/strategies/scenario-selector.js +37 -0
  41. package/dist/core/strategies/think-time-strategy.d.ts +15 -0
  42. package/dist/core/strategies/think-time-strategy.js +71 -0
  43. package/dist/core/test-runner.d.ts +4 -11
  44. package/dist/core/test-runner.js +105 -312
  45. package/dist/core/virtual-user.d.ts +7 -37
  46. package/dist/core/virtual-user.js +29 -269
  47. package/dist/dashboard/routes/api.d.ts +64 -0
  48. package/dist/dashboard/routes/api.js +569 -0
  49. package/dist/dashboard/routes/index.d.ts +2 -0
  50. package/dist/dashboard/routes/index.js +7 -0
  51. package/dist/dashboard/routes/static.d.ts +6 -0
  52. package/dist/dashboard/routes/static.js +76 -0
  53. package/dist/dashboard/server.d.ts +8 -84
  54. package/dist/dashboard/server.js +76 -2007
  55. package/dist/dashboard/services/file-scanner.d.ts +7 -0
  56. package/dist/dashboard/services/file-scanner.js +114 -0
  57. package/dist/dashboard/services/index.d.ts +5 -0
  58. package/dist/dashboard/services/index.js +13 -0
  59. package/dist/dashboard/services/influxdb-service.d.ts +41 -0
  60. package/dist/dashboard/services/influxdb-service.js +329 -0
  61. package/dist/dashboard/services/metrics-parser.d.ts +12 -0
  62. package/dist/dashboard/services/metrics-parser.js +209 -0
  63. package/dist/dashboard/services/results-manager.d.ts +17 -0
  64. package/dist/dashboard/services/results-manager.js +311 -0
  65. package/dist/dashboard/services/test-executor.d.ts +41 -0
  66. package/dist/dashboard/services/test-executor.js +250 -0
  67. package/dist/dashboard/services/workers-manager.d.ts +13 -0
  68. package/dist/dashboard/services/workers-manager.js +81 -0
  69. package/dist/dashboard/templates/index.html +122 -0
  70. package/dist/dashboard/templates/scripts/main.js +3280 -0
  71. package/dist/dashboard/templates/styles.css +402 -0
  72. package/dist/dashboard/types.d.ts +168 -0
  73. package/dist/dashboard/types.js +2 -0
  74. package/dist/distributed/result-aggregator.js +1 -3
  75. package/dist/metrics/batch/batch-processor.d.ts +27 -0
  76. package/dist/metrics/batch/batch-processor.js +85 -0
  77. package/dist/metrics/batch/index.d.ts +1 -0
  78. package/dist/metrics/batch/index.js +5 -0
  79. package/dist/metrics/collector.d.ts +46 -45
  80. package/dist/metrics/collector.js +179 -640
  81. package/dist/metrics/core/error-tracker.d.ts +9 -0
  82. package/dist/metrics/core/error-tracker.js +52 -0
  83. package/dist/metrics/core/index.d.ts +3 -0
  84. package/dist/metrics/core/index.js +9 -0
  85. package/dist/metrics/core/result-storage.d.ts +19 -0
  86. package/dist/metrics/core/result-storage.js +56 -0
  87. package/dist/metrics/core/statistics-engine.d.ts +27 -0
  88. package/dist/metrics/core/statistics-engine.js +91 -0
  89. package/dist/metrics/output/file-writer.d.ts +19 -0
  90. package/dist/metrics/output/file-writer.js +129 -0
  91. package/dist/metrics/output/index.d.ts +2 -0
  92. package/dist/metrics/output/index.js +10 -0
  93. package/dist/metrics/output/influxdb-writer.d.ts +89 -0
  94. package/dist/metrics/output/influxdb-writer.js +404 -0
  95. package/dist/metrics/realtime/dispatcher.d.ts +18 -0
  96. package/dist/metrics/realtime/dispatcher.js +45 -0
  97. package/dist/metrics/realtime/endpoints/graphite.d.ts +3 -0
  98. package/dist/metrics/realtime/endpoints/graphite.js +61 -0
  99. package/dist/metrics/realtime/endpoints/influxdb.d.ts +3 -0
  100. package/dist/metrics/realtime/endpoints/influxdb.js +35 -0
  101. package/dist/metrics/realtime/endpoints/webhook.d.ts +3 -0
  102. package/dist/metrics/realtime/endpoints/webhook.js +22 -0
  103. package/dist/metrics/realtime/endpoints/websocket.d.ts +3 -0
  104. package/dist/metrics/realtime/endpoints/websocket.js +25 -0
  105. package/dist/metrics/realtime/index.d.ts +5 -0
  106. package/dist/metrics/realtime/index.js +13 -0
  107. package/dist/metrics/reporting/index.d.ts +3 -0
  108. package/dist/metrics/reporting/index.js +9 -0
  109. package/dist/metrics/reporting/step-statistics.d.ts +6 -0
  110. package/dist/metrics/reporting/step-statistics.js +59 -0
  111. package/dist/metrics/reporting/summary-generator.d.ts +16 -0
  112. package/dist/metrics/reporting/summary-generator.js +46 -0
  113. package/dist/metrics/reporting/timeline-calculator.d.ts +7 -0
  114. package/dist/metrics/reporting/timeline-calculator.js +86 -0
  115. package/dist/metrics/types.d.ts +58 -0
  116. package/dist/outputs/csv.d.ts +2 -0
  117. package/dist/outputs/csv.js +21 -2
  118. package/dist/outputs/json.js +6 -2
  119. package/dist/protocols/rest/handler.d.ts +4 -53
  120. package/dist/protocols/rest/handler.js +73 -454
  121. package/dist/protocols/rest/request/auth-handler.d.ts +4 -0
  122. package/dist/protocols/rest/request/auth-handler.js +30 -0
  123. package/dist/protocols/rest/request/body-processor.d.ts +11 -0
  124. package/dist/protocols/rest/request/body-processor.js +62 -0
  125. package/dist/protocols/rest/request/index.d.ts +2 -0
  126. package/dist/protocols/rest/request/index.js +7 -0
  127. package/dist/protocols/rest/response/checks.d.ts +6 -0
  128. package/dist/protocols/rest/response/checks.js +71 -0
  129. package/dist/protocols/rest/response/index.d.ts +2 -0
  130. package/dist/protocols/rest/response/index.js +7 -0
  131. package/dist/protocols/rest/response/size-calculator.d.ts +12 -0
  132. package/dist/protocols/rest/response/size-calculator.js +64 -0
  133. package/dist/protocols/web/browser/highlight.d.ts +7 -0
  134. package/dist/protocols/web/browser/highlight.js +47 -0
  135. package/dist/protocols/web/browser/index.d.ts +4 -0
  136. package/dist/protocols/web/browser/index.js +11 -0
  137. package/dist/protocols/web/browser/manager.d.ts +20 -0
  138. package/dist/protocols/web/browser/manager.js +189 -0
  139. package/dist/protocols/web/browser/screenshot.d.ts +8 -0
  140. package/dist/protocols/web/browser/screenshot.js +69 -0
  141. package/dist/protocols/web/browser/storage.d.ts +5 -0
  142. package/dist/protocols/web/browser/storage.js +45 -0
  143. package/dist/protocols/web/commands/index.d.ts +5 -0
  144. package/dist/protocols/web/commands/index.js +11 -0
  145. package/dist/protocols/web/commands/interaction.d.ts +13 -0
  146. package/dist/protocols/web/commands/interaction.js +68 -0
  147. package/dist/protocols/web/commands/measurement.d.ts +16 -0
  148. package/dist/protocols/web/commands/measurement.js +33 -0
  149. package/dist/protocols/web/commands/navigation.d.ts +11 -0
  150. package/dist/protocols/web/commands/navigation.js +43 -0
  151. package/dist/protocols/web/commands/types.d.ts +12 -0
  152. package/dist/protocols/web/commands/types.js +2 -0
  153. package/dist/protocols/web/commands/verification.d.ts +12 -0
  154. package/dist/protocols/web/commands/verification.js +118 -0
  155. package/dist/protocols/web/handler.d.ts +19 -30
  156. package/dist/protocols/web/handler.js +164 -651
  157. package/dist/protocols/web/network/capture.d.ts +19 -0
  158. package/dist/protocols/web/network/capture.js +225 -0
  159. package/dist/protocols/web/network/filters.d.ts +5 -0
  160. package/dist/protocols/web/network/filters.js +49 -0
  161. package/dist/protocols/web/network/index.d.ts +4 -0
  162. package/dist/protocols/web/network/index.js +9 -0
  163. package/dist/protocols/web/network/types.d.ts +13 -0
  164. package/dist/protocols/web/network/types.js +2 -0
  165. package/dist/protocols/web/network/utils.d.ts +8 -0
  166. package/dist/protocols/web/network/utils.js +29 -0
  167. package/dist/recorder/continue-recorder.d.ts +11 -0
  168. package/dist/recorder/continue-recorder.js +872 -0
  169. package/dist/reporting/chart-data/index.d.ts +5 -0
  170. package/dist/reporting/chart-data/index.js +13 -0
  171. package/dist/reporting/chart-data/network.d.ts +25 -0
  172. package/dist/reporting/chart-data/network.js +78 -0
  173. package/dist/reporting/chart-data/scenario.d.ts +37 -0
  174. package/dist/reporting/chart-data/scenario.js +76 -0
  175. package/dist/reporting/chart-data/step-statistics.d.ts +24 -0
  176. package/dist/reporting/chart-data/step-statistics.js +94 -0
  177. package/dist/reporting/chart-data/throughput.d.ts +16 -0
  178. package/dist/reporting/chart-data/throughput.js +24 -0
  179. package/dist/reporting/chart-data/timeline.d.ts +17 -0
  180. package/dist/reporting/chart-data/timeline.js +46 -0
  181. package/dist/reporting/handlebars-helpers.d.ts +1 -0
  182. package/dist/reporting/handlebars-helpers.js +63 -0
  183. package/dist/reporting/{enhanced-html-generator.d.ts → html-generator.d.ts} +1 -1
  184. package/dist/reporting/{enhanced-html-generator.js → html-generator.js} +10 -7
  185. package/dist/reporting/templates/{enhanced-report.hbs → report.hbs} +9 -9
  186. package/dist/utils/data-utils.d.ts +17 -0
  187. package/dist/utils/data-utils.js +129 -0
  188. package/dist/utils/template.js +2 -2
  189. package/package.json +5 -2
  190. package/dist/core/csv-data-provider.d.ts +0 -47
  191. package/dist/core/csv-data-provider.js +0 -265
  192. package/dist/reporting/generator.d.ts +0 -42
  193. package/dist/reporting/generator.js +0 -1217
  194. package/dist/reporting/templates/html.hbs +0 -2453
@@ -2,147 +2,55 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VirtualUser = void 0;
4
4
  const step_executor_1 = require("./step-executor");
5
- const csv_data_provider_1 = require("./csv-data-provider");
6
5
  const hooks_manager_1 = require("./hooks-manager");
7
- const time_1 = require("../utils/time");
8
6
  const logger_1 = require("../utils/logger");
7
+ const data_1 = require("./data");
8
+ const strategies_1 = require("./strategies");
9
9
  class VirtualUser {
10
10
  constructor(id, metrics, handlers, testName = 'Load Test', vuHooks, globalThinkTime, globalCSV) {
11
11
  this.isActive = true;
12
12
  this.scenarios = [];
13
- this.csvProviders = new Map();
14
13
  logger_1.logger.debug(`VirtualUser ${id} created`);
15
14
  this.id = id;
16
15
  this.metrics = metrics;
17
16
  this.handlers = handlers;
18
17
  this.testName = testName;
19
- this.globalThinkTime = globalThinkTime; // Store global think time
20
- this.stepExecutor = new step_executor_1.StepExecutor(handlers, testName); // Pass testName to StepExecutor
18
+ this.stepExecutor = new step_executor_1.StepExecutor(handlers, testName);
21
19
  this.vuHooksManager = new hooks_manager_1.VUHooksManager(testName, id, vuHooks);
22
- // Initialize global CSV if configured
23
- if (globalCSV?.config) {
24
- this.globalCSVMode = globalCSV.mode || 'next';
25
- this.globalCSVProvider = csv_data_provider_1.CSVDataProvider.getInstance(globalCSV.config);
26
- logger_1.logger.debug(`VU${id}: Global CSV configured (mode: ${this.globalCSVMode})`);
27
- }
20
+ // Initialize managers and strategies
21
+ this.dataManager = new data_1.DataManager(id, globalCSV);
22
+ this.thinkTimeStrategy = new strategies_1.ThinkTimeStrategy(globalThinkTime);
23
+ this.scenarioSelector = new strategies_1.ScenarioSelector();
28
24
  this.context = {
29
25
  vu_id: id,
30
26
  iteration: 0,
31
27
  variables: {},
32
28
  extracted_data: {}
33
- // csv_data and global_csv_data will be added only when needed
34
29
  };
35
30
  }
36
- // FIXED: Now async to support CSV initialization
37
31
  async setScenarios(scenarios) {
38
32
  logger_1.logger.debug(`VU${this.id}: setScenarios called with ${scenarios.length} scenarios`);
39
33
  this.scenarios = scenarios;
40
- // Debug: Let's see what the scenarios look like
41
34
  for (const scenario of scenarios) {
42
35
  logger_1.logger.debug(`VU${this.id}: Scenario "${scenario.name}" config: ${JSON.stringify(scenario, null, 2)}`);
43
36
  }
44
- // Initialize CSV providers only if needed
45
- await this.initializeCSVProvidersIfNeeded();
37
+ await this.dataManager.initializeForScenarios(scenarios);
46
38
  logger_1.logger.debug(`VU${this.id}: setScenarios completed`);
47
39
  }
48
- /**
49
- * Initialize CSV providers only for scenarios that need them
50
- */
51
- async initializeCSVProvidersIfNeeded() {
52
- const csvScenarios = this.scenarios.filter(s => s.csv_data);
53
- if (csvScenarios.length === 0) {
54
- // No CSV scenarios - skip initialization entirely
55
- logger_1.logger.debug(`VU${this.id}: No CSV scenarios found, skipping CSV initialization`);
56
- return;
57
- }
58
- logger_1.logger.debug(`VU${this.id}: Found ${csvScenarios.length} scenarios with CSV data`);
59
- for (const scenario of csvScenarios) {
60
- const csvScenario = scenario; // Cast to access CSV properties
61
- logger_1.logger.debug(`VU${this.id}: Processing CSV for scenario "${scenario.name}": ${JSON.stringify(csvScenario.csv_data)}`);
62
- if (csvScenario.csv_data) {
63
- try {
64
- const provider = csv_data_provider_1.CSVDataProvider.getInstance(csvScenario.csv_data);
65
- await provider.loadData();
66
- this.csvProviders.set(scenario.name, provider);
67
- logger_1.logger.debug(`VU${this.id}: Initialized CSV provider for scenario "${scenario.name}"`);
68
- }
69
- catch (error) {
70
- logger_1.logger.warn(`VU${this.id}: Failed to initialize CSV for scenario "${scenario.name}":`, error);
71
- // Don't fail the entire VU - just log the warning and continue
72
- }
73
- }
74
- }
75
- logger_1.logger.debug(`VU${this.id}: CSV initialization completed. Providers: ${this.csvProviders.size}`);
76
- }
77
- /**
78
- * Load global CSV data and merge into context variables
79
- * Called once at the start of each VU execution cycle
80
- */
81
- async loadGlobalCSVData() {
82
- if (!this.globalCSVProvider) {
83
- return;
84
- }
85
- try {
86
- await this.globalCSVProvider.loadData();
87
- let csvData = null;
88
- switch (this.globalCSVMode) {
89
- case 'unique':
90
- csvData = await this.globalCSVProvider.getUniqueRow(this.id);
91
- break;
92
- case 'random':
93
- csvData = await this.globalCSVProvider.getRandomRow(this.id);
94
- break;
95
- case 'next':
96
- default:
97
- csvData = await this.globalCSVProvider.getNextRow(this.id);
98
- break;
99
- }
100
- if (csvData) {
101
- this.context.global_csv_data = csvData;
102
- // Merge global CSV data into variables (can be overridden by scenario CSV)
103
- logger_1.logger.debug(`VU${this.id}: Adding global CSV columns to variables: ${Object.keys(csvData).join(', ')}`);
104
- for (const [key, value] of Object.entries(csvData)) {
105
- this.context.variables[key] = value;
106
- logger_1.logger.debug(`VU${this.id}: Set global CSV variable: ${key} = ${value}`);
107
- }
108
- logger_1.logger.debug(`VU ${this.id}: Loaded global CSV data: ${Object.keys(csvData).join(', ')}`);
109
- }
110
- else {
111
- logger_1.logger.debug(`VU${this.id}: Global CSV data exhausted - stopping VU`);
112
- delete this.context.global_csv_data;
113
- await this.stop();
114
- throw new Error(`VU${this.id} terminated due to global CSV data exhaustion`);
115
- }
116
- }
117
- catch (error) {
118
- if (error instanceof Error && error.message.includes('terminated due to global CSV')) {
119
- throw error;
120
- }
121
- logger_1.logger.warn(`📊 VU ${this.id}: Failed to load global CSV data:`, error);
122
- delete this.context.global_csv_data;
123
- }
124
- }
125
- // This method executes scenarios once (called repeatedly by load patterns)
126
40
  async executeScenarios() {
127
41
  if (!this.isActive || this.scenarios.length === 0) {
128
42
  return;
129
43
  }
130
44
  // Load global CSV data first (available to all scenarios)
131
- try {
132
- await this.loadGlobalCSVData();
133
- }
134
- catch (error) {
135
- if (error instanceof Error && error.message.includes('terminated due to global CSV')) {
136
- logger_1.logger.warn(`📊 VU ${this.id}: Stopping due to global CSV exhaustion`);
137
- return;
138
- }
139
- // Log but continue if global CSV fails for other reasons
140
- logger_1.logger.warn(`📊 VU ${this.id}: Global CSV loading failed, continuing without:`, error);
45
+ const globalCsvLoaded = await this.dataManager.loadGlobalData(this.context);
46
+ if (!globalCsvLoaded) {
47
+ logger_1.logger.warn(`📊 VU ${this.id}: Stopping due to global CSV exhaustion`);
48
+ await this.stop();
49
+ return;
141
50
  }
142
51
  // Execute beforeVU hook
143
52
  try {
144
53
  const beforeVUResult = await this.vuHooksManager.executeBeforeVU(this.context.variables, this.context.extracted_data);
145
- // Merge any variables returned by beforeVU hook
146
54
  if (beforeVUResult?.variables) {
147
55
  Object.assign(this.context.variables, beforeVUResult.variables);
148
56
  logger_1.logger.debug(`VU${this.id}: beforeVU hook set variables: ${Object.keys(beforeVUResult.variables).join(', ')}`);
@@ -150,10 +58,9 @@ class VirtualUser {
150
58
  }
151
59
  catch (error) {
152
60
  logger_1.logger.error(`❌ VU ${this.id} beforeVU hook failed:`, error);
153
- // Continue execution even if beforeVU fails
154
61
  }
155
62
  try {
156
- const selectedScenarios = this.selectScenarios(this.scenarios);
63
+ const selectedScenarios = this.scenarioSelector.selectScenarios(this.scenarios);
157
64
  for (const scenario of selectedScenarios) {
158
65
  if (!this.isActive)
159
66
  break;
@@ -167,7 +74,6 @@ class VirtualUser {
167
74
  }
168
75
  }
169
76
  finally {
170
- // Execute teardownVU hook
171
77
  try {
172
78
  await this.vuHooksManager.executeTeardownVU(this.context.variables, this.context.extracted_data);
173
79
  }
@@ -191,7 +97,12 @@ class VirtualUser {
191
97
  }
192
98
  // Load CSV data if this scenario uses it (completely optional)
193
99
  logger_1.logger.debug(`About to load CSV data if needed...`);
194
- await this.loadCSVDataIfNeeded(scenario);
100
+ const csvLoaded = await this.dataManager.loadScenarioData(scenario, this.context);
101
+ if (!csvLoaded) {
102
+ logger_1.logger.debug(`VU${this.id}: CSV data exhausted for scenario "${scenario.name}" - stopping`);
103
+ await this.stop();
104
+ throw new Error(`VU${this.id} terminated due to CSV data exhaustion in scenario "${scenario.name}"`);
105
+ }
195
106
  logger_1.logger.debug(`CSV data loading completed`);
196
107
  logger_1.logger.debug(`Context variables after CSV setup: ${JSON.stringify(this.context.variables)}`);
197
108
  // Create scenario hooks manager
@@ -237,11 +148,8 @@ class VirtualUser {
237
148
  catch (error) {
238
149
  logger_1.logger.error(`❌ VU ${this.id} beforeLoop hook failed:`, error);
239
150
  }
240
- // For unique CSV mode, get new CSV data each iteration
241
- const csvScenario = scenario;
242
- if (csvScenario.csv_mode === 'unique' && iteration > 0 && this.csvProviders.has(scenario.name)) {
243
- await this.loadCSVDataIfNeeded(scenario);
244
- }
151
+ // Start iteration tracking for data manager (handles change_policy)
152
+ this.dataManager.startIteration(iteration);
245
153
  try {
246
154
  // Execute all steps in sequence
247
155
  for (let stepIndex = 0; stepIndex < scenario.steps.length; stepIndex++) {
@@ -258,18 +166,12 @@ class VirtualUser {
258
166
  this.metrics.recordResult(result);
259
167
  }
260
168
  // Apply hierarchical think time: step > scenario > global
261
- // Skip think time if the NEXT step is a verification/wait step - they measure app
262
- // responsiveness and should run immediately after the triggering action
169
+ // Skip think time if the NEXT step is a verification/wait step
263
170
  const nextStep = scenario.steps[stepIndex + 1];
264
- const nextCommand = nextStep?.action?.command || '';
265
- const nextIsVerificationOrWait = nextCommand.startsWith('verify_') ||
266
- nextCommand.startsWith('wait_for_') ||
267
- nextCommand === 'measure_web_vitals' ||
268
- nextCommand === 'performance_audit';
269
- if (!nextIsVerificationOrWait) {
270
- const effectiveThinkTime = this.getEffectiveThinkTime(step, scenario);
171
+ if (!this.thinkTimeStrategy.shouldSkipThinkTime(nextStep)) {
172
+ const effectiveThinkTime = this.thinkTimeStrategy.getEffectiveThinkTime(step, scenario);
271
173
  if (effectiveThinkTime !== undefined) {
272
- await this.applyThinkTime(effectiveThinkTime);
174
+ await this.thinkTimeStrategy.applyThinkTime(effectiveThinkTime);
273
175
  }
274
176
  }
275
177
  }
@@ -296,9 +198,11 @@ class VirtualUser {
296
198
  }
297
199
  throw error;
298
200
  }
201
+ // End iteration tracking (releases checked-out data for unique scope)
202
+ this.dataManager.endIteration(iteration);
299
203
  // Think time between iterations (except after last iteration)
300
204
  if (iteration < loops - 1) {
301
- await this.applyThinkTime(scenario.think_time);
205
+ await this.thinkTimeStrategy.applyThinkTime(scenario.think_time);
302
206
  }
303
207
  }
304
208
  }
@@ -316,97 +220,6 @@ class VirtualUser {
316
220
  }
317
221
  }
318
222
  }
319
- // ... rest of your existing methods remain exactly the same
320
- async loadCSVDataIfNeeded(scenario) {
321
- const csvScenario = scenario;
322
- logger_1.logger.debug(`VU${this.id}: Checking CSV need for scenario "${scenario.name}"`);
323
- logger_1.logger.debug(`VU${this.id}: Has csv_data config: ${!!csvScenario.csv_data}`);
324
- logger_1.logger.debug(`VU${this.id}: Has CSV provider: ${this.csvProviders.has(scenario.name)}`);
325
- if (!csvScenario.csv_data || !this.csvProviders.has(scenario.name)) {
326
- logger_1.logger.debug(`VU${this.id}: No CSV data needed for scenario "${scenario.name}"`);
327
- delete this.context.csv_data;
328
- return;
329
- }
330
- try {
331
- logger_1.logger.debug(`VU${this.id}: Loading CSV data for scenario "${scenario.name}"...`);
332
- const csvData = await this.loadCSVDataForScenario(csvScenario);
333
- if (csvData) {
334
- this.context.csv_data = csvData;
335
- logger_1.logger.debug(`VU${this.id}: Adding CSV columns to variables: ${Object.keys(csvData).join(', ')}`);
336
- for (const [key, value] of Object.entries(csvData)) {
337
- if (!(key in this.context.variables)) {
338
- this.context.variables[key] = value;
339
- logger_1.logger.debug(`VU${this.id}: Added CSV variable: ${key} = ${value}`);
340
- }
341
- else {
342
- logger_1.logger.debug(`VU${this.id}: Skipped CSV variable ${key} (already in variables)`);
343
- }
344
- }
345
- logger_1.logger.debug(`VU ${this.id}: Loaded CSV data for scenario "${scenario.name}": ${Object.keys(csvData).join(', ')}`);
346
- }
347
- else {
348
- logger_1.logger.debug(`VU${this.id}: No CSV data available - terminating this VU`);
349
- delete this.context.csv_data;
350
- this.stop();
351
- throw new Error(`VU${this.id} terminated due to CSV data exhaustion in scenario "${scenario.name}"`);
352
- }
353
- }
354
- catch (error) {
355
- if (error instanceof Error && error.message.includes('terminated due to CSV data exhaustion')) {
356
- throw error;
357
- }
358
- logger_1.logger.warn(`VU ${this.id}: Failed to load CSV data for scenario "${scenario.name}":`, error);
359
- delete this.context.csv_data;
360
- logger_1.logger.debug(`VU${this.id}: Continuing with fallback variables after CSV error`);
361
- }
362
- }
363
- async loadCSVDataForScenario(scenario) {
364
- const provider = this.csvProviders.get(scenario.name);
365
- if (!provider) {
366
- return null;
367
- }
368
- const mode = scenario.csv_mode || 'next';
369
- switch (mode) {
370
- case 'unique':
371
- return await provider.getUniqueRow(this.id);
372
- case 'random':
373
- return await provider.getRandomRow();
374
- case 'next':
375
- default:
376
- return await provider.getNextRow(this.id);
377
- }
378
- }
379
- /**
380
- * Select scenarios based on weights using proportional distribution.
381
- *
382
- * Weights determine the probability of a scenario being selected:
383
- * - scenario1 (weight: 50) + scenario2 (weight: 25) + scenario3 (weight: 25) = 100
384
- * - 50% of VUs will run scenario1, 25% scenario2, 25% scenario3
385
- *
386
- * If weights don't sum to 100, they are normalized proportionally.
387
- */
388
- selectScenarios(scenarios) {
389
- if (scenarios.length === 0) {
390
- return [];
391
- }
392
- if (scenarios.length === 1) {
393
- return scenarios;
394
- }
395
- // Calculate total weight
396
- const totalWeight = scenarios.reduce((sum, s) => sum + (s.weight ?? 100), 0);
397
- // Generate random value between 0 and totalWeight
398
- const random = Math.random() * totalWeight;
399
- // Select scenario based on cumulative weight
400
- let cumulative = 0;
401
- for (const scenario of scenarios) {
402
- cumulative += (scenario.weight ?? 100);
403
- if (random < cumulative) {
404
- return [scenario];
405
- }
406
- }
407
- // Fallback to last scenario (should not reach here)
408
- return [scenarios[scenarios.length - 1]];
409
- }
410
223
  async executeSetup(setupScript) {
411
224
  try {
412
225
  await this.executeScript(setupScript, 'setup');
@@ -435,59 +248,6 @@ class VirtualUser {
435
248
  timeoutPromise
436
249
  ]);
437
250
  }
438
- /**
439
- * Get effective think time using hierarchical override:
440
- * Step think_time > Scenario think_time > Global think_time
441
- */
442
- getEffectiveThinkTime(step, scenario) {
443
- // Step level has highest priority
444
- if (step.think_time !== undefined) {
445
- return step.think_time;
446
- }
447
- // Scenario level is next
448
- if (scenario.think_time !== undefined) {
449
- return scenario.think_time;
450
- }
451
- // Global level is fallback
452
- return this.globalThinkTime;
453
- }
454
- async applyThinkTime(thinkTime) {
455
- if (!thinkTime) {
456
- // No think time specified at any level - skip
457
- return;
458
- }
459
- if (typeof thinkTime === 'number') {
460
- logger_1.logger.debug(`Applying thinktime: ${thinkTime} seconds`);
461
- await (0, time_1.sleep)(thinkTime * 1000);
462
- return;
463
- }
464
- const rangeMatch = thinkTime.match(/^(\d+(?:\.\d+)?)-(\d+(?:\.\d+)?)([sm])?$/);
465
- if (rangeMatch) {
466
- const [, minStr, maxStr, unit] = rangeMatch;
467
- let min = parseFloat(minStr);
468
- let max = parseFloat(maxStr);
469
- if (unit === 's' || !unit) {
470
- min *= 1000;
471
- max *= 1000;
472
- }
473
- const thinkTimeMs = (0, time_1.randomBetween)(min, max);
474
- await (0, time_1.sleep)(thinkTimeMs);
475
- }
476
- else {
477
- try {
478
- const thinkTimeMs = (0, time_1.parseTime)(thinkTime);
479
- await (0, time_1.sleep)(thinkTimeMs);
480
- }
481
- catch (error) {
482
- logger_1.logger.warn(`⚠️ Invalid think time format: ${thinkTime}`);
483
- await (0, time_1.sleep)((0, time_1.randomBetween)(1000, 3000));
484
- }
485
- }
486
- }
487
- // stop(): void {
488
- // this.isActive = false;
489
- // logger.debug(`⏹️ VU ${this.id} stopped`);
490
- // }
491
251
  async stop() {
492
252
  this.isActive = false;
493
253
  logger_1.logger.debug(`⏹️ VU ${this.id} stopping...`);
@@ -0,0 +1,64 @@
1
+ import * as http from 'http';
2
+ import { LiveTest, InfrastructureMetrics } from '../types';
3
+ import { FileScanner } from '../services/file-scanner';
4
+ import { ResultsManager } from '../services/results-manager';
5
+ import { TestExecutor } from '../services/test-executor';
6
+ import { WorkersManager } from '../services/workers-manager';
7
+ import { InfluxDBService } from '../services/influxdb-service';
8
+ export declare class ApiRoutes {
9
+ private fileScanner;
10
+ private resultsManager;
11
+ private testExecutor;
12
+ private workersManager;
13
+ private liveTests;
14
+ private influxService;
15
+ private testMetricsWriter;
16
+ private onInfraUpdate?;
17
+ constructor(fileScanner: FileScanner, resultsManager: ResultsManager, testExecutor: TestExecutor, workersManager: WorkersManager, liveTests: Map<string, LiveTest>, callbacks?: {
18
+ onInfraUpdate?: (data: InfrastructureMetrics) => void;
19
+ }, influxService?: InfluxDBService);
20
+ initialize(): Promise<void>;
21
+ handleGetResults(res: http.ServerResponse): Promise<void>;
22
+ handleGetResult(res: http.ServerResponse, id: string): Promise<void>;
23
+ handleDeleteResult(res: http.ServerResponse, id: string): Promise<void>;
24
+ handleExportResult(res: http.ServerResponse, id: string, url: URL): Promise<void>;
25
+ handleImportResult(req: http.IncomingMessage, res: http.ServerResponse): Promise<void>;
26
+ private resultToCSV;
27
+ handleCompare(res: http.ServerResponse, ids: string[]): Promise<void>;
28
+ handleGetLive(res: http.ServerResponse): void;
29
+ handleGetTests(res: http.ServerResponse): Promise<void>;
30
+ handleRunTest(req: http.IncomingMessage, res: http.ServerResponse): Promise<void>;
31
+ handleStopTest(res: http.ServerResponse, testId: string): void;
32
+ handleGetWorkers(res: http.ServerResponse): Promise<void>;
33
+ handleInfraMetrics(req: http.IncomingMessage, res: http.ServerResponse): Promise<void>;
34
+ handleGetInfra(res: http.ServerResponse, host?: string): Promise<void>;
35
+ handleGetInfraByTestRun(res: http.ServerResponse, startTime: string, endTime: string): Promise<void>;
36
+ handleExportInfra(req: http.IncomingMessage, res: http.ServerResponse, url: URL): Promise<void>;
37
+ handleImportInfra(req: http.IncomingMessage, res: http.ServerResponse, url: URL): Promise<void>;
38
+ handleGetInfraStatus(res: http.ServerResponse): Promise<void>;
39
+ /**
40
+ * Get a snapshot of all current infrastructure metrics for saving with test results
41
+ */
42
+ getInfraSnapshot(): Record<string, InfrastructureMetrics[]>;
43
+ /**
44
+ * Query infrastructure metrics for a specific test run time range
45
+ */
46
+ getInfraForTestRun(startTime: Date, endTime: Date): Promise<Record<string, InfrastructureMetrics[]>>;
47
+ /**
48
+ * Get list of test runs stored in InfluxDB
49
+ */
50
+ handleGetTestRuns(res: http.ServerResponse): Promise<void>;
51
+ /**
52
+ * Query test results from InfluxDB for a specific test run
53
+ */
54
+ handleGetTestMetrics(res: http.ServerResponse, url: URL): Promise<void>;
55
+ /**
56
+ * Export test data from InfluxDB
57
+ */
58
+ handleExportTestData(res: http.ServerResponse, url: URL): Promise<void>;
59
+ /**
60
+ * Get InfluxDB status for test metrics
61
+ */
62
+ handleGetTestMetricsStatus(res: http.ServerResponse): Promise<void>;
63
+ private readBody;
64
+ }