@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/src/utils.ts ADDED
@@ -0,0 +1,249 @@
1
+ import { spawn } from 'child_process'
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+
5
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
6
+ export const { Log } = require('debug-level')
7
+
8
+ type Logger = {
9
+ error: (...args: unknown[]) => void
10
+ warn: (...args: unknown[]) => void
11
+ info: (...args: unknown[]) => void
12
+ debug: (...args: unknown[]) => void
13
+ log: (...args: unknown[]) => void
14
+ }
15
+
16
+ export function logger(name: string, options = {}): Logger {
17
+ return new Log(name, { splitLine: false, ...options })
18
+ }
19
+
20
+ export class LoggerInterface {
21
+ name?: string
22
+
23
+ private logInit(args: unknown[]): void {
24
+ if (this.name) {
25
+ args.unshift(`[${this.name}]`)
26
+ }
27
+ }
28
+
29
+ debug(...args: unknown[]): void {
30
+ this.logInit(args)
31
+ log.debug(...args)
32
+ }
33
+
34
+ info(...args: unknown[]): void {
35
+ this.logInit(args)
36
+ log.info(...args)
37
+ }
38
+
39
+ warn(...args: unknown[]): void {
40
+ this.logInit(args)
41
+ log.warn(...args)
42
+ }
43
+
44
+ error(...args: unknown[]): void {
45
+ this.logInit(args)
46
+ log.error(...args)
47
+ }
48
+
49
+ log(...args: unknown[]): void {
50
+ this.logInit(args)
51
+ log.log(...args)
52
+ }
53
+ }
54
+
55
+ const log = logger('webrtcperf:utils')
56
+
57
+ /**
58
+ * Resolves the absolute path from the package installation directory.
59
+ * @param relativePath The relative path.
60
+ * @returns The absolute path.
61
+ */
62
+ export function resolvePackagePath(relativePath: string): string {
63
+ if ('__nexe' in process) {
64
+ return relativePath
65
+ }
66
+ if (process.env.WEBPACK) {
67
+ return path.join(path.dirname(__filename), relativePath)
68
+ }
69
+ for (const d of ['..', '../..']) {
70
+ const p = path.join(__dirname, d, relativePath)
71
+ if (fs.existsSync(p)) {
72
+ return require.resolve(p)
73
+ }
74
+ }
75
+ throw new Error(`resolvePackagePath: ${relativePath} not found`)
76
+ }
77
+
78
+ /**
79
+ * Format number to the specified precision.
80
+ * @param value value to format
81
+ * @param precision precision
82
+ */
83
+ export function toPrecision(value: number, precision = 3): string {
84
+ return (Math.round(value * 10 ** precision) / 10 ** precision).toFixed(
85
+ precision,
86
+ )
87
+ }
88
+
89
+ export async function getDefaultNetworkInterface(): Promise<string> {
90
+ const { stdout } = await runShellCommand(
91
+ `ip route | awk '/default/ {print $5; exit}' | tr -d ''`,
92
+ )
93
+ return stdout.trim()
94
+ }
95
+
96
+ export async function checkNetworkInterface(device: string): Promise<void> {
97
+ if (device === 'lo') return
98
+ await runShellCommand(`ip route | grep -q "dev ${device}"`)
99
+ }
100
+
101
+ /** Runs the shell command asynchronously. */
102
+ export async function runShellCommand(
103
+ cmd: string,
104
+ verbose = false,
105
+ ): Promise<{ stdout: string; stderr: string }> {
106
+ if (verbose) log.debug(`runShellCommand cmd: ${cmd}`)
107
+ return new Promise((resolve, reject) => {
108
+ const p = spawn(cmd, {
109
+ shell: true,
110
+ stdio: ['ignore', 'pipe', 'pipe'],
111
+ detached: true,
112
+ })
113
+ let stdout = ''
114
+ let stderr = ''
115
+ p.stdout.on('data', data => {
116
+ if (stdout.length > 512 * 1024) {
117
+ stdout = stdout.slice(data.length)
118
+ }
119
+ stdout += data
120
+ })
121
+ p.stderr.on('data', data => {
122
+ if (stderr.length > 512 * 1024) {
123
+ stderr = stderr.slice(data.length)
124
+ }
125
+ stderr += data
126
+ })
127
+ p.once('error', err => reject(err))
128
+ p.once('close', code => {
129
+ if (code !== 0) {
130
+ reject(
131
+ new Error(
132
+ `runShellCommand cmd: ${cmd} failed with code ${code}: ${stderr}`,
133
+ ),
134
+ )
135
+ } else {
136
+ if (verbose)
137
+ log.debug(`runShellCommand cmd: ${cmd} done`, { stdout, stderr })
138
+ resolve({ stdout, stderr })
139
+ }
140
+ })
141
+ })
142
+ }
143
+
144
+ /** Exit handler callback. */
145
+ export type ExitHandler = (signal?: string) => Promise<void>
146
+
147
+ const exitHandlers = new Set<ExitHandler>()
148
+
149
+ /**
150
+ * Register an {@link ExitHandler} callback that will be executed at the
151
+ * nodejs process exit.
152
+ * @param exitHandler
153
+ */
154
+ export function registerExitHandler(exitHandler: ExitHandler): void {
155
+ exitHandlers.add(exitHandler)
156
+ }
157
+
158
+ /**
159
+ * Un-registers the {@link ExitHandler} callback.
160
+ * @param exitHandler
161
+ */
162
+ export function unregisterExitHandler(exitHandler: ExitHandler): void {
163
+ exitHandlers.delete(exitHandler)
164
+ }
165
+
166
+ const runExitHandlers = async (signal?: string): Promise<void> => {
167
+ let i = 0
168
+ for (const exitHandler of exitHandlers.values()) {
169
+ const id = `${i + 1}/${exitHandlers.size}`
170
+ log.debug(`running exitHandler ${id}`)
171
+ try {
172
+ await exitHandler(signal)
173
+ log.debug(` exitHandler ${id} done`)
174
+ } catch (err) {
175
+ log.error(`exitHandler ${id} error: ${err}`)
176
+ }
177
+ i++
178
+ }
179
+ exitHandlers.clear()
180
+ }
181
+
182
+ let runExitHandlersPromise: Promise<void> | null = null
183
+
184
+ /**
185
+ * Runs the registered exit handlers immediately.
186
+ * @param signal The process exit signal.
187
+ */
188
+ export async function runExitHandlersNow(signal?: string): Promise<void> {
189
+ if (!runExitHandlersPromise) {
190
+ runExitHandlersPromise = runExitHandlers(signal)
191
+ }
192
+ await runExitHandlersPromise
193
+ }
194
+
195
+ const SIGNALS = [
196
+ 'beforeExit',
197
+ 'uncaughtException',
198
+ 'unhandledRejection',
199
+ 'SIGHUP',
200
+ 'SIGINT',
201
+ 'SIGQUIT',
202
+ 'SIGILL',
203
+ 'SIGTRAP',
204
+ 'SIGABRT',
205
+ 'SIGBUS',
206
+ 'SIGFPE',
207
+ 'SIGUSR1',
208
+ 'SIGSEGV',
209
+ 'SIGUSR2',
210
+ 'SIGTERM',
211
+ ]
212
+ process.setMaxListeners(process.getMaxListeners() + SIGNALS.length)
213
+ SIGNALS.forEach(event =>
214
+ process.once(event, async signal => {
215
+ if (signal instanceof Error) {
216
+ log.error(`Exit on error: ${signal.stack || signal.message}`)
217
+ } else {
218
+ log.debug(`Exit on signal: ${signal}`)
219
+ }
220
+ await runExitHandlersNow(signal)
221
+ process.exit(0)
222
+ }),
223
+ )
224
+
225
+ export async function getProcessChildren(pid: number): Promise<number[]> {
226
+ log.debug(`getProcessChildren pid=${pid}`)
227
+ const pids = []
228
+ try {
229
+ const p = await runShellCommand(`pgrep -P ${pid}`)
230
+ for (const pid of p.stdout.trim().split('\n').map(Number)) {
231
+ pids.push(pid)
232
+ try {
233
+ const childPids = await getProcessChildren(pid)
234
+ for (const p of childPids) {
235
+ pids.push(p)
236
+ }
237
+ } catch (err) {
238
+ log.debug(
239
+ `Error getting process ${pid} children: ${(err as Error).message}`,
240
+ )
241
+ }
242
+ }
243
+ } catch (err) {
244
+ log.debug(
245
+ `Error getting process ${pid} children: ${(err as Error).message}`,
246
+ )
247
+ }
248
+ return pids
249
+ }
package/throttler.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ (()=>{var e={859:function(e,t,r){"use strict";e=r.nmd(e);var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const o=r(423),s=r(200),i=n(r(896)),a=n(r(865)),c=n(r(251)),l=r(28),d=r(670),u=r(185),p=(0,u.logger)("throttler");r.c[r.s]===e&&async function(){!function(){if(-1!==process.argv.findIndex((e=>0===e.localeCompare("--help")))){const e=(0,l.getConfigDocs)();let t="Params:\n --version\n It shows the package version.\n";Object.entries(e).forEach((([e,r])=>{t+=` --${(0,o.paramCase)(e)}\n${(0,c.default)(r.doc,{width:72,indent:" "})}\n Default: ${r.default}\n`})),console.log(t),process.exit(0)}else if(-1!==process.argv.findIndex((e=>0===e.localeCompare("--version")))){const e=a.default.parse(i.default.readFileSync((0,u.resolvePackagePath)("package.json")).toString()).version;console.log(e),process.exit(0)}}();const e=(0,l.loadConfig)(process.argv[2]),t=new Set;await(0,d.startThrottle)(e.throttleConfig);const r=async()=>{p.info("Stopping...");for(const e of t){const t=await(0,u.getProcessChildren)(e);p.debug(`Killing process ${e} and children: ${t}`);try{process.kill(e,"SIGKILL")}catch(t){p.debug(`Error killing process ${e}: ${t.stack}`)}for(const e of t)try{process.kill(e,"SIGKILL")}catch(t){p.debug(`Error killing child process ${e}: ${t.stack}`)}}await(0,d.stopThrottle)(),process.exit(0)};(0,u.registerExitHandler)((()=>r()));const n=e.commandConfig?a.default.parse(e.commandConfig):[];for(const e of n){const{session:r,command:n}=e,o=n.split(" ")[0],i=(0,d.getSessionThrottleIndex)(r||0),a=await(0,d.throttleLauncher)(n,i);try{const e=(0,s.spawn)(a,{shell:!1,stdio:["ignore","pipe","pipe"],detached:!1});e.pid&&t.add(e.pid),e.stdout.on("data",(e=>{p.info(`[${o}][stdout]`,e.toString().trim())})),e.stderr.on("data",(e=>{p.info(`[${o}][stderr]`,e.toString().trim())})),e.on("error",(e=>{e.message.startsWith("The operation was aborted")||p.error(`Error running command "${n}": ${e.stack}`)})),e.once("exit",(r=>{p.info(`Command "${n}" exited with code: ${r||0}`),e.pid&&t.delete(e.pid)}))}catch(e){p.error(`Error running command "${n}": ${e.stack}`)}}e.runDuration>0&&setTimeout(r,1e3*e.runDuration),console.log("Press [q] to stop the throttler"),process.stdin.setRawMode(!0),process.stdin.resume(),process.stdin.on("data",(async e=>{if(p.debug("[stdin]",e[0]),e[0]==="q".charCodeAt(0))try{await r()}catch(e){p.error(`stop error: ${e.stack}`),process.exit(1)}else console.log("Press [q] to stop the throttler")}))}().catch((e=>{console.error(e),process.exit(-1)}))},28:function(e,t,r){"use strict";var n,o=this&&this.__createBinding||(Object.create?function(e,t,r,n){void 0===n&&(n=r);var o=Object.getOwnPropertyDescriptor(t,r);o&&!("get"in o?!t.__esModule:o.writable||o.configurable)||(o={enumerable:!0,get:function(){return t[r]}}),Object.defineProperty(e,n,o)}:function(e,t,r,n){void 0===n&&(n=r),e[n]=t[r]}),s=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),i=this&&this.__importStar||(n=function(e){return n=Object.getOwnPropertyNames||function(e){var t=[];for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[t.length]=r);return t},n(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r=n(e),i=0;i<r.length;i++)"default"!==r[i]&&o(t,e,r[i]);return s(t,e),t});Object.defineProperty(t,"__esModule",{value:!0}),t.getConfigDocs=function(){return g({},null,f.getSchema())},t.loadConfig=function(e,t){e&&(0,l.existsSync)(e)?(d.debug(`Loading config from ${e}`),f.loadFile(e)):t?(d.debug("Loading config from values."),f.load(t)):(d.debug("Using default values."),f.load({}));f.validate({allowed:"strict"});const r=f.getProperties();return d.debug("Using config:",r),r};const a=i(r(950)),c=r(618),l=r(896),d=(0,r(185).logger)("throttler:config"),u={name:"float",coerce:e=>parseFloat(e),validate:e=>{if(!Number.isFinite(e))throw new Error(`Invalid float: ${e}`)}},p={name:"index",coerce:e=>e,validate:e=>{if("string"==typeof e){if("true"===e||"false"===e||""===e)return;if(-1!==e.indexOf("-"))return void e.split("-").forEach((e=>{if(isNaN(parseInt(e))||!isFinite(parseInt(e)))throw new Error(`Invalid index: ${e}`)}));if(-1!==e.indexOf(","))return void e.split(",").forEach((e=>{if(isNaN(parseInt(e))||!isFinite(parseInt(e)))throw new Error(`Invalid index: ${e}`)}));if(isNaN(parseInt(e))||!isFinite(parseInt(e)))throw new Error(`Invalid index: ${e}`)}else if("number"==typeof e||"boolean"==typeof e)return;throw new Error(`Invalid index: ${e}`)}};(0,a.addFormats)({ipaddress:c.ipaddress,url:c.url,float:u,index:p});const f=(0,a.default)({runDuration:{doc:"If greater than 0, the test will stop after the provided number of seconds.",format:"nat",default:0,env:"RUN_DURATION",arg:"run-duration"},throttleConfig:{doc:"A JSON5 string with a valid network throttling configuration. Example: \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\". 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'), only the packets with the specified protocol will be affected by the shaping rules. ",format:String,nullable:!0,default:"",env:"THROTTLE_CONFIG",arg:"throttle-config"},commandConfig:{doc:'The commands configuration.Example: \n ```javascript\n [{\n session: 0,\n command: "firefox https://www.speedtest.net",\n }]\n ```\n',format:String,nullable:!0,default:"",env:"COMMAND_CONFIG",arg:"command-config"}});function g(e,t,r){return r._cvtProperties?(Object.entries(r._cvtProperties).forEach((([r,n])=>{g(e,`${t?`${t}.`:""}${r}`,n)})),e):(t&&(e[t]={doc:r.doc,format:JSON.stringify(r.format,null,2),default:JSON.stringify(r.default,null,2)}),e)}f.getProperties()},670:function(e,t,r){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.startThrottle=async function(e){if("linux"!==a.default.platform())throw new Error("Throttle option is only supported on Linux");try{d=i.default.parse(e),l.debug("Starting throttle with config:",d),await g(),await async function(){if(!d||!d.length)return;let e=d[0].device;if(e)try{await(0,c.checkNetworkInterface)(e)}catch(t){l.warn(`Network interface ${e} not found, using default.`),e=""}e||(e=await(0,c.getDefaultNetworkInterface)());await(0,c.runShellCommand)(`set -e;\n\nsudo -n modprobe ifb || true;\nsudo -n ip link add ifb0 type ifb || true;\nsudo -n ip link set dev ifb0 up;\n\nsudo -n tc qdisc add dev ${e} root handle 1: htb default 1;\nsudo -n tc class add dev ${e} parent 1: classid 1:1 htb rate 1Gbit ceil 1Gbit;\n\nsudo -n tc qdisc add dev ifb0 root handle 1: htb default 1;\nsudo -n tc class add dev ifb0 parent 1: classid 1:1 htb rate 1Gbit ceil 1Gbit;\n\nsudo -n tc qdisc add dev ${e} ingress handle ffff: || true;\nsudo -n tc filter add dev ${e} parent ffff: protocol ip u32 match u32 0 0 action connmark action mirred egress redirect dev ifb0 flowid 1:1;\n`,!0);let t=0;for(const r of d)r.up&&await $(r,"up",e,t,r.protocol,r.match),r.down&&await $(r,"down","ifb0",t,r.protocol,r.match),r.capture&&p.set(t,w(t,r.capture,r.protocol)),t++}()}catch(t){throw l.error(`startThrottle "${e}" error: ${t.stack}`),await m(),t}},t.stopThrottle=m,t.getSessionThrottleIndex=function(e){if(!d)return-1;for(const[t,r]of d.entries())if(void 0!==r.sessions&&""!==r.sessions)try{if(r.sessions.includes("-")){const[n,o]=r.sessions.split("-").map(Number);if(e>=n&&e<=o)return t}else if(r.sessions.includes(",")){if(r.sessions.split(",").map(Number).includes(e))return t}else if(e===Number(r.sessions))return t}catch(t){l.error(`getSessionThrottleId sessionId=${e} error: ${t.stack}`)}return-1},t.getSessionThrottleValues=function(e,t){if(e<0)return{};return f[t].get(e)||{}},t.throttleLauncher=async function(e,t){if(l.debug(`throttleLauncher executablePath=${e} index=${t}`),!d||t<0)return e;const r=d[t],n=t+1,o=`/tmp/throttler-launcher-${t}`,i=`throttler${t}`,a=`${r.protocol?`-p ${r.protocol}`:""}${r.skipSourcePorts?` -m multiport ! --sports ${r.skipSourcePorts}`:""}${r.skipDestinationPorts?` -m multiport ! --dports ${r.skipDestinationPorts}`:""}${r.filter?` ${r.filter}`:""}`;return await s.default.promises.writeFile(o,`#!/bin/bash\ngetent group ${i} >/dev/null || sudo -n addgroup --system ${i}\nsudo -n adduser $USER ${i} --quiet\n\nrule=$(sudo -n iptables -t mangle -L OUTPUT --line-numbers | grep "owner GID match ${i}" | awk '{print $1}')\nif [ -n "$rule" ]; then\n sudo -n iptables -t mangle -R OUTPUT \${rule} ${a} -m owner --gid-owner ${i} -j MARK --set-mark ${n} \nelse\n sudo -n iptables -t mangle -I OUTPUT 1 ${a} -m owner --gid-owner ${i} -j MARK --set-mark ${n}\nfi\n\nsudo -n iptables -t mangle -L PREROUTING | grep -q "CONNMARK restore" || sudo -n iptables -t mangle -I PREROUTING 1 -j CONNMARK --restore-mark\nsudo -n iptables -t mangle -L POSTROUTING | grep -q "CONNMARK save" || sudo -n iptables -t mangle -I POSTROUTING 1 -j CONNMARK --save-mark\n\nfunction stop() {\n echo "Stopping throttler"\n}\ntrap stop SIGINT SIGTERM\n\necho "running: ${e} $@"\nexec newgrp ${i} <<EOF\n${e} $@\nEOF`),await s.default.promises.chmod(o,493),o};const o=r(200),s=n(r(896)),i=n(r(865)),a=n(r(857)),c=r(185),l=(0,c.logger)("throttler:throttle");let d=null;const u=new Set,p=new Map,f={up:new Map,down:new Map};async function g(){await Promise.allSettled([...p.values()].map((e=>e()))),p.clear(),u.forEach((e=>clearTimeout(e))),u.clear(),f.up.clear(),f.down.clear();let e=d?.length?d[0].device:"";e||(e=await(0,c.getDefaultNetworkInterface)()),await(0,c.runShellCommand)(`sudo -n tc qdisc del dev ${e} root || true;\nsudo -n tc class del dev ${e} || true;\nsudo -n tc filter del dev ${e} || true;\nsudo -n tc qdisc del dev ${e} ingress || true;\n\nsudo -n tc qdisc del dev ifb0 root || true;\nsudo -n tc class del dev ifb0 root || true;\nsudo -n tc filter del dev ifb0 root || true;\n`),await async function(){if(!d?.length)return;l.debug(`cleanupRules (${d.length})`);try{await(0,c.runShellCommand)(`for i in $(seq 0 ${d.length}); do\n rule=$(sudo -n iptables -t mangle -L OUTPUT --line-numbers | grep "owner GID match throttler\${i}" | awk '{print $1}');\n if [ -n "$rule" ]; then\n sudo -n iptables -t mangle -D OUTPUT \${rule};\n fi;\ndone;`)}catch(e){l.error(`cleanupRules error: ${e.stack}`)}}()}function h(e,t,r=1500){return Math.ceil(1.5*e*1e3/8*(t/1e3)/r)}async function $(e,t,r,n,o,s){let i=e[t];if(i){l.info(`applyRules device=${r} index=${n} protocol=${o} match=${s} ${JSON.stringify(i)}`),Array.isArray(i)||(i=[i]),i.sort(((e,t)=>(e.at||0)-(t.at||0)));for(const[e,a]of i.entries()){const{rate:i,delay:d,delayJitter:p,delayJitterCorrelation:g,delayDistribution:$,reorder:m,reorderCorrelation:w,reorderGap:b,loss:v,lossBurst:y,queue:I,at:x}=a,S=I??h(i||0,d||0),k=n+1,P=n+2;if(0===e){const e=[`'meta(nf_mark eq ${k})'`];"udp"===o?e.push("'cmp(u8 at 9 layer network eq 0x11)'"):"tcp"===o&&e.push("'cmp(u8 at 9 layer network eq 0x6)'"),s&&e.push(s);const t=`set -e;\n\nsudo -n tc class add dev ${r} parent 1: classid 1:${P} htb rate 1Gbit ceil 1Gbit;\n\nsudo -n tc qdisc add dev ${r} parent 1:${P} handle ${P}: netem; \nsudo -n tc filter add dev ${r} parent 1: protocol ip basic match ${e.join(" and ")} flowid 1:${P};\n`;try{await(0,c.runShellCommand)(t,!0)}catch(e){throw l.error(`error running "${t}": ${e.stack}`),e}}const O=setTimeout((async()=>{let e="";if(i&&i>0&&(e+=` rate ${i}kbit`),S&&S>0&&(e+=` limit ${S}`),d&&d>0&&(e+=` delay ${d}ms`,p&&p>0&&(e+=` ${p}ms`,g&&g>0&&(e+=` ${g}`)),$&&(e+=` distribution ${$}`)),v&&v>0)if(y&&y>0){const t=100*v/(y*(100-v)),r=100/y;e+=` loss gemodel ${(0,c.toPrecision)(t,2)} ${(0,c.toPrecision)(r,2)}`}else e+=` loss ${(0,c.toPrecision)(v,2)}%`;m&&m>0&&(e+=` reorder ${(0,c.toPrecision)(m,2)}%`,w&&w>0&&(e+=` ${(0,c.toPrecision)(w,2)}`),b&&b>0&&(e+=` gap ${b}`)),l.info(`applying rules on ${r} (${k}): ${e}`);const o=`sudo -n tc qdisc change dev ${r} parent 1:${P} handle ${P}: netem ${e}`;try{u.delete(O),await(0,c.runShellCommand)(o),f[t].set(n,{rate:i?1e3*i:void 0,delay:d||void 0,loss:v||void 0,queue:S||void 0})}catch(e){l.error(`error running "${o}": ${e.stack}`)}}),1e3*(x||0));u.add(O)}}}async function m(){if("linux"!==a.default.platform())throw new Error("Throttle option is only supported on Linux");try{l.debug("Stopping throttle"),await g(),l.debug("Stopping throttle done"),d=null}catch(e){l.error(`Stop throttle error: ${e.stack}`)}}function w(e,t,r){const n=e+1;l.info(`Starting capture ${t}`);const s=`#!/bin/bash\nsudo -n iptables -L INPUT | grep -q "nflog-group ${n}" || sudo -n iptables -A INPUT ${r?`-p ${r}`:""} -m connmark --mark ${n} -j NFLOG --nflog-group ${n}\nsudo -n iptables -L OUTPUT | grep -q "nflog-group ${n}" || sudo -n iptables -A OUTPUT ${r?`-p ${r}`:""} -m connmark --mark ${n} -j NFLOG --nflog-group ${n}\nexec dumpcap -q -i nflog:${n} -w ${t}\n`,i=(0,o.spawn)(s,{shell:!0,stdio:["ignore","ignore","pipe"],detached:!0});let a="";i.stderr.on("data",(e=>{a+=e})),i.on("error",(e=>{l.error(`Error running command capturePackets ${e}: ${a}`)})),i.once("exit",(e=>{e?l.error(`capturePackets exited with code ${e}: ${a}`):l.info("capturePackets exited")}));return async()=>{l.info(`Stopping capture ${t}`),i.kill("SIGINT"),await(0,c.runShellCommand)(`#!/bin/bash\nsudo -n iptables -D INPUT ${r?`-p ${r}`:""} -m connmark --mark ${n} -j NFLOG --nflog-group ${n}\nsudo -n iptables -D OUTPUT ${r?`-p ${r}`:""} -m connmark --mark ${n} -j NFLOG --nflog-group ${n}\n`)}}},185:function(e,t,r){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.LoggerInterface=t.Log=void 0,t.logger=a,t.resolvePackagePath=function(e){if("__nexe"in process)return e;return i.default.join(i.default.dirname(__filename),e);for(const t of["..","../.."]){const n=i.default.join(__dirname,t,e);if(s.default.existsSync(n))return r(589).resolve(n)}throw new Error(`resolvePackagePath: ${e} not found`)},t.toPrecision=function(e,t=3){return(Math.round(e*10**t)/10**t).toFixed(t)},t.getDefaultNetworkInterface=async function(){const{stdout:e}=await l("ip route | awk '/default/ {print $5; exit}' | tr -d ''");return e.trim()},t.checkNetworkInterface=async function(e){if("lo"===e)return;await l(`ip route | grep -q "dev ${e}"`)},t.runShellCommand=l,t.registerExitHandler=function(e){d.add(e)},t.unregisterExitHandler=function(e){d.delete(e)},t.runExitHandlersNow=f,t.getProcessChildren=async function e(t){c.debug(`getProcessChildren pid=${t}`);const r=[];try{const n=await l(`pgrep -P ${t}`);for(const t of n.stdout.trim().split("\n").map(Number)){r.push(t);try{const n=await e(t);for(const e of n)r.push(e)}catch(e){c.debug(`Error getting process ${t} children: ${e.message}`)}}}catch(e){c.debug(`Error getting process ${t} children: ${e.message}`)}return r};const o=r(200),s=n(r(896)),i=n(r(928));function a(e,r={}){return new t.Log(e,{splitLine:!1,...r})}t.Log=r(940).Log;t.LoggerInterface=class{name;logInit(e){this.name&&e.unshift(`[${this.name}]`)}debug(...e){this.logInit(e),c.debug(...e)}info(...e){this.logInit(e),c.info(...e)}warn(...e){this.logInit(e),c.warn(...e)}error(...e){this.logInit(e),c.error(...e)}log(...e){this.logInit(e),c.log(...e)}};const c=a("webrtcperf:utils");async function l(e,t=!1){return t&&c.debug(`runShellCommand cmd: ${e}`),new Promise(((r,n)=>{const s=(0,o.spawn)(e,{shell:!0,stdio:["ignore","pipe","pipe"],detached:!0});let i="",a="";s.stdout.on("data",(e=>{i.length>524288&&(i=i.slice(e.length)),i+=e})),s.stderr.on("data",(e=>{a.length>524288&&(a=a.slice(e.length)),a+=e})),s.once("error",(e=>n(e))),s.once("close",(o=>{0!==o?n(new Error(`runShellCommand cmd: ${e} failed with code ${o}: ${a}`)):(t&&c.debug(`runShellCommand cmd: ${e} done`,{stdout:i,stderr:a}),r({stdout:i,stderr:a}))}))}))}const d=new Set;const u=async e=>{let t=0;for(const r of d.values()){const n=`${t+1}/${d.size}`;c.debug(`running exitHandler ${n}`);try{await r(e),c.debug(` exitHandler ${n} done`)}catch(e){c.error(`exitHandler ${n} error: ${e}`)}t++}d.clear()};let p=null;async function f(e){p||(p=u(e)),await p}const g=["beforeExit","uncaughtException","unhandledRejection","SIGHUP","SIGINT","SIGQUIT","SIGILL","SIGTRAP","SIGABRT","SIGBUS","SIGFPE","SIGUSR1","SIGSEGV","SIGUSR2","SIGTERM"];process.setMaxListeners(process.getMaxListeners()+g.length),g.forEach((e=>process.once(e,(async e=>{e instanceof Error?c.error(`Exit on error: ${e.stack||e.message}`):c.debug(`Exit on signal: ${e}`),await f(e),process.exit(0)}))))},589:e=>{function t(e){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}t.keys=()=>[],t.resolve=t,t.id=589,e.exports=t},423:e=>{"use strict";e.exports=require("change-case")},200:e=>{"use strict";e.exports=require("child_process")},950:e=>{"use strict";e.exports=require("convict")},618:e=>{"use strict";e.exports=require("convict-format-with-validator")},940:e=>{"use strict";e.exports=require("debug-level")},865:e=>{"use strict";e.exports=require("json5")},251:e=>{"use strict";e.exports=require("word-wrap")},896:e=>{"use strict";e.exports=require("fs")},857:e=>{"use strict";e.exports=require("os")},928:e=>{"use strict";e.exports=require("path")}},t={};function r(n){var o=t[n];if(void 0!==o)return o.exports;var s=t[n]={id:n,loaded:!1,exports:{}};return e[n].call(s.exports,s,s.exports,r),s.loaded=!0,s.exports}r.c=t,r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.nmd=e=>(e.paths=[],e.children||(e.children=[]),e);r(r.s=859)})();