@vpalmisano/webrtcperf 4.2.0 → 4.3.2

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/app.ts CHANGED
@@ -2,7 +2,6 @@ import { getSessionThrottleIndex, startThrottle, stopThrottle } from '@vpalmisan
2
2
  import { paramCase } from 'change-case'
3
3
  import fs from 'fs'
4
4
  import json5 from 'json5'
5
- import wrap from 'word-wrap'
6
5
 
7
6
  import { Config, getConfigDocs, loadConfig, loadConfigFromPrompt } from './config'
8
7
  import { MediaPath, prepareFakeMedia } from './media'
@@ -23,163 +22,202 @@ import {
23
22
  import { calculateVisqolScore } from './visqol'
24
23
  import { calculateVmafScore, convertToIvf, prepareVideo } from './vmaf'
25
24
  import path from 'path'
25
+ import { markedTerminal } from 'marked-terminal'
26
+ import { EventEmitter } from 'events'
27
+
28
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
29
+ const { marked } = require('marked')
30
+ marked.use(markedTerminal({ reflowText: true, tab: 2 }))
26
31
 
27
32
  const log = logger('webrtcperf')
28
33
 
29
34
  function showHelpOrVersion(): void {
30
- if (process.argv.findIndex(a => a.localeCompare('--help') === 0) !== -1) {
35
+ if (process.argv.includes('--help') || process.argv.includes('-h')) {
31
36
  const docs = getConfigDocs()
32
- let out = `Params:\n --version\n It shows the package version.\n`
37
+ let out = marked.parse(`**Webrtcperf parameters**
38
+
39
+ \`--version\` It shows the package version.
40
+ `)
33
41
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
42
  Object.entries(docs).forEach(([name, value]: [string, any]) => {
35
- out += ` --${paramCase(name)}
36
- ${wrap(value.doc, { width: 72, indent: ' ' })}
37
- Default: ${value.default}\n`
43
+ out += marked.parse(
44
+ `
45
+ \`--${paramCase(name)}\`
46
+ ${value.doc}
47
+ Default value: \`${value.default}\`
48
+ `,
49
+ )
38
50
  })
39
51
  console.log(out)
40
52
  process.exit(0)
41
- } else if (process.argv.findIndex(a => a.localeCompare('--version') === 0) !== -1) {
53
+ } else if (process.argv.includes('--version') || process.argv.includes('-v')) {
42
54
  const version = json5.parse(fs.readFileSync(resolvePackagePath('package.json')).toString()).version
43
55
  console.log(version)
44
56
  process.exit(0)
45
57
  }
46
58
  }
47
59
 
48
- async function postTest(config: Config): Promise<void> {
49
- // vmaf score.
50
- if (config.vmafPath) {
51
- console.log('Calculating VMAF score...')
52
- try {
53
- await calculateVmafScore(config)
54
- } catch (err: unknown) {
55
- log.error(`vmaf score error: ${(err as Error).stack}`)
60
+ export class Application extends EventEmitter {
61
+ readonly config: Config
62
+ readonly stats: Stats
63
+ readonly server?: Server
64
+ private mediaPaths: MediaPath[] = []
65
+
66
+ constructor(config: Config) {
67
+ super()
68
+ if (!config.startTimestamp) {
69
+ config.startTimestamp = Date.now()
70
+ }
71
+ this.config = config
72
+ this.stats = new Stats(config)
73
+ if (config.serverPort) {
74
+ this.server = new Server(config, this.stats)
56
75
  }
57
76
  }
58
77
 
59
- // visqol score
60
- if (config.visqolPath) {
61
- console.log('Calculating Visqol score...')
62
- try {
63
- await calculateVisqolScore(config)
64
- } catch (err: unknown) {
65
- log.error(`visqol score error: ${(err as Error).stack}`)
78
+ async start() {
79
+ log.debug(`start (runDuration: ${this.config.runDuration})`)
80
+ await this.stats.start()
81
+ if (this.server) {
82
+ await this.server.start()
66
83
  }
67
- }
68
- }
84
+ const config = this.config
69
85
 
70
- export async function setupApplication(config: Config): Promise<{ stats: Stats; stop: () => Promise<void> }> {
71
- if (!config.startTimestamp) {
72
- config.startTimestamp = Date.now()
73
- }
86
+ // Handle vmaf commands.
87
+ if (config.vmafPrepareVideo) {
88
+ await prepareVideo(config, true)
89
+ }
90
+ if (config.vmafProcessVideo) {
91
+ await convertToIvf(
92
+ config.vmafProcessVideo,
93
+ config.vmafVideoCrop,
94
+ config.vmafKeepSourceFiles,
95
+ config.vmafSkipDuplicated,
96
+ )
97
+ }
74
98
 
75
- // Stats.
76
- const stats = new Stats(config)
77
- await stats.start()
99
+ // Handle sessions.
100
+ if (config.sessions > 0) {
101
+ // Prepare fake video and audio.
102
+ if (config.videoPath && !this.mediaPaths.length) {
103
+ for (const videoPath of config.videoPath.split(',')) {
104
+ const ret = await prepareFakeMedia({ ...config, videoPath })
105
+ this.mediaPaths.push(ret)
106
+ }
107
+ }
78
108
 
79
- // Control server.
80
- let server: Server | undefined
81
- if (config.serverPort) {
82
- server = new Server(config, stats)
83
- await server.start()
84
- }
109
+ // Network throttle.
110
+ if (config.throttleConfig) {
111
+ await startThrottle(config.throttleConfig)
112
+ }
85
113
 
86
- // If sessions are set, prepare fake video/audio and start sessions.
87
- if (config.sessions > 0) {
88
- // Prepare fake video and audio.
89
- const mediaPaths: MediaPath[] = []
90
- if (config.videoPath) {
91
- for (const videoPath of config.videoPath.split(',')) {
92
- const ret = await prepareFakeMedia({ ...config, videoPath })
93
- mediaPaths.push(ret)
114
+ // Download browser if necessary.
115
+ if (!config.chromiumUrl && !config.chromiumPath) {
116
+ await checkChromeExecutable()
94
117
  }
95
- }
96
118
 
97
- // Network throttle.
98
- if (config.throttleConfig) {
99
- await startThrottle(config.throttleConfig)
119
+ // Start the local sessions.
120
+ if (config.randomAudioPeriod) {
121
+ startRandomActivateAudio(
122
+ this.stats.sessions,
123
+ config.randomAudioPeriod,
124
+ config.randomAudioProbability,
125
+ config.randomAudioRange,
126
+ )
127
+ }
128
+ const spawnPeriod = 1000 / config.spawnRate
129
+ log.debug(`Starting ${config.sessions} sessions (spawnPeriod: ${spawnPeriod}ms)`)
130
+ const startTime = Date.now()
131
+ for (let i = 0; i < config.sessions; i += 1) {
132
+ const id = this.stats.consumeSessionId(config.tabsPerSession)
133
+ await this.startSession(id, spawnPeriod)
134
+ // If not the last session, sleep.
135
+ if (i < config.sessions - 1) {
136
+ await sleep(spawnPeriod)
137
+ }
138
+ }
139
+ const elapsed = Math.round((Date.now() - startTime) / 1000)
140
+ const spawnRate = (config.sessions * config.tabsPerSession) / elapsed
141
+ log.debug(`${config.sessions * config.tabsPerSession} pages started in ${elapsed}s (${spawnRate.toFixed(2)}/s)`)
100
142
  }
101
143
 
102
- // Download browser if necessary.
103
- if (!config.chromiumUrl && !config.chromiumPath) {
104
- await checkChromeExecutable()
144
+ if (config.runDuration || config.vmafPath || config.visqolPath) {
145
+ setTimeout(() => this.stop(), config.runDuration * 1000)
105
146
  }
147
+ }
106
148
 
107
- // Start session function.
108
- const startLocalSession = async (id: number, spawnPeriod: number): Promise<void> => {
109
- const throttleIndex = getSessionThrottleIndex(id)
110
- const mediaPath = mediaPaths.length ? mediaPaths[id % mediaPaths.length] : undefined
111
- const session = new Session({
112
- ...config,
113
- mediaPath,
114
- spawnPeriod,
115
- id,
116
- throttleIndex,
117
- })
118
- session.once('stop', () => {
119
- console.warn(`Session ${id} stopped, reloading...`)
120
- setTimeout(startLocalSession, spawnPeriod, id)
121
- })
122
- stats.addSession(session)
123
- await session.start()
124
- }
149
+ private async startSession(id: number, spawnPeriod: number) {
150
+ log.debug(`startSession ${id}`)
151
+ const throttleIndex = getSessionThrottleIndex(id)
152
+ const mediaPath = this.mediaPaths.length ? this.mediaPaths[id % this.mediaPaths.length] : undefined
153
+ const session = new Session({
154
+ ...this.config,
155
+ mediaPath,
156
+ spawnPeriod,
157
+ id,
158
+ throttleIndex,
159
+ })
160
+ session.once('stop', () => {
161
+ console.warn(`Session ${id} stopped, reloading...`)
162
+ setTimeout(() => this.startSession(id, spawnPeriod), spawnPeriod)
163
+ })
164
+ this.stats.addSession(session)
165
+ await session.start()
166
+ }
125
167
 
126
- // Start the local sessions.
127
- if (config.randomAudioPeriod) {
128
- startRandomActivateAudio(
129
- stats.sessions,
130
- config.randomAudioPeriod,
131
- config.randomAudioProbability,
132
- config.randomAudioRange,
133
- )
168
+ private async postTest() {
169
+ log.debug('postTest')
170
+
171
+ // vmaf score.
172
+ if (this.config.vmafPath) {
173
+ console.log('Calculating VMAF score...')
174
+ try {
175
+ await calculateVmafScore(this.config)
176
+ } catch (err: unknown) {
177
+ log.error(`vmaf score error: ${(err as Error).stack}`)
178
+ }
134
179
  }
135
- const spawnPeriod = 1000 / config.spawnRate
136
- log.debug(`Starting ${config.sessions} sessions (spawnPeriod: ${spawnPeriod}ms)`)
137
- const startTime = Date.now()
138
- for (let i = 0; i < config.sessions; i += 1) {
139
- const id = stats.consumeSessionId(config.tabsPerSession)
140
- await startLocalSession(id, spawnPeriod)
141
- // If not the last session, sleep
142
- if (i < config.sessions - 1) {
143
- await sleep(spawnPeriod)
180
+
181
+ // visqol score
182
+ if (this.config.visqolPath) {
183
+ console.log('Calculating Visqol score...')
184
+ try {
185
+ await calculateVisqolScore(this.config)
186
+ } catch (err: unknown) {
187
+ log.error(`visqol score error: ${(err as Error).stack}`)
144
188
  }
145
189
  }
146
- const elapsed = Math.round((Date.now() - startTime) / 1000)
147
- const spawnRate = (config.sessions * config.tabsPerSession) / elapsed
148
- log.debug(`${config.sessions * config.tabsPerSession} pages started in ${elapsed}s (${spawnRate.toFixed(2)}/s)`)
149
190
  }
150
191
 
151
- return {
152
- stats,
153
- stop: async (): Promise<void> => {
154
- log.debug('Stopping')
192
+ async stop(canceled = false) {
193
+ log.debug(`stop (canceled: ${canceled})`)
155
194
 
156
- stopRandomActivateAudio()
195
+ stopRandomActivateAudio()
157
196
 
158
- await stats.stop()
197
+ await this.stats.stop()
159
198
 
160
- if (config.throttleConfig) {
161
- await stopThrottle()
162
- }
199
+ if (this.config.throttleConfig) {
200
+ await stopThrottle()
201
+ }
163
202
 
164
- stopTimers()
203
+ stopTimers()
165
204
 
166
- await postTest(config)
205
+ await this.postTest()
167
206
 
168
- // Copy docker logs to data directory.
169
- if (config.pageLogPath) {
170
- try {
171
- const logPath = await getDockerLogsPath()
172
- const dataDir = path.dirname(config.pageLogPath)
173
- await fs.promises.cp(logPath, path.resolve(dataDir, 'docker.log'))
174
- } catch (err: unknown) {
175
- log.debug(`docker logs not found: ${(err as Error).message}`)
176
- }
207
+ // Copy docker logs to data directory.
208
+ if (this.config.pageLogPath) {
209
+ try {
210
+ const logPath = await getDockerLogsPath()
211
+ const dataDir = path.dirname(this.config.pageLogPath)
212
+ await fs.promises.cp(logPath, path.resolve(dataDir, 'docker.log'))
213
+ } catch (err: unknown) {
214
+ log.debug(`docker logs not found: ${(err as Error).message}`)
177
215
  }
216
+ }
178
217
 
179
- server?.stop()
218
+ this.server?.stop()
180
219
 
181
- log.debug('Stopped')
182
- },
220
+ this.emit('stop', canceled)
183
221
  }
184
222
  }
185
223
 
@@ -189,39 +227,49 @@ export async function setupApplication(config: Config): Promise<{ stats: Stats;
189
227
  async function main(): Promise<void> {
190
228
  showHelpOrVersion()
191
229
 
192
- const config =
193
- process.argv[2] === '--prompt'
194
- ? await loadConfigFromPrompt(process.argv.slice(3).join(' '))
195
- : await loadConfig(process.argv[2])
230
+ let configs: Config[]
196
231
 
197
- if (config.vmafPrepareVideo) {
198
- await prepareVideo(config, true)
199
- process.exit(0)
200
- }
201
-
202
- if (config.vmafProcessVideo) {
203
- await convertToIvf(
204
- config.vmafProcessVideo,
205
- config.vmafVideoCrop,
206
- config.vmafKeepSourceFiles,
207
- config.vmafSkipDuplicated,
232
+ if (process.argv.slice(2).includes('--prompt')) {
233
+ const params = await loadConfigFromPrompt(
234
+ process.argv
235
+ .slice(2)
236
+ .filter(s => !['--prompt', '--dry-run'].includes(s))
237
+ .join(' '),
208
238
  )
209
- process.exit(0)
239
+ if (process.argv.slice(2).includes('--dry-run')) {
240
+ console.log(json5.stringify(params, null, 2))
241
+ process.exit(0)
242
+ }
243
+ configs = await loadConfig(undefined, params)
244
+ } else {
245
+ configs = await loadConfig(process.argv[2])
210
246
  }
211
247
 
212
- const { stop: stopApplication } = await setupApplication(config)
248
+ if (!configs.length) throw new Error('No configuration found')
213
249
 
214
- const stop = async (): Promise<void> => {
215
- console.log('Exiting...')
250
+ let application: Application
251
+ const runNext = () => {
252
+ const config = configs.splice(0, 1)[0]
216
253
 
217
- await stopApplication()
254
+ application = new Application(config)
255
+ application.once('stop', canceled => {
256
+ if (!canceled && configs.length) {
257
+ log.info(`Application stopped, running next (${configs.length} left)...`)
258
+ runNext()
259
+ } else {
260
+ process.exit(0)
261
+ }
262
+ })
263
+ return application.start()
264
+ }
218
265
 
219
- process.exit(0)
266
+ const stop = async () => {
267
+ console.log('Exiting...')
268
+ await application.stop(true)
220
269
  }
221
270
  registerExitHandler(() => stop())
222
271
 
223
- // Stop after a configured duration.
224
- setTimeout(stop, config.runDuration * 1000)
272
+ await runNext()
225
273
 
226
274
  // Command line interface.
227
275
  if (process.stdin && process.stdin.setRawMode) {
package/src/config.ts CHANGED
@@ -6,6 +6,7 @@ import path, { join } from 'path'
6
6
  import json5 from 'json5'
7
7
  import yaml from 'yaml'
8
8
  import toml from 'toml'
9
+ import fs from 'fs'
9
10
 
10
11
  // eslint-disable-next-line @typescript-eslint/no-require-imports
11
12
  const puppeteer = require('puppeteer-core')
@@ -56,8 +57,7 @@ convict.addParser([
56
57
  { extension: 'toml', parse: toml.parse },
57
58
  ])
58
59
 
59
- // config schema
60
- const configSchema = convict({
60
+ const configSchema = {
61
61
  url: {
62
62
  doc: `The page url to load.`,
63
63
  format: String,
@@ -413,7 +413,7 @@ calculated using \`Date.now()\``,
413
413
  showPageLog: {
414
414
  doc: `If \`true\`, the pages console logs will be shown on console. Set to false to disable the page logs.`,
415
415
  format: 'Boolean',
416
- default: true,
416
+ default: false,
417
417
  env: 'SHOW_PAGE_LOG',
418
418
  arg: 'show-page-log',
419
419
  },
@@ -445,7 +445,7 @@ on the console. Regexp string allowed.`,
445
445
  userAgent: {
446
446
  doc: `The user agent override.`,
447
447
  format: String,
448
- default: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36',
448
+ default: `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${puppeteer.PUPPETEER_REVISIONS.chrome} Safari/537.36`,
449
449
  nullable: true,
450
450
  env: 'USER_AGENT',
451
451
  arg: 'user-agent',
@@ -456,7 +456,9 @@ If set, the files contents will be executed inside each opened tab page; \
456
456
  the following global variables will be attached to the \`webrtcperf\` global object: \
457
457
  \`WEBRTC_PERF_SESSION\` the session number (0-indexed); \
458
458
  \`WEBRTC_PERF_TAB\` the tab number inside the same session (0-indexed); \
459
- \`WEBRTC_PERF_INDEX\` the page absolute index (0-indexed). \
459
+ \`WEBRTC_PERF_INDEX\` the page absolute index (0-indexed).
460
+ Suggested values:
461
+ - With meet.google.com: https://raw.githubusercontent.com/vpalmisano/webrtcperf/refs/heads/devel/examples/google-meet.js
460
462
  `,
461
463
  format: String,
462
464
  default: '',
@@ -857,7 +859,7 @@ E.g. \`{ w: "iw-10", h: "ih-5", x: "10", y: '5' }\``,
857
859
  env: 'VISQOL_KEEP_SOURCE_FILES',
858
860
  arg: 'visqol-keep-source-files',
859
861
  },
860
- })
862
+ }
861
863
 
862
864
  type ConfigDocs = Record<string, { doc: string; format: string; default: string }>
863
865
 
@@ -895,10 +897,10 @@ function formatDocs(
895
897
  * It returns the formatted configuration docs.
896
898
  */
897
899
  export function getConfigDocs(): ConfigDocs {
898
- return formatDocs({}, null, configSchema.getSchema())
900
+ return formatDocs({}, null, convict(configSchema).getSchema())
899
901
  }
900
902
 
901
- const _schemaProperties = configSchema.getProperties()
903
+ const _schemaProperties = convict(configSchema).getProperties()
902
904
 
903
905
  /** [[include:config.md]] */
904
906
  export type Config = typeof _schemaProperties
@@ -907,7 +909,8 @@ export type Config = typeof _schemaProperties
907
909
  * Loads the config object.
908
910
  */
909
911
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
910
- export async function loadConfig(filePath?: string, values?: any): Promise<Config> {
912
+ export async function loadConfig(filePath?: string, values?: any): Promise<Config[]> {
913
+ const configs: Config[] = []
911
914
  if (filePath) {
912
915
  if (filePath.startsWith('http')) {
913
916
  log.debug(`Loading config from url: ${filePath}`)
@@ -915,41 +918,45 @@ export async function loadConfig(filePath?: string, values?: any): Promise<Confi
915
918
  if (!res?.data) {
916
919
  throw new Error(`Failed to download configuration from: ${filePath}`)
917
920
  }
918
- const values =
921
+ values =
919
922
  res.contentType === 'application/x-yaml'
920
923
  ? yaml.parse(res.data)
921
924
  : res.contentType === 'application/toml'
922
925
  ? toml.parse(res.data)
923
926
  : json5.parse(res.data)
924
- configSchema.load(values)
925
927
  } else if (existsSync(filePath)) {
926
928
  log.debug(`Loading config from local file: ${filePath}`)
927
929
  if (filePath.endsWith('.js') || filePath.endsWith('.mjs')) {
928
930
  const module = await import(/* webpackIgnore: true */ path.resolve(filePath))
929
- configSchema.load(await module.default())
931
+ values = await module.default()
930
932
  } else {
931
- configSchema.loadFile(filePath)
933
+ const data = String(await fs.promises.readFile(filePath))
934
+ values =
935
+ filePath.endsWith('.yml') || filePath.endsWith('.yaml')
936
+ ? yaml.parse(data)
937
+ : filePath.endsWith('.toml')
938
+ ? toml.parse(data)
939
+ : json5.parse(data)
932
940
  }
933
941
  }
934
- } else if (values) {
935
- log.debug('Loading config from values.')
936
- configSchema.load(values)
937
- } else {
938
- log.debug('Loading config from default values.')
939
- configSchema.load({})
940
942
  }
941
-
942
- configSchema.validate({ allowed: 'strict' })
943
- const config = configSchema.getProperties()
944
-
945
- log.debug('Using config:', config)
946
- return config
943
+ if (!Array.isArray(values)) {
944
+ values = [values || {}]
945
+ }
946
+ for (const value of values) {
947
+ const schema = convict(configSchema)
948
+ schema.load(value || {})
949
+ schema.validate({ allowed: 'strict' })
950
+ configs.push(schema.getProperties())
951
+ }
952
+ log.debug('Using config:', configs)
953
+ return configs
947
954
  }
948
955
 
949
956
  function getFunctionDeclaration() {
950
957
  const properties: Record<string, { type: string; description: string; nullable?: boolean }> = {}
951
958
  const required: string[] = []
952
- const schema = configSchema.getSchema()
959
+ const schema = convict(configSchema).getSchema()
953
960
 
954
961
  Object.entries(schema._cvtProperties).forEach(([name, value]) => {
955
962
  const { format, doc, nullable } = value as SchemaObj
@@ -996,8 +1003,8 @@ export async function loadConfigFromPrompt(prompt: string) {
996
1003
  })
997
1004
  if (response.functionCalls && response.functionCalls.length > 0) {
998
1005
  const functionCall = response.functionCalls[0]
999
- log.info('Using function call:', functionCall.name, functionCall.args)
1000
- return loadConfig(undefined, functionCall.args)
1006
+ log.debug('Using function call:', functionCall.name, functionCall.args)
1007
+ return functionCall.args
1001
1008
  } else {
1002
1009
  throw new Error('No function call found in the response. Please check the prompt and try again.')
1003
1010
  }
package/src/server.ts CHANGED
@@ -11,10 +11,12 @@ import { WebSocketServer } from 'ws'
11
11
  import zlib from 'zlib'
12
12
  import auth from 'basic-auth'
13
13
 
14
- import { loadConfig } from './config'
14
+ import { Config, loadConfig } from './config'
15
15
  import { Session, SessionParams } from './session'
16
16
  import { Stats } from './stats'
17
17
  import { logger, runShellCommand, getDockerLogsPath } from './utils'
18
+ import { getSessionThrottleIndex } from '@vpalmisano/throttler'
19
+ import { MediaPath, prepareFakeMedia } from './media'
18
20
 
19
21
  const log = logger('webrtcperf:server')
20
22
 
@@ -272,7 +274,8 @@ export class Server {
272
274
  private async putSession(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {
273
275
  log.debug(`PUT /session`, req.body)
274
276
  try {
275
- const id = this.stats.consumeSessionId()
277
+ const config = req.body as Config
278
+ const id = this.stats.consumeSessionId(config.tabsPerSession)
276
279
  await this.startLocalSession(id, req.body)
277
280
  res.json({
278
281
  message: `Session created`,
@@ -293,10 +296,10 @@ export class Server {
293
296
  private async putSessions(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {
294
297
  log.debug(`PUT /sessions`, req.body)
295
298
  try {
296
- const { sessions } = req.body
299
+ const { sessions, tabsPerSession } = req.body as Config
297
300
  const sessionsIds = []
298
301
  for (let i = 0; i < sessions; i++) {
299
- const id = this.stats.sessions.size
302
+ const id = this.stats.consumeSessionId(tabsPerSession)
300
303
  await this.startLocalSession(id, req.body)
301
304
  sessionsIds.push(id)
302
305
  }
@@ -471,12 +474,25 @@ export class Server {
471
474
  * @param config The session configuration.
472
475
  */
473
476
  private async startLocalSession(id: number, config: SessionParams): Promise<Session> {
474
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
475
- const sessionConfig = loadConfig(undefined, config) as any
476
- const session = new Session({ ...sessionConfig, id })
477
+ const configs = await loadConfig(undefined, config)
478
+ const sessionConfig = configs[0]
479
+ const throttleIndex = getSessionThrottleIndex(id)
480
+ const spawnPeriod = 1000 / sessionConfig.spawnRate
481
+
482
+ // Prepare fake video and audio.
483
+ const mediaPaths: MediaPath[] = []
484
+ if (sessionConfig.videoPath) {
485
+ for (const videoPath of sessionConfig.videoPath.split(',')) {
486
+ const ret = await prepareFakeMedia({ ...sessionConfig, videoPath })
487
+ mediaPaths.push(ret)
488
+ }
489
+ }
490
+ const mediaPath = mediaPaths.length ? mediaPaths[id % mediaPaths.length] : undefined
491
+
492
+ const session = new Session({ ...sessionConfig, throttleIndex, spawnPeriod, mediaPath, id })
477
493
  session.once('stop', () => {
478
494
  console.warn(`Session ${id} stopped, reloading...`)
479
- setTimeout(this.startLocalSession.bind(this), sessionConfig.spawnPeriod, id, config)
495
+ setTimeout(this.startLocalSession.bind(this), spawnPeriod, id, config)
480
496
  })
481
497
  this.stats.addSession(session)
482
498
  try {
@@ -510,13 +526,17 @@ export class Server {
510
526
  log.debug('start')
511
527
  if (this.serverUseHttps) {
512
528
  const destDir = path.join(os.homedir(), '.webrtcperf/ssl')
513
- await runShellCommand(
514
- `mkdir -p ${destDir} && openssl req -newkey rsa:2048 -nodes -keyout ${destDir}/domain.key -x509 -days 365 -out ${destDir}/domain.crt -subj "/C=EU/ST=London/L=London/O=Global Security/OU=IT Department/CN=example.com"`,
515
- )
529
+ const keyPath = path.join(destDir, 'domain.key')
530
+ const crtPath = path.join(destDir, 'domain.crt')
531
+ if (!fs.existsSync(keyPath) || !fs.existsSync(crtPath)) {
532
+ await runShellCommand(
533
+ `mkdir -p ${destDir} && openssl req -newkey rsa:2048 -nodes -keyout ${keyPath} -x509 -days 365 -out ${crtPath} -subj "/C=EU/ST=London/L=London/O=Global Security/OU=IT Department/CN=example.com"`,
534
+ )
535
+ }
516
536
  this.server = _createServer(
517
537
  {
518
- key: fs.readFileSync(`${destDir}/domain.key`),
519
- cert: fs.readFileSync(`${destDir}/domain.crt`),
538
+ key: fs.readFileSync(keyPath),
539
+ cert: fs.readFileSync(crtPath),
520
540
  },
521
541
  this.app,
522
542
  )
package/src/session.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { getSessionThrottleValues, throttleLauncher } from '@vpalmisano/throttler'
2
2
  import assert from 'assert'
3
3
  import axios from 'axios'
4
- import chalk from 'chalk'
5
4
  import EventEmitter from 'events'
6
5
  import fs from 'fs'
7
6
  import JSON5 from 'json5'
@@ -50,6 +49,9 @@ import {
50
49
  } from './utils'
51
50
  import { MediaPath } from './media'
52
51
 
52
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
53
+ const { default: chalk } = require('chalk-template')
54
+
53
55
  // eslint-disable-next-line @typescript-eslint/no-require-imports
54
56
  const NavigatorHardwareConcurrency = require('puppeteer-extra-plugin-stealth/evasions/navigator.hardwareConcurrency')
55
57