@ably-labs/locust 0.0.1 → 0.0.5
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 +13 -1
- package/lib/Worker.d.ts +27 -1
- package/lib/Worker.js +64 -8
- package/lib/constants.d.ts +1 -1
- package/lib/constants.js +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ _[Ably](https://ably.com) is the platform that powers synchronized digital exper
|
|
|
10
10
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
|
-
See [example/users.
|
|
13
|
+
See [example/users.ts](/example/users.ts) for an example of defining Locust users in TypeScript, and [example/index.ts](/example/index.ts) for an example program which connects to a Locust master and runs the defined users during a load test.
|
|
14
14
|
|
|
15
15
|
The example can be run by copying `example/.env.sample` to `example/.env`, setting `ABLY_API_KEY` to your Ably API key, and running [Docker Compose](https://docs.docker.com/compose/):
|
|
16
16
|
|
|
@@ -83,3 +83,15 @@ worker.register('ExampleUser', () => new User());
|
|
|
83
83
|
|
|
84
84
|
worker.run();
|
|
85
85
|
```
|
|
86
|
+
|
|
87
|
+
## Testing
|
|
88
|
+
|
|
89
|
+
Start an instance of Locust and run the tests:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
cd tests
|
|
93
|
+
|
|
94
|
+
docker compose up --build
|
|
95
|
+
|
|
96
|
+
npm test
|
|
97
|
+
```
|
package/lib/Worker.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import winston from 'winston';
|
|
1
|
+
import * as winston from 'winston';
|
|
2
2
|
import { Dealer } from 'zeromq';
|
|
3
3
|
import { Message, MessageType } from './Message';
|
|
4
4
|
import { Stats } from './Stats';
|
|
@@ -48,6 +48,10 @@ interface User {
|
|
|
48
48
|
* A function which is registered with a worker to initialise a User.
|
|
49
49
|
*/
|
|
50
50
|
declare type UserFn = () => User;
|
|
51
|
+
/**
|
|
52
|
+
* A function which is called when an event happens.
|
|
53
|
+
*/
|
|
54
|
+
declare type EventFn = (data: any) => void;
|
|
51
55
|
/**
|
|
52
56
|
* A Locust worker which connects to a Locust master ZeroMQ socket and spawns
|
|
53
57
|
* Locust users during an active load test.
|
|
@@ -105,6 +109,12 @@ export declare class Worker {
|
|
|
105
109
|
* discard outdated messages.
|
|
106
110
|
*/
|
|
107
111
|
lastReceivedSpawnTimestamp: number;
|
|
112
|
+
/**
|
|
113
|
+
* A set of functions which are called when events happen.
|
|
114
|
+
*/
|
|
115
|
+
callbacks: {
|
|
116
|
+
[key: string]: EventFn[];
|
|
117
|
+
};
|
|
108
118
|
constructor(opts: Options);
|
|
109
119
|
/**
|
|
110
120
|
* Register a function to initialise users of the given class name.
|
|
@@ -120,6 +130,18 @@ export declare class Worker {
|
|
|
120
130
|
*
|
|
121
131
|
*/
|
|
122
132
|
run(): Promise<void>;
|
|
133
|
+
/**
|
|
134
|
+
* Register a callback to be called when an event happens.
|
|
135
|
+
*/
|
|
136
|
+
on(event: string, callback: (data: any) => void): void;
|
|
137
|
+
/**
|
|
138
|
+
* Register a callback to be called when a user starts.
|
|
139
|
+
*/
|
|
140
|
+
onUserStart(callback: EventFn): void;
|
|
141
|
+
/**
|
|
142
|
+
* Register a callback to be called when a user stops.
|
|
143
|
+
*/
|
|
144
|
+
onUserStop(callback: EventFn): void;
|
|
123
145
|
/**
|
|
124
146
|
* Send a protocol message to the Locust master.
|
|
125
147
|
*/
|
|
@@ -195,5 +217,9 @@ export declare class Worker {
|
|
|
195
217
|
};
|
|
196
218
|
user_count: number;
|
|
197
219
|
};
|
|
220
|
+
/**
|
|
221
|
+
* Invoke all callbacks for the given event with the given data.
|
|
222
|
+
*/
|
|
223
|
+
emit(event: string, data: any): void;
|
|
198
224
|
}
|
|
199
225
|
export {};
|
package/lib/Worker.js
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
+
}) : (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
o[k2] = m[k];
|
|
8
|
+
}));
|
|
9
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
10
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
11
|
+
}) : function(o, v) {
|
|
12
|
+
o["default"] = v;
|
|
13
|
+
});
|
|
14
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
15
|
+
if (mod && mod.__esModule) return mod;
|
|
16
|
+
var result = {};
|
|
17
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
18
|
+
__setModuleDefault(result, mod);
|
|
19
|
+
return result;
|
|
20
|
+
};
|
|
2
21
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
22
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
23
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -42,12 +61,9 @@ var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
|
42
61
|
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
43
62
|
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
44
63
|
};
|
|
45
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
46
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
47
|
-
};
|
|
48
64
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
65
|
exports.Worker = void 0;
|
|
50
|
-
var
|
|
66
|
+
var winston = __importStar(require("winston"));
|
|
51
67
|
var zeromq_1 = require("zeromq");
|
|
52
68
|
var constants_1 = require("./constants");
|
|
53
69
|
var Message_1 = require("./Message");
|
|
@@ -72,15 +88,16 @@ var Worker = /** @class */ (function () {
|
|
|
72
88
|
this.userFns = {};
|
|
73
89
|
this.users = {};
|
|
74
90
|
this.state = "ready" /* Ready */;
|
|
75
|
-
this.log =
|
|
76
|
-
format:
|
|
91
|
+
this.log = winston.createLogger({
|
|
92
|
+
format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
|
|
77
93
|
transports: [
|
|
78
|
-
new
|
|
94
|
+
new winston.transports.Console({
|
|
79
95
|
level: opts.logLevel || 'info',
|
|
80
96
|
}),
|
|
81
97
|
],
|
|
82
98
|
});
|
|
83
99
|
this.lastReceivedSpawnTimestamp = 0;
|
|
100
|
+
this.callbacks = {};
|
|
84
101
|
}
|
|
85
102
|
/**
|
|
86
103
|
* Register a function to initialise users of the given class name.
|
|
@@ -127,7 +144,7 @@ var Worker = /** @class */ (function () {
|
|
|
127
144
|
data = _c.value[0];
|
|
128
145
|
try {
|
|
129
146
|
msg = Message_1.Message.decode(data);
|
|
130
|
-
this.log.
|
|
147
|
+
this.log.debug("Received '" + msg.type + "' message");
|
|
131
148
|
this.handle(msg);
|
|
132
149
|
}
|
|
133
150
|
catch (err) {
|
|
@@ -157,6 +174,27 @@ var Worker = /** @class */ (function () {
|
|
|
157
174
|
});
|
|
158
175
|
});
|
|
159
176
|
};
|
|
177
|
+
/**
|
|
178
|
+
* Register a callback to be called when an event happens.
|
|
179
|
+
*/
|
|
180
|
+
Worker.prototype.on = function (event, callback) {
|
|
181
|
+
if (!this.callbacks[event]) {
|
|
182
|
+
this.callbacks[event] = [];
|
|
183
|
+
}
|
|
184
|
+
this.callbacks[event].push(callback);
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* Register a callback to be called when a user starts.
|
|
188
|
+
*/
|
|
189
|
+
Worker.prototype.onUserStart = function (callback) {
|
|
190
|
+
this.on('user.start', callback);
|
|
191
|
+
};
|
|
192
|
+
/**
|
|
193
|
+
* Register a callback to be called when a user stops.
|
|
194
|
+
*/
|
|
195
|
+
Worker.prototype.onUserStop = function (callback) {
|
|
196
|
+
this.on('user.stop', callback);
|
|
197
|
+
};
|
|
160
198
|
/**
|
|
161
199
|
* Send a protocol message to the Locust master.
|
|
162
200
|
*/
|
|
@@ -199,6 +237,7 @@ var Worker = /** @class */ (function () {
|
|
|
199
237
|
this.lastReceivedSpawnTimestamp = data.timestamp;
|
|
200
238
|
this.state = "spawning" /* Spawning */;
|
|
201
239
|
this.send("spawning" /* Spawning */, null);
|
|
240
|
+
this.log.info("Handling spawn message: expected = " + JSON.stringify(data.user_classes_count) + ", actual = " + JSON.stringify(this.spawnState().user_classes_count));
|
|
202
241
|
for (var _i = 0, _a = Object.keys(data.user_classes_count); _i < _a.length; _i++) {
|
|
203
242
|
var userClass = _a[_i];
|
|
204
243
|
// check we have a registered function for the given class
|
|
@@ -279,11 +318,13 @@ var Worker = /** @class */ (function () {
|
|
|
279
318
|
* Start the given number of users.
|
|
280
319
|
*/
|
|
281
320
|
Worker.prototype.startUsers = function (userClass, userFn, count) {
|
|
321
|
+
this.log.debug("Starting " + count + " " + userClass + " users");
|
|
282
322
|
for (var i = 0; i < count; i++) {
|
|
283
323
|
try {
|
|
284
324
|
var user = userFn();
|
|
285
325
|
user.start();
|
|
286
326
|
this.users[userClass].push(user);
|
|
327
|
+
this.emit('user.start', { userClass: userClass });
|
|
287
328
|
}
|
|
288
329
|
catch (err) {
|
|
289
330
|
this.logException("Error initialising " + userClass + " user: " + err);
|
|
@@ -301,10 +342,12 @@ var Worker = /** @class */ (function () {
|
|
|
301
342
|
* Stop the given number of users.
|
|
302
343
|
*/
|
|
303
344
|
Worker.prototype.stopUsers = function (userClass, count) {
|
|
345
|
+
this.log.debug("Stopping " + count + " " + userClass + " users");
|
|
304
346
|
for (var i = 0; i < count; i++) {
|
|
305
347
|
var user = this.users[userClass].pop();
|
|
306
348
|
if (user !== undefined) {
|
|
307
349
|
user.stop();
|
|
350
|
+
this.emit('user.stop', { userClass: userClass });
|
|
308
351
|
}
|
|
309
352
|
}
|
|
310
353
|
};
|
|
@@ -378,6 +421,19 @@ var Worker = /** @class */ (function () {
|
|
|
378
421
|
user_count: Object.values(this.users).reduce(function (sum, users) { return sum + users.length; }, 0),
|
|
379
422
|
};
|
|
380
423
|
};
|
|
424
|
+
/**
|
|
425
|
+
* Invoke all callbacks for the given event with the given data.
|
|
426
|
+
*/
|
|
427
|
+
Worker.prototype.emit = function (event, data) {
|
|
428
|
+
var callbacks = this.callbacks[event];
|
|
429
|
+
if (!callbacks) {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
for (var _i = 0, callbacks_1 = callbacks; _i < callbacks_1.length; _i++) {
|
|
433
|
+
var callback = callbacks_1[_i];
|
|
434
|
+
callback(data);
|
|
435
|
+
}
|
|
436
|
+
};
|
|
381
437
|
return Worker;
|
|
382
438
|
}());
|
|
383
439
|
exports.Worker = Worker;
|
package/lib/constants.d.ts
CHANGED
package/lib/constants.js
CHANGED
|
@@ -9,7 +9,7 @@ exports.STATS_INTERVAL = exports.HEARTBEAT_INTERVAL = exports.PROTOCOL_VERSION =
|
|
|
9
9
|
*
|
|
10
10
|
* See https://github.com/locustio/locust/blob/2.4.3/locust/runners.py#L879-L886
|
|
11
11
|
*/
|
|
12
|
-
exports.PROTOCOL_VERSION = '2.
|
|
12
|
+
exports.PROTOCOL_VERSION = '2.5.0';
|
|
13
13
|
/**
|
|
14
14
|
* The interval between successive heartbeats.
|
|
15
15
|
*/
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ably-labs/locust",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "A JavaScript load generator for Locust.io.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
6
7
|
"scripts": {
|
|
7
8
|
"build": "tsc --build",
|
|
8
9
|
"clean": "tsc --build --clean",
|
|
@@ -11,7 +12,7 @@
|
|
|
11
12
|
"test": "jest"
|
|
12
13
|
},
|
|
13
14
|
"files": [
|
|
14
|
-
"lib"
|
|
15
|
+
"lib/**/*"
|
|
15
16
|
],
|
|
16
17
|
"repository": {
|
|
17
18
|
"type": "git",
|