@depup/artillery 2.0.30-depup.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 +63 -0
- package/bin/run +29 -0
- package/bin/run.cmd +3 -0
- package/changes.json +138 -0
- package/console-reporter.js +1 -0
- package/lib/artillery-global.js +33 -0
- package/lib/cli/banner.js +8 -0
- package/lib/cli/common-flags.js +80 -0
- package/lib/cli/hooks/version.js +20 -0
- package/lib/cmds/dino.js +109 -0
- package/lib/cmds/quick.js +122 -0
- package/lib/cmds/report.js +34 -0
- package/lib/cmds/run-aci.js +91 -0
- package/lib/cmds/run-fargate.js +192 -0
- package/lib/cmds/run-lambda.js +96 -0
- package/lib/cmds/run.js +671 -0
- package/lib/console-capture.js +92 -0
- package/lib/console-reporter.js +438 -0
- package/lib/create-bom/built-in-plugins.js +12 -0
- package/lib/create-bom/create-bom.js +301 -0
- package/lib/dispatcher.js +9 -0
- package/lib/dist.js +222 -0
- package/lib/index.js +5 -0
- package/lib/launch-platform.js +439 -0
- package/lib/load-plugins.js +113 -0
- package/lib/platform/aws/aws-cloudwatch.js +106 -0
- package/lib/platform/aws/aws-create-sqs-queue.js +58 -0
- package/lib/platform/aws/aws-ensure-s3-bucket-exists.js +78 -0
- package/lib/platform/aws/aws-get-account-id.js +26 -0
- package/lib/platform/aws/aws-get-bucket-region.js +18 -0
- package/lib/platform/aws/aws-get-credentials.js +28 -0
- package/lib/platform/aws/aws-get-default-region.js +26 -0
- package/lib/platform/aws/aws-whoami.js +15 -0
- package/lib/platform/aws/constants.js +7 -0
- package/lib/platform/aws/iam-cf-templates/aws-iam-fargate-cf-template.yml +219 -0
- package/lib/platform/aws/iam-cf-templates/aws-iam-lambda-cf-template.yml +125 -0
- package/lib/platform/aws/iam-cf-templates/gh-oidc-fargate.yml +241 -0
- package/lib/platform/aws/iam-cf-templates/gh-oidc-lambda.yml +153 -0
- package/lib/platform/aws-ecs/ecs.js +247 -0
- package/lib/platform/aws-ecs/legacy/aws-util.js +134 -0
- package/lib/platform/aws-ecs/legacy/bom.js +528 -0
- package/lib/platform/aws-ecs/legacy/constants.js +27 -0
- package/lib/platform/aws-ecs/legacy/create-s3-client.js +24 -0
- package/lib/platform/aws-ecs/legacy/create-test.js +247 -0
- package/lib/platform/aws-ecs/legacy/errors.js +34 -0
- package/lib/platform/aws-ecs/legacy/find-public-subnets.js +149 -0
- package/lib/platform/aws-ecs/legacy/plugins/artillery-plugin-inspect-script/index.js +27 -0
- package/lib/platform/aws-ecs/legacy/plugins/artillery-plugin-sqs-reporter/azure-aqs.js +80 -0
- package/lib/platform/aws-ecs/legacy/plugins/artillery-plugin-sqs-reporter/index.js +202 -0
- package/lib/platform/aws-ecs/legacy/plugins.js +16 -0
- package/lib/platform/aws-ecs/legacy/run-cluster.js +1994 -0
- package/lib/platform/aws-ecs/legacy/sqs-reporter.js +401 -0
- package/lib/platform/aws-ecs/legacy/tags.js +22 -0
- package/lib/platform/aws-ecs/legacy/test-run-status.js +9 -0
- package/lib/platform/aws-ecs/legacy/time.js +67 -0
- package/lib/platform/aws-ecs/legacy/util.js +97 -0
- package/lib/platform/aws-ecs/worker/Dockerfile +64 -0
- package/lib/platform/aws-ecs/worker/helpers.sh +80 -0
- package/lib/platform/aws-ecs/worker/loadgen-worker +656 -0
- package/lib/platform/aws-lambda/dependencies.js +130 -0
- package/lib/platform/aws-lambda/index.js +734 -0
- package/lib/platform/aws-lambda/lambda-handler/a9-handler-dependencies.js +73 -0
- package/lib/platform/aws-lambda/lambda-handler/a9-handler-helpers.js +43 -0
- package/lib/platform/aws-lambda/lambda-handler/a9-handler-index.js +235 -0
- package/lib/platform/aws-lambda/lambda-handler/package.json +15 -0
- package/lib/platform/aws-lambda/prices.js +29 -0
- package/lib/platform/az/aci.js +694 -0
- package/lib/platform/az/aqs-queue-consumer.js +88 -0
- package/lib/platform/az/regions.js +52 -0
- package/lib/platform/cloud/api.js +72 -0
- package/lib/platform/cloud/cloud.js +448 -0
- package/lib/platform/cloud/http-client.js +19 -0
- package/lib/platform/local/artillery-worker-local.js +154 -0
- package/lib/platform/local/index.js +174 -0
- package/lib/platform/local/worker.js +261 -0
- package/lib/platform/worker-states.js +13 -0
- package/lib/queue-consumer/index.js +56 -0
- package/lib/stash.js +41 -0
- package/lib/telemetry.js +78 -0
- package/lib/util/await-on-ee.js +24 -0
- package/lib/util/generate-id.js +9 -0
- package/lib/util/parse-tag-string.js +21 -0
- package/lib/util/prepare-test-execution-plan.js +216 -0
- package/lib/util/sleep.js +7 -0
- package/lib/util/validate-script.js +132 -0
- package/lib/util.js +294 -0
- package/lib/utils-config.js +31 -0
- package/package.json +323 -0
- package/types.d.ts +317 -0
- package/util.js +1 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
4
|
+
|
|
5
|
+
// TODO: async-ify this
|
|
6
|
+
|
|
7
|
+
const path = require('node:path');
|
|
8
|
+
const fs = require('node:fs');
|
|
9
|
+
const A = require('async');
|
|
10
|
+
const debug = require('debug')('bom');
|
|
11
|
+
const _ = require('lodash');
|
|
12
|
+
const Table = require('cli-table3');
|
|
13
|
+
const { getCustomJsDependencies } = require('../platform/aws-ecs/legacy/bom');
|
|
14
|
+
|
|
15
|
+
const { readScript, parseScript } = require('../util');
|
|
16
|
+
|
|
17
|
+
const BUILTIN_PLUGINS = require('./built-in-plugins');
|
|
18
|
+
|
|
19
|
+
// NOTE: Presumes ALL paths are absolute.
|
|
20
|
+
async function createBOM(absoluteScriptPath, extraFiles, opts, callback) {
|
|
21
|
+
A.waterfall(
|
|
22
|
+
[
|
|
23
|
+
A.constant(absoluteScriptPath),
|
|
24
|
+
readScript,
|
|
25
|
+
parseScript,
|
|
26
|
+
(scriptData, next) => {
|
|
27
|
+
return next(null, {
|
|
28
|
+
opts: {
|
|
29
|
+
scriptData,
|
|
30
|
+
absoluteScriptPath
|
|
31
|
+
},
|
|
32
|
+
localFilePaths: [absoluteScriptPath],
|
|
33
|
+
npmModules: []
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
getPlugins,
|
|
37
|
+
getCustomEngines,
|
|
38
|
+
getCustomJsDependencies,
|
|
39
|
+
getVariableDataFiles,
|
|
40
|
+
// getFileUploadPluginFiles,
|
|
41
|
+
getExtraFiles
|
|
42
|
+
// expandDirectories
|
|
43
|
+
],
|
|
44
|
+
|
|
45
|
+
(err, context) => {
|
|
46
|
+
if (err) {
|
|
47
|
+
return callback(err, null);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
context.localFilePaths = context.localFilePaths.concat(extraFiles);
|
|
51
|
+
|
|
52
|
+
// TODO: Entries in localFilePaths may be directories
|
|
53
|
+
|
|
54
|
+
// Handle case with only one entry, where the string itself
|
|
55
|
+
// will be the common prefix, meaning that when we substring() on it later, we'll
|
|
56
|
+
// get an empty string, ending up with a manifest like:
|
|
57
|
+
// { files:
|
|
58
|
+
// [ { orig: '/Users/h/tmp/artillery/hello.yaml', noPrefix: '' } ],
|
|
59
|
+
// modules: [] }
|
|
60
|
+
//
|
|
61
|
+
let prefix = '';
|
|
62
|
+
if (context.localFilePaths.length === 1) {
|
|
63
|
+
prefix = context.localFilePaths[0].substring(
|
|
64
|
+
0,
|
|
65
|
+
context.localFilePaths[0].length -
|
|
66
|
+
path.basename(context.localFilePaths[0]).length
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// This may still be an empty string if the script path is just 'hello.yml':
|
|
70
|
+
prefix = prefix.length === 0 ? context.localFilePaths[0] : prefix;
|
|
71
|
+
} else {
|
|
72
|
+
prefix = commonPrefix(context.localFilePaths);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
debug('prefix', prefix);
|
|
76
|
+
|
|
77
|
+
//
|
|
78
|
+
// include package.json / package-lock.json / yarn.lock
|
|
79
|
+
//
|
|
80
|
+
let packageDescriptionFiles = ['.npmrc'];
|
|
81
|
+
if (opts.packageJsonPath) {
|
|
82
|
+
packageDescriptionFiles.push(opts.packageJsonPath);
|
|
83
|
+
} else {
|
|
84
|
+
packageDescriptionFiles = packageDescriptionFiles.concat([
|
|
85
|
+
'package.json',
|
|
86
|
+
'package-lock.json',
|
|
87
|
+
'yarn.lock'
|
|
88
|
+
]);
|
|
89
|
+
}
|
|
90
|
+
const dependencyFiles = packageDescriptionFiles.map((s) =>
|
|
91
|
+
path.join(prefix, s)
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
debug(dependencyFiles);
|
|
95
|
+
|
|
96
|
+
dependencyFiles.forEach((p) => {
|
|
97
|
+
try {
|
|
98
|
+
if (fs.statSync(p)) {
|
|
99
|
+
context.localFilePaths.push(p);
|
|
100
|
+
}
|
|
101
|
+
} catch (_ignoredErr) {}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const files = context.localFilePaths.map((p) => {
|
|
105
|
+
return { orig: p, noPrefix: p.substring(prefix.length, p.length) };
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const pkgPath = _.find(files, (f) => {
|
|
109
|
+
return f.noPrefix === 'package.json';
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (pkgPath) {
|
|
113
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath.orig, 'utf8'));
|
|
114
|
+
const pkgDeps = [].concat(
|
|
115
|
+
Object.keys(pkg.dependencies || {}),
|
|
116
|
+
Object.keys(pkg.devDependencies || {})
|
|
117
|
+
);
|
|
118
|
+
context.pkgDeps = pkgDeps;
|
|
119
|
+
context.npmModules = _.uniq(context.npmModules.concat(pkgDeps)).sort();
|
|
120
|
+
} else {
|
|
121
|
+
context.pkgDeps = [];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return callback(null, {
|
|
125
|
+
files: _.uniqWith(files, _.isEqual),
|
|
126
|
+
modules: _.uniq(context.npmModules).filter(
|
|
127
|
+
(m) =>
|
|
128
|
+
m !== 'artillery' &&
|
|
129
|
+
m !== 'playwright' &&
|
|
130
|
+
!m.startsWith('@playwright/')
|
|
131
|
+
),
|
|
132
|
+
pkgDeps: context.pkgDeps
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function getPlugins(context, next) {
|
|
139
|
+
const environmentPlugins = _.reduce(
|
|
140
|
+
_.get(context, 'opts.scriptData.config.environments', {}),
|
|
141
|
+
function getEnvironmentPlugins(acc, envSpec, _envName) {
|
|
142
|
+
acc = acc.concat(Object.keys(envSpec.plugins || []));
|
|
143
|
+
return acc;
|
|
144
|
+
},
|
|
145
|
+
[]
|
|
146
|
+
);
|
|
147
|
+
const pluginNames = Object.keys(
|
|
148
|
+
_.get(context, 'opts.scriptData.config.plugins', {})
|
|
149
|
+
).concat(environmentPlugins);
|
|
150
|
+
|
|
151
|
+
const pluginPackages = _.uniq(
|
|
152
|
+
pluginNames
|
|
153
|
+
.filter((p) => BUILTIN_PLUGINS.indexOf(p) === -1)
|
|
154
|
+
.map((p) => `artillery-plugin-${p}`)
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
debug(pluginPackages);
|
|
158
|
+
context.npmModules = context.npmModules.concat(pluginPackages);
|
|
159
|
+
|
|
160
|
+
return next(null, context);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function getCustomEngines(context, next) {
|
|
164
|
+
// TODO: Environment-specific engines (see getPlugins())
|
|
165
|
+
const engineNames = _.uniq(
|
|
166
|
+
Object.keys(_.get(context, 'opts.scriptData.config.engines', {}))
|
|
167
|
+
);
|
|
168
|
+
const enginePackages = engineNames.map((x) => `artillery-engine-${x}`);
|
|
169
|
+
context.npmModules = context.npmModules.concat(enginePackages);
|
|
170
|
+
|
|
171
|
+
return next(null, context);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function getVariableDataFiles(context, next) {
|
|
175
|
+
// NOTE: Presuming that the script has been run through the functions
|
|
176
|
+
// that normalize the config.payload definition (presume it's an array).
|
|
177
|
+
// Also assuming that context.opts.scriptData contains both the config and
|
|
178
|
+
// the scenarios section.
|
|
179
|
+
|
|
180
|
+
// Iterate over environments
|
|
181
|
+
|
|
182
|
+
function resolvePayloadPaths(obj) {
|
|
183
|
+
const result = [];
|
|
184
|
+
if (obj.payload) {
|
|
185
|
+
if (_.isArray(obj.payload)) {
|
|
186
|
+
obj.payload.forEach((payloadSpec) => {
|
|
187
|
+
result.push(
|
|
188
|
+
path.resolve(
|
|
189
|
+
path.dirname(context.opts.absoluteScriptPath),
|
|
190
|
+
payloadSpec.path
|
|
191
|
+
)
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
} else if (_.isObject(obj.payload)) {
|
|
195
|
+
// NOTE: isObject returns true for arrays, so this branch must
|
|
196
|
+
// come second.
|
|
197
|
+
result.push(
|
|
198
|
+
path.resolve(
|
|
199
|
+
path.dirname(context.opts.absoluteScriptPath),
|
|
200
|
+
obj.payload.path
|
|
201
|
+
)
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
context.localFilePaths = context.localFilePaths.concat(
|
|
209
|
+
resolvePayloadPaths(context.opts.scriptData.config)
|
|
210
|
+
);
|
|
211
|
+
context.opts.scriptData.config.environments =
|
|
212
|
+
context.opts.scriptData.config.environments || {};
|
|
213
|
+
Object.keys(context.opts.scriptData.config.environments).forEach(
|
|
214
|
+
(envName) => {
|
|
215
|
+
const envSpec = context.opts.scriptData.config.environments[envName];
|
|
216
|
+
context.localFilePaths = context.localFilePaths.concat(
|
|
217
|
+
resolvePayloadPaths(envSpec)
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
);
|
|
221
|
+
return next(null, context);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function getExtraFiles(context, next) {
|
|
225
|
+
if (
|
|
226
|
+
context.opts.scriptData.config?.includeFiles
|
|
227
|
+
) {
|
|
228
|
+
const absPaths = _.map(context.opts.scriptData.config.includeFiles, (p) => {
|
|
229
|
+
const includePath = path.resolve(
|
|
230
|
+
path.dirname(context.opts.absoluteScriptPath),
|
|
231
|
+
p
|
|
232
|
+
);
|
|
233
|
+
debug('includeFile:', includePath);
|
|
234
|
+
return includePath;
|
|
235
|
+
});
|
|
236
|
+
context.localFilePaths = context.localFilePaths.concat(absPaths);
|
|
237
|
+
return next(null, context);
|
|
238
|
+
} else {
|
|
239
|
+
return next(null, context);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function commonPrefix(paths, separator) {
|
|
244
|
+
if (
|
|
245
|
+
!paths ||
|
|
246
|
+
paths.length === 0 ||
|
|
247
|
+
paths.filter((s) => typeof s !== 'string').length > 0
|
|
248
|
+
) {
|
|
249
|
+
return '';
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (paths.includes('/')) {
|
|
253
|
+
return '/';
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const sep = separator ? separator : path.sep;
|
|
257
|
+
|
|
258
|
+
const splitPaths = paths.map((p) => p.split(sep));
|
|
259
|
+
const shortestPath = splitPaths.reduce((a, b) => {
|
|
260
|
+
return a.length < b.length ? a : b;
|
|
261
|
+
}, splitPaths[0]);
|
|
262
|
+
|
|
263
|
+
let furthestIndex = shortestPath.length;
|
|
264
|
+
|
|
265
|
+
for (const p of splitPaths) {
|
|
266
|
+
for (let i = 0; i < furthestIndex; i++) {
|
|
267
|
+
if (p[i] !== shortestPath[i]) {
|
|
268
|
+
furthestIndex = i;
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const joined = shortestPath.slice(0, furthestIndex).join(sep);
|
|
275
|
+
|
|
276
|
+
if (joined.length > 0) {
|
|
277
|
+
// Check if joined path already ends with separator which
|
|
278
|
+
// will happen when input is a root drive on Windows, e.g. "C:\"
|
|
279
|
+
return joined.endsWith(sep) ? joined : joined + sep;
|
|
280
|
+
} else {
|
|
281
|
+
return '';
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function prettyPrint(manifest) {
|
|
286
|
+
const t = new Table({ head: ['Name', 'Type', 'Notes'] });
|
|
287
|
+
for (const f of manifest.files) {
|
|
288
|
+
t.push([f.noPrefix, 'file']);
|
|
289
|
+
}
|
|
290
|
+
for (const m of manifest.modules) {
|
|
291
|
+
t.push([
|
|
292
|
+
m,
|
|
293
|
+
'package',
|
|
294
|
+
manifest.pkgDeps.indexOf(m) === -1 ? 'not in package.json' : ''
|
|
295
|
+
]);
|
|
296
|
+
}
|
|
297
|
+
artillery.log(t.toString());
|
|
298
|
+
artillery.log();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
module.exports = { createBOM, commonPrefix, prettyPrint };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const core = require('@artilleryio/int-core');
|
|
8
|
+
|
|
9
|
+
module.exports = core;
|
package/lib/dist.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const assert = require('node:assert');
|
|
8
|
+
const L = require('lodash');
|
|
9
|
+
const isIdlePhase = require('@artilleryio/int-core').isIdlePhase;
|
|
10
|
+
|
|
11
|
+
module.exports = divideWork;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
*
|
|
15
|
+
* Create a number of scripts for workers from the script given to use by user.
|
|
16
|
+
*
|
|
17
|
+
* @param {Script} script
|
|
18
|
+
* @param {number} numWorkers
|
|
19
|
+
* @returns {Script[]} array of scripts distributed representing the work for each worker
|
|
20
|
+
*
|
|
21
|
+
* @todo: Distribute payload data to workers
|
|
22
|
+
*/
|
|
23
|
+
function divideWork(script, numWorkers) {
|
|
24
|
+
const workerScripts = createWorkerScriptBases(numWorkers, script);
|
|
25
|
+
for (const phase of script.config.phases) {
|
|
26
|
+
// switching on phase type to determine how to distribute work
|
|
27
|
+
switch (true) {
|
|
28
|
+
case !!phase.rampTo: {
|
|
29
|
+
handleRampToPhase(phase, numWorkers, workerScripts);
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
case !!phase.arrivalRate: {
|
|
33
|
+
handleArrivalRatePhase(phase, numWorkers, workerScripts);
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
case !!phase.arrivalCount: {
|
|
37
|
+
// arrivalCount is executed in the first worker
|
|
38
|
+
// and replaced with a `pause` phase in the others
|
|
39
|
+
handleArrivalCountPhase(workerScripts, phase, numWorkers);
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
case !!phase.pause: {
|
|
43
|
+
// nothing to adjust here, pause is executed in all workers
|
|
44
|
+
for (let i = 0; i < numWorkers; i++) {
|
|
45
|
+
workerScripts[i].config.phases.push(L.cloneDeep(phase));
|
|
46
|
+
}
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
default: {
|
|
50
|
+
console.log(
|
|
51
|
+
'Unknown phase spec definition, skipping.\n%j\n' +
|
|
52
|
+
'This should not happen',
|
|
53
|
+
phase
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Filter out scripts which have only idle phases
|
|
60
|
+
const result = workerScripts.filter(
|
|
61
|
+
(workerScript) => !workerScript.config.phases.every(isIdlePhase)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// Add worker and totalWorkers properties to phases
|
|
65
|
+
const hasPayload = scriptHasPayload(script);
|
|
66
|
+
for (let i = 0; i < result.length; i++) {
|
|
67
|
+
for (const phase of result[i].config.phases) {
|
|
68
|
+
phase.totalWorkers = result.length;
|
|
69
|
+
phase.worker = i + 1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Distribute payload data to workers
|
|
73
|
+
if (hasPayload) {
|
|
74
|
+
for (
|
|
75
|
+
let payloadIdx = 0;
|
|
76
|
+
payloadIdx < script.config.payload.length;
|
|
77
|
+
payloadIdx++
|
|
78
|
+
) {
|
|
79
|
+
// If there are more workers than payload data, then we will repeat the payload data
|
|
80
|
+
const scriptPayloadData = script.config.payload[payloadIdx].data;
|
|
81
|
+
const idxToMatch = i % scriptPayloadData.length;
|
|
82
|
+
result[i].config.payload[payloadIdx].data = scriptPayloadData.filter(
|
|
83
|
+
(_, index) => index % result.length === idxToMatch
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function scriptHasPayload(script) {
|
|
93
|
+
return script.config.payload && script.config.payload.length > 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function handleArrivalCountPhase(workerScripts, phase, numWorkers) {
|
|
97
|
+
workerScripts[0].config.phases.push(L.cloneDeep(phase));
|
|
98
|
+
|
|
99
|
+
for (let i = 1; i < numWorkers; i++) {
|
|
100
|
+
workerScripts[i].config.phases.push({
|
|
101
|
+
name: phase.name,
|
|
102
|
+
pause: phase.duration
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function handleArrivalRatePhase(phase, numWorkers, workerScripts) {
|
|
108
|
+
const rates = distribute(phase.arrivalRate, numWorkers);
|
|
109
|
+
const activeWorkers = rates.reduce(
|
|
110
|
+
(acc, rate) => acc + (rate > 0 ? 1 : 0),
|
|
111
|
+
0
|
|
112
|
+
);
|
|
113
|
+
const maxVusers = phase.maxVusers
|
|
114
|
+
? distribute(phase.maxVusers, activeWorkers)
|
|
115
|
+
: false;
|
|
116
|
+
for (let i = 0; i < numWorkers; i++) {
|
|
117
|
+
const newPhase = L.cloneDeep(phase);
|
|
118
|
+
newPhase.arrivalRate = rates[i];
|
|
119
|
+
if (maxVusers) {
|
|
120
|
+
newPhase.maxVusers = maxVusers[i];
|
|
121
|
+
}
|
|
122
|
+
workerScripts[i].config.phases.push(newPhase);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function handleRampToPhase(phase, numWorkers, workerScripts) {
|
|
127
|
+
phase.arrivalRate = phase.arrivalRate || 0;
|
|
128
|
+
|
|
129
|
+
const rate = phase.arrivalRate / numWorkers;
|
|
130
|
+
const ramp = phase.rampTo / numWorkers;
|
|
131
|
+
const activeWorkers = rate > 0 || ramp > 0 ? numWorkers : 0;
|
|
132
|
+
const maxVusers = phase.maxVusers
|
|
133
|
+
? distribute(phase.maxVusers, activeWorkers)
|
|
134
|
+
: false;
|
|
135
|
+
|
|
136
|
+
for (let i = 0; i < numWorkers; i++) {
|
|
137
|
+
const newPhase = L.cloneDeep(phase);
|
|
138
|
+
newPhase.arrivalRate = rate;
|
|
139
|
+
newPhase.rampTo = ramp;
|
|
140
|
+
if (maxVusers) {
|
|
141
|
+
newPhase.maxVusers = maxVusers[i];
|
|
142
|
+
}
|
|
143
|
+
workerScripts[i].config.phases.push(newPhase);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function createWorkerScriptBases(numWorkers, script) {
|
|
148
|
+
const bases = [];
|
|
149
|
+
for (let i = 0; i < numWorkers; i++) {
|
|
150
|
+
const newScript = L.cloneDeep({
|
|
151
|
+
...script,
|
|
152
|
+
config: {
|
|
153
|
+
...script.config,
|
|
154
|
+
phases: [],
|
|
155
|
+
...(scriptHasPayload(script) && {
|
|
156
|
+
payload: script.config.payload.map((payload) => {
|
|
157
|
+
return {
|
|
158
|
+
...payload,
|
|
159
|
+
data: []
|
|
160
|
+
};
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
// 'before' and 'after' hooks are executed in the main thread
|
|
166
|
+
delete newScript.before;
|
|
167
|
+
delete newScript.after;
|
|
168
|
+
|
|
169
|
+
bases.push(newScript);
|
|
170
|
+
}
|
|
171
|
+
return bases;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function distribute(m, n) {
|
|
175
|
+
m = Number(m);
|
|
176
|
+
n = Number(n);
|
|
177
|
+
|
|
178
|
+
const result = [];
|
|
179
|
+
|
|
180
|
+
if (m < n) {
|
|
181
|
+
for (let i = 0; i < n; i++) {
|
|
182
|
+
result.push(i < m ? 1 : 0);
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
const baseCount = Math.floor(m / n);
|
|
186
|
+
let extraItems = m % n;
|
|
187
|
+
for (let i = 0; i < n; i++) {
|
|
188
|
+
result.push(baseCount);
|
|
189
|
+
if (extraItems > 0) {
|
|
190
|
+
result[i]++;
|
|
191
|
+
extraItems--;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
assert(m === sum(result), `${m} === ${sum(result)}`);
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function sum(a) {
|
|
200
|
+
let result = 0;
|
|
201
|
+
for (let i = 0; i < a.length; i++) {
|
|
202
|
+
result += a[i];
|
|
203
|
+
}
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (require.main === module) {
|
|
208
|
+
console.log(distribute(1, 4));
|
|
209
|
+
console.log(distribute(1, 10));
|
|
210
|
+
console.log(distribute(4, 4));
|
|
211
|
+
console.log(distribute(87, 4));
|
|
212
|
+
console.log(distribute(50, 8));
|
|
213
|
+
console.log(distribute(39, 20));
|
|
214
|
+
console.log(distribute(20, 4));
|
|
215
|
+
console.log(distribute(19, 4));
|
|
216
|
+
console.log(distribute(20, 3));
|
|
217
|
+
console.log(distribute(61, 4));
|
|
218
|
+
console.log(distribute(121, 4));
|
|
219
|
+
console.log(distribute(32, 3));
|
|
220
|
+
console.log(distribute(700, 31));
|
|
221
|
+
console.log(distribute(700, 29));
|
|
222
|
+
}
|