@testsmith/perfornium 0.1.0
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 +360 -0
- package/dist/cli/cli.d.ts +2 -0
- package/dist/cli/cli.js +192 -0
- package/dist/cli/commands/distributed.d.ts +11 -0
- package/dist/cli/commands/distributed.js +179 -0
- package/dist/cli/commands/import.d.ts +23 -0
- package/dist/cli/commands/import.js +461 -0
- package/dist/cli/commands/init.d.ts +7 -0
- package/dist/cli/commands/init.js +923 -0
- package/dist/cli/commands/mock.d.ts +7 -0
- package/dist/cli/commands/mock.js +281 -0
- package/dist/cli/commands/report.d.ts +5 -0
- package/dist/cli/commands/report.js +70 -0
- package/dist/cli/commands/run.d.ts +12 -0
- package/dist/cli/commands/run.js +260 -0
- package/dist/cli/commands/validate.d.ts +3 -0
- package/dist/cli/commands/validate.js +35 -0
- package/dist/cli/commands/worker.d.ts +27 -0
- package/dist/cli/commands/worker.js +320 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.js +20 -0
- package/dist/config/parser.d.ts +19 -0
- package/dist/config/parser.js +330 -0
- package/dist/config/types/global-config.d.ts +74 -0
- package/dist/config/types/global-config.js +2 -0
- package/dist/config/types/hooks.d.ts +58 -0
- package/dist/config/types/hooks.js +3 -0
- package/dist/config/types/import-types.d.ts +33 -0
- package/dist/config/types/import-types.js +2 -0
- package/dist/config/types/index.d.ts +11 -0
- package/dist/config/types/index.js +27 -0
- package/dist/config/types/load-config.d.ts +32 -0
- package/dist/config/types/load-config.js +9 -0
- package/dist/config/types/output-config.d.ts +10 -0
- package/dist/config/types/output-config.js +2 -0
- package/dist/config/types/report-config.d.ts +10 -0
- package/dist/config/types/report-config.js +2 -0
- package/dist/config/types/runtime-types.d.ts +6 -0
- package/dist/config/types/runtime-types.js +2 -0
- package/dist/config/types/scenario-config.d.ts +30 -0
- package/dist/config/types/scenario-config.js +2 -0
- package/dist/config/types/step-types.d.ts +139 -0
- package/dist/config/types/step-types.js +2 -0
- package/dist/config/types/test-configuration.d.ts +18 -0
- package/dist/config/types/test-configuration.js +2 -0
- package/dist/config/types/worker-config.d.ts +12 -0
- package/dist/config/types/worker-config.js +2 -0
- package/dist/config/validator.d.ts +19 -0
- package/dist/config/validator.js +198 -0
- package/dist/core/csv-data-provider.d.ts +47 -0
- package/dist/core/csv-data-provider.js +265 -0
- package/dist/core/hooks-manager.d.ts +33 -0
- package/dist/core/hooks-manager.js +129 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +11 -0
- package/dist/core/script-executor.d.ts +14 -0
- package/dist/core/script-executor.js +290 -0
- package/dist/core/step-executor.d.ts +41 -0
- package/dist/core/step-executor.js +680 -0
- package/dist/core/test-runner.d.ts +34 -0
- package/dist/core/test-runner.js +465 -0
- package/dist/core/threshold-evaluator.d.ts +43 -0
- package/dist/core/threshold-evaluator.js +170 -0
- package/dist/core/virtual-user-pool.d.ts +42 -0
- package/dist/core/virtual-user-pool.js +136 -0
- package/dist/core/virtual-user.d.ts +51 -0
- package/dist/core/virtual-user.js +488 -0
- package/dist/distributed/coordinator.d.ts +34 -0
- package/dist/distributed/coordinator.js +158 -0
- package/dist/distributed/health-monitor.d.ts +18 -0
- package/dist/distributed/health-monitor.js +72 -0
- package/dist/distributed/load-distributor.d.ts +17 -0
- package/dist/distributed/load-distributor.js +106 -0
- package/dist/distributed/remote-worker.d.ts +37 -0
- package/dist/distributed/remote-worker.js +241 -0
- package/dist/distributed/result-aggregator.d.ts +43 -0
- package/dist/distributed/result-aggregator.js +146 -0
- package/dist/dsl/index.d.ts +3 -0
- package/dist/dsl/index.js +11 -0
- package/dist/dsl/test-builder.d.ts +111 -0
- package/dist/dsl/test-builder.js +514 -0
- package/dist/importers/har-importer.d.ts +17 -0
- package/dist/importers/har-importer.js +172 -0
- package/dist/importers/open-api-importer.d.ts +23 -0
- package/dist/importers/open-api-importer.js +181 -0
- package/dist/importers/wsdl-importer.d.ts +42 -0
- package/dist/importers/wsdl-importer.js +440 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +17 -0
- package/dist/load-patterns/arrivals.d.ts +7 -0
- package/dist/load-patterns/arrivals.js +118 -0
- package/dist/load-patterns/base.d.ts +9 -0
- package/dist/load-patterns/base.js +2 -0
- package/dist/load-patterns/basic.d.ts +7 -0
- package/dist/load-patterns/basic.js +117 -0
- package/dist/load-patterns/stepping.d.ts +6 -0
- package/dist/load-patterns/stepping.js +122 -0
- package/dist/metrics/collector.d.ts +72 -0
- package/dist/metrics/collector.js +662 -0
- package/dist/metrics/types.d.ts +135 -0
- package/dist/metrics/types.js +2 -0
- package/dist/outputs/base.d.ts +7 -0
- package/dist/outputs/base.js +2 -0
- package/dist/outputs/csv.d.ts +13 -0
- package/dist/outputs/csv.js +163 -0
- package/dist/outputs/graphite.d.ts +13 -0
- package/dist/outputs/graphite.js +126 -0
- package/dist/outputs/influxdb.d.ts +12 -0
- package/dist/outputs/influxdb.js +82 -0
- package/dist/outputs/json.d.ts +14 -0
- package/dist/outputs/json.js +107 -0
- package/dist/outputs/streaming-csv.d.ts +37 -0
- package/dist/outputs/streaming-csv.js +254 -0
- package/dist/outputs/streaming-json.d.ts +43 -0
- package/dist/outputs/streaming-json.js +353 -0
- package/dist/outputs/webhook.d.ts +16 -0
- package/dist/outputs/webhook.js +96 -0
- package/dist/protocols/base.d.ts +33 -0
- package/dist/protocols/base.js +2 -0
- package/dist/protocols/rest/handler.d.ts +67 -0
- package/dist/protocols/rest/handler.js +776 -0
- package/dist/protocols/soap/handler.d.ts +12 -0
- package/dist/protocols/soap/handler.js +165 -0
- package/dist/protocols/web/core-web-vitals.d.ts +121 -0
- package/dist/protocols/web/core-web-vitals.js +373 -0
- package/dist/protocols/web/handler.d.ts +50 -0
- package/dist/protocols/web/handler.js +706 -0
- package/dist/recorder/native-recorder.d.ts +14 -0
- package/dist/recorder/native-recorder.js +533 -0
- package/dist/recorder/scenario-recorder.d.ts +55 -0
- package/dist/recorder/scenario-recorder.js +296 -0
- package/dist/reporting/constants.d.ts +94 -0
- package/dist/reporting/constants.js +82 -0
- package/dist/reporting/enhanced-html-generator.d.ts +55 -0
- package/dist/reporting/enhanced-html-generator.js +965 -0
- package/dist/reporting/generator.d.ts +42 -0
- package/dist/reporting/generator.js +1217 -0
- package/dist/reporting/statistics.d.ts +144 -0
- package/dist/reporting/statistics.js +742 -0
- package/dist/reporting/templates/enhanced-report.hbs +2812 -0
- package/dist/reporting/templates/html.hbs +2453 -0
- package/dist/utils/faker-manager.d.ts +55 -0
- package/dist/utils/faker-manager.js +166 -0
- package/dist/utils/file-manager.d.ts +33 -0
- package/dist/utils/file-manager.js +154 -0
- package/dist/utils/handlebars-manager.d.ts +42 -0
- package/dist/utils/handlebars-manager.js +172 -0
- package/dist/utils/logger.d.ts +16 -0
- package/dist/utils/logger.js +46 -0
- package/dist/utils/template.d.ts +80 -0
- package/dist/utils/template.js +513 -0
- package/dist/utils/test-output-writer.d.ts +56 -0
- package/dist/utils/test-output-writer.js +643 -0
- package/dist/utils/time.d.ts +3 -0
- package/dist/utils/time.js +23 -0
- package/dist/utils/timestamp-helper.d.ts +17 -0
- package/dist/utils/timestamp-helper.js +53 -0
- package/dist/workers/manager.d.ts +18 -0
- package/dist/workers/manager.js +95 -0
- package/dist/workers/server.d.ts +21 -0
- package/dist/workers/server.js +205 -0
- package/dist/workers/worker.d.ts +19 -0
- package/dist/workers/worker.js +147 -0
- package/package.json +102 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ThresholdEvaluator = void 0;
|
|
4
|
+
const logger_1 = require("../utils/logger");
|
|
5
|
+
class ThresholdEvaluator {
|
|
6
|
+
/**
|
|
7
|
+
* Evaluate all thresholds against a test result
|
|
8
|
+
*/
|
|
9
|
+
static evaluate(thresholds, result, stepName) {
|
|
10
|
+
const failures = [];
|
|
11
|
+
for (const threshold of thresholds) {
|
|
12
|
+
try {
|
|
13
|
+
const failure = this.evaluateThreshold(threshold, result, stepName);
|
|
14
|
+
if (failure) {
|
|
15
|
+
failures.push(failure);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
logger_1.logger.warn(`Error evaluating threshold for metric ${threshold.metric}: ${error}`);
|
|
20
|
+
failures.push({
|
|
21
|
+
threshold,
|
|
22
|
+
actualValue: 'ERROR',
|
|
23
|
+
expectedValue: threshold.value,
|
|
24
|
+
message: `Threshold evaluation failed: ${error}`,
|
|
25
|
+
severity: 'error'
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
passed: failures.length === 0,
|
|
31
|
+
failures
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Evaluate a single threshold
|
|
36
|
+
*/
|
|
37
|
+
static evaluateThreshold(threshold, result, stepName) {
|
|
38
|
+
const actualValue = this.extractMetricValue(threshold.metric, result);
|
|
39
|
+
const expectedValue = this.parseExpectedValue(threshold.value);
|
|
40
|
+
const operator = threshold.operator || 'lte'; // Default to less than or equal
|
|
41
|
+
const passed = this.compareValues(actualValue, expectedValue, operator);
|
|
42
|
+
if (!passed) {
|
|
43
|
+
const stepInfo = stepName ? ` in step "${stepName}"` : '';
|
|
44
|
+
const description = threshold.description ||
|
|
45
|
+
`${threshold.metric} threshold exceeded${stepInfo}`;
|
|
46
|
+
return {
|
|
47
|
+
threshold,
|
|
48
|
+
actualValue,
|
|
49
|
+
expectedValue,
|
|
50
|
+
message: `${description}: ${actualValue} ${this.getOperatorSymbol(operator)} ${expectedValue}`,
|
|
51
|
+
severity: threshold.severity || 'error'
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Extract the actual metric value from test result
|
|
58
|
+
*/
|
|
59
|
+
static extractMetricValue(metric, result) {
|
|
60
|
+
switch (metric) {
|
|
61
|
+
case 'response_time':
|
|
62
|
+
return result.duration;
|
|
63
|
+
case 'status_code':
|
|
64
|
+
return result.status || 0;
|
|
65
|
+
case 'error_rate':
|
|
66
|
+
// This would need to be calculated at a higher level
|
|
67
|
+
// For now, return 0 for success, 100 for failure
|
|
68
|
+
return result.success ? 0 : 100;
|
|
69
|
+
case 'throughput':
|
|
70
|
+
// Calculate requests per second (would need time window context)
|
|
71
|
+
return result.duration > 0 ? 1000 / result.duration : 0;
|
|
72
|
+
case 'custom':
|
|
73
|
+
// Custom metrics would be stored in result.custom_metrics
|
|
74
|
+
return result.custom_metrics || 0;
|
|
75
|
+
default:
|
|
76
|
+
throw new Error(`Unknown metric type: ${metric}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Parse expected value, handling units like "1s", "500ms", etc.
|
|
81
|
+
*/
|
|
82
|
+
static parseExpectedValue(value) {
|
|
83
|
+
if (typeof value === 'number') {
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
const str = value.toString().toLowerCase();
|
|
87
|
+
// Handle time units
|
|
88
|
+
if (str.endsWith('ms')) {
|
|
89
|
+
return parseFloat(str.slice(0, -2));
|
|
90
|
+
}
|
|
91
|
+
if (str.endsWith('s')) {
|
|
92
|
+
return parseFloat(str.slice(0, -1)) * 1000; // Convert to ms
|
|
93
|
+
}
|
|
94
|
+
if (str.endsWith('m')) {
|
|
95
|
+
return parseFloat(str.slice(0, -1)) * 60000; // Convert to ms
|
|
96
|
+
}
|
|
97
|
+
// Handle percentage
|
|
98
|
+
if (str.endsWith('%')) {
|
|
99
|
+
return parseFloat(str.slice(0, -1));
|
|
100
|
+
}
|
|
101
|
+
// Default: try to parse as number
|
|
102
|
+
const parsed = parseFloat(str);
|
|
103
|
+
if (isNaN(parsed)) {
|
|
104
|
+
throw new Error(`Cannot parse threshold value: ${value}`);
|
|
105
|
+
}
|
|
106
|
+
return parsed;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Compare actual vs expected values using the specified operator
|
|
110
|
+
*/
|
|
111
|
+
static compareValues(actual, expected, operator) {
|
|
112
|
+
const actualNum = typeof actual === 'string' ? parseFloat(actual.toString()) : actual;
|
|
113
|
+
if (isNaN(actualNum)) {
|
|
114
|
+
return operator === 'ne'; // Only "not equals" passes for non-numeric values
|
|
115
|
+
}
|
|
116
|
+
switch (operator) {
|
|
117
|
+
case 'lt': return actualNum < expected;
|
|
118
|
+
case 'lte': return actualNum <= expected;
|
|
119
|
+
case 'gt': return actualNum > expected;
|
|
120
|
+
case 'gte': return actualNum >= expected;
|
|
121
|
+
case 'eq': return actualNum === expected;
|
|
122
|
+
case 'ne': return actualNum !== expected;
|
|
123
|
+
default:
|
|
124
|
+
throw new Error(`Unknown operator: ${operator}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Get human-readable operator symbol
|
|
129
|
+
*/
|
|
130
|
+
static getOperatorSymbol(operator) {
|
|
131
|
+
const symbols = {
|
|
132
|
+
'lt': '<',
|
|
133
|
+
'lte': 'โค',
|
|
134
|
+
'gt': '>',
|
|
135
|
+
'gte': 'โฅ',
|
|
136
|
+
'eq': '=',
|
|
137
|
+
'ne': 'โ '
|
|
138
|
+
};
|
|
139
|
+
return symbols[operator] || operator;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Execute threshold action based on severity and configuration
|
|
143
|
+
*/
|
|
144
|
+
static async executeThresholdActions(evaluationResult, stepName) {
|
|
145
|
+
for (const failure of evaluationResult.failures) {
|
|
146
|
+
const action = failure.threshold.action || 'log';
|
|
147
|
+
const stepInfo = stepName ? ` [Step: ${stepName}]` : '';
|
|
148
|
+
switch (action) {
|
|
149
|
+
case 'log':
|
|
150
|
+
logger_1.logger.warn(`Threshold violation${stepInfo}: ${failure.message}`);
|
|
151
|
+
break;
|
|
152
|
+
case 'fail_step':
|
|
153
|
+
logger_1.logger.error(`Step threshold violation${stepInfo}: ${failure.message}`);
|
|
154
|
+
throw new Error(`Step failed due to threshold violation: ${failure.message}`);
|
|
155
|
+
case 'fail_scenario':
|
|
156
|
+
logger_1.logger.error(`Scenario threshold violation${stepInfo}: ${failure.message}`);
|
|
157
|
+
throw new Error(`Scenario failed due to threshold violation: ${failure.message}`);
|
|
158
|
+
case 'fail_test':
|
|
159
|
+
logger_1.logger.error(`Test threshold violation${stepInfo}: ${failure.message}`);
|
|
160
|
+
throw new Error(`Test failed due to threshold violation: ${failure.message}`);
|
|
161
|
+
case 'abort':
|
|
162
|
+
logger_1.logger.error(`Critical threshold violation${stepInfo}: ${failure.message}`);
|
|
163
|
+
process.exit(1);
|
|
164
|
+
default:
|
|
165
|
+
logger_1.logger.warn(`Unknown threshold action: ${action}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
exports.ThresholdEvaluator = ThresholdEvaluator;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { VirtualUser } from './virtual-user';
|
|
2
|
+
import { MetricsCollector } from '../metrics/collector';
|
|
3
|
+
import { ProtocolHandler } from '../protocols/base';
|
|
4
|
+
import { Scenario } from '../config/types/hooks';
|
|
5
|
+
import { EventEmitter } from 'events';
|
|
6
|
+
export interface VUPoolConfig {
|
|
7
|
+
maxWorkers?: number;
|
|
8
|
+
maxVUsPerWorker?: number;
|
|
9
|
+
reuseVUs?: boolean;
|
|
10
|
+
preWarmVUs?: number;
|
|
11
|
+
workerIdleTimeout?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface VUTask {
|
|
14
|
+
id: number;
|
|
15
|
+
scenarios: Scenario[];
|
|
16
|
+
priority?: number;
|
|
17
|
+
}
|
|
18
|
+
export declare class VirtualUserPool extends EventEmitter {
|
|
19
|
+
private config;
|
|
20
|
+
private metrics;
|
|
21
|
+
private handlers;
|
|
22
|
+
private testName;
|
|
23
|
+
private vuPool;
|
|
24
|
+
private availableVUs;
|
|
25
|
+
private busyVUs;
|
|
26
|
+
private workers;
|
|
27
|
+
private taskQueue;
|
|
28
|
+
private nextVUId;
|
|
29
|
+
private readonly DEFAULT_MAX_WORKERS;
|
|
30
|
+
private readonly DEFAULT_MAX_VUS_PER_WORKER;
|
|
31
|
+
private readonly DEFAULT_WORKER_IDLE_TIMEOUT;
|
|
32
|
+
constructor(metrics: MetricsCollector, handlers: Map<string, ProtocolHandler>, testName?: string, config?: VUPoolConfig);
|
|
33
|
+
initialize(): Promise<void>;
|
|
34
|
+
private preWarmVUs;
|
|
35
|
+
private createVU;
|
|
36
|
+
getVU(scenarios: Scenario[]): Promise<VirtualUser>;
|
|
37
|
+
releaseVU(vuId: number): void;
|
|
38
|
+
executeTask(task: VUTask): Promise<void>;
|
|
39
|
+
private startWorkerCleanup;
|
|
40
|
+
private terminateWorker;
|
|
41
|
+
shutdown(): Promise<void>;
|
|
42
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VirtualUserPool = void 0;
|
|
4
|
+
const virtual_user_1 = require("./virtual-user");
|
|
5
|
+
const logger_1 = require("../utils/logger");
|
|
6
|
+
const events_1 = require("events");
|
|
7
|
+
class VirtualUserPool extends events_1.EventEmitter {
|
|
8
|
+
constructor(metrics, handlers, testName = 'Load Test', config) {
|
|
9
|
+
super();
|
|
10
|
+
this.vuPool = new Map();
|
|
11
|
+
this.availableVUs = new Set();
|
|
12
|
+
this.busyVUs = new Set();
|
|
13
|
+
this.workers = new Map();
|
|
14
|
+
this.taskQueue = [];
|
|
15
|
+
this.nextVUId = 1;
|
|
16
|
+
this.DEFAULT_MAX_WORKERS = 4;
|
|
17
|
+
this.DEFAULT_MAX_VUS_PER_WORKER = 50;
|
|
18
|
+
this.DEFAULT_WORKER_IDLE_TIMEOUT = 30000;
|
|
19
|
+
this.metrics = metrics;
|
|
20
|
+
this.handlers = handlers;
|
|
21
|
+
this.testName = testName;
|
|
22
|
+
this.config = {
|
|
23
|
+
maxWorkers: config?.maxWorkers || this.DEFAULT_MAX_WORKERS,
|
|
24
|
+
maxVUsPerWorker: config?.maxVUsPerWorker || this.DEFAULT_MAX_VUS_PER_WORKER,
|
|
25
|
+
reuseVUs: config?.reuseVUs ?? true,
|
|
26
|
+
preWarmVUs: config?.preWarmVUs || 0,
|
|
27
|
+
workerIdleTimeout: config?.workerIdleTimeout || this.DEFAULT_WORKER_IDLE_TIMEOUT
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
async initialize() {
|
|
31
|
+
logger_1.logger.debug(`๐ Initializing VU pool with config:`, this.config);
|
|
32
|
+
if (this.config.preWarmVUs && this.config.preWarmVUs > 0) {
|
|
33
|
+
await this.preWarmVUs(this.config.preWarmVUs);
|
|
34
|
+
}
|
|
35
|
+
this.startWorkerCleanup();
|
|
36
|
+
}
|
|
37
|
+
async preWarmVUs(count) {
|
|
38
|
+
logger_1.logger.debug(`๐ฅ Pre-warming ${count} virtual users...`);
|
|
39
|
+
const promises = [];
|
|
40
|
+
for (let i = 0; i < count; i++) {
|
|
41
|
+
promises.push(this.createVU());
|
|
42
|
+
}
|
|
43
|
+
await Promise.all(promises);
|
|
44
|
+
logger_1.logger.debug(`โ
Pre-warmed ${count} virtual users`);
|
|
45
|
+
}
|
|
46
|
+
async createVU() {
|
|
47
|
+
const vuId = this.nextVUId++;
|
|
48
|
+
const vu = new virtual_user_1.VirtualUser(vuId, this.metrics, this.handlers, this.testName);
|
|
49
|
+
this.vuPool.set(vuId, vu);
|
|
50
|
+
this.availableVUs.add(vuId);
|
|
51
|
+
return vuId;
|
|
52
|
+
}
|
|
53
|
+
async getVU(scenarios) {
|
|
54
|
+
let vuId;
|
|
55
|
+
if (this.config.reuseVUs && this.availableVUs.size > 0) {
|
|
56
|
+
const iterator = this.availableVUs.values().next();
|
|
57
|
+
if (!iterator.done) {
|
|
58
|
+
vuId = iterator.value;
|
|
59
|
+
this.availableVUs.delete(vuId);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
vuId = await this.createVU();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
vuId = await this.createVU();
|
|
67
|
+
}
|
|
68
|
+
this.busyVUs.add(vuId);
|
|
69
|
+
const vu = this.vuPool.get(vuId);
|
|
70
|
+
await vu.setScenarios(scenarios);
|
|
71
|
+
return vu;
|
|
72
|
+
}
|
|
73
|
+
releaseVU(vuId) {
|
|
74
|
+
if (!this.busyVUs.has(vuId)) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
this.busyVUs.delete(vuId);
|
|
78
|
+
if (this.config.reuseVUs) {
|
|
79
|
+
this.availableVUs.add(vuId);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
const vu = this.vuPool.get(vuId);
|
|
83
|
+
if (vu) {
|
|
84
|
+
vu.stop();
|
|
85
|
+
this.vuPool.delete(vuId);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async executeTask(task) {
|
|
90
|
+
const vu = await this.getVU(task.scenarios);
|
|
91
|
+
try {
|
|
92
|
+
await vu.executeScenarios();
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
this.releaseVU(vu.getId());
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
startWorkerCleanup() {
|
|
99
|
+
if (!this.config.workerIdleTimeout || this.config.workerIdleTimeout <= 0) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
setInterval(() => {
|
|
103
|
+
const now = Date.now();
|
|
104
|
+
for (const [workerId, state] of this.workers.entries()) {
|
|
105
|
+
if (state.isIdle &&
|
|
106
|
+
state.activeVUs.size === 0 &&
|
|
107
|
+
now - state.lastActivity > this.config.workerIdleTimeout) {
|
|
108
|
+
this.terminateWorker(workerId);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}, 10000);
|
|
112
|
+
}
|
|
113
|
+
terminateWorker(workerId) {
|
|
114
|
+
const state = this.workers.get(workerId);
|
|
115
|
+
if (!state)
|
|
116
|
+
return;
|
|
117
|
+
state.worker.terminate();
|
|
118
|
+
this.workers.delete(workerId);
|
|
119
|
+
logger_1.logger.debug(`๐งน Terminated idle worker ${workerId}`);
|
|
120
|
+
}
|
|
121
|
+
async shutdown() {
|
|
122
|
+
logger_1.logger.debug('๐ Shutting down VU pool...');
|
|
123
|
+
for (const [vuId, vu] of this.vuPool) {
|
|
124
|
+
await vu.stop();
|
|
125
|
+
}
|
|
126
|
+
for (const [workerId, state] of this.workers) {
|
|
127
|
+
await state.worker.terminate();
|
|
128
|
+
}
|
|
129
|
+
this.vuPool.clear();
|
|
130
|
+
this.availableVUs.clear();
|
|
131
|
+
this.busyVUs.clear();
|
|
132
|
+
this.workers.clear();
|
|
133
|
+
logger_1.logger.debug('โ
VU pool shut down complete');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
exports.VirtualUserPool = VirtualUserPool;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Scenario, VUHooks } from '../config/types/hooks';
|
|
2
|
+
import { MetricsCollector } from '../metrics/collector';
|
|
3
|
+
import { ProtocolHandler } from '../protocols/base';
|
|
4
|
+
import { GlobalCSVConfig } from '../config';
|
|
5
|
+
export interface GlobalCSVOptions {
|
|
6
|
+
config: GlobalCSVConfig;
|
|
7
|
+
mode?: 'next' | 'unique' | 'random';
|
|
8
|
+
}
|
|
9
|
+
export declare class VirtualUser {
|
|
10
|
+
private id;
|
|
11
|
+
private context;
|
|
12
|
+
private metrics;
|
|
13
|
+
private stepExecutor;
|
|
14
|
+
private isActive;
|
|
15
|
+
private scenarios;
|
|
16
|
+
private csvProviders;
|
|
17
|
+
private vuHooksManager;
|
|
18
|
+
private testName;
|
|
19
|
+
private handlers;
|
|
20
|
+
private globalThinkTime?;
|
|
21
|
+
private globalCSVProvider?;
|
|
22
|
+
private globalCSVMode?;
|
|
23
|
+
constructor(id: number, metrics: MetricsCollector, handlers: Map<string, ProtocolHandler>, testName?: string, vuHooks?: VUHooks, globalThinkTime?: string | number, globalCSV?: GlobalCSVOptions);
|
|
24
|
+
setScenarios(scenarios: Scenario[]): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Initialize CSV providers only for scenarios that need them
|
|
27
|
+
*/
|
|
28
|
+
private initializeCSVProvidersIfNeeded;
|
|
29
|
+
/**
|
|
30
|
+
* Load global CSV data and merge into context variables
|
|
31
|
+
* Called once at the start of each VU execution cycle
|
|
32
|
+
*/
|
|
33
|
+
private loadGlobalCSVData;
|
|
34
|
+
executeScenarios(): Promise<void>;
|
|
35
|
+
executeScenario(scenario: Scenario): Promise<void>;
|
|
36
|
+
private loadCSVDataIfNeeded;
|
|
37
|
+
private loadCSVDataForScenario;
|
|
38
|
+
private selectScenarios;
|
|
39
|
+
private executeSetup;
|
|
40
|
+
private executeTeardown;
|
|
41
|
+
private executeScript;
|
|
42
|
+
/**
|
|
43
|
+
* Get effective think time using hierarchical override:
|
|
44
|
+
* Step think_time > Scenario think_time > Global think_time
|
|
45
|
+
*/
|
|
46
|
+
private getEffectiveThinkTime;
|
|
47
|
+
private applyThinkTime;
|
|
48
|
+
stop(): Promise<void>;
|
|
49
|
+
getId(): number;
|
|
50
|
+
isRunning(): boolean;
|
|
51
|
+
}
|