@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.
Files changed (90) hide show
  1. package/README.md +63 -0
  2. package/bin/run +29 -0
  3. package/bin/run.cmd +3 -0
  4. package/changes.json +138 -0
  5. package/console-reporter.js +1 -0
  6. package/lib/artillery-global.js +33 -0
  7. package/lib/cli/banner.js +8 -0
  8. package/lib/cli/common-flags.js +80 -0
  9. package/lib/cli/hooks/version.js +20 -0
  10. package/lib/cmds/dino.js +109 -0
  11. package/lib/cmds/quick.js +122 -0
  12. package/lib/cmds/report.js +34 -0
  13. package/lib/cmds/run-aci.js +91 -0
  14. package/lib/cmds/run-fargate.js +192 -0
  15. package/lib/cmds/run-lambda.js +96 -0
  16. package/lib/cmds/run.js +671 -0
  17. package/lib/console-capture.js +92 -0
  18. package/lib/console-reporter.js +438 -0
  19. package/lib/create-bom/built-in-plugins.js +12 -0
  20. package/lib/create-bom/create-bom.js +301 -0
  21. package/lib/dispatcher.js +9 -0
  22. package/lib/dist.js +222 -0
  23. package/lib/index.js +5 -0
  24. package/lib/launch-platform.js +439 -0
  25. package/lib/load-plugins.js +113 -0
  26. package/lib/platform/aws/aws-cloudwatch.js +106 -0
  27. package/lib/platform/aws/aws-create-sqs-queue.js +58 -0
  28. package/lib/platform/aws/aws-ensure-s3-bucket-exists.js +78 -0
  29. package/lib/platform/aws/aws-get-account-id.js +26 -0
  30. package/lib/platform/aws/aws-get-bucket-region.js +18 -0
  31. package/lib/platform/aws/aws-get-credentials.js +28 -0
  32. package/lib/platform/aws/aws-get-default-region.js +26 -0
  33. package/lib/platform/aws/aws-whoami.js +15 -0
  34. package/lib/platform/aws/constants.js +7 -0
  35. package/lib/platform/aws/iam-cf-templates/aws-iam-fargate-cf-template.yml +219 -0
  36. package/lib/platform/aws/iam-cf-templates/aws-iam-lambda-cf-template.yml +125 -0
  37. package/lib/platform/aws/iam-cf-templates/gh-oidc-fargate.yml +241 -0
  38. package/lib/platform/aws/iam-cf-templates/gh-oidc-lambda.yml +153 -0
  39. package/lib/platform/aws-ecs/ecs.js +247 -0
  40. package/lib/platform/aws-ecs/legacy/aws-util.js +134 -0
  41. package/lib/platform/aws-ecs/legacy/bom.js +528 -0
  42. package/lib/platform/aws-ecs/legacy/constants.js +27 -0
  43. package/lib/platform/aws-ecs/legacy/create-s3-client.js +24 -0
  44. package/lib/platform/aws-ecs/legacy/create-test.js +247 -0
  45. package/lib/platform/aws-ecs/legacy/errors.js +34 -0
  46. package/lib/platform/aws-ecs/legacy/find-public-subnets.js +149 -0
  47. package/lib/platform/aws-ecs/legacy/plugins/artillery-plugin-inspect-script/index.js +27 -0
  48. package/lib/platform/aws-ecs/legacy/plugins/artillery-plugin-sqs-reporter/azure-aqs.js +80 -0
  49. package/lib/platform/aws-ecs/legacy/plugins/artillery-plugin-sqs-reporter/index.js +202 -0
  50. package/lib/platform/aws-ecs/legacy/plugins.js +16 -0
  51. package/lib/platform/aws-ecs/legacy/run-cluster.js +1994 -0
  52. package/lib/platform/aws-ecs/legacy/sqs-reporter.js +401 -0
  53. package/lib/platform/aws-ecs/legacy/tags.js +22 -0
  54. package/lib/platform/aws-ecs/legacy/test-run-status.js +9 -0
  55. package/lib/platform/aws-ecs/legacy/time.js +67 -0
  56. package/lib/platform/aws-ecs/legacy/util.js +97 -0
  57. package/lib/platform/aws-ecs/worker/Dockerfile +64 -0
  58. package/lib/platform/aws-ecs/worker/helpers.sh +80 -0
  59. package/lib/platform/aws-ecs/worker/loadgen-worker +656 -0
  60. package/lib/platform/aws-lambda/dependencies.js +130 -0
  61. package/lib/platform/aws-lambda/index.js +734 -0
  62. package/lib/platform/aws-lambda/lambda-handler/a9-handler-dependencies.js +73 -0
  63. package/lib/platform/aws-lambda/lambda-handler/a9-handler-helpers.js +43 -0
  64. package/lib/platform/aws-lambda/lambda-handler/a9-handler-index.js +235 -0
  65. package/lib/platform/aws-lambda/lambda-handler/package.json +15 -0
  66. package/lib/platform/aws-lambda/prices.js +29 -0
  67. package/lib/platform/az/aci.js +694 -0
  68. package/lib/platform/az/aqs-queue-consumer.js +88 -0
  69. package/lib/platform/az/regions.js +52 -0
  70. package/lib/platform/cloud/api.js +72 -0
  71. package/lib/platform/cloud/cloud.js +448 -0
  72. package/lib/platform/cloud/http-client.js +19 -0
  73. package/lib/platform/local/artillery-worker-local.js +154 -0
  74. package/lib/platform/local/index.js +174 -0
  75. package/lib/platform/local/worker.js +261 -0
  76. package/lib/platform/worker-states.js +13 -0
  77. package/lib/queue-consumer/index.js +56 -0
  78. package/lib/stash.js +41 -0
  79. package/lib/telemetry.js +78 -0
  80. package/lib/util/await-on-ee.js +24 -0
  81. package/lib/util/generate-id.js +9 -0
  82. package/lib/util/parse-tag-string.js +21 -0
  83. package/lib/util/prepare-test-execution-plan.js +216 -0
  84. package/lib/util/sleep.js +7 -0
  85. package/lib/util/validate-script.js +132 -0
  86. package/lib/util.js +294 -0
  87. package/lib/utils-config.js +31 -0
  88. package/package.json +323 -0
  89. package/types.d.ts +317 -0
  90. package/util.js +1 -0
@@ -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;