@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.
- package/dist/cli/cli.js +16 -1
- package/dist/cli/commands/distributed.js +2 -2
- package/dist/cli/commands/report.js +2 -2
- package/dist/cli/commands/run.js +2 -0
- package/dist/config/parser.js +2 -2
- package/dist/config/types/global-config.d.ts +82 -2
- package/dist/config/types/scenario-config.d.ts +2 -2
- package/dist/config/types/step-types.d.ts +1 -1
- package/dist/core/data/data-manager.d.ts +70 -0
- package/dist/core/data/data-manager.js +186 -0
- package/dist/core/data/data-provider.d.ts +85 -0
- package/dist/core/data/data-provider.js +468 -0
- package/dist/core/data/index.d.ts +8 -0
- package/dist/core/data/index.js +13 -0
- package/dist/core/execution/check-evaluator.d.ts +10 -0
- package/dist/core/execution/check-evaluator.js +79 -0
- package/dist/core/execution/data-extractor.d.ts +6 -0
- package/dist/core/execution/data-extractor.js +70 -0
- package/dist/core/execution/index.d.ts +3 -0
- package/dist/core/execution/index.js +9 -0
- package/dist/core/execution/json-payload-processor.d.ts +7 -0
- package/dist/core/execution/json-payload-processor.js +140 -0
- package/dist/core/factories/index.d.ts +2 -0
- package/dist/core/factories/index.js +7 -0
- package/dist/core/factories/output-handler-factory.d.ts +10 -0
- package/dist/core/factories/output-handler-factory.js +91 -0
- package/dist/core/factories/protocol-handler-factory.d.ts +12 -0
- package/dist/core/factories/protocol-handler-factory.js +96 -0
- package/dist/core/index.d.ts +3 -2
- package/dist/core/index.js +8 -3
- package/dist/core/reporting/dashboard-reporter.d.ts +17 -0
- package/dist/core/reporting/dashboard-reporter.js +127 -0
- package/dist/core/reporting/index.d.ts +1 -0
- package/dist/core/reporting/index.js +5 -0
- package/dist/core/step-executor.d.ts +6 -20
- package/dist/core/step-executor.js +72 -366
- package/dist/core/strategies/index.d.ts +2 -0
- package/dist/core/strategies/index.js +7 -0
- package/dist/core/strategies/scenario-selector.d.ts +13 -0
- package/dist/core/strategies/scenario-selector.js +37 -0
- package/dist/core/strategies/think-time-strategy.d.ts +15 -0
- package/dist/core/strategies/think-time-strategy.js +71 -0
- package/dist/core/test-runner.d.ts +4 -11
- package/dist/core/test-runner.js +105 -312
- package/dist/core/virtual-user.d.ts +7 -37
- package/dist/core/virtual-user.js +29 -269
- package/dist/dashboard/routes/api.d.ts +64 -0
- package/dist/dashboard/routes/api.js +569 -0
- package/dist/dashboard/routes/index.d.ts +2 -0
- package/dist/dashboard/routes/index.js +7 -0
- package/dist/dashboard/routes/static.d.ts +6 -0
- package/dist/dashboard/routes/static.js +76 -0
- package/dist/dashboard/server.d.ts +8 -84
- package/dist/dashboard/server.js +76 -2007
- package/dist/dashboard/services/file-scanner.d.ts +7 -0
- package/dist/dashboard/services/file-scanner.js +114 -0
- package/dist/dashboard/services/index.d.ts +5 -0
- package/dist/dashboard/services/index.js +13 -0
- package/dist/dashboard/services/influxdb-service.d.ts +41 -0
- package/dist/dashboard/services/influxdb-service.js +329 -0
- package/dist/dashboard/services/metrics-parser.d.ts +12 -0
- package/dist/dashboard/services/metrics-parser.js +209 -0
- package/dist/dashboard/services/results-manager.d.ts +17 -0
- package/dist/dashboard/services/results-manager.js +311 -0
- package/dist/dashboard/services/test-executor.d.ts +41 -0
- package/dist/dashboard/services/test-executor.js +250 -0
- package/dist/dashboard/services/workers-manager.d.ts +13 -0
- package/dist/dashboard/services/workers-manager.js +81 -0
- package/dist/dashboard/templates/index.html +122 -0
- package/dist/dashboard/templates/scripts/main.js +3280 -0
- package/dist/dashboard/templates/styles.css +402 -0
- package/dist/dashboard/types.d.ts +168 -0
- package/dist/dashboard/types.js +2 -0
- package/dist/distributed/result-aggregator.js +1 -3
- package/dist/metrics/batch/batch-processor.d.ts +27 -0
- package/dist/metrics/batch/batch-processor.js +85 -0
- package/dist/metrics/batch/index.d.ts +1 -0
- package/dist/metrics/batch/index.js +5 -0
- package/dist/metrics/collector.d.ts +46 -45
- package/dist/metrics/collector.js +179 -640
- package/dist/metrics/core/error-tracker.d.ts +9 -0
- package/dist/metrics/core/error-tracker.js +52 -0
- package/dist/metrics/core/index.d.ts +3 -0
- package/dist/metrics/core/index.js +9 -0
- package/dist/metrics/core/result-storage.d.ts +19 -0
- package/dist/metrics/core/result-storage.js +56 -0
- package/dist/metrics/core/statistics-engine.d.ts +27 -0
- package/dist/metrics/core/statistics-engine.js +91 -0
- package/dist/metrics/output/file-writer.d.ts +19 -0
- package/dist/metrics/output/file-writer.js +129 -0
- package/dist/metrics/output/index.d.ts +2 -0
- package/dist/metrics/output/index.js +10 -0
- package/dist/metrics/output/influxdb-writer.d.ts +89 -0
- package/dist/metrics/output/influxdb-writer.js +404 -0
- package/dist/metrics/realtime/dispatcher.d.ts +18 -0
- package/dist/metrics/realtime/dispatcher.js +45 -0
- package/dist/metrics/realtime/endpoints/graphite.d.ts +3 -0
- package/dist/metrics/realtime/endpoints/graphite.js +61 -0
- package/dist/metrics/realtime/endpoints/influxdb.d.ts +3 -0
- package/dist/metrics/realtime/endpoints/influxdb.js +35 -0
- package/dist/metrics/realtime/endpoints/webhook.d.ts +3 -0
- package/dist/metrics/realtime/endpoints/webhook.js +22 -0
- package/dist/metrics/realtime/endpoints/websocket.d.ts +3 -0
- package/dist/metrics/realtime/endpoints/websocket.js +25 -0
- package/dist/metrics/realtime/index.d.ts +5 -0
- package/dist/metrics/realtime/index.js +13 -0
- package/dist/metrics/reporting/index.d.ts +3 -0
- package/dist/metrics/reporting/index.js +9 -0
- package/dist/metrics/reporting/step-statistics.d.ts +6 -0
- package/dist/metrics/reporting/step-statistics.js +59 -0
- package/dist/metrics/reporting/summary-generator.d.ts +16 -0
- package/dist/metrics/reporting/summary-generator.js +46 -0
- package/dist/metrics/reporting/timeline-calculator.d.ts +7 -0
- package/dist/metrics/reporting/timeline-calculator.js +86 -0
- package/dist/metrics/types.d.ts +58 -0
- package/dist/outputs/csv.d.ts +2 -0
- package/dist/outputs/csv.js +21 -2
- package/dist/outputs/json.js +6 -2
- package/dist/protocols/rest/handler.d.ts +4 -53
- package/dist/protocols/rest/handler.js +73 -454
- package/dist/protocols/rest/request/auth-handler.d.ts +4 -0
- package/dist/protocols/rest/request/auth-handler.js +30 -0
- package/dist/protocols/rest/request/body-processor.d.ts +11 -0
- package/dist/protocols/rest/request/body-processor.js +62 -0
- package/dist/protocols/rest/request/index.d.ts +2 -0
- package/dist/protocols/rest/request/index.js +7 -0
- package/dist/protocols/rest/response/checks.d.ts +6 -0
- package/dist/protocols/rest/response/checks.js +71 -0
- package/dist/protocols/rest/response/index.d.ts +2 -0
- package/dist/protocols/rest/response/index.js +7 -0
- package/dist/protocols/rest/response/size-calculator.d.ts +12 -0
- package/dist/protocols/rest/response/size-calculator.js +64 -0
- package/dist/protocols/web/browser/highlight.d.ts +7 -0
- package/dist/protocols/web/browser/highlight.js +47 -0
- package/dist/protocols/web/browser/index.d.ts +4 -0
- package/dist/protocols/web/browser/index.js +11 -0
- package/dist/protocols/web/browser/manager.d.ts +20 -0
- package/dist/protocols/web/browser/manager.js +189 -0
- package/dist/protocols/web/browser/screenshot.d.ts +8 -0
- package/dist/protocols/web/browser/screenshot.js +69 -0
- package/dist/protocols/web/browser/storage.d.ts +5 -0
- package/dist/protocols/web/browser/storage.js +45 -0
- package/dist/protocols/web/commands/index.d.ts +5 -0
- package/dist/protocols/web/commands/index.js +11 -0
- package/dist/protocols/web/commands/interaction.d.ts +13 -0
- package/dist/protocols/web/commands/interaction.js +68 -0
- package/dist/protocols/web/commands/measurement.d.ts +16 -0
- package/dist/protocols/web/commands/measurement.js +33 -0
- package/dist/protocols/web/commands/navigation.d.ts +11 -0
- package/dist/protocols/web/commands/navigation.js +43 -0
- package/dist/protocols/web/commands/types.d.ts +12 -0
- package/dist/protocols/web/commands/types.js +2 -0
- package/dist/protocols/web/commands/verification.d.ts +12 -0
- package/dist/protocols/web/commands/verification.js +118 -0
- package/dist/protocols/web/handler.d.ts +19 -30
- package/dist/protocols/web/handler.js +164 -651
- package/dist/protocols/web/network/capture.d.ts +19 -0
- package/dist/protocols/web/network/capture.js +225 -0
- package/dist/protocols/web/network/filters.d.ts +5 -0
- package/dist/protocols/web/network/filters.js +49 -0
- package/dist/protocols/web/network/index.d.ts +4 -0
- package/dist/protocols/web/network/index.js +9 -0
- package/dist/protocols/web/network/types.d.ts +13 -0
- package/dist/protocols/web/network/types.js +2 -0
- package/dist/protocols/web/network/utils.d.ts +8 -0
- package/dist/protocols/web/network/utils.js +29 -0
- package/dist/recorder/continue-recorder.d.ts +11 -0
- package/dist/recorder/continue-recorder.js +872 -0
- package/dist/reporting/chart-data/index.d.ts +5 -0
- package/dist/reporting/chart-data/index.js +13 -0
- package/dist/reporting/chart-data/network.d.ts +25 -0
- package/dist/reporting/chart-data/network.js +78 -0
- package/dist/reporting/chart-data/scenario.d.ts +37 -0
- package/dist/reporting/chart-data/scenario.js +76 -0
- package/dist/reporting/chart-data/step-statistics.d.ts +24 -0
- package/dist/reporting/chart-data/step-statistics.js +94 -0
- package/dist/reporting/chart-data/throughput.d.ts +16 -0
- package/dist/reporting/chart-data/throughput.js +24 -0
- package/dist/reporting/chart-data/timeline.d.ts +17 -0
- package/dist/reporting/chart-data/timeline.js +46 -0
- package/dist/reporting/handlebars-helpers.d.ts +1 -0
- package/dist/reporting/handlebars-helpers.js +63 -0
- package/dist/reporting/{enhanced-html-generator.d.ts → html-generator.d.ts} +1 -1
- package/dist/reporting/{enhanced-html-generator.js → html-generator.js} +10 -7
- package/dist/reporting/templates/{enhanced-report.hbs → report.hbs} +9 -9
- package/dist/utils/data-utils.d.ts +17 -0
- package/dist/utils/data-utils.js +129 -0
- package/dist/utils/template.js +2 -2
- package/package.json +5 -2
- package/dist/core/csv-data-provider.d.ts +0 -47
- package/dist/core/csv-data-provider.js +0 -265
- package/dist/reporting/generator.d.ts +0 -42
- package/dist/reporting/generator.js +0 -1217
- 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.
|
|
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
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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.
|
|
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
|
-
//
|
|
241
|
-
|
|
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
|
|
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
|
-
|
|
265
|
-
|
|
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
|
+
}
|