@vpalmisano/webrtcperf 4.4.9 → 4.4.10

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.
@@ -0,0 +1,148 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import { FastStats } from './stats'
4
+ import { ThrottleConfig, ThrottleRule } from '@vpalmisano/throttler'
5
+ import { Config } from './config'
6
+
7
+ export async function parseStatsFile(filePath: string) {
8
+ const fileData = await fs.promises.readFile(filePath, 'utf-8')
9
+ const lines = fileData.split('\n')
10
+ const headers = lines[0].split(',')
11
+ const data = lines.slice(1).map(line =>
12
+ line.split(',').reduce(
13
+ (acc, value, index) => {
14
+ if (value !== '') {
15
+ acc[headers[index]] = isNaN(Number(value)) ? value : Number(value)
16
+ }
17
+ return acc
18
+ },
19
+ {} as Record<string, string | number>,
20
+ ),
21
+ )
22
+ return data
23
+ }
24
+
25
+ export async function aggregateStatsSummary({
26
+ dirPath = 'logs',
27
+ senderParticipantName = 'Participant-000001',
28
+ receiverParticipantName = 'Participant-000000',
29
+ nameParser = (name: string) => {
30
+ const [_, id, scenario] = name.split('_')
31
+ return { id, scenario }
32
+ },
33
+ }) {
34
+ const stats = [] as {
35
+ timestamp: number
36
+ id: string
37
+ scenario: string
38
+ videoRecvBitratePerPixel: FastStats
39
+ videoRecvFps: FastStats
40
+ videoSentFps: FastStats
41
+ }[]
42
+ const results = await fs.promises.readdir(dirPath)
43
+ for (const test of results) {
44
+ const filePath = path.join(dirPath, test, 'detailed-stats-summary.csv')
45
+ if (!fs.existsSync(filePath)) continue
46
+ const timestamp = fs.statSync(path.join(dirPath, test)).ctime.getTime()
47
+ const data = await parseStatsFile(filePath)
48
+ const { id, scenario } = nameParser(test)
49
+
50
+ const aggregated = {
51
+ timestamp,
52
+ id,
53
+ scenario,
54
+ videoRecvBitratePerPixel: new FastStats(),
55
+ videoRecvFps: new FastStats(),
56
+ videoSentFps: new FastStats(),
57
+ }
58
+ data.forEach(v => {
59
+ const { participantName, trackId } = v as { participantName: string; trackId: string }
60
+ const metrics = v as Record<string, number>
61
+ if (participantName === receiverParticipantName) {
62
+ if (trackId?.endsWith('-v') && metrics.videoRecvFrames > 0) {
63
+ const videoRecvBitratePerPixel =
64
+ metrics.videoRecvBitrates / (metrics.videoRecvWidth * metrics.videoRecvHeight)
65
+ if (!isNaN(videoRecvBitratePerPixel)) aggregated.videoRecvBitratePerPixel.push(videoRecvBitratePerPixel)
66
+ if (!isNaN(metrics.videoRecvFps)) aggregated.videoRecvFps.push(metrics.videoRecvFps)
67
+ }
68
+ } else if (participantName === senderParticipantName) {
69
+ if (trackId?.endsWith('-v') && metrics.videoSentFrames > 0) {
70
+ if (!isNaN(metrics.videoSentFps)) aggregated.videoSentFps.push(metrics.videoSentFps)
71
+ }
72
+ }
73
+ })
74
+ stats.push(aggregated)
75
+ }
76
+ return stats.sort((a, b) => a.timestamp - b.timestamp)
77
+ }
78
+
79
+ export type ThrottleDirection = 'up' | 'down' | 'bidi'
80
+
81
+ export function formatThrottleRule(throttleRule: ThrottleRule, direction: ThrottleDirection) {
82
+ const { rate, loss, delay } = throttleRule
83
+ return `${direction}-r${rate}-l${loss}-d${delay}`
84
+ }
85
+
86
+ export function parseThrottleRule(throttleDesc: string) {
87
+ const match = throttleDesc.match(/(up|down|bidi)-r(\d+)-l([\d.]+)-d(\d+)/)
88
+ if (!match) throw new Error(`Invalid throttle description: ${throttleDesc}`)
89
+ const direction = match[1] as ThrottleDirection
90
+ const rate = parseInt(match[2])
91
+ const loss = parseInt(match[3])
92
+ const delay = parseInt(match[4])
93
+ return { direction, rate, loss, delay }
94
+ }
95
+
96
+ export async function simpleTestWithRateLossDelay(
97
+ id: string,
98
+ { rate, loss, delay, direction }: { rate: number; loss: number; delay: number; direction: ThrottleDirection },
99
+ repeat: 1,
100
+ ) {
101
+ const throttle: ThrottleConfig = {}
102
+ const queue = 25
103
+ if (direction === 'down' || direction === 'bidi') {
104
+ throttle.down = [
105
+ { rate: 20000, loss: 0, delay: 0, queue },
106
+ { rate, loss, delay, queue, at: 30 },
107
+ ]
108
+ }
109
+ if (direction === 'up' || direction === 'bidi') {
110
+ throttle.up = [
111
+ { rate: 20000, loss: 0, delay, queue },
112
+ { rate, loss, delay, queue, at: 30 },
113
+ ]
114
+ }
115
+ const throttleDesc = formatThrottleRule({ rate, loss, delay }, direction)
116
+ const now = Date.now()
117
+ const ret: Partial<Config>[] = []
118
+ for (let i = 0; i < repeat; i++) {
119
+ const basePath = `logs/${now}-${i + 1}_${id}_${throttleDesc}`
120
+ const sessions = direction === 'bidi' ? '0-1' : direction === 'down' ? '0' : '1'
121
+ ret.push({
122
+ sessions: 2,
123
+ runDuration: 60 * 3,
124
+ debuggingPort: 9000,
125
+ prometheusPushgateway: 'http://localhost:9091',
126
+ prometheusPushgatewayJobName: id,
127
+ statsPath: `${basePath}/stats.csv`,
128
+ detailedStatsPath: `${basePath}/detailed-stats.csv`,
129
+ showPageLog: false,
130
+ showStats: false,
131
+ statsInterval: 5,
132
+ scriptParams: JSON.stringify({
133
+ enableMic: '0-1',
134
+ enableCam: '1',
135
+ }),
136
+ throttleConfig: JSON.stringify([
137
+ {
138
+ sessions,
139
+ protocol: 'udp',
140
+ skipSourcePorts: '53,80,443',
141
+ skipDestinationPorts: '53,80,443',
142
+ ...throttle,
143
+ },
144
+ ]),
145
+ })
146
+ }
147
+ return ret
148
+ }
package/src/utils.ts CHANGED
@@ -23,7 +23,6 @@ import pidusage from 'pidusage'
23
23
  import puppeteer, { ImageFormat, Page } from 'puppeteer-core'
