@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,439 @@
|
|
|
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
|
+
const { SSMS } = require('@artilleryio/int-core').ssms;
|
|
6
|
+
const { loadPlugins, loadPluginsConfig } = require('./load-plugins');
|
|
7
|
+
|
|
8
|
+
const EventEmitter = require('eventemitter3');
|
|
9
|
+
const debug = require('debug')('core');
|
|
10
|
+
|
|
11
|
+
const p = require('node:util').promisify;
|
|
12
|
+
const _ = require('lodash');
|
|
13
|
+
|
|
14
|
+
const PlatformLocal = require('./platform/local');
|
|
15
|
+
const PlatformLambda = require('./platform/aws-lambda');
|
|
16
|
+
const PlatformAzureACI = require('./platform/az/aci');
|
|
17
|
+
|
|
18
|
+
async function createLauncher(script, payload, opts, launcherOpts) {
|
|
19
|
+
launcherOpts = launcherOpts || {
|
|
20
|
+
platform: 'local',
|
|
21
|
+
mode: 'distribute'
|
|
22
|
+
};
|
|
23
|
+
let l;
|
|
24
|
+
try {
|
|
25
|
+
l = new Launcher(script, payload, opts, launcherOpts);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.log(err);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return l;
|
|
32
|
+
}
|
|
33
|
+
class Launcher {
|
|
34
|
+
constructor(script, payload, opts, launcherOpts) {
|
|
35
|
+
this.script = script;
|
|
36
|
+
this.payload = payload;
|
|
37
|
+
this.opts = opts;
|
|
38
|
+
|
|
39
|
+
this.exitedWorkersCount = 0;
|
|
40
|
+
this.workerMessageBuffer = [];
|
|
41
|
+
|
|
42
|
+
this.metricsByPeriod = {}; // individual intermediates by worker
|
|
43
|
+
this.finalReportsByWorker = {};
|
|
44
|
+
|
|
45
|
+
this.events = new EventEmitter();
|
|
46
|
+
|
|
47
|
+
this.pluginEvents = new EventEmitter();
|
|
48
|
+
this.pluginEventsLegacy = new EventEmitter();
|
|
49
|
+
|
|
50
|
+
this.launcherOpts = launcherOpts;
|
|
51
|
+
|
|
52
|
+
this.periodsReportedFor = [];
|
|
53
|
+
|
|
54
|
+
if (launcherOpts.platform === 'local') {
|
|
55
|
+
this.platform = new PlatformLocal(script, payload, opts, launcherOpts);
|
|
56
|
+
} else if (launcherOpts.platform === 'aws:lambda') {
|
|
57
|
+
this.platform = new PlatformLambda(script, payload, opts, launcherOpts);
|
|
58
|
+
} else if (launcherOpts.platform === 'az:aci') {
|
|
59
|
+
this.platform = new PlatformAzureACI(script, payload, opts, launcherOpts);
|
|
60
|
+
} else {
|
|
61
|
+
throw new Error(`Unknown platform: ${launcherOpts.platform}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.phaseStartedEventsSeen = {};
|
|
65
|
+
this.phaseCompletedEventsSeen = {};
|
|
66
|
+
|
|
67
|
+
this.eventsByWorker = {};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async initWorkerEvents(workerEvents) {
|
|
71
|
+
workerEvents.on('workerError', (_workerId, message) => {
|
|
72
|
+
const { id, error, level, aggregatable, logs } = message;
|
|
73
|
+
|
|
74
|
+
if (level !== 'warn') {
|
|
75
|
+
this.exitedWorkersCount++;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (aggregatable) {
|
|
79
|
+
this.workerMessageBuffer.push(message);
|
|
80
|
+
} else {
|
|
81
|
+
global.artillery.log(`[${id}]: ${error.message}`);
|
|
82
|
+
if (logs) {
|
|
83
|
+
global.artillery.log(logs);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.events.emit('workerError', message);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
workerEvents.on('phaseStarted', (_workerId, message) => {
|
|
91
|
+
// Note - we send only the first event for a phase, not all of them
|
|
92
|
+
if (
|
|
93
|
+
typeof this.phaseStartedEventsSeen[message.phase.index] === 'undefined'
|
|
94
|
+
) {
|
|
95
|
+
this.phaseStartedEventsSeen[message.phase.index] = Date.now();
|
|
96
|
+
const fullPhase = {
|
|
97
|
+
//get back original phase without any splitting for workers
|
|
98
|
+
...this.script.config.phases[message.phase.index],
|
|
99
|
+
index: message.phase.index,
|
|
100
|
+
id: message.phase.id,
|
|
101
|
+
startTime: this.phaseStartedEventsSeen[message.phase.index]
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
this.events.emit('phaseStarted', fullPhase);
|
|
105
|
+
this.pluginEvents.emit('phaseStarted', fullPhase);
|
|
106
|
+
this.pluginEventsLegacy.emit('phaseStarted', fullPhase);
|
|
107
|
+
|
|
108
|
+
global.artillery.globalEvents.emit('phaseStarted', fullPhase);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
workerEvents.on('phaseCompleted', (_workerId, message) => {
|
|
113
|
+
if (
|
|
114
|
+
typeof this.phaseCompletedEventsSeen[message.phase.index] ===
|
|
115
|
+
'undefined'
|
|
116
|
+
) {
|
|
117
|
+
this.phaseCompletedEventsSeen[message.phase.index] = Date.now();
|
|
118
|
+
const fullPhase = {
|
|
119
|
+
//get back original phase without any splitting for workers
|
|
120
|
+
...this.script.config.phases[message.phase.index],
|
|
121
|
+
id: message.phase.id,
|
|
122
|
+
index: message.phase.index,
|
|
123
|
+
startTime: this.phaseStartedEventsSeen[message.phase.index],
|
|
124
|
+
endTime: message.phase.endTime
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
this.events.emit('phaseCompleted', fullPhase);
|
|
128
|
+
this.pluginEvents.emit('phaseCompleted', fullPhase);
|
|
129
|
+
this.pluginEventsLegacy.emit('phaseCompleted', fullPhase);
|
|
130
|
+
global.artillery.globalEvents.emit('phaseCompleted', fullPhase);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// We are not going to receive stats events from workers
|
|
135
|
+
// which have zero arrivals for a phase. (This can only happen
|
|
136
|
+
// in "distribute" mode.)
|
|
137
|
+
workerEvents.on('stats', (_workerId, message) => {
|
|
138
|
+
const workerStats = SSMS.deserializeMetrics(message.stats);
|
|
139
|
+
const period = workerStats.period;
|
|
140
|
+
if (typeof this.metricsByPeriod[period] === 'undefined') {
|
|
141
|
+
this.metricsByPeriod[period] = [];
|
|
142
|
+
}
|
|
143
|
+
// TODO: might want the full message here, with worker ID etc
|
|
144
|
+
this.metricsByPeriod[period].push(workerStats);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
workerEvents.on('done', async (workerId, message) => {
|
|
148
|
+
this.exitedWorkersCount++;
|
|
149
|
+
this.finalReportsByWorker[workerId] = SSMS.deserializeMetrics(
|
|
150
|
+
message.report
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
workerEvents.on('log', async (_workerId, message) => {
|
|
155
|
+
artillery.globalEvents.emit('log', ...message.args);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
workerEvents.on('setSuggestedExitCode', (_workerId, message) => {
|
|
159
|
+
artillery.suggestedExitCode = message.code;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async initPlugins() {
|
|
164
|
+
const plugins = await loadPlugins(
|
|
165
|
+
this.script.config.plugins,
|
|
166
|
+
this.script,
|
|
167
|
+
this.opts
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
//
|
|
171
|
+
// init plugins
|
|
172
|
+
//
|
|
173
|
+
for (const [name, result] of Object.entries(plugins)) {
|
|
174
|
+
if (result.isLoaded) {
|
|
175
|
+
if (result.version === 3) {
|
|
176
|
+
// TODO: load the plugin, subscribe to events
|
|
177
|
+
// global.artillery.plugins[name] = result.plugin;
|
|
178
|
+
} else {
|
|
179
|
+
// global.artillery.log(`WARNING: Legacy plugin detected: ${name}
|
|
180
|
+
// See https://artillery.io/docs/resources/core/v2.html for more details.`,
|
|
181
|
+
// 'warn');
|
|
182
|
+
|
|
183
|
+
// NOTE:
|
|
184
|
+
// We are giving v1 and v2 plugins a throw-away script
|
|
185
|
+
// object because we only care about the plugin setting
|
|
186
|
+
// up event handlers here. The plugins will be loaded
|
|
187
|
+
// properly in individual workers where they will have the
|
|
188
|
+
// opportunity to attach custom code, modify the script
|
|
189
|
+
// object etc.
|
|
190
|
+
// If we let a plugin access to the actual script object,
|
|
191
|
+
// and it happens to attach code to it (with a custom
|
|
192
|
+
// processor function for example) - spawning a worker
|
|
193
|
+
// will fail.
|
|
194
|
+
const dummyScript = JSON.parse(JSON.stringify(this.script));
|
|
195
|
+
dummyScript.config = {
|
|
196
|
+
...dummyScript.config,
|
|
197
|
+
// Load additional plugins configuration from the environment
|
|
198
|
+
plugins: loadPluginsConfig(this.script.config.plugins)
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
if (result.version === 1) {
|
|
202
|
+
result.plugin = new result.PluginExport(
|
|
203
|
+
dummyScript.config,
|
|
204
|
+
this.pluginEventsLegacy
|
|
205
|
+
);
|
|
206
|
+
global.artillery.plugins.push(result);
|
|
207
|
+
} else if (result.version === 2) {
|
|
208
|
+
if (result.PluginExport.LEGACY_METRICS_FORMAT === false) {
|
|
209
|
+
result.plugin = new result.PluginExport.Plugin(
|
|
210
|
+
dummyScript,
|
|
211
|
+
this.pluginEvents,
|
|
212
|
+
this.opts
|
|
213
|
+
);
|
|
214
|
+
} else {
|
|
215
|
+
result.plugin = new result.PluginExport.Plugin(
|
|
216
|
+
dummyScript,
|
|
217
|
+
this.pluginEventsLegacy,
|
|
218
|
+
this.opts
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
global.artillery.plugins.push(result);
|
|
222
|
+
} else {
|
|
223
|
+
// TODO: print warning
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
} else {
|
|
227
|
+
global.artillery.log(`WARNING: Could not load plugin: ${name}`, 'warn');
|
|
228
|
+
global.artillery.log(result.msg, 'warn');
|
|
229
|
+
// global.artillery.log(result.error, 'warn');
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async handleAllWorkersFinished() {
|
|
235
|
+
const allWorkersDone =
|
|
236
|
+
this.exitedWorkersCount === this.platform.getDesiredWorkerCount();
|
|
237
|
+
if (allWorkersDone) {
|
|
238
|
+
clearInterval(this.i1);
|
|
239
|
+
clearInterval(this.i2);
|
|
240
|
+
|
|
241
|
+
// Flush messages from workers
|
|
242
|
+
await this.flushWorkerMessages(0);
|
|
243
|
+
await this.flushIntermediateMetrics(true);
|
|
244
|
+
|
|
245
|
+
const pds = Object.keys(this.finalReportsByWorker).map(
|
|
246
|
+
(k) => this.finalReportsByWorker[k]
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
const statsByPeriod = Object.values(SSMS.mergeBuckets(pds));
|
|
250
|
+
const stats = SSMS.pack(statsByPeriod);
|
|
251
|
+
|
|
252
|
+
stats.summaries = {};
|
|
253
|
+
for (const [name, value] of Object.entries(stats.histograms || {})) {
|
|
254
|
+
const summary = SSMS.summarizeHistogram(value);
|
|
255
|
+
stats.summaries[name] = summary;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
clearInterval(this.workerExitWatcher);
|
|
259
|
+
|
|
260
|
+
// Relay event to workers
|
|
261
|
+
this.pluginEvents.emit('done', stats);
|
|
262
|
+
|
|
263
|
+
global.artillery.globalEvents.emit('done', stats);
|
|
264
|
+
this.pluginEventsLegacy.emit('done', SSMS.legacyReport(stats));
|
|
265
|
+
|
|
266
|
+
this.events.emit('done', stats);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async flushWorkerMessages(maxAge = 9000) {
|
|
271
|
+
// Collect messages older than maxAge msec and group by log message:
|
|
272
|
+
const now = Date.now();
|
|
273
|
+
const okToPrint = this.workerMessageBuffer.filter(
|
|
274
|
+
(m) => now - m.ts > maxAge
|
|
275
|
+
);
|
|
276
|
+
this.workerMessageBuffer = this.workerMessageBuffer.filter(
|
|
277
|
+
(m) => now - m.ts <= maxAge
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
const readyMessages = okToPrint.reduce((acc, message) => {
|
|
281
|
+
const { error } = message;
|
|
282
|
+
// TODO: Take event type and level into account
|
|
283
|
+
if (typeof acc[error.message] === 'undefined') {
|
|
284
|
+
acc[error.message] = [];
|
|
285
|
+
}
|
|
286
|
+
acc[error.message].push(message);
|
|
287
|
+
return acc;
|
|
288
|
+
}, {});
|
|
289
|
+
|
|
290
|
+
for (const [_logMessage, messageObjects] of Object.entries(readyMessages)) {
|
|
291
|
+
if (messageObjects[0].error) {
|
|
292
|
+
global.artillery.log(
|
|
293
|
+
`[${messageObjects[0].id}] ${messageObjects[0].error.message}`,
|
|
294
|
+
messageObjects[0].level
|
|
295
|
+
);
|
|
296
|
+
} else {
|
|
297
|
+
// Expect a msg property:
|
|
298
|
+
global.artillery.log(
|
|
299
|
+
`[${messageObjects[0].id}] ${messageObjects[0].msg}`,
|
|
300
|
+
messageObjects[0].level
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async flushIntermediateMetrics(flushAll = false) {
|
|
307
|
+
if (Object.keys(this.metricsByPeriod).length === 0) {
|
|
308
|
+
debug('No metrics received yet');
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// We always look at the earliest period available so that reports come in chronological order
|
|
313
|
+
const unreportedPeriods = Object.keys(this.metricsByPeriod)
|
|
314
|
+
.filter((x) => this.periodsReportedFor.indexOf(x) === -1)
|
|
315
|
+
.sort();
|
|
316
|
+
|
|
317
|
+
const earliestPeriodAvailable = unreportedPeriods[0];
|
|
318
|
+
|
|
319
|
+
// TODO: better name. One above is earliestNotAlreadyReported
|
|
320
|
+
const earliest = Object.keys(this.metricsByPeriod).sort()[0];
|
|
321
|
+
if (this.periodsReportedFor.indexOf(earliest) > -1) {
|
|
322
|
+
global.artillery.log(
|
|
323
|
+
'Warning: multiple batches of metrics for period',
|
|
324
|
+
earliest,
|
|
325
|
+
new Date(Number(earliest))
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
delete this.metricsByPeriod[earliest]; // FIXME: need to merge them in for the final report
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Dynamically adjust the duration we're willing to wait for. This matters on SQS where messages are received
|
|
332
|
+
// in batches of 10 and more workers => need to wait longer.
|
|
333
|
+
const MAX_WAIT_FOR_PERIOD_MS =
|
|
334
|
+
(Math.ceil(this.platform.getDesiredWorkerCount() / 10) * 3 + 30) * 1000;
|
|
335
|
+
|
|
336
|
+
debug({
|
|
337
|
+
now: Date.now(),
|
|
338
|
+
count: this.platform.getDesiredWorkerCount(),
|
|
339
|
+
earliestPeriodAvailable,
|
|
340
|
+
earliest,
|
|
341
|
+
MAX_WAIT_FOR_PERIOD_MS,
|
|
342
|
+
numReports: this.metricsByPeriod[earliestPeriodAvailable]?.length,
|
|
343
|
+
periodsReportedFor: this.periodsReportedFor,
|
|
344
|
+
metricsByPeriod: Object.keys(this.metricsByPeriod)
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
const allWorkersReportedForPeriod =
|
|
348
|
+
this.metricsByPeriod[earliestPeriodAvailable]?.length ===
|
|
349
|
+
this.platform.getDesiredWorkerCount();
|
|
350
|
+
const waitedLongEnough =
|
|
351
|
+
Date.now() - Number(earliestPeriodAvailable) > MAX_WAIT_FOR_PERIOD_MS;
|
|
352
|
+
|
|
353
|
+
if (flushAll) {
|
|
354
|
+
for (const period of unreportedPeriods) {
|
|
355
|
+
this.emitIntermediatesForPeriod(period);
|
|
356
|
+
}
|
|
357
|
+
} else if (
|
|
358
|
+
typeof earliestPeriodAvailable !== 'undefined' &&
|
|
359
|
+
(allWorkersReportedForPeriod || waitedLongEnough)
|
|
360
|
+
) {
|
|
361
|
+
this.emitIntermediatesForPeriod(earliestPeriodAvailable);
|
|
362
|
+
// TODO: autoscaling. Handle workers that drop off or join, and update count
|
|
363
|
+
} else {
|
|
364
|
+
debug('Waiting for more workerStats before emitting stats event');
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
emitIntermediatesForPeriod(period) {
|
|
369
|
+
debug(
|
|
370
|
+
'Report @',
|
|
371
|
+
new Date(Number(period)),
|
|
372
|
+
'made up of items:',
|
|
373
|
+
this.metricsByPeriod[String(period)].length
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
// TODO: Track how many workers provided metrics in the metrics report
|
|
377
|
+
// summarize histograms for console reporter:
|
|
378
|
+
const merged = SSMS.mergeBuckets(this.metricsByPeriod[String(period)]);
|
|
379
|
+
const stats = merged[String(period)];
|
|
380
|
+
|
|
381
|
+
stats.summaries = {};
|
|
382
|
+
for (const [name, value] of Object.entries(stats.histograms || {})) {
|
|
383
|
+
const summary = SSMS.summarizeHistogram(value);
|
|
384
|
+
stats.summaries[name] = summary;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
delete this.metricsByPeriod[String(period)];
|
|
388
|
+
|
|
389
|
+
this.periodsReportedFor.push(period);
|
|
390
|
+
this.pluginEvents.emit('stats', stats);
|
|
391
|
+
global.artillery.globalEvents.emit('stats', stats);
|
|
392
|
+
this.pluginEventsLegacy.emit('stats', SSMS.legacyReport(stats));
|
|
393
|
+
|
|
394
|
+
this.events.emit('stats', stats);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async run() {
|
|
398
|
+
await this.initPlugins();
|
|
399
|
+
|
|
400
|
+
this.i1 = setInterval(async () => {
|
|
401
|
+
await this.flushWorkerMessages();
|
|
402
|
+
}, 1 * 1000).unref();
|
|
403
|
+
|
|
404
|
+
this.i2 = setInterval(async () => {
|
|
405
|
+
this.flushIntermediateMetrics();
|
|
406
|
+
}, 2 * 1000).unref();
|
|
407
|
+
|
|
408
|
+
this.workerExitWatcher = setInterval(async () => {
|
|
409
|
+
await this.handleAllWorkersFinished();
|
|
410
|
+
}, 2 * 1000);
|
|
411
|
+
|
|
412
|
+
await this.initWorkerEvents(this.platform.events);
|
|
413
|
+
await this.platform.startJob();
|
|
414
|
+
debug('workers running');
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async shutdown() {
|
|
418
|
+
await this.platform.shutdown();
|
|
419
|
+
|
|
420
|
+
// TODO: flush worker messages, and intermediate stats
|
|
421
|
+
|
|
422
|
+
// Unload plugins
|
|
423
|
+
// TODO: v3 plugins
|
|
424
|
+
if (global.artillery?.plugins) {
|
|
425
|
+
for (const o of global.artillery.plugins) {
|
|
426
|
+
if (o.plugin.cleanup) {
|
|
427
|
+
try {
|
|
428
|
+
await p(o.plugin.cleanup.bind(o.plugin))();
|
|
429
|
+
debug('plugin unloaded:', o.name);
|
|
430
|
+
} catch (cleanupErr) {
|
|
431
|
+
global.artillery.log(cleanupErr, 'error');
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
module.exports = createLauncher;
|
|
@@ -0,0 +1,113 @@
|
|
|
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
|
+
const debug = require('debug')('core');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
|
|
8
|
+
// Additional paths to load plugins can be set via ARTILLERY_PLUGIN_PATH
|
|
9
|
+
// Additional plugin config mafy be set via ARTILLERY_PLUGINS (as JSON)
|
|
10
|
+
// Version may be: v1, v2, v3 or any
|
|
11
|
+
function loadPluginsConfig(pluginSpecs) {
|
|
12
|
+
let additionalPlugins = {};
|
|
13
|
+
|
|
14
|
+
if (process.env.ARTILLERY_PLUGINS) {
|
|
15
|
+
try {
|
|
16
|
+
additionalPlugins = JSON.parse(process.env.ARTILLERY_PLUGINS);
|
|
17
|
+
} catch (ignoreErr) {
|
|
18
|
+
debug(ignoreErr);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return Object.assign({}, pluginSpecs, additionalPlugins);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function loadPlugins(pluginSpecs, testScript) {
|
|
26
|
+
let requirePaths = [''];
|
|
27
|
+
|
|
28
|
+
// requirePaths = requirePaths.concat(pro.getPluginPath());
|
|
29
|
+
|
|
30
|
+
if (process.env.ARTILLERY_PLUGIN_PATH) {
|
|
31
|
+
requirePaths = requirePaths.concat(
|
|
32
|
+
process.env.ARTILLERY_PLUGIN_PATH.split(':')
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
pluginSpecs = loadPluginsConfig(pluginSpecs);
|
|
37
|
+
|
|
38
|
+
const results = {};
|
|
39
|
+
for (const [name, config] of Object.entries(pluginSpecs)) {
|
|
40
|
+
const result = await loadPlugin(name, config, requirePaths, testScript);
|
|
41
|
+
results[name] = result;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return results;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function loadPlugin(name, config, requirePaths, testScript) {
|
|
48
|
+
// TODO: Take scope in directly - don't need the full script
|
|
49
|
+
const pluginConfigScope = config.scope || testScript.config.pluginsScope;
|
|
50
|
+
const pluginPrefix = pluginConfigScope
|
|
51
|
+
? pluginConfigScope
|
|
52
|
+
: 'artillery-plugin-';
|
|
53
|
+
const requireString = pluginPrefix + name;
|
|
54
|
+
let PluginExport, pluginErr, loadedFrom, version;
|
|
55
|
+
|
|
56
|
+
for (const p of requirePaths) {
|
|
57
|
+
debug('Looking for plugin in:', p);
|
|
58
|
+
try {
|
|
59
|
+
loadedFrom = path.join(p, requireString);
|
|
60
|
+
PluginExport = require(loadedFrom);
|
|
61
|
+
if (typeof PluginExport === 'function') {
|
|
62
|
+
version = 1;
|
|
63
|
+
} else if (
|
|
64
|
+
typeof PluginExport === 'object' &&
|
|
65
|
+
typeof PluginExport.Plugin === 'function'
|
|
66
|
+
) {
|
|
67
|
+
version = 2;
|
|
68
|
+
} // TODO: Add v3
|
|
69
|
+
} catch (err) {
|
|
70
|
+
debug(err);
|
|
71
|
+
pluginErr = err;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (typeof PluginExport !== 'undefined') {
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!PluginExport) {
|
|
80
|
+
let msg;
|
|
81
|
+
|
|
82
|
+
if (!pluginErr) {
|
|
83
|
+
msg = `WARNING: Could not initialize plugin: ${name}`;
|
|
84
|
+
} else {
|
|
85
|
+
if (pluginErr.code === 'MODULE_NOT_FOUND') {
|
|
86
|
+
msg = `WARNING: Plugin ${name} specified but module ${requireString} could not be found (${pluginErr.code})`;
|
|
87
|
+
} else {
|
|
88
|
+
msg = `WARNING: Could not initialize plugin: ${name} (${pluginErr.message})`;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
name,
|
|
94
|
+
isLoaded: false,
|
|
95
|
+
isInitialized: false,
|
|
96
|
+
msg: msg,
|
|
97
|
+
error: pluginErr
|
|
98
|
+
};
|
|
99
|
+
} else {
|
|
100
|
+
debug('Plugin %s loaded from %s', name, requireString);
|
|
101
|
+
return {
|
|
102
|
+
name,
|
|
103
|
+
isLoaded: true,
|
|
104
|
+
isInitialized: false,
|
|
105
|
+
|
|
106
|
+
PluginExport,
|
|
107
|
+
loadedFrom,
|
|
108
|
+
version
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = { loadPlugins, loadPlugin, loadPluginsConfig };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const {
|
|
2
|
+
CloudWatchLogsClient,
|
|
3
|
+
PutRetentionPolicyCommand
|
|
4
|
+
} = require('@aws-sdk/client-cloudwatch-logs');
|
|
5
|
+
const debug = require('debug')('artillery:aws-cloudwatch');
|
|
6
|
+
|
|
7
|
+
const allowedRetentionDays = [
|
|
8
|
+
1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1096, 1827,
|
|
9
|
+
2192, 2557, 2922, 3288, 3653
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
async function _putCloudwatchRetentionPolicy(
|
|
13
|
+
logGroupName,
|
|
14
|
+
retentionInDays,
|
|
15
|
+
region
|
|
16
|
+
) {
|
|
17
|
+
const cloudwatchlogs = new CloudWatchLogsClient({
|
|
18
|
+
apiVersion: '2014-11-06',
|
|
19
|
+
region
|
|
20
|
+
});
|
|
21
|
+
const putRetentionPolicyParams = {
|
|
22
|
+
logGroupName,
|
|
23
|
+
retentionInDays
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return cloudwatchlogs.send(
|
|
27
|
+
new PutRetentionPolicyCommand(putRetentionPolicyParams)
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function setCloudwatchRetention(
|
|
32
|
+
logGroupName,
|
|
33
|
+
retentionInDays,
|
|
34
|
+
region,
|
|
35
|
+
options = { maxRetries: 5, waitPerRetry: 1000 }
|
|
36
|
+
) {
|
|
37
|
+
if (!allowedRetentionDays.includes(retentionInDays)) {
|
|
38
|
+
console.log(
|
|
39
|
+
`WARNING: Skipping setting CloudWatch retention, as invalid value specified: ${retentionInDays}. Allowed values are: ${allowedRetentionDays.join(
|
|
40
|
+
', '
|
|
41
|
+
)}`
|
|
42
|
+
);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const interval = setInterval(
|
|
47
|
+
async (opts) => {
|
|
48
|
+
debug(
|
|
49
|
+
`Trying to set CloudWatch Log group ${logGroupName} retention policy to ${retentionInDays} days`
|
|
50
|
+
);
|
|
51
|
+
opts.incr = (opts.incr || 0) + 1;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const _res = await _putCloudwatchRetentionPolicy(
|
|
55
|
+
logGroupName,
|
|
56
|
+
retentionInDays,
|
|
57
|
+
region
|
|
58
|
+
);
|
|
59
|
+
debug(
|
|
60
|
+
`Successfully set CloudWatch Logs retention policy to ${retentionInDays} days`
|
|
61
|
+
);
|
|
62
|
+
clearInterval(interval);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
const resumeTestMessage =
|
|
65
|
+
'The test will continue without setting the retention policy.';
|
|
66
|
+
if (error?.code === 'AccessDeniedException') {
|
|
67
|
+
console.log(`\n${error.message}`);
|
|
68
|
+
console.log(
|
|
69
|
+
'\nWARNING: Missing logs:PutRetentionPolicy permission to set CloudWatch retention policy. Please ensure the IAM role has the necessary permissions:\nhttps://docs.art/fargate#iam-permissions'
|
|
70
|
+
);
|
|
71
|
+
console.log(`${resumeTestMessage}\n`);
|
|
72
|
+
clearInterval(interval);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (error?.code !== 'ResourceNotFoundException') {
|
|
77
|
+
console.log(`\n${error.message}`);
|
|
78
|
+
console.log(
|
|
79
|
+
'\nWARNING: Unexpected error setting CloudWatch retention policy\n'
|
|
80
|
+
);
|
|
81
|
+
console.log(`${resumeTestMessage}\n`);
|
|
82
|
+
clearInterval(interval);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (opts.incr >= opts.maxRetries) {
|
|
87
|
+
console.log(`\n${error.message}`);
|
|
88
|
+
console.log(
|
|
89
|
+
`\nWARNING: Cannot find log group ${logGroupName}\nMax retries exceeded setting CloudWatch retention policy:`
|
|
90
|
+
);
|
|
91
|
+
console.log(`${resumeTestMessage}\n`);
|
|
92
|
+
clearInterval(interval);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
options.waitPerRetry,
|
|
98
|
+
options
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
return interval;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
setCloudwatchRetention
|
|
106
|
+
};
|