@vpalmisano/webrtcperf 4.0.0
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/LICENSE +661 -0
- package/README.md +296 -0
- package/app.min.js +2 -0
- package/build/src/app.d.ts +6 -0
- package/build/src/app.js +207 -0
- package/build/src/app.js.map +1 -0
- package/build/src/config.d.ts +104 -0
- package/build/src/config.js +880 -0
- package/build/src/config.js.map +1 -0
- package/build/src/generate-config-docs.d.ts +1 -0
- package/build/src/generate-config-docs.js +41 -0
- package/build/src/generate-config-docs.js.map +1 -0
- package/build/src/index.d.ts +9 -0
- package/build/src/index.js +26 -0
- package/build/src/index.js.map +1 -0
- package/build/src/media.d.ts +33 -0
- package/build/src/media.js +113 -0
- package/build/src/media.js.map +1 -0
- package/build/src/rtcstats.d.ts +302 -0
- package/build/src/rtcstats.js +418 -0
- package/build/src/rtcstats.js.map +1 -0
- package/build/src/server.d.ts +173 -0
- package/build/src/server.js +639 -0
- package/build/src/server.js.map +1 -0
- package/build/src/session.d.ts +277 -0
- package/build/src/session.js +1552 -0
- package/build/src/session.js.map +1 -0
- package/build/src/stats.d.ts +243 -0
- package/build/src/stats.js +1383 -0
- package/build/src/stats.js.map +1 -0
- package/build/src/utils.d.ts +249 -0
- package/build/src/utils.js +1220 -0
- package/build/src/utils.js.map +1 -0
- package/build/src/visqol.d.ts +6 -0
- package/build/src/visqol.js +61 -0
- package/build/src/visqol.js.map +1 -0
- package/build/src/vmaf.d.ts +83 -0
- package/build/src/vmaf.js +624 -0
- package/build/src/vmaf.js.map +1 -0
- package/build/tsconfig.tsbuildinfo +1 -0
- package/package.json +129 -0
- package/src/app.ts +241 -0
- package/src/config.ts +852 -0
- package/src/generate-config-docs.ts +47 -0
- package/src/index.ts +9 -0
- package/src/media.ts +151 -0
- package/src/rtcstats.ts +507 -0
- package/src/server.ts +645 -0
- package/src/session.ts +1908 -0
- package/src/stats.ts +1668 -0
- package/src/utils.ts +1295 -0
- package/src/visqol.ts +62 -0
- package/src/vmaf.ts +771 -0
package/src/app.ts
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { getSessionThrottleIndex, startThrottle, stopThrottle } from '@vpalmisano/throttler'
|
|
2
|
+
import { paramCase } from 'change-case'
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import json5 from 'json5'
|
|
5
|
+
import wrap from 'word-wrap'
|
|
6
|
+
|
|
7
|
+
import { Config, getConfigDocs, loadConfig } from './config'
|
|
8
|
+
import { MediaPath, prepareFakeMedia } from './media'
|
|
9
|
+
import { Server } from './server'
|
|
10
|
+
import { Session } from './session'
|
|
11
|
+
import { Stats } from './stats'
|
|
12
|
+
import {
|
|
13
|
+
checkChromeExecutable,
|
|
14
|
+
getDockerLogsPath,
|
|
15
|
+
logger,
|
|
16
|
+
registerExitHandler,
|
|
17
|
+
resolvePackagePath,
|
|
18
|
+
sleep,
|
|
19
|
+
startRandomActivateAudio,
|
|
20
|
+
stopRandomActivateAudio,
|
|
21
|
+
stopTimers,
|
|
22
|
+
} from './utils'
|
|
23
|
+
import { calculateVisqolScore } from './visqol'
|
|
24
|
+
import { calculateVmafScore, convertToIvf, prepareVideo } from './vmaf'
|
|
25
|
+
import path from 'path'
|
|
26
|
+
|
|
27
|
+
const log = logger('webrtcperf')
|
|
28
|
+
|
|
29
|
+
function showHelpOrVersion(): void {
|
|
30
|
+
if (process.argv.findIndex(a => a.localeCompare('--help') === 0) !== -1) {
|
|
31
|
+
const docs = getConfigDocs()
|
|
32
|
+
let out = `Params:\n --version\n It shows the package version.\n`
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34
|
+
Object.entries(docs).forEach(([name, value]: [string, any]) => {
|
|
35
|
+
out += ` --${paramCase(name)}
|
|
36
|
+
${wrap(value.doc, { width: 72, indent: ' ' })}
|
|
37
|
+
Default: ${value.default}\n`
|
|
38
|
+
})
|
|
39
|
+
console.log(out)
|
|
40
|
+
process.exit(0)
|
|
41
|
+
} else if (process.argv.findIndex(a => a.localeCompare('--version') === 0) !== -1) {
|
|
42
|
+
const version = json5.parse(fs.readFileSync(resolvePackagePath('package.json')).toString()).version
|
|
43
|
+
console.log(version)
|
|
44
|
+
process.exit(0)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function setupApplication(config: Config): Promise<{ stats: Stats; stop: () => Promise<void> }> {
|
|
49
|
+
if (!config.startTimestamp) {
|
|
50
|
+
config.startTimestamp = Date.now()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Stats.
|
|
54
|
+
const stats = new Stats(config)
|
|
55
|
+
await stats.start()
|
|
56
|
+
|
|
57
|
+
// Control server.
|
|
58
|
+
let server: Server
|
|
59
|
+
if (config.serverPort) {
|
|
60
|
+
server = new Server(config, stats)
|
|
61
|
+
await server.start()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Prepare fake video and audio.
|
|
65
|
+
const mediaPaths: MediaPath[] = []
|
|
66
|
+
if (config.videoPath) {
|
|
67
|
+
for (const videoPath of config.videoPath.split(',')) {
|
|
68
|
+
const ret = await prepareFakeMedia({ ...config, videoPath })
|
|
69
|
+
mediaPaths.push(ret)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Network throttle.
|
|
74
|
+
if (config.throttleConfig) {
|
|
75
|
+
await startThrottle(config.throttleConfig)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Download browser if necessary.
|
|
79
|
+
if (!config.chromiumUrl && !config.chromiumPath) {
|
|
80
|
+
await checkChromeExecutable()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Start session function.
|
|
84
|
+
const startLocalSession = async (id: number, spawnPeriod: number): Promise<void> => {
|
|
85
|
+
const throttleIndex = getSessionThrottleIndex(id)
|
|
86
|
+
const mediaPath = mediaPaths.length ? mediaPaths[id % mediaPaths.length] : undefined
|
|
87
|
+
const session = new Session({
|
|
88
|
+
...config,
|
|
89
|
+
mediaPath,
|
|
90
|
+
spawnPeriod,
|
|
91
|
+
id,
|
|
92
|
+
throttleIndex,
|
|
93
|
+
})
|
|
94
|
+
session.once('stop', () => {
|
|
95
|
+
console.warn(`Session ${id} stopped, reloading...`)
|
|
96
|
+
setTimeout(startLocalSession, spawnPeriod, id)
|
|
97
|
+
})
|
|
98
|
+
stats.addSession(session)
|
|
99
|
+
await session.start()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Start the local sessions.
|
|
103
|
+
if (config.sessions > 0) {
|
|
104
|
+
if (config.randomAudioPeriod) {
|
|
105
|
+
startRandomActivateAudio(
|
|
106
|
+
stats.sessions,
|
|
107
|
+
config.randomAudioPeriod,
|
|
108
|
+
config.randomAudioProbability,
|
|
109
|
+
config.randomAudioRange,
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
const spawnPeriod = 1000 / config.spawnRate
|
|
113
|
+
log.debug(`Starting ${config.sessions} sessions (spawnPeriod: ${spawnPeriod}ms)`)
|
|
114
|
+
const startTime = Date.now()
|
|
115
|
+
for (let i = 0; i < config.sessions; i += 1) {
|
|
116
|
+
const id = stats.consumeSessionId(config.tabsPerSession)
|
|
117
|
+
await startLocalSession(id, spawnPeriod)
|
|
118
|
+
// If not the last session, sleep
|
|
119
|
+
if (i < config.sessions - 1) {
|
|
120
|
+
await sleep(spawnPeriod)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000)
|
|
124
|
+
const spawnRate = (config.sessions * config.tabsPerSession) / elapsed
|
|
125
|
+
log.debug(`${config.sessions * config.tabsPerSession} pages started in ${elapsed}s (${spawnRate.toFixed(2)}/s)`)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
stats,
|
|
130
|
+
stop: async (): Promise<void> => {
|
|
131
|
+
log.debug('Stopping')
|
|
132
|
+
|
|
133
|
+
stopRandomActivateAudio()
|
|
134
|
+
|
|
135
|
+
await stats.stop()
|
|
136
|
+
|
|
137
|
+
if (config.throttleConfig) {
|
|
138
|
+
await stopThrottle()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
stopTimers()
|
|
142
|
+
|
|
143
|
+
// vmaf score.
|
|
144
|
+
if (config.vmafPath) {
|
|
145
|
+
try {
|
|
146
|
+
await calculateVmafScore(config)
|
|
147
|
+
} catch (err: unknown) {
|
|
148
|
+
log.error(`vmaf score error: ${(err as Error).stack}`)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// visqol score
|
|
153
|
+
if (config.visqolPath) {
|
|
154
|
+
try {
|
|
155
|
+
await calculateVisqolScore(config)
|
|
156
|
+
} catch (err: unknown) {
|
|
157
|
+
log.error(`visqol score error: ${(err as Error).stack}`)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Copy docker logs to data directory.
|
|
162
|
+
if (config.pageLogPath) {
|
|
163
|
+
try {
|
|
164
|
+
const logPath = await getDockerLogsPath()
|
|
165
|
+
const dataDir = path.dirname(config.pageLogPath)
|
|
166
|
+
await fs.promises.cp(logPath, path.resolve(dataDir, 'docker.log'))
|
|
167
|
+
} catch (err: unknown) {
|
|
168
|
+
log.debug(`docker logs not found: ${(err as Error).message}`)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (server) {
|
|
173
|
+
server.stop()
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
log.debug('Stopped')
|
|
177
|
+
},
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Main function
|
|
183
|
+
*/
|
|
184
|
+
async function main(): Promise<void> {
|
|
185
|
+
showHelpOrVersion()
|
|
186
|
+
|
|
187
|
+
const config = loadConfig(process.argv[2])
|
|
188
|
+
|
|
189
|
+
if (config.vmafPrepareVideo) {
|
|
190
|
+
await prepareVideo(config, true)
|
|
191
|
+
process.exit(0)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (config.vmafProcessVideo) {
|
|
195
|
+
await convertToIvf(config.vmafProcessVideo, config.vmafVideoCrop, false)
|
|
196
|
+
process.exit(0)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const { stop: stopApplication } = await setupApplication(config)
|
|
200
|
+
|
|
201
|
+
const stop = async (): Promise<void> => {
|
|
202
|
+
console.log('Exiting...')
|
|
203
|
+
|
|
204
|
+
await stopApplication()
|
|
205
|
+
|
|
206
|
+
process.exit(0)
|
|
207
|
+
}
|
|
208
|
+
registerExitHandler(() => stop())
|
|
209
|
+
|
|
210
|
+
// Stop after a configured duration.
|
|
211
|
+
if (config.runDuration > 0) {
|
|
212
|
+
setTimeout(stop, config.runDuration * 1000)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Command line interface.
|
|
216
|
+
if (process.stdin && process.stdin.setRawMode) {
|
|
217
|
+
console.log('Press [q] to quit')
|
|
218
|
+
process.stdin.setRawMode(true)
|
|
219
|
+
process.stdin.resume()
|
|
220
|
+
process.stdin.on('data', async data => {
|
|
221
|
+
log.debug('[stdin]', data[0])
|
|
222
|
+
if (data[0] === 'q'.charCodeAt(0)) {
|
|
223
|
+
try {
|
|
224
|
+
await stop()
|
|
225
|
+
} catch (err: unknown) {
|
|
226
|
+
log.error(`stop error: ${(err as Error).stack}`)
|
|
227
|
+
process.exit(1)
|
|
228
|
+
}
|
|
229
|
+
} else if (data[0] === 'x'.charCodeAt(0)) {
|
|
230
|
+
process.exit(1)
|
|
231
|
+
}
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (require.main === module) {
|
|
237
|
+
main().catch(err => {
|
|
238
|
+
console.error(err)
|
|
239
|
+
process.exit(-1)
|
|
240
|
+
})
|
|
241
|
+
}
|