Skip to content

Commit 4231c8d

Browse files
authored
Merge pull request #129 from vpalmisano/allow-to-add-multiple-network-throttle-rules
allow to add multiple network throttle rules
2 parents e5c9593 + 123f854 commit 4231c8d

File tree

8 files changed

+332
-90
lines changed

8 files changed

+332
-90
lines changed

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ RUN \
7676
sudo \
7777
net-tools \
7878
iproute2 \
79+
iptables \
7980
mesa-va-drivers \
8081
gnupg \
8182
apt-utils \

scripts/peer-connection-stats.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ async function getPeerConnectionStats(
150150
framesPerSecond,
151151
qualityLimitationResolutionChanges,
152152
qualityLimitationDurations,
153-
// firCount,
153+
firCount,
154154
pliCount,
155155
packetsLost,
156156
nackCount,
@@ -169,7 +169,7 @@ async function getPeerConnectionStats(
169169
frameWidth,
170170
frameHeight,
171171
framesPerSecond,
172-
// firCountReceived: firCount,
172+
firCountReceived: firCount,
173173
pliCountReceived: pliCount,
174174
totalRoundTripTime,
175175
roundTripTimeMeasurements,
@@ -207,7 +207,7 @@ async function getPeerConnectionStats(
207207
'frameWidth',
208208
'frameHeight',
209209
'framesPerSecond',
210-
// 'firCountReceived',
210+
'firCountReceived',
211211
'pliCountReceived',
212212
'totalRoundTripTime',
213213
'roundTripTimeMeasurements',

src/app.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ import { prepareFakeMedia } from './media'
88
import { Server } from './server'
99
import { Session } from './session'
1010
import { Stats } from './stats'
11-
import { startThrottle, stopThrottle } from './throttle'
11+
import {
12+
getSessionThrottleIndex,
13+
startThrottle,
14+
stopThrottle,
15+
} from './throttle'
1216
import {
1317
checkChromiumExecutable,
1418
logger,
@@ -138,7 +142,8 @@ async function main(): Promise<void> {
138142
id: number,
139143
spawnPeriod: number,
140144
): Promise<void> => {
141-
const session = new Session({ ...config, spawnPeriod, id })
145+
const throttleIndex = getSessionThrottleIndex(id)
146+
const session = new Session({ ...config, spawnPeriod, id, throttleIndex })
142147
session.once('stop', () => {
143148
console.warn(`Session ${id} stopped, reloading...`)
144149
setTimeout(startLocalSession, spawnPeriod, id)

src/config.ts

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -138,31 +138,42 @@ seconds.`,
138138
arg: 'run-duration',
139139
},
140140
throttleConfig: {
141-
doc: `A JSON5 string with a valid network throttling configuration, e.g.: \
141+
doc: `A JSON5 string with a valid network throttling configuration. \
142+
Example: \
142143
143144
\`\`\`javascript
144-
{
145+
[{
146+
sessions: '0-1',
147+
device: 'eth0',
148+
protocol: 'udp',
145149
up: {
146-
rate: 1000
147-
rtt: 50
148-
loss: '5%'
149-
queue: 10
150-
protocol: 'udp'
150+
rate: 1000,
151+
rtt: 50,
152+
loss: 5,
153+
queue: 10,
151154
at: 60
152155
},
153156
down: {
154-
rate: 2000
155-
rtt: 50
156-
loss: '5%'
157-
queue: 20
158-
protocol: 'udp'
157+
rate: 2000,
158+
rtt: 50,
159+
loss: 5,
160+
queue: 20,
159161
at: 60
160162
}
161-
}
163+
}]
162164
\`\`\`
163-
165+
The sessions field represents the sessions IDs range that will be affected by \
166+
the rule, e.g.: "0-10", "2,4" or simply "2". \
167+
The device, protocol, up, down fields are optional. When device is not net, the \
168+
default route device will be used. If protocol is specified ('udp' or 'tcp'), \
169+
only the packets with the specified protocol will be affected by the shaping rules. \
164170
When used with docker, run \`sudo modprobe ifb numifbs=1\` first and add the \
165-
\`--cap-add=NET_ADMIN\` docker option.`,
171+
\`--cap-add=NET_ADMIN\` docker option.
172+
When running as regular user, add the following sudo configuration: \
173+
\`\`\`
174+
%sudo ALL=(ALL) NOPASSWD: /usr/sbin/iptables,/usr/sbin/addgroup,/usr/sbin/adduser,/usr/sbin/tc,/usr/sbin/modprobe
175+
\`\`\`
176+
`,
166177
format: String,
167178
nullable: true,
168179
default: '',

src/rtcstats.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,24 @@ export enum PageStatsNames {
4949
* It does't include the video encode/decode time and the jitter buffer time.
5050
*/
5151
videoEndToEndNetworkDelay = 'videoEndToEndNetworkDelay',
52+
53+
/** The throttle upload rate limitation. */
54+
throttleUpRate = 'throttleUpRate',
55+
/** The throttle upload delay. */
56+
throttleUpDelay = 'throttleUpDelay',
57+
/** The throttle upload packet loss. */
58+
throttleUpLoss = 'throttleUpLoss',
59+
/** The throttle upload packet queue. */
60+
throttleUpQueue = 'throttleUpQueue',
61+
62+
/** The throttle download rate limitation. */
63+
throttleDownRate = 'throttleDownRate',
64+
/** The throttle download delay. */
65+
throttleDownDelay = 'throttleDownDelay',
66+
/** The throttle download packet loss. */
67+
throttleDownLoss = 'throttleDownLoss',
68+
/** The throttle download packet queue. */
69+
throttleDownQueue = 'throttleDownQueue',
5270
}
5371

5472
/**
@@ -76,7 +94,8 @@ export enum RtcStatsMetricNames {
7694

7795
/** The sent video codec. */
7896
videoSentCodec = 'videoSentCodec',
79-
//'videoFirCountReceived',
97+
/** The FIR requests received by the video sender. */
98+
videoFirCountReceived = 'videoFirCountReceived',
8099
/** The PLI requests received by the video sender. */
81100
videoPliCountReceived = 'videoPliCountReceived',
82101
/** The sent video encode latency. */
@@ -118,7 +137,8 @@ export enum RtcStatsMetricNames {
118137

119138
/** The sent screen codec. */
120139
screenSentCodec = 'screenSentCodec',
121-
//'screenFirCountReceived',
140+
/** The received FIR from screen sender. */
141+
screenFirCountReceived = 'screenFirCountReceived',
122142
/** The received PLI from screen sender. */
123143
screenPliCountReceived = 'screenPliCountReceived',
124144
/** The sent screen encode latency. */
@@ -178,7 +198,7 @@ export enum RtcStatsMetricNames {
178198
audioRecvRemovedSamplesForAcceleration = 'audioRecvRemovedSamplesForAcceleration',
179199
// inbound video,
180200
videoRecvCodec = 'videoRecvCodec',
181-
//'videoFirCountSent',
201+
videoFirCountSent = 'videoFirCountSent',
182202
videoPliCountSent = 'videoPliCountSent',
183203
videoDecodeLatency = 'videoDecodeLatency',
184204
//'videoFramesDecoded',
@@ -200,7 +220,7 @@ export enum RtcStatsMetricNames {
200220
videoTotalFreezesDuration = 'videoTotalFreezesDuration',
201221
// inbound screen,
202222
screenRecvCodec = 'screenRecvCodec',
203-
//'screenFirCountSent',
223+
screenFirCountSent = 'screenFirCountSent',
204224
screenPliCountSent = 'screenPliCountSent',
205225
screenDecodeLatency = 'screenDecodeLatency',
206226
//'screenFramesDecoded',
@@ -443,7 +463,12 @@ export function updateRtcStats(
443463
inboundRtp.frameWidth,
444464
)
445465
//setStats(stats, prefix + 'TotalDecodeTime', key, inboundRtp.totalDecodeTime)
446-
//setStats(stats, prefix + 'FirCountSent', key, inboundRtp.firCount)
466+
setStats(
467+
stats,
468+
(prefix + 'FirCountSent') as RtcStatsMetricNames,
469+
key,
470+
inboundRtp.firCount,
471+
)
447472
setStats(
448473
stats,
449474
(prefix + 'PliCountSent') as RtcStatsMetricNames,
@@ -571,7 +596,12 @@ export function updateRtcStats(
571596
key,
572597
outboundRtp.framesPerSecond,
573598
)
574-
//setStats(stats, prefix + 'FirCountReceived', key, outboundRtp.firCountReceived)
599+
setStats(
600+
stats,
601+
(prefix + 'FirCountReceived') as RtcStatsMetricNames,
602+
key,
603+
outboundRtp.firCountReceived,
604+
)
575605
setStats(
576606
stats,
577607
(prefix + 'PliCountReceived') as RtcStatsMetricNames,

src/session.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ require('puppeteer-extra-plugin-user-preferences')
5050
//
5151

5252
import { rtcStatKey, RtcStats, updateRtcStats } from './rtcstats'
53+
import { getSessionThrottleValues } from './throttle'
5354
import {
5455
checkChromiumExecutable,
5556
downloadUrl,
@@ -190,6 +191,7 @@ export type SessionParams = {
190191
pageLogPath: string
191192
userAgent: string
192193
id: number
194+
throttleIndex: number
193195
// eslint-disable-next-line @typescript-eslint/no-explicit-any
194196
evaluateAfter?: any[]
195197
exposedFunctions?: string
@@ -283,6 +285,8 @@ export class Session extends EventEmitter {
283285

284286
/** The numeric id assigned to the session. */
285287
readonly id: number
288+
/** The throttle configuration index assigned to the session. */
289+
readonly throttleIndex: number
286290
/** The test page url. */
287291
readonly url: string
288292
/** The url query. */
@@ -377,6 +381,7 @@ export class Session extends EventEmitter {
377381
pageLogPath,
378382
userAgent,
379383
id,
384+
throttleIndex,
380385
evaluateAfter,
381386
exposedFunctions,
382387
scriptParams,
@@ -476,6 +481,7 @@ export class Session extends EventEmitter {
476481
this.serverUseHttps = serverUseHttps
477482

478483
this.id = id
484+
this.throttleIndex = throttleIndex
479485
this.evaluateAfter = evaluateAfter || []
480486
this.exposedFunctions = exposedFunctions || {}
481487
if (scriptParams) {
@@ -680,6 +686,32 @@ export class Session extends EventEmitter {
680686
log.debug(`using executablePath=${executablePath}`)
681687
}
682688

689+
// Create process wrapper.
690+
if (this.throttleIndex > -1) {
691+
const mark = this.throttleIndex + 1
692+
const executableWrapperPath = `/tmp/webrtcperf-launcher-${mark}`
693+
const group = `webrtcperf${mark}`
694+
await fs.promises.writeFile(
695+
executableWrapperPath,
696+
`#!/bin/bash
697+
getent group ${group} || sudo addgroup --system ${group}
698+
sudo adduser $USER ${group}
699+
sudo iptables -t mangle --list OUTPUT | grep -q "owner GID match ${group}" || sudo iptables -t mangle -A OUTPUT -m owner --gid-owner ${group} -j MARK --set-mark ${mark}
700+
sudo iptables -t mangle --list PREROUTING | grep -q "CONNMARK restore" || sudo iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark
701+
sudo iptables -t mangle --list POSTROUTING | grep -q "CONNMARK save" || sudo iptables -t mangle -A POSTROUTING -j CONNMARK --save-mark
702+
703+
cat <<EOF > /tmp/webrtcperf-launcher-${mark}-browser
704+
#!/bin/bash
705+
exec ${executablePath} $@
706+
EOF
707+
chmod +x /tmp/webrtcperf-launcher-${mark}-browser
708+
709+
exec sg ${group} -c /tmp/webrtcperf-launcher-${mark}-browser`,
710+
)
711+
await fs.promises.chmod(executableWrapperPath, 0o755)
712+
executablePath = executableWrapperPath
713+
}
714+
683715
const env = { ...process.env }
684716
if (!this.display) {
685717
delete env.DISPLAY
@@ -1476,6 +1508,15 @@ window.SERVER_USE_HTTPS = ${this.serverUseHttps};
14761508
const pageCpu: Record<string, number> = {}
14771509
const pageMemory: Record<string, number> = {}
14781510

1511+
const throttleUpValuesRate: Record<string, number> = {}
1512+
const throttleUpValuesDelay: Record<string, number> = {}
1513+
const throttleUpValuesLoss: Record<string, number> = {}
1514+
const throttleUpValuesQueue: Record<string, number> = {}
1515+
const throttleDownValuesRate: Record<string, number> = {}
1516+
const throttleDownValuesDelay: Record<string, number> = {}
1517+
const throttleDownValuesLoss: Record<string, number> = {}
1518+
const throttleDownValuesQueue: Record<string, number> = {}
1519+
14791520
const customStats: Record<string, Record<string, number | string>> = {}
14801521

14811522
for (const [pageIndex, page] of this.pages.entries()) {
@@ -1602,6 +1643,41 @@ window.SERVER_USE_HTTPS = ${this.serverUseHttps};
16021643
this.pagesMetrics.set(pageIndex, metrics)
16031644
}
16041645
}
1646+
1647+
// Collect throttle metrics
1648+
const throttleUpValues = getSessionThrottleValues(
1649+
this.throttleIndex,
1650+
'up',
1651+
)
1652+
if (throttleUpValues.rate !== undefined) {
1653+
throttleUpValuesRate[pageKey] = throttleUpValues.rate
1654+
}
1655+
if (throttleUpValues.delay !== undefined) {
1656+
throttleUpValuesDelay[pageKey] = throttleUpValues.delay
1657+
}
1658+
if (throttleUpValues.loss !== undefined) {
1659+
throttleUpValuesLoss[pageKey] = throttleUpValues.loss
1660+
}
1661+
if (throttleUpValues.queue !== undefined) {
1662+
throttleUpValuesQueue[pageKey] = throttleUpValues.queue
1663+
}
1664+
1665+
const throttleDownValues = getSessionThrottleValues(
1666+
this.throttleIndex,
1667+
'down',
1668+
)
1669+
if (throttleDownValues.rate !== undefined) {
1670+
throttleDownValuesRate[pageKey] = throttleDownValues.rate
1671+
}
1672+
if (throttleDownValues.delay !== undefined) {
1673+
throttleDownValuesDelay[pageKey] = throttleDownValues.delay
1674+
}
1675+
if (throttleDownValues.loss !== undefined) {
1676+
throttleDownValuesLoss[pageKey] = throttleDownValues.loss
1677+
}
1678+
if (throttleDownValues.queue !== undefined) {
1679+
throttleDownValuesQueue[pageKey] = throttleDownValues.queue
1680+
}
16051681
} catch (err) {
16061682
log.error(`collectPeerConnectionStats error: ${(err as Error).stack}`)
16071683
}
@@ -1617,6 +1693,14 @@ window.SERVER_USE_HTTPS = ${this.serverUseHttps};
16171693
collectedStats.httpRecvLatency = httpRecvLatencyStats
16181694
collectedStats.pageCpu = pageCpu
16191695
collectedStats.pageMemory = pageMemory
1696+
collectedStats.throttleUpRate = throttleUpValuesRate
1697+
collectedStats.throttleUpDelay = throttleUpValuesDelay
1698+
collectedStats.throttleUpLoss = throttleUpValuesLoss
1699+
collectedStats.throttleUpQueue = throttleUpValuesQueue
1700+
collectedStats.throttleDownRate = throttleDownValuesRate
1701+
collectedStats.throttleDownDelay = throttleDownValuesDelay
1702+
collectedStats.throttleDownLoss = throttleDownValuesLoss
1703+
collectedStats.throttleDownQueue = throttleDownValuesQueue
16201704

16211705
Object.assign(collectedStats, customStats)
16221706

0 commit comments

Comments
 (0)