24
24
 
25
25
  import { Session } from './session'
26
- import { FastStats } from './stats'
27
26
 
28
27
  // eslint-disable-next-line
29
28
  const ps = require('pidusage/lib/ps')
@@ -1256,75 +1255,3 @@ export async function getDockerLogsPath(): Promise<string> {
1256
1255
  }
1257
1256
  return logPath
1258
1257
  }
1259
-
1260
- export async function parseStatsFile(filePath: string) {
1261
- const fileData = await fs.promises.readFile(filePath, 'utf-8')
1262
- const lines = fileData.split('\n')
1263
- const headers = lines[0].split(',')
1264
- const data = lines.slice(1).map(line =>
1265
- line.split(',').reduce(
1266
- (acc, value, index) => {
1267
- if (value !== '') {
1268
- acc[headers[index]] = isNaN(Number(value)) ? value : Number(value)
1269
- }
1270
- return acc
1271
- },
1272
- {} as Record<string, string | number>,
1273
- ),
1274
- )
1275
- return data
1276
- }
1277
-
1278
- export async function aggregateStatsSummary({
1279
- dirPath = 'logs',
1280
- senderParticipantName = 'Participant-000001',
1281
- receiverParticipantName = 'Participant-000000',
1282
- nameParser = (name: string) => {
1283
- const [destination, scenario] = name.split('_')
1284
- return { destination, scenario }
1285
- },
1286
- }) {
1287
- const stats = [] as {
1288
- timestamp: number
1289
- destination: string
1290
- scenario: string
1291
- videoRecvBitratePerPixel: FastStats
1292
- videoRecvFps: FastStats
1293
- videoSentFps: FastStats
1294
- }[]
1295
- const results = await fs.promises.readdir(dirPath)
1296
- for (const test of results) {
1297
- const filePath = path.join(dirPath, test, 'detailed-stats-summary.csv')
1298
- if (!fs.existsSync(filePath)) continue
1299
- const timestamp = fs.statSync(path.join(dirPath, test)).ctime.getTime()
1300
- const data = await parseStatsFile(filePath)
1301
- const { destination, scenario } = nameParser(test)
1302
-
1303
- const aggregated = {
1304
- timestamp,
1305
- destination,
1306
- scenario,
1307
- videoRecvBitratePerPixel: new FastStats(),
1308
- videoRecvFps: new FastStats(),
1309
- videoSentFps: new FastStats(),
1310
- }
1311
- data.forEach(v => {
1312
- const { participantName, trackId } = v as { participantName: string; trackId: string }
1313
- const metrics = v as Record<string, number>
1314
- if (participantName === receiverParticipantName) {
1315
- if (trackId?.endsWith('-v') && metrics.videoRecvFrames > 0) {
1316
- const videoRecvBitratePerPixel =
1317
- metrics.videoRecvBitrates / (metrics.videoRecvWidth * metrics.videoRecvHeight)
1318
- if (!isNaN(videoRecvBitratePerPixel)) aggregated.videoRecvBitratePerPixel.push(videoRecvBitratePerPixel)
1319
- if (!isNaN(metrics.videoRecvFps)) aggregated.videoRecvFps.push(metrics.videoRecvFps)
1320
- }
1321
- } else if (participantName === senderParticipantName) {
1322
- if (trackId?.endsWith('-v') && metrics.videoSentFrames > 0) {
1323
- if (!isNaN(metrics.videoSentFps)) aggregated.videoSentFps.push(metrics.videoSentFps)
1324
- }
1325
- }
1326
- })
1327
- stats.push(aggregated)
1328
- }
1329
- return stats.sort((a, b) => a.timestamp - b.timestamp)
1330
- }