@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
package/lib/cmds/run.js
ADDED
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
const { Command, Flags, Args } = require('@oclif/core');
|
|
2
|
+
const { CommonRunFlags } = require('../cli/common-flags');
|
|
3
|
+
|
|
4
|
+
const p = require('node:util').promisify;
|
|
5
|
+
const _csv = require('csv-parse');
|
|
6
|
+
const debug = require('debug')('commands:run');
|
|
7
|
+
const dotenv = require('dotenv');
|
|
8
|
+
const _ = require('lodash');
|
|
9
|
+
|
|
10
|
+
const fs = require('node:fs');
|
|
11
|
+
const path = require('node:path');
|
|
12
|
+
const crypto = require('node:crypto');
|
|
13
|
+
const os = require('node:os');
|
|
14
|
+
const createLauncher = require('../launch-platform');
|
|
15
|
+
const createConsoleReporter = require('../../console-reporter');
|
|
16
|
+
|
|
17
|
+
const moment = require('moment');
|
|
18
|
+
|
|
19
|
+
const { SSMS } = require('@artilleryio/int-core').ssms;
|
|
20
|
+
const telemetry = require('../telemetry');
|
|
21
|
+
|
|
22
|
+
const { Plugin: CloudPlugin } = require('../platform/cloud/cloud');
|
|
23
|
+
|
|
24
|
+
const parseTagString = require('../util/parse-tag-string');
|
|
25
|
+
|
|
26
|
+
const generateId = require('../util/generate-id');
|
|
27
|
+
const prepareTestExecutionPlan = require('../util/prepare-test-execution-plan');
|
|
28
|
+
|
|
29
|
+
class RunCommand extends Command {
|
|
30
|
+
static aliases = ['run'];
|
|
31
|
+
// Enable multiple args:
|
|
32
|
+
static strict = false;
|
|
33
|
+
|
|
34
|
+
async run() {
|
|
35
|
+
const { flags, argv, args } = await this.parse(RunCommand);
|
|
36
|
+
|
|
37
|
+
if (flags.platform === 'aws:ecs') {
|
|
38
|
+
// Delegate to existing implementation
|
|
39
|
+
const RunFargateCommand = require('./run-fargate');
|
|
40
|
+
return await RunFargateCommand.run(argv);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await RunCommand.runCommandImplementation(flags, argv, args);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// async catch(err) {
|
|
47
|
+
// throw err;
|
|
48
|
+
// }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Line no. 2 onwards is the description in help output
|
|
52
|
+
RunCommand.description = `run a test script locally or on AWS Lambda
|
|
53
|
+
Run a test script
|
|
54
|
+
|
|
55
|
+
Examples:
|
|
56
|
+
|
|
57
|
+
To run a test script in my-test.yml to completion from the local machine:
|
|
58
|
+
|
|
59
|
+
$ artillery run my-test.yml
|
|
60
|
+
|
|
61
|
+
To run a test script but override target dynamically:
|
|
62
|
+
|
|
63
|
+
$ artillery run -t https://app2.acmecorp.internal my-test.yml
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
// TODO: Link to an Examples section in the docs
|
|
67
|
+
|
|
68
|
+
RunCommand.flags = {
|
|
69
|
+
...CommonRunFlags,
|
|
70
|
+
// TODO: Deprecation notices for commands below:
|
|
71
|
+
payload: Flags.string({
|
|
72
|
+
char: 'p',
|
|
73
|
+
description: 'Specify a CSV file for dynamic data'
|
|
74
|
+
}),
|
|
75
|
+
solo: Flags.boolean({
|
|
76
|
+
char: 's',
|
|
77
|
+
description: 'Create only one virtual user'
|
|
78
|
+
}),
|
|
79
|
+
platform: Flags.string({
|
|
80
|
+
description: 'Runtime platform',
|
|
81
|
+
default: 'local',
|
|
82
|
+
options: ['local', 'aws:lambda', 'az:aci']
|
|
83
|
+
}),
|
|
84
|
+
'platform-opt': Flags.string({
|
|
85
|
+
description:
|
|
86
|
+
'Set a platform-specific option, e.g. --platform-opt region=eu-west-1 for AWS Lambda',
|
|
87
|
+
multiple: true
|
|
88
|
+
}),
|
|
89
|
+
count: Flags.string({
|
|
90
|
+
// locally defaults to number of CPUs with mode = distribute
|
|
91
|
+
default: '1'
|
|
92
|
+
})
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
RunCommand.args = {
|
|
96
|
+
script: Args.string({
|
|
97
|
+
name: 'script',
|
|
98
|
+
required: true
|
|
99
|
+
})
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
let cloud;
|
|
103
|
+
RunCommand.runCommandImplementation = async (flags, argv, args) => {
|
|
104
|
+
// Collect all input files for reading/parsing - via args, --config, or -i
|
|
105
|
+
const inputFiles = argv.concat(flags.input || [], flags.config || []);
|
|
106
|
+
|
|
107
|
+
const tagResult = parseTagString(flags.tags);
|
|
108
|
+
if (tagResult.errors.length > 0) {
|
|
109
|
+
console.log(
|
|
110
|
+
'WARNING: could not parse some tags:',
|
|
111
|
+
tagResult.errors.join(', ')
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (tagResult.tags.length > 10) {
|
|
116
|
+
console.log('A maximum of 10 tags is supported');
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// TODO: Move into PlatformLocal
|
|
121
|
+
if (flags.dotenv) {
|
|
122
|
+
const dotEnvPath = path.resolve(process.cwd(), flags.dotenv);
|
|
123
|
+
try {
|
|
124
|
+
fs.statSync(dotEnvPath);
|
|
125
|
+
} catch (_err) {
|
|
126
|
+
console.log(`WARNING: could not read dotenv file: ${flags.dotenv}`);
|
|
127
|
+
}
|
|
128
|
+
dotenv.config({ path: dotEnvPath });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (flags.output) {
|
|
132
|
+
if (!checkDirExists(flags.output)) {
|
|
133
|
+
console.error('Path does not exist:', flags.output);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const testRunId = process.env.ARTILLERY_TEST_RUN_ID || generateId('t');
|
|
139
|
+
console.log('Test run id:', testRunId);
|
|
140
|
+
global.artillery.testRunId = testRunId;
|
|
141
|
+
cloud = new CloudPlugin(null, null, { flags });
|
|
142
|
+
global.artillery.cloudEnabled = cloud.enabled;
|
|
143
|
+
|
|
144
|
+
if (cloud.enabled) {
|
|
145
|
+
try {
|
|
146
|
+
await cloud.init();
|
|
147
|
+
} catch (err) {
|
|
148
|
+
if (err.name === 'CloudAPIKeyMissing') {
|
|
149
|
+
console.error(
|
|
150
|
+
'Error: API key is required to record test results to Artillery Cloud'
|
|
151
|
+
);
|
|
152
|
+
console.error(
|
|
153
|
+
'See https://docs.art/get-started-cloud for more information'
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
await gracefulShutdown({ exitCode: 7 });
|
|
157
|
+
} else if (err.name === 'APIKeyUnauthorized') {
|
|
158
|
+
console.error(
|
|
159
|
+
'Error: API key is not recognized or is not authorized to record tests'
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
await gracefulShutdown({ exitCode: 7 });
|
|
163
|
+
} else if (err.name === 'PingFailed') {
|
|
164
|
+
console.error(
|
|
165
|
+
'Error: unable to reach Artillery Cloud API. This could be due to firewall restrictions on your network'
|
|
166
|
+
);
|
|
167
|
+
console.log('https://docs.art/cloud/err-ping');
|
|
168
|
+
await gracefulShutdown({ exitCode: 7 });
|
|
169
|
+
} else {
|
|
170
|
+
console.error(
|
|
171
|
+
'Error: something went wrong connecting to Artillery Cloud'
|
|
172
|
+
);
|
|
173
|
+
console.error('Check https://status.artillery.io for status updates');
|
|
174
|
+
console.error(err);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let script;
|
|
180
|
+
try {
|
|
181
|
+
script = await prepareTestExecutionPlan(inputFiles, flags, args);
|
|
182
|
+
} catch (err) {
|
|
183
|
+
console.error('Error:', err.message);
|
|
184
|
+
await gracefulShutdown({ exitCode: 1 });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
var runnerOpts = {
|
|
188
|
+
environment: flags.environment,
|
|
189
|
+
// This is used in the worker to resolve
|
|
190
|
+
// the path to the processor module
|
|
191
|
+
scriptPath: args.script,
|
|
192
|
+
// TODO: This should be an array of files, like inputFiles above
|
|
193
|
+
absoluteScriptPath: path.resolve(process.cwd(), args.script),
|
|
194
|
+
plugins: [],
|
|
195
|
+
scenarioName: flags['scenario-name']
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// Set "name" tag if not set explicitly
|
|
199
|
+
if (tagResult.tags.filter((t) => t.name === 'name').length === 0) {
|
|
200
|
+
tagResult.tags.push({
|
|
201
|
+
name: 'name',
|
|
202
|
+
value: path.basename(runnerOpts.scriptPath)
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
// Override the "name" tag with the value of --name if set
|
|
206
|
+
if (flags.name) {
|
|
207
|
+
for (const t of tagResult.tags) {
|
|
208
|
+
if (t.name === 'name') {
|
|
209
|
+
t.value = flags.name;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (flags.config) {
|
|
215
|
+
runnerOpts.absoluteConfigPath = path.resolve(process.cwd(), flags.config);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (process.env.WORKERS) {
|
|
219
|
+
runnerOpts.count = parseInt(process.env.WORKERS, 10) || 1;
|
|
220
|
+
}
|
|
221
|
+
if (flags.solo) {
|
|
222
|
+
runnerOpts.count = 1;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const platformConfig = {};
|
|
226
|
+
if (flags['platform-opt']) {
|
|
227
|
+
for (const opt of flags['platform-opt']) {
|
|
228
|
+
const [k, v] = opt.split('=');
|
|
229
|
+
platformConfig[k] = v;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const launcherOpts = {
|
|
234
|
+
platform: flags.platform,
|
|
235
|
+
platformConfig,
|
|
236
|
+
mode: flags.platform === 'local' ? 'distribute' : 'multiply',
|
|
237
|
+
count: parseInt(flags.count || 1, 10),
|
|
238
|
+
cliArgs: flags,
|
|
239
|
+
testRunId
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
var launcher = await createLauncher(
|
|
243
|
+
script,
|
|
244
|
+
script.config.payload,
|
|
245
|
+
runnerOpts,
|
|
246
|
+
launcherOpts
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
if (!launcher) {
|
|
250
|
+
console.log('Failed to create launcher');
|
|
251
|
+
await gracefulShutdown({ exitCode: 1 });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const intermediates = [];
|
|
255
|
+
|
|
256
|
+
const metricsToSuppress = getPluginMetricsToSuppress(script);
|
|
257
|
+
// TODO: Wire up workerLog or something like that
|
|
258
|
+
const consoleReporter = createConsoleReporter(launcher.events, {
|
|
259
|
+
quiet: flags.quiet || false,
|
|
260
|
+
metricsToSuppress
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
var reporters = [consoleReporter];
|
|
264
|
+
if (process.env.CUSTOM_REPORTERS) {
|
|
265
|
+
const customReporterNames = process.env.CUSTOM_REPORTERS.split(',');
|
|
266
|
+
customReporterNames.forEach((name) => {
|
|
267
|
+
const createReporter = require(name);
|
|
268
|
+
const reporter = createReporter(launcher.events, flags);
|
|
269
|
+
reporters.push(reporter);
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
launcher.events.on('phaseStarted', (_phase) => {});
|
|
274
|
+
|
|
275
|
+
launcher.events.on('stats', (stats) => {
|
|
276
|
+
if (artillery.runtimeOptions.legacyReporting) {
|
|
277
|
+
const report = SSMS.legacyReport(stats).report();
|
|
278
|
+
intermediates.push(report);
|
|
279
|
+
} else {
|
|
280
|
+
intermediates.push(stats);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
launcher.events.on('done', async (stats) => {
|
|
285
|
+
let report;
|
|
286
|
+
if (artillery.runtimeOptions.legacyReporting) {
|
|
287
|
+
report = SSMS.legacyReport(stats).report();
|
|
288
|
+
report.phases = _.get(script, 'config.phases', []);
|
|
289
|
+
} else {
|
|
290
|
+
report = stats;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (flags.output) {
|
|
294
|
+
const logfile = getLogFilename(flags.output);
|
|
295
|
+
if (!flags.quiet) {
|
|
296
|
+
console.log('Log file: %s', logfile);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
for (const ix of intermediates) {
|
|
300
|
+
delete ix.histograms;
|
|
301
|
+
ix.histograms = ix.summaries;
|
|
302
|
+
}
|
|
303
|
+
delete report.histograms;
|
|
304
|
+
report.histograms = report.summaries;
|
|
305
|
+
|
|
306
|
+
fs.writeFileSync(
|
|
307
|
+
logfile,
|
|
308
|
+
JSON.stringify(
|
|
309
|
+
{
|
|
310
|
+
aggregate: report,
|
|
311
|
+
intermediate: intermediates
|
|
312
|
+
},
|
|
313
|
+
null,
|
|
314
|
+
2
|
|
315
|
+
),
|
|
316
|
+
{ flag: 'w' }
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// This is used in the beforeExit event handler in gracefulShutdown
|
|
321
|
+
finalReport = report;
|
|
322
|
+
await gracefulShutdown();
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
global.artillery.ext({
|
|
326
|
+
ext: 'beforeExit',
|
|
327
|
+
method: async (event) => {
|
|
328
|
+
try {
|
|
329
|
+
const duration = Math.round(
|
|
330
|
+
(event.report?.lastMetricAt - event.report?.firstMetricAt) / 1000
|
|
331
|
+
);
|
|
332
|
+
await sendTelemetry(script, flags, { duration });
|
|
333
|
+
} catch (_err) {}
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
global.artillery.globalEvents.emit('test:init', {
|
|
338
|
+
flags,
|
|
339
|
+
testRunId,
|
|
340
|
+
tags: tagResult.tags,
|
|
341
|
+
metadata: {
|
|
342
|
+
testId: testRunId,
|
|
343
|
+
startedAt: Date.now(),
|
|
344
|
+
count: runnerOpts.count || Number(flags.count),
|
|
345
|
+
tags: tagResult.tags,
|
|
346
|
+
launchType: flags.platform,
|
|
347
|
+
artilleryVersion: {
|
|
348
|
+
core: global.artillery.version
|
|
349
|
+
},
|
|
350
|
+
// Properties from the runnable script object:
|
|
351
|
+
testConfig: {
|
|
352
|
+
target: script.config.target,
|
|
353
|
+
phases: script.config.phases,
|
|
354
|
+
plugins: script.config.plugins,
|
|
355
|
+
environment: script._environment,
|
|
356
|
+
scriptPath: script._scriptPath,
|
|
357
|
+
configPath: script._configPath
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
launcher.run();
|
|
363
|
+
|
|
364
|
+
var finalReport = {};
|
|
365
|
+
var shuttingDown = false;
|
|
366
|
+
process.on('SIGINT', async () => {
|
|
367
|
+
gracefulShutdown({ earlyStop: true, exitCode: 130 });
|
|
368
|
+
});
|
|
369
|
+
process.on('SIGTERM', async () => {
|
|
370
|
+
gracefulShutdown({ earlyStop: true, exitCode: 143 });
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
async function gracefulShutdown(opts = { exitCode: 0 }) {
|
|
374
|
+
debug('shutting down 🦑');
|
|
375
|
+
if (shuttingDown) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
debug('Graceful shutdown initiated');
|
|
380
|
+
|
|
381
|
+
shuttingDown = true;
|
|
382
|
+
global.artillery.globalEvents.emit('shutdown:start', opts);
|
|
383
|
+
|
|
384
|
+
// Run beforeExit first, and then onShutdown
|
|
385
|
+
|
|
386
|
+
const ps = [];
|
|
387
|
+
for (const e of global.artillery.extensionEvents) {
|
|
388
|
+
const testInfo = { endTime: Date.now() };
|
|
389
|
+
if (e.ext === 'beforeExit') {
|
|
390
|
+
ps.push(
|
|
391
|
+
e.method({
|
|
392
|
+
...opts,
|
|
393
|
+
report: finalReport,
|
|
394
|
+
flags,
|
|
395
|
+
runnerOpts,
|
|
396
|
+
testInfo
|
|
397
|
+
})
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
await Promise.allSettled(ps);
|
|
402
|
+
|
|
403
|
+
const ps2 = [];
|
|
404
|
+
for (const e of global.artillery.extensionEvents) {
|
|
405
|
+
if (e.ext === 'onShutdown') {
|
|
406
|
+
ps2.push(e.method(opts));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
await Promise.allSettled(ps2);
|
|
410
|
+
|
|
411
|
+
if (launcher) {
|
|
412
|
+
await launcher.shutdown();
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
await (async () => {
|
|
416
|
+
if (reporters) {
|
|
417
|
+
for (const r of reporters) {
|
|
418
|
+
if (r.cleanup) {
|
|
419
|
+
try {
|
|
420
|
+
await p(r.cleanup.bind(r))();
|
|
421
|
+
} catch (cleanupErr) {
|
|
422
|
+
debug(cleanupErr);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (
|
|
429
|
+
global.artillery.hasTypescriptProcessor &&
|
|
430
|
+
!process.env.ARTILLERY_TS_KEEP_BUNDLE
|
|
431
|
+
) {
|
|
432
|
+
try {
|
|
433
|
+
fs.unlinkSync(global.artillery.hasTypescriptProcessor);
|
|
434
|
+
} catch (err) {
|
|
435
|
+
console.log(
|
|
436
|
+
`WARNING: Failed to remove typescript bundled file: ${global.artillery.hasTypescriptProcessor}`
|
|
437
|
+
);
|
|
438
|
+
console.log(err);
|
|
439
|
+
}
|
|
440
|
+
try {
|
|
441
|
+
fs.rmdirSync(path.dirname(global.artillery.hasTypescriptProcessor));
|
|
442
|
+
} catch (_err) {}
|
|
443
|
+
}
|
|
444
|
+
debug('Cleanup finished');
|
|
445
|
+
process.exit(artillery.suggestedExitCode || opts.exitCode);
|
|
446
|
+
})();
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
global.artillery.shutdown = gracefulShutdown;
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
async function sendTelemetry(script, flags, extraProps) {
|
|
453
|
+
if (process.env.WORKER_ID) {
|
|
454
|
+
debug('Telemetry: Running in cloud worker, skipping test run event');
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function hash(str) {
|
|
459
|
+
return crypto.createHash('sha1').update(str).digest('base64');
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const properties = {};
|
|
463
|
+
|
|
464
|
+
if (script.config?.__createdByQuickCommand) {
|
|
465
|
+
properties.quick = true;
|
|
466
|
+
}
|
|
467
|
+
if (cloud?.enabled && cloud.user) {
|
|
468
|
+
properties.cloud = cloud.user;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
properties.solo = flags.solo;
|
|
472
|
+
try {
|
|
473
|
+
// One-way hash of target endpoint:
|
|
474
|
+
if (script.config?.target) {
|
|
475
|
+
const targetHash = hash(script.config.target);
|
|
476
|
+
properties.targetHash = targetHash;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (flags.target) {
|
|
480
|
+
const targetHash = hash(flags.target);
|
|
481
|
+
properties.targetHash = targetHash;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
properties.platform = flags.platform;
|
|
485
|
+
|
|
486
|
+
properties.count = flags.count;
|
|
487
|
+
|
|
488
|
+
if (properties.targetHash) {
|
|
489
|
+
properties.distinctId = properties.targetHash;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
let macaddr;
|
|
493
|
+
const nonInternalIpv6Interfaces = [];
|
|
494
|
+
for (const [_iface, descrs] of Object.entries(os.networkInterfaces())) {
|
|
495
|
+
for (const o of descrs) {
|
|
496
|
+
if (o.internal === true) {
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
//prefer ipv4 interface when available
|
|
501
|
+
if (o.family !== 'IPv4') {
|
|
502
|
+
nonInternalIpv6Interfaces.push(o);
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
macaddr = o.mac;
|
|
507
|
+
break;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
//default to first ipv6 interface if no ipv4 interface is available
|
|
512
|
+
if (!macaddr && nonInternalIpv6Interfaces.length > 0) {
|
|
513
|
+
macaddr = nonInternalIpv6Interfaces[0].mac;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (macaddr) {
|
|
517
|
+
properties.macHash = hash(macaddr);
|
|
518
|
+
}
|
|
519
|
+
properties.hostnameHash = hash(os.hostname());
|
|
520
|
+
properties.usernameHash = hash(os.userInfo().username);
|
|
521
|
+
|
|
522
|
+
if (script.config?.engines) {
|
|
523
|
+
properties.loadsEngines = true;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
properties.enginesUsed = [];
|
|
527
|
+
const OSS_ENGINES = [
|
|
528
|
+
'http',
|
|
529
|
+
'socketio',
|
|
530
|
+
'ws',
|
|
531
|
+
|
|
532
|
+
'playwright',
|
|
533
|
+
'kinesis',
|
|
534
|
+
'socketio-v3',
|
|
535
|
+
'rediscluster',
|
|
536
|
+
'kafka',
|
|
537
|
+
'tcp',
|
|
538
|
+
'grpc',
|
|
539
|
+
'meteor',
|
|
540
|
+
'graphql-ws',
|
|
541
|
+
'ldap',
|
|
542
|
+
'lambda'
|
|
543
|
+
];
|
|
544
|
+
|
|
545
|
+
for (const scenario of script.scenarios || []) {
|
|
546
|
+
if (OSS_ENGINES.indexOf(scenario.engine || 'http') > -1) {
|
|
547
|
+
if (properties.enginesUsed.indexOf(scenario.engine || 'http') === -1) {
|
|
548
|
+
properties.enginesUsed.push(scenario.engine || 'http');
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Official plugins:
|
|
554
|
+
if (script.config.plugins) {
|
|
555
|
+
properties.plugins = true;
|
|
556
|
+
properties.officialPlugins = [];
|
|
557
|
+
const OFFICIAL_PLUGINS = [
|
|
558
|
+
'apdex',
|
|
559
|
+
'expect',
|
|
560
|
+
'publish-metrics',
|
|
561
|
+
'metrics-by-endpoint',
|
|
562
|
+
'hls',
|
|
563
|
+
'fuzzer',
|
|
564
|
+
'ensure',
|
|
565
|
+
'memory-inspector',
|
|
566
|
+
'fake-data',
|
|
567
|
+
'slack'
|
|
568
|
+
];
|
|
569
|
+
for (const p of OFFICIAL_PLUGINS) {
|
|
570
|
+
if (script.config.plugins[p]) {
|
|
571
|
+
properties.officialPlugins.push(p);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// publish-metrics reporters
|
|
577
|
+
if (script.config.plugins['publish-metrics']) {
|
|
578
|
+
const OFFICIAL_REPORTERS = [
|
|
579
|
+
'datadog',
|
|
580
|
+
'open-telemetry',
|
|
581
|
+
'newrelic',
|
|
582
|
+
'splunk',
|
|
583
|
+
'dynatrace',
|
|
584
|
+
'cloudwatch',
|
|
585
|
+
'honeycomb',
|
|
586
|
+
'mixpanel',
|
|
587
|
+
'prometheus'
|
|
588
|
+
];
|
|
589
|
+
|
|
590
|
+
properties.officialMonitoringReporters = script.config.plugins[
|
|
591
|
+
'publish-metrics'
|
|
592
|
+
]
|
|
593
|
+
.map((reporter) => {
|
|
594
|
+
if (OFFICIAL_REPORTERS.includes(reporter.type)) {
|
|
595
|
+
return reporter.type;
|
|
596
|
+
}
|
|
597
|
+
return undefined;
|
|
598
|
+
})
|
|
599
|
+
.filter((type) => type !== undefined);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// before/after hooks
|
|
603
|
+
if (script.before) {
|
|
604
|
+
properties.beforeHook = true;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
if (script.after) {
|
|
608
|
+
properties.afterHook = true;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
Object.assign(properties, extraProps);
|
|
612
|
+
} catch (err) {
|
|
613
|
+
debug(err);
|
|
614
|
+
} finally {
|
|
615
|
+
telemetry.capture('test run', properties);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function checkDirExists(output) {
|
|
620
|
+
if (!output) {
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
// If destination is a file check only path to its directory
|
|
624
|
+
const exists = path.extname(output)
|
|
625
|
+
? fs.existsSync(path.dirname(output))
|
|
626
|
+
: fs.existsSync(output);
|
|
627
|
+
|
|
628
|
+
return exists;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function getLogFilename(output, nameFormat) {
|
|
632
|
+
let logfile;
|
|
633
|
+
|
|
634
|
+
// is the destination a directory that exists?
|
|
635
|
+
let isDir = false;
|
|
636
|
+
if (output) {
|
|
637
|
+
try {
|
|
638
|
+
isDir = fs.statSync(output).isDirectory();
|
|
639
|
+
} catch (_err) {
|
|
640
|
+
// ENOENT do nothing, handled in checkDirExists before test run
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const defaultFormat = '[artillery_report_]YMMDD_HHmmSS[.json]';
|
|
645
|
+
if (!isDir && output) {
|
|
646
|
+
// -o is set with a filename (existing or not)
|
|
647
|
+
logfile = output;
|
|
648
|
+
} else if (!isDir && !output) {
|
|
649
|
+
// no -o set
|
|
650
|
+
} else {
|
|
651
|
+
// -o is set with a directory
|
|
652
|
+
logfile = path.join(output, moment().format(nameFormat || defaultFormat));
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
return logfile;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function getPluginMetricsToSuppress(script) {
|
|
659
|
+
if (!script.config.plugins) {
|
|
660
|
+
return [];
|
|
661
|
+
}
|
|
662
|
+
const metrics = [];
|
|
663
|
+
for (const [plugin, options] of Object.entries(script.config.plugins)) {
|
|
664
|
+
if (options.suppressOutput) {
|
|
665
|
+
metrics.push(`plugins.${plugin}`);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return metrics;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
module.exports = RunCommand;
|