artillery-engine-skip 0.0.1

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 (3) hide show
  1. package/README.md +9 -0
  2. package/index.js +147 -0
  3. package/package.json +15 -0
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # artillery-engine-skip
2
+
3
+ This engine adds support for load testing [skip](https://skiplabs.io) services with Artillery.
4
+
5
+ # Features
6
+
7
+ - Open and tail skip streams, with custom event handlers.
8
+ - Issue writes to the skip service's input collections.
9
+ - Loop a fixed number of times/until a condition is met.
package/index.js ADDED
@@ -0,0 +1,147 @@
1
+ const A = require('async');
2
+ const { EventSource } = require('eventsource');
3
+ const _ = require('lodash');
4
+ const engineUtil = require('@artilleryio/int-commons').engine_util;
5
+
6
+ class SkipEngine {
7
+ constructor(script, events, helpers) {
8
+ this.script = script;
9
+ this.helpers = helpers;
10
+ this.target = script.config?.target;
11
+ this.skipConfig = script.config?.engines?.['skip'] || {};
12
+
13
+ return this;
14
+ }
15
+
16
+ createScenario(spec, events) {
17
+ const self = this;
18
+
19
+ return (initialContext, vuDone) => {
20
+ const steps = [(callback) => {
21
+ // NOTE: This is undocumented by artillery, but required.
22
+ events.emit('started');
23
+ return callback(null, initialContext);
24
+ }];
25
+ for (const step of spec.flow) {
26
+ steps.push(self.step(step, events));
27
+ }
28
+
29
+ A.waterfall(steps, (err, context) => {
30
+ vuDone(err, context);
31
+ });
32
+ }
33
+ }
34
+
35
+ step(step, events) {
36
+ if (step.stream) {
37
+ return this.stepStream(step, events);
38
+ } else if (step.write) {
39
+ return this.stepWrite(step, events);
40
+ } else if (step.log) {
41
+ return this.stepLog(step, events);
42
+ } else if (step.think) {
43
+ return this.stepThink(step, events);
44
+ } else if (step.loop) {
45
+ return this.stepLoop(step, events);
46
+ } else if (step == 'close') {
47
+ return this.stepClose(step, events);
48
+ } else {
49
+ console.error('Unrecognized step', step);
50
+ return null;
51
+ }
52
+ }
53
+
54
+ stepStream(step, events) {
55
+ const self = this;
56
+ return (context, callback) => {
57
+ fetch(`${self.skipConfig.control_url}/v1/streams/${step.stream.resource}`, {
58
+ method: "POST"
59
+ }).then(resp => resp.text())
60
+ .then(uuid => {
61
+ const es = new EventSource(`${self.skipConfig.stream_url}/v1/streams/${uuid}`);
62
+ context.es = es;
63
+
64
+ es.onerror = (err) => {
65
+ if (err.status) {
66
+ events.emit('counter', `skip.errored_streams.${err.status}`, 1)
67
+ } else {
68
+ events.emit('counter', 'skip.errored_streams', 1);
69
+ }
70
+ };
71
+
72
+ if(step.stream.onInit) {
73
+ es.addEventListener('init', (e) => {
74
+ self.script.config.processor[step.stream.onInit].call(null, e, context, events);
75
+ });
76
+ }
77
+
78
+ if(step.stream.onUpdate) {
79
+ es.addEventListener('update', (e) => {
80
+ self.script.config.processor[step.stream.onUpdate].call(null, e, context, events);
81
+ });
82
+ }
83
+
84
+ es.onopen = () => {
85
+ events.emit('counter', 'skip.opened_streams', 1);
86
+ callback(null, context);
87
+ };
88
+ });
89
+ };
90
+ }
91
+
92
+ stepWrite(step, events) {
93
+ const self = this;
94
+
95
+ return (context, callback) => {
96
+ const data = self.helpers.template(step.write.data, context);
97
+ if (step.write.hook) {
98
+ self.script.config.processor[step.write.hook].call(null, step.write.collection, data, context, events);
99
+ }
100
+ fetch(`${self.skipConfig.control_url}/v1/inputs/${step.write.collection}`, {
101
+ method: 'PATCH',
102
+ body: JSON.stringify(data),
103
+ headers: {
104
+ 'Content-Type': 'application/json',
105
+ }
106
+ }).then(_ => callback(null, context)).catch(e => callback(e, context));
107
+ };
108
+ }
109
+
110
+ stepLog(step, events) {
111
+ const self = this;
112
+
113
+ return (context, callback) => {
114
+ console.log(self.helpers.template(step.log, context));
115
+ return process.nextTick(() => callback(null, context));
116
+ };
117
+ }
118
+
119
+ stepThink(step, events) {
120
+ const self = this;
121
+
122
+ return this.helpers.createThink(step, self.script.config.defaults?.think || {});
123
+ }
124
+
125
+ stepLoop(step, events) {
126
+ const self = this;
127
+
128
+ const steps = _.map(step.loop, function (rs) {
129
+ return self.step(rs, events);
130
+ });
131
+ return engineUtil.createLoopWithCount(step.count || -1, steps, {
132
+ loopValue: step.loopValue || '$loopCount',
133
+ loopElement: step.loopElement || '$loopElement',
134
+ overValues: step.over,
135
+ whileTrue: self.script.config.processor ? self.script.config.processor[step.whileTrue] : undefined,
136
+ });
137
+ }
138
+
139
+ stepClose(step, events) {
140
+ return (context, callback) => {
141
+ context.es?.close();
142
+ callback(null, context);
143
+ };
144
+ }
145
+ }
146
+
147
+ module.exports = SkipEngine;
package/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "artillery-engine-skip",
3
+ "version": "0.0.1",
4
+ "description": "Skip support for Artillery",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": [],
10
+ "author": "Lucas Hosseini <lucas@skiplabs.io>",
11
+ "dependencies": {
12
+ "async": "^3.2.4",
13
+ "eventsource": "^3.0.6"
14
+ }
15
+ }