@vpalmisano/throttler 0.0.7

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 ADDED
@@ -0,0 +1,40 @@
1
+ ![logo](media/logo.svg "Throttler")
2
+ # Throttler
3
+ [GitHub page](https://github.com/vpalmisano/throttler) | [Documentation](https://vpalmisano.github.io/throttler)
4
+
5
+ A Linux tool that allows to apply network constraints to a single or a group of processes.
6
+
7
+ ## Install
8
+ ```bash
9
+ echo '@vpalmisano:registry=https://npm.pkg.github.com' >> ~/.npmrc
10
+
11
+ npm install -g @vpalmisano/throttler
12
+ ```
13
+
14
+ System configuration:
15
+ ```bash
16
+ # Allow to run the required comamnds without password:
17
+ echo "$(whoami) ALL=(ALL) NOPASSWD: $(which iptables),$(which addgroup),$(which adduser),$(which tc),$(which modprobe),$(which ip)" | sudo tee /etc/sudoers.d/throttler
18
+
19
+ # Install wireshark and allow regular user to capture packets:
20
+ sudo apt install -y wireshark
21
+ sudo dpkg-reconfigure wireshark-common
22
+ sudo usermod -a -G wireshark $(whoami)
23
+ # Logout and login again
24
+ ```
25
+
26
+ ## Examples
27
+ Throttle all the traffic of a single process (e.g. firefox):
28
+ ```bash
29
+ throttler \
30
+ --throttle-config '[{sessions:"0",up:[{delay:20,rate:5000}],down:[{delay:20,rate:5000}]}]' \
31
+ --command-config '[{session:0,command:"firefox https://www.speedtest.net"}]'
32
+ # press q to stop the throttler
33
+ ```
34
+
35
+ Throttle the udp traffic of a single process (e.g. firefox) and capture the packets:
36
+ ```bash
37
+ throttler \
38
+ --throttle-config '[{sessions:"0",protocol:"udp",capture:"capture.pcap",up:[{delay:50,loss:1,rate:2000}],down:[{delay:20,loss:1,rate:2000}]}]' \
39
+ --command-config '[{session:0,command:"firefox https://meet.jit.si/"}]'
40
+ ```
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const change_case_1 = require("change-case");
7
+ const child_process_1 = require("child_process");
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const json5_1 = __importDefault(require("json5"));
10
+ const word_wrap_1 = __importDefault(require("word-wrap"));
11
+ const config_1 = require("./config");
12
+ const throttle_1 = require("./throttle");
13
+ const utils_1 = require("./utils");
14
+ const log = (0, utils_1.logger)('throttler');
15
+ function showHelpOrVersion() {
16
+ if (process.argv.findIndex(a => a.localeCompare('--help') === 0) !== -1) {
17
+ const docs = (0, config_1.getConfigDocs)();
18
+ let out = `Params:\n --version\n It shows the package version.\n`;
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ Object.entries(docs).forEach(([name, value]) => {
21
+ out += ` --${(0, change_case_1.paramCase)(name)}
22
+ ${(0, word_wrap_1.default)(value.doc, { width: 72, indent: ' ' })}
23
+ Default: ${value.default}\n`;
24
+ });
25
+ console.log(out);
26
+ process.exit(0);
27
+ }
28
+ else if (process.argv.findIndex(a => a.localeCompare('--version') === 0) !== -1) {
29
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
30
+ const version = json5_1.default.parse(fs_1.default.readFileSync((0, utils_1.resolvePackagePath)('package.json')).toString()).version;
31
+ console.log(version);
32
+ process.exit(0);
33
+ }
34
+ }
35
+ async function main() {
36
+ showHelpOrVersion();
37
+ const config = (0, config_1.loadConfig)(process.argv[2]);
38
+ const pids = new Set();
39
+ await (0, throttle_1.startThrottle)(config.throttleConfig);
40
+ const stop = async () => {
41
+ log.info('Stopping...');
42
+ for (const pid of pids) {
43
+ const childPids = await (0, utils_1.getProcessChildren)(pid);
44
+ log.debug(`Killing process ${pid} and children: ${childPids}`);
45
+ try {
46
+ process.kill(pid, 'SIGKILL');
47
+ }
48
+ catch (err) {
49
+ log.debug(`Error killing process ${pid}: ${err.stack}`);
50
+ }
51
+ for (const childPid of childPids) {
52
+ try {
53
+ process.kill(childPid, 'SIGKILL');
54
+ }
55
+ catch (err) {
56
+ log.debug(`Error killing child process ${childPid}: ${err.stack}`);
57
+ }
58
+ }
59
+ }
60
+ await (0, throttle_1.stopThrottle)();
61
+ process.exit(0);
62
+ };
63
+ (0, utils_1.registerExitHandler)(() => stop());
64
+ const commands = config.commandConfig
65
+ ? json5_1.default.parse(config.commandConfig)
66
+ : [];
67
+ for (const c of commands) {
68
+ const { session, command } = c;
69
+ const shortName = command.split(' ')[0];
70
+ const index = (0, throttle_1.getSessionThrottleIndex)(session || 0);
71
+ const launcher = await (0, throttle_1.throttleLauncher)(command, index);
72
+ try {
73
+ const proc = (0, child_process_1.spawn)(launcher, {
74
+ shell: false,
75
+ stdio: ['ignore', 'pipe', 'pipe'],
76
+ detached: false,
77
+ });
78
+ if (proc.pid)
79
+ pids.add(proc.pid);
80
+ proc.stdout.on('data', data => {
81
+ log.info(`[${shortName}][stdout]`, data.toString().trim());
82
+ });
83
+ proc.stderr.on('data', data => {
84
+ log.info(`[${shortName}][stderr]`, data.toString().trim());
85
+ });
86
+ proc.on('error', err => {
87
+ if (err.message.startsWith('The operation was aborted'))
88
+ return;
89
+ log.error(`Error running command "${command}": ${err.stack}`);
90
+ });
91
+ proc.once('exit', code => {
92
+ log.info(`Command "${command}" exited with code: ${code || 0}`);
93
+ if (proc.pid)
94
+ pids.delete(proc.pid);
95
+ /* fs.promises.unlink(launcher).catch(err => {
96
+ log.warn(`Error unlinking "${launcher}": ${(err as Error).stack}`)
97
+ }) */
98
+ });
99
+ }
100
+ catch (err) {
101
+ log.error(`Error running command "${command}": ${err.stack}`);
102
+ }
103
+ }
104
+ // Stop after a configured duration.
105
+ if (config.runDuration > 0) {
106
+ setTimeout(stop, config.runDuration * 1000);
107
+ }
108
+ // Command line interface.
109
+ console.log('Press [q] to stop the throttler');
110
+ process.stdin.setRawMode(true);
111
+ process.stdin.resume();
112
+ process.stdin.on('data', async (data) => {
113
+ log.debug('[stdin]', data[0]);
114
+ if (data[0] === 'q'.charCodeAt(0)) {
115
+ try {
116
+ await stop();
117
+ }
118
+ catch (err) {
119
+ log.error(`stop error: ${err.stack}`);
120
+ process.exit(1);
121
+ }
122
+ }
123
+ else {
124
+ console.log('Press [q] to stop the throttler');
125
+ }
126
+ });
127
+ }
128
+ if (require.main === module) {
129
+ main().catch(err => {
130
+ console.error(err);
131
+ process.exit(-1);
132
+ });
133
+ }
134
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/app.ts"],"names":[],"mappings":"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","sourcesContent":["import { paramCase } from 'change-case'\nimport { spawn } from 'child_process'\nimport fs from 'fs'\nimport json5 from 'json5'\nimport wrap from 'word-wrap'\n\nimport { getConfigDocs, loadConfig } from './config'\nimport {\n getSessionThrottleIndex,\n startThrottle,\n stopThrottle,\n throttleLauncher,\n} from './throttle'\nimport {\n getProcessChildren,\n logger,\n registerExitHandler,\n resolvePackagePath,\n} from './utils'\n\nconst log = logger('throttler')\n\nfunction showHelpOrVersion(): void {\n if (process.argv.findIndex(a => a.localeCompare('--help') === 0) !== -1) {\n const docs = getConfigDocs()\n let out = `Params:\\n --version\\n It shows the package version.\\n`\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n Object.entries(docs).forEach(([name, value]: [string, any]) => {\n out += ` --${paramCase(name)}\n${wrap(value.doc, { width: 72, indent: ' ' })}\n Default: ${value.default}\\n`\n })\n console.log(out)\n process.exit(0)\n } else if (\n process.argv.findIndex(a => a.localeCompare('--version') === 0) !== -1\n ) {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const version = json5.parse(\n fs.readFileSync(resolvePackagePath('package.json')).toString(),\n ).version\n console.log(version)\n process.exit(0)\n }\n}\n\ntype Command = {\n command: string\n session?: number\n}\n\nasync function main(): Promise<void> {\n showHelpOrVersion()\n\n const config = loadConfig(process.argv[2])\n const pids = new Set<number>()\n\n await startThrottle(config.throttleConfig)\n\n const stop = async (): Promise<void> => {\n log.info('Stopping...')\n for (const pid of pids) {\n const childPids = await getProcessChildren(pid)\n log.debug(`Killing process ${pid} and children: ${childPids}`)\n try {\n process.kill(pid, 'SIGKILL')\n } catch (err: unknown) {\n log.debug(`Error killing process ${pid}: ${(err as Error).stack}`)\n }\n for (const childPid of childPids) {\n try {\n process.kill(childPid, 'SIGKILL')\n } catch (err: unknown) {\n log.debug(\n `Error killing child process ${childPid}: ${(err as Error).stack}`,\n )\n }\n }\n }\n await stopThrottle()\n process.exit(0)\n }\n registerExitHandler(() => stop())\n\n const commands = config.commandConfig\n ? (json5.parse(config.commandConfig) as Command[])\n : []\n for (const c of commands) {\n const { session, command } = c\n const shortName = command.split(' ')[0]\n const index = getSessionThrottleIndex(session || 0)\n const launcher = await throttleLauncher(command, index)\n try {\n const proc = spawn(launcher, {\n shell: false,\n stdio: ['ignore', 'pipe', 'pipe'],\n detached: false,\n })\n if (proc.pid) pids.add(proc.pid)\n proc.stdout.on('data', data => {\n log.info(`[${shortName}][stdout]`, data.toString().trim())\n })\n proc.stderr.on('data', data => {\n log.info(`[${shortName}][stderr]`, data.toString().trim())\n })\n proc.on('error', err => {\n if (err.message.startsWith('The operation was aborted')) return\n log.error(`Error running command \"${command}\": ${(err as Error).stack}`)\n })\n proc.once('exit', code => {\n log.info(`Command \"${command}\" exited with code: ${code || 0}`)\n if (proc.pid) pids.delete(proc.pid)\n /* fs.promises.unlink(launcher).catch(err => {\n log.warn(`Error unlinking \"${launcher}\": ${(err as Error).stack}`)\n }) */\n })\n } catch (err: unknown) {\n log.error(`Error running command \"${command}\": ${(err as Error).stack}`)\n }\n }\n\n // Stop after a configured duration.\n if (config.runDuration > 0) {\n setTimeout(stop, config.runDuration * 1000)\n }\n\n // Command line interface.\n console.log('Press [q] to stop the throttler')\n process.stdin.setRawMode(true)\n process.stdin.resume()\n process.stdin.on('data', async data => {\n log.debug('[stdin]', data[0])\n if (data[0] === 'q'.charCodeAt(0)) {\n try {\n await stop()\n } catch (err: unknown) {\n log.error(`stop error: ${(err as Error).stack}`)\n process.exit(1)\n }\n } else {\n console.log('Press [q] to stop the throttler')\n }\n })\n}\n\nif (require.main === module) {\n main().catch(err => {\n console.error(err)\n process.exit(-1)\n })\n}\n"]}
@@ -0,0 +1,21 @@
1
+ type ConfigDocs = Record<string, {
2
+ doc: string;
3
+ format: string;
4
+ default: string;
5
+ }>;
6
+ /**
7
+ * It returns the formatted configuration docs.
8
+ */
9
+ export declare function getConfigDocs(): ConfigDocs;
10
+ declare const schemaProperties: {
11
+ runDuration: number;
12
+ throttleConfig: string;
13
+ commandConfig: string;
14
+ };
15
+ /** [[include:config.md]] */
16
+ export type Config = typeof schemaProperties;
17
+ /**
18
+ * Loads the config object.
19
+ */
20
+ export declare function loadConfig(filePath?: string, values?: any): Config;
21
+ export {};
@@ -0,0 +1,199 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getConfigDocs = getConfigDocs;
37
+ exports.loadConfig = loadConfig;
38
+ const convict_1 = __importStar(require("convict"));
39
+ const convict_format_with_validator_1 = require("convict-format-with-validator");
40
+ const fs_1 = require("fs");
41
+ const utils_1 = require("./utils");
42
+ const log = (0, utils_1.logger)('throttler:config');
43
+ const float = {
44
+ name: 'float',
45
+ coerce: (v) => parseFloat(v),
46
+ validate: (v) => {
47
+ if (!Number.isFinite(v))
48
+ throw new Error(`Invalid float: ${v}`);
49
+ },
50
+ };
51
+ const index = {
52
+ name: 'index',
53
+ coerce: (v) => v,
54
+ validate: (v) => {
55
+ if (typeof v === 'string') {
56
+ if (v === 'true' || v === 'false' || v === '')
57
+ return;
58
+ if (v.indexOf('-') !== -1) {
59
+ v.split('-').forEach(n => {
60
+ if (isNaN(parseInt(n)) || !isFinite(parseInt(n)))
61
+ throw new Error(`Invalid index: ${n}`);
62
+ });
63
+ return;
64
+ }
65
+ if (v.indexOf(',') !== -1) {
66
+ v.split(',').forEach(n => {
67
+ if (isNaN(parseInt(n)) || !isFinite(parseInt(n)))
68
+ throw new Error(`Invalid index: ${n}`);
69
+ });
70
+ return;
71
+ }
72
+ if (isNaN(parseInt(v)) || !isFinite(parseInt(v)))
73
+ throw new Error(`Invalid index: ${v}`);
74
+ }
75
+ else if (typeof v === 'number' || typeof v === 'boolean') {
76
+ return;
77
+ }
78
+ throw new Error(`Invalid index: ${v}`);
79
+ },
80
+ };
81
+ (0, convict_1.addFormats)({ ipaddress: convict_format_with_validator_1.ipaddress, url: convict_format_with_validator_1.url, float, index });
82
+ // config schema
83
+ const configSchema = (0, convict_1.default)({
84
+ runDuration: {
85
+ doc: `If greater than 0, the test will stop after the provided number of \
86
+ seconds.`,
87
+ format: 'nat',
88
+ default: 0,
89
+ env: 'RUN_DURATION',
90
+ arg: 'run-duration',
91
+ },
92
+ throttleConfig: {
93
+ doc: `A JSON5 string with a valid network throttling configuration. \
94
+ Example: \
95
+
96
+ \`\`\`javascript
97
+ [{
98
+ sessions: '0-1',
99
+ device: 'eth0',
100
+ protocol: 'udp',
101
+ capture: 'capture.pcap',
102
+ up: {
103
+ rate: 1000,
104
+ delay: 50,
105
+ loss: 5,
106
+ queue: 10,
107
+ },
108
+ down: [
109
+ { rate: 2000, delay: 50, loss: 2, queue: 20 },
110
+ { rate: 1000, delay: 50, loss: 2, queue: 20, at: 60 },
111
+ ]
112
+ }]
113
+ \`\`\`
114
+ The sessions field represents the sessions IDs range that will be affected by the rule, e.g.: "0-10", "2,4" or simply "2". \
115
+ The device, protocol, up, down fields are optional. When device is not set, the default route device will be used. If protocol is specified ('udp' or 'tcp'), \
116
+ only the packets with the specified protocol will be affected by the shaping rules. \
117
+ \
118
+ `,
119
+ format: String,
120
+ nullable: true,
121
+ default: '',
122
+ env: 'THROTTLE_CONFIG',
123
+ arg: 'throttle-config',
124
+ },
125
+ commandConfig: {
126
+ doc: `The commands configuration.\
127
+ Example: \
128
+
129
+ \`\`\`javascript
130
+ [{
131
+ session: 0,
132
+ command: "firefox https://www.speedtest.net",
133
+ }]
134
+ \`\`\`
135
+ `,
136
+ format: String,
137
+ nullable: true,
138
+ default: '',
139
+ env: 'COMMAND_CONFIG',
140
+ arg: 'command-config',
141
+ },
142
+ });
143
+ /**
144
+ * Formats the schema documentation, calling the same function recursively.
145
+ * @param docs the documentation object to extend
146
+ * @param property the root property
147
+ * @param schema the config schema fragment
148
+ * @return the documentation object
149
+ */
150
+ function formatDocs(docs, property,
151
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
152
+ schema) {
153
+ if (schema._cvtProperties) {
154
+ Object.entries(schema._cvtProperties).forEach(([name, value]) => {
155
+ formatDocs(docs, `${property ? `${property}.` : ''}${name}`, value);
156
+ });
157
+ return docs;
158
+ }
159
+ if (property) {
160
+ docs[property] =
161
+ // eslint-disable-line no-param-reassign
162
+ {
163
+ doc: schema.doc,
164
+ format: JSON.stringify(schema.format, null, 2),
165
+ default: JSON.stringify(schema.default, null, 2),
166
+ };
167
+ }
168
+ return docs;
169
+ }
170
+ /**
171
+ * It returns the formatted configuration docs.
172
+ */
173
+ function getConfigDocs() {
174
+ return formatDocs({}, null, configSchema.getSchema());
175
+ }
176
+ const schemaProperties = configSchema.getProperties();
177
+ /**
178
+ * Loads the config object.
179
+ */
180
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
181
+ function loadConfig(filePath, values) {
182
+ if (filePath && (0, fs_1.existsSync)(filePath)) {
183
+ log.debug(`Loading config from ${filePath}`);
184
+ configSchema.loadFile(filePath);
185
+ }
186
+ else if (values) {
187
+ log.debug('Loading config from values.');
188
+ configSchema.load(values);
189
+ }
190
+ else {
191
+ log.debug('Using default values.');
192
+ configSchema.load({});
193
+ }
194
+ configSchema.validate({ allowed: 'strict' });
195
+ const config = configSchema.getProperties();
196
+ log.debug('Using config:', config);
197
+ return config;
198
+ }
199
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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","sourcesContent":["import convict, { addFormats } from 'convict'\nimport { ipaddress, url } from 'convict-format-with-validator'\nimport { existsSync } from 'fs'\n\nimport { logger } from './utils'\nconst log = logger('throttler:config')\n\nconst float = {\n name: 'float',\n coerce: (v: string) => parseFloat(v),\n validate: (v: number) => {\n if (!Number.isFinite(v)) throw new Error(`Invalid float: ${v}`)\n },\n}\n\nconst index = {\n name: 'index',\n coerce: (v: unknown) => v,\n validate: (v: boolean | string | number) => {\n if (typeof v === 'string') {\n if (v === 'true' || v === 'false' || v === '') return\n if (v.indexOf('-') !== -1) {\n v.split('-').forEach(n => {\n if (isNaN(parseInt(n)) || !isFinite(parseInt(n)))\n throw new Error(`Invalid index: ${n}`)\n })\n return\n }\n if (v.indexOf(',') !== -1) {\n v.split(',').forEach(n => {\n if (isNaN(parseInt(n)) || !isFinite(parseInt(n)))\n throw new Error(`Invalid index: ${n}`)\n })\n return\n }\n if (isNaN(parseInt(v)) || !isFinite(parseInt(v)))\n throw new Error(`Invalid index: ${v}`)\n } else if (typeof v === 'number' || typeof v === 'boolean') {\n return\n }\n throw new Error(`Invalid index: ${v}`)\n },\n}\n\naddFormats({ ipaddress, url, float, index })\n\n// config schema\nconst configSchema = convict({\n runDuration: {\n doc: `If greater than 0, the test will stop after the provided number of \\\nseconds.`,\n format: 'nat',\n default: 0,\n env: 'RUN_DURATION',\n arg: 'run-duration',\n },\n throttleConfig: {\n doc: `A JSON5 string with a valid network throttling configuration. \\\nExample: \\\n\n \\`\\`\\`javascript\n [{\n sessions: '0-1',\n device: 'eth0',\n protocol: 'udp',\n capture: 'capture.pcap',\n up: {\n rate: 1000,\n delay: 50,\n loss: 5,\n queue: 10,\n },\n down: [\n { rate: 2000, delay: 50, loss: 2, queue: 20 },\n { rate: 1000, delay: 50, loss: 2, queue: 20, at: 60 },\n ]\n }]\n \\`\\`\\`\nThe sessions field represents the sessions IDs range that will be affected by the rule, e.g.: \"0-10\", \"2,4\" or simply \"2\". \\\nThe device, protocol, up, down fields are optional. When device is not set, the default route device will be used. If protocol is specified ('udp' or 'tcp'), \\\nonly the packets with the specified protocol will be affected by the shaping rules. \\\n\\\n`,\n format: String,\n nullable: true,\n default: '',\n env: 'THROTTLE_CONFIG',\n arg: 'throttle-config',\n },\n commandConfig: {\n doc: `The commands configuration.\\\nExample: \\\n\n \\`\\`\\`javascript\n [{\n session: 0,\n command: \"firefox https://www.speedtest.net\",\n }]\n \\`\\`\\`\n`,\n format: String,\n nullable: true,\n default: '',\n env: 'COMMAND_CONFIG',\n arg: 'command-config',\n },\n})\n\ntype ConfigDocs = Record<\n string,\n { doc: string; format: string; default: string }\n>\n\n/**\n * Formats the schema documentation, calling the same function recursively.\n * @param docs the documentation object to extend\n * @param property the root property\n * @param schema the config schema fragment\n * @return the documentation object\n */\nfunction formatDocs(\n docs: ConfigDocs,\n property: string | null,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n schema: any,\n): ConfigDocs {\n if (schema._cvtProperties) {\n Object.entries(schema._cvtProperties).forEach(([name, value]) => {\n formatDocs(docs, `${property ? `${property}.` : ''}${name}`, value)\n })\n return docs\n }\n\n if (property) {\n docs[property] =\n // eslint-disable-line no-param-reassign\n {\n doc: schema.doc,\n format: JSON.stringify(schema.format, null, 2),\n default: JSON.stringify(schema.default, null, 2),\n }\n }\n return docs\n}\n\n/**\n * It returns the formatted configuration docs.\n */\nexport function getConfigDocs(): ConfigDocs {\n return formatDocs({}, null, configSchema.getSchema())\n}\n\nconst schemaProperties = configSchema.getProperties()\n\n/** [[include:config.md]] */\nexport type Config = typeof schemaProperties\n\n/**\n * Loads the config object.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function loadConfig(filePath?: string, values?: any): Config {\n if (filePath && existsSync(filePath)) {\n log.debug(`Loading config from ${filePath}`)\n configSchema.loadFile(filePath)\n } else if (values) {\n log.debug('Loading config from values.')\n configSchema.load(values)\n } else {\n log.debug('Using default values.')\n configSchema.load({})\n }\n\n configSchema.validate({ allowed: 'strict' })\n const config = configSchema.getProperties()\n\n log.debug('Using config:', config)\n return config\n}\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const promises_1 = require("fs/promises");
4
+ const config_1 = require("./config");
5
+ function formatJson(data) {
6
+ return `\`${data.replace(/\n/g, '')}\``;
7
+ }
8
+ let data = `
9
+ The configuration properties are applied in the following order (from higher to
10
+ lower precedence):
11
+
12
+ - arguments passed to the executable in kebab case (e.g. \`--run-duration\`);
13
+ - environment variables in uppercase snake format (e.g. \`RUN_DURATION\`);
14
+ - \`config.json\` configuration file;
15
+ - default values.
16
+
17
+ `;
18
+ const configDocs = (0, config_1.getConfigDocs)();
19
+ Object.entries(configDocs).forEach(entry => {
20
+ const [name, value] = entry;
21
+ data += `\
22
+ ## ${name}
23
+ ${value.doc}
24
+
25
+ *Type*: \`${value.format === '"nat"'
26
+ ? 'positive int'
27
+ : value.format.replace(/^"(.+)"$/, '$1')}\`
28
+
29
+ *Default*: ${formatJson(value.default)}
30
+
31
+ `;
32
+ });
33
+ data += `
34
+
35
+ ---
36
+
37
+ `;
38
+ (0, promises_1.writeFile)('docs/config.md', data).then(() => {
39
+ console.log('done'); // eslint-disable-line
40
+ }, err => {
41
+ console.error(`Error writing file: ${err.message}`); // eslint-disable-line
42
+ });
43
+ //# sourceMappingURL=generate-config-docs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-config-docs.js","sourceRoot":"","sources":["../../src/generate-config-docs.ts"],"names":[],"mappings":";;AAAA,0CAAuC;AAEvC,qCAAwC;AAExC,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,KAAK,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAA;AACzC,CAAC;AAED,IAAI,IAAI,GAAG;;;;;;;;;CASV,CAAA;AAED,MAAM,UAAU,GAAG,IAAA,sBAAa,GAAE,CAAA;AAClC,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;IACzC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,KAAK,CAAA;IAC3B,IAAI,IAAI;KACL,IAAI;EACP,KAAK,CAAC,GAAG;;YAGP,KAAK,CAAC,MAAM,KAAK,OAAO;QACtB,CAAC,CAAC,cAAc;QAChB,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAC3C;;aAEW,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC;;CAErC,CAAA;AACD,CAAC,CAAC,CAAA;AAEF,IAAI,IAAI;;;;CAIP,CAAA;AAED,IAAA,oBAAS,EAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC,IAAI,CACpC,GAAG,EAAE;IACJ,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,sBAAsB;AAC5C,CAAC,EACD,GAAG,CAAC,EAAE;IACL,OAAO,CAAC,KAAK,CAAC,uBAAuB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,sBAAsB;AAC5E,CAAC,CACF,CAAA","sourcesContent":["import { writeFile } from 'fs/promises'\n\nimport { getConfigDocs } from './config'\n\nfunction formatJson(data: string): string {\n return `\\`${data.replace(/\\n/g, '')}\\``\n}\n\nlet data = `\nThe configuration properties are applied in the following order (from higher to\nlower precedence):\n\n- arguments passed to the executable in kebab case (e.g. \\`--run-duration\\`);\n- environment variables in uppercase snake format (e.g. \\`RUN_DURATION\\`);\n- \\`config.json\\` configuration file;\n- default values.\n\n`\n\nconst configDocs = getConfigDocs()\nObject.entries(configDocs).forEach(entry => {\n const [name, value] = entry\n data += `\\\n## ${name}\n${value.doc}\n\n*Type*: \\`${\n value.format === '\"nat\"'\n ? 'positive int'\n : value.format.replace(/^\"(.+)\"$/, '$1')\n }\\`\n\n*Default*: ${formatJson(value.default)}\n\n`\n})\n\ndata += `\n\n---\n\n`\n\nwriteFile('docs/config.md', data).then(\n () => {\n\t console.log('done'); // eslint-disable-line\n },\n err => {\n\t console.error(`Error writing file: ${err.message}`); // eslint-disable-line\n },\n)\n"]}
@@ -0,0 +1,4 @@
1
+ export * from './app';
2
+ export * from './config';
3
+ export * from './throttle';
4
+ export * from './utils';
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./app"), exports);
18
+ __exportStar(require("./config"), exports);
19
+ __exportStar(require("./throttle"), exports);
20
+ __exportStar(require("./utils"), exports);
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,wCAAqB;AACrB,2CAAwB;AACxB,6CAA0B;AAC1B,0CAAuB","sourcesContent":["export * from './app'\nexport * from './config'\nexport * from './throttle'\nexport * from './utils'\n"]}
@@ -0,0 +1,85 @@
1
+ /** The network throttle rules to be applied to uplink or downlink. */
2
+ export type ThrottleRule = {
3
+ /** The available bandwidth (Kbps). */
4
+ rate?: number;
5
+ /** The one-way delay (ms). */
6
+ delay?: number;
7
+ /** The one-way delay jitter (ms). */
8
+ delayJitter?: number;
9
+ /** The one-way delay jitter correlation. */
10
+ delayJitterCorrelation?: number;
11
+ /** The delay distribution. */
12
+ delayDistribution?: 'uniform' | 'normal' | 'pareto' | 'paretonormal';
13
+ /** The packet reordering percentage. */
14
+ reorder?: number;
15
+ /** The packet reordering correlation. */
16
+ reorderCorrelation?: number;
17
+ /** The packet reordering gap. */
18
+ reorderGap?: number;
19
+ /** The packet loss percentage. */
20
+ loss?: number;
21
+ /** The packet loss burst. */
22
+ lossBurst?: number;
23
+ /** The packet queue size. */
24
+ queue?: number;
25
+ /** If set, the rule will be applied after the specified number of seconds. */
26
+ at?: number;
27
+ };
28
+ /**
29
+ * The network throttling rules.
30
+ * Specify multiple {@link ThrottleRule} with different `at` values to schedule
31
+ * network bandwidth/delay fluctuations during the test run, e.g.:
32
+ *
33
+ * ```javascript
34
+ * {
35
+ device: "eth0",
36
+ sessions: "0-1",
37
+ protocol: "udp",
38
+ down: [
39
+ { rate: 1000000, delay: 50, loss: 0, queue: 5 },
40
+ { rate: 200000, delay: 100, loss: 5, queue: 5, at: 60},
41
+ ],
42
+ up: { rate: 100000, delay: 50, queue: 5 },
43
+ capture: 'capture.pcap',
44
+ }
45
+ * ```
46
+ */
47
+ export type ThrottleConfig = {
48
+ /** The network interface to throttle. If not specified, the default interface will be used. */
49
+ device?: string;
50
+ /** The sessions to throttle. It could be a single index ("0"), a range ("0-2") or a comma-separated list ("0,3,4"). */
51
+ sessions?: string;
52
+ /** The protocol to throttle. */
53
+ protocol?: 'udp' | 'tcp';
54
+ /** A comma-separated list of source ports that will not be throttled. */
55
+ skipSourcePorts?: string;
56
+ /** A comma-separated list of destination ports that will not be throttled. */
57
+ skipDestinationPorts?: string;
58
+ /** An additional IPTables packet filter rule. */
59
+ filter?: string;
60
+ /** An additional TC match expression used to filter packets (https://man7.org/linux/man-pages/man8/tc-ematch.8.html). */
61
+ match?: string;
62
+ /** If set, the packets matching the provided session and protocol will be captured at that file location. */
63
+ capture?: string;
64
+ /** The uplink throttle rules. */
65
+ up?: ThrottleRule | ThrottleRule[];
66
+ /** The downlink throttle rules. */
67
+ down?: ThrottleRule | ThrottleRule[];
68
+ };
69
+ /**
70
+ * Starts a network throttle configuration
71
+ * @param config A JSON5 configuration parsed as {@link ThrottleConfig}.
72
+ */
73
+ export declare function startThrottle(config: string): Promise<void>;
74
+ /**
75
+ * Stops the network throttle.
76
+ */
77
+ export declare function stopThrottle(): Promise<void>;
78
+ export declare function getSessionThrottleIndex(sessionId: number): number;
79
+ export declare function getSessionThrottleValues(index: number, direction: 'up' | 'down'): {
80
+ rate?: number;
81
+ delay?: number;
82
+ loss?: number;
83
+ queue?: number;
84
+ };
85
+ export declare function throttleLauncher(executablePath: string, index: number): Promise<string>;