@vpalmisano/webrtcperf 4.1.5 → 4.1.9

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/server.ts CHANGED
@@ -119,14 +119,14 @@ export class Server {
119
119
  log.error(`mkdir ${this.serverData} error: ${err.message}`)
120
120
  })
121
121
  this.app.get('/data', this.getDataArchive.bind(this))
122
- this.app.get('/data/*', this.getData.bind(this))
122
+ this.app.get('/data/:path', this.getData.bind(this))
123
123
  }
124
124
  if (this.videoCachePath) {
125
125
  log.debug(`using videoCachePath: ${this.videoCachePath}`)
126
126
  fs.promises.mkdir(this.videoCachePath, { recursive: true }).catch(err => {
127
127
  log.error(`mkdir ${this.videoCachePath} error: ${err.message}`)
128
128
  })
129
- this.app.get('/cache/*', this.getCache.bind(this))
129
+ this.app.get('/cache/:path', this.getCache.bind(this))
130
130
  }
131
131
 
132
132
  this.app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
@@ -429,7 +429,7 @@ export class Server {
429
429
  * content in tar.gz format.
430
430
  */
431
431
  private getData(req: express.Request, res: express.Response, next: express.NextFunction): void {
432
- const paramPath = path.normalize(req.params[0]).replace(/^(\.\.(\/|\\|$))+/, '')
432
+ const paramPath = path.normalize(req.params.path).replace(/^(\.\.(\/|\\|$))+/, '')
433
433
  log.debug(`GET /data/${paramPath}`, req.query)
434
434
  const fpath = path.resolve(this.serverData, paramPath)
435
435
  if (!fs.existsSync(fpath)) {
@@ -453,7 +453,7 @@ export class Server {
453
453
  }
454
454
 
455
455
  private getCache(req: express.Request, res: express.Response, next: express.NextFunction): void {
456
- const paramPath = path.normalize(req.params[0]).replace(/^(\.\.(\/|\\|$))+/, '')
456
+ const paramPath = path.normalize(req.params.path).replace(/^(\.\.(\/|\\|$))+/, '')
457
457
  log.debug(`GET /cache/${paramPath}`, req.query)
458
458
  const fpath = path.resolve(this.videoCachePath, paramPath)
459
459
  if (!fs.existsSync(fpath)) {
package/src/session.ts CHANGED
@@ -15,6 +15,7 @@ import puppeteer, {
15
15
  CDPSession,
16
16
  CookieParam,
17
17
  ElementHandle,
18
+ ImageFormat,
18
19
  KeyInput,
19
20
  Metrics,
20
21
  Page,
@@ -176,7 +177,7 @@ export interface SessionParams {
176
177
  debuggingAddress: string
177
178
  randomAudioPeriod: number
178
179
  maxVideoDecoders: number
179
- maxVideoDecodersAt: number
180
+ maxVideoDecodersRange: string
180
181
  incognito: boolean
181
182
  serverPort: number
182
183
  serverSecret: string
@@ -245,7 +246,7 @@ export class Session extends EventEmitter {
245
246
  private readonly responseModifiers: Record<
246
247
  string,
247
248
  {
248
- search?: RegExp
249
+ search?: string | RegExp
249
250
  replace?: string
250
251
  file?: string
251
252
  headers?: Record<string, string>
@@ -260,7 +261,7 @@ export class Session extends EventEmitter {
260
261
  private readonly debuggingAddress: string
261
262
  private readonly randomAudioPeriod: number
262
263
  private readonly maxVideoDecoders: number
263
- private readonly maxVideoDecodersAt: number
264
+ private readonly maxVideoDecodersRange: string
264
265
  private readonly incognito: boolean
265
266
  private readonly serverPort: number
266
267
  private readonly serverSecret: string
@@ -390,7 +391,7 @@ export class Session extends EventEmitter {
390
391
  debuggingAddress,
391
392
  randomAudioPeriod,
392
393
  maxVideoDecoders,
393
- maxVideoDecodersAt,
394
+ maxVideoDecodersRange,
394
395
  incognito,
395
396
  serverPort,
396
397
  serverSecret,
@@ -464,7 +465,7 @@ export class Session extends EventEmitter {
464
465
  this.userAgent = userAgent
465
466
  this.randomAudioPeriod = randomAudioPeriod
466
467
  this.maxVideoDecoders = maxVideoDecoders
467
- this.maxVideoDecodersAt = maxVideoDecodersAt
468
+ this.maxVideoDecodersRange = maxVideoDecodersRange
468
469
  this.incognito = incognito
469
470
  this.serverPort = serverPort
470
471
  this.serverSecret = serverSecret
@@ -511,8 +512,8 @@ export class Session extends EventEmitter {
511
512
  `responseModifiers replacements should be an array of { search, replace, body, headers } objects: ${replacements}`,
512
513
  )
513
514
  }
514
- this.responseModifiers[url] = replacements.map(({ search, replace, file, headers }) => ({
515
- search: search ? new RegExp(search, 'g') : undefined,
515
+ this.responseModifiers[url] = replacements.map(({ search, regexp, replace, file, headers }) => ({
516
+ search: regexp ? new RegExp(regexp, 'g') : search,
516
517
  replace,
517
518
  file,
518
519
  headers,
@@ -596,7 +597,7 @@ export class Session extends EventEmitter {
596
597
  env.CHROME_LOG_FILE = path.resolve(pageLogDir, `chrome-${this.id}.log`)
597
598
  }
598
599
 
599
- if (this.maxVideoDecoders !== -1 && this.id >= this.maxVideoDecodersAt) {
600
+ if (this.maxVideoDecoders !== -1 && enabledForSession(this.id, this.maxVideoDecodersRange)) {
600
601
  fieldTrials = `WebRTC-MaxVideoDecoders/${this.maxVideoDecoders}/` + fieldTrials
601
602
  }
602
603
  if (fieldTrials.length) {
@@ -1921,7 +1922,7 @@ webrtcperf.config.AUDIO_URL = "http${this.serverUseHttps ? 's' : ''}://localhost
1921
1922
  if (!page) {
1922
1923
  throw new Error(`Page ${index} not found`)
1923
1924
  }
1924
- const filePath = `/tmp/screenshot-${index}.${format}`
1925
+ const filePath = `/tmp/screenshot-${index}.${format}` as `${string}.${ImageFormat}`
1925
1926
  await page.screenshot({
1926
1927
  path: filePath,
1927
1928
  fullPage: true,
package/src/stats.ts CHANGED
@@ -795,10 +795,12 @@ export class Stats extends events.EventEmitter {
795
795
  this.collectedStatsConfig.pages = 0
796
796
  this.collectedStatsConfig.startTime = this.startTimestamp
797
797
  // Reset collectedStats object.
798
- Object.values(this.collectedStats).forEach(stats => {
798
+ const prevByParticipantAndTrackStats = {} as Record<string, Set<string>>
799
+ Object.entries(this.collectedStats).forEach(([name, stats]) => {
799
800
  stats.all.reset()
800
801
  Object.values(stats.byHost).forEach(s => s.reset())
801
802
  Object.values(stats.byCodec).forEach(s => s.reset())
803
+ prevByParticipantAndTrackStats[name] = new Set(Object.keys(stats.byParticipantAndTrack))
802
804
  stats.byParticipantAndTrack = {}
803
805
  })
804
806
  for (const [sessionId, session] of this.sessions.entries()) {
@@ -812,6 +814,7 @@ export class Stats extends events.EventEmitter {
812
814
  //log.log(name, obj)
813
815
  try {
814
816
  const collectedStats = this.collectedStats[name]
817
+ const prevByParticipantAndTrack = prevByParticipantAndTrackStats[name]
815
818
  if (typeof obj === 'number' && isFinite(obj)) {
816
819
  collectedStats.all.push(obj)
817
820
  } else {
@@ -828,7 +831,9 @@ export class Stats extends events.EventEmitter {
828
831
  stats.push(value)
829
832
  // Push participant and track values.
830
833
  if (enabledForSession(sessionId, this.enableDetailedStats) && participantName) {
831
- collectedStats.byParticipantAndTrack[`${participantName}:${trackId || ''}`] = value
834
+ const label = `${participantName}:${trackId || ''}`
835
+ collectedStats.byParticipantAndTrack[label] = value
836
+ prevByParticipantAndTrack.delete(label)
832
837
  }
833
838
  } else if (typeof value === 'string') {
834
839
  // Codec stats.
@@ -869,6 +874,7 @@ export class Stats extends events.EventEmitter {
869
874
  return
870
875
  }
871
876
  const collectedStats = this.collectedStats[name]
877
+ const prevByParticipantAndTrack = prevByParticipantAndTrackStats[name]
872
878
  collectedStats.all.push(stats.all)
873
879
  Object.entries(stats.byHost).forEach(([host, values]) => {
874
880
  if (!collectedStats.byHost[host]) {
@@ -884,6 +890,7 @@ export class Stats extends events.EventEmitter {
884
890
  })
885
891
  Object.entries(stats.byParticipantAndTrack).forEach(([label, value]) => {
886
892
  collectedStats.byParticipantAndTrack[label] = value
893
+ prevByParticipantAndTrack.delete(label)
887
894
  })
888
895
  })
889
896
  }
@@ -930,7 +937,7 @@ export class Stats extends events.EventEmitter {
930
937
  await Promise.allSettled([
931
938
  this.writeStats(),
932
939
  this.writeDetailedStats(),
933
- this.sendToPushGateway(),
940
+ this.sendToPushGateway(prevByParticipantAndTrackStats),
934
941
  this.writeAlertRulesReport(),
935
942
  ])
936
943
  }
@@ -1062,7 +1069,7 @@ export class Stats extends events.EventEmitter {
1062
1069
  /**
1063
1070
  * sendToPushGateway
1064
1071
  */
1065
- async sendToPushGateway(): Promise<void> {
1072
+ async sendToPushGateway(removedByParticipantAndTrackStats: Record<string, Set<string>>): Promise<void> {
1066
1073
  if (!this.gateway || !this.running) {
1067
1074
  return
1068
1075
  }
@@ -1109,6 +1116,20 @@ export class Stats extends events.EventEmitter {
1109
1116
  })
1110
1117
  }
1111
1118
 
1119
+ // Remove metrics for removed participants and tracks.
1120
+ const removedByParticipantAndTrack = removedByParticipantAndTrackStats[name]
1121
+ if (removedByParticipantAndTrack) {
1122
+ for (const label of removedByParticipantAndTrack) {
1123
+ const [participantName, trackId] = label.split(':', 2)
1124
+ metric.value?.remove({
1125
+ participantName,
1126
+ trackId,
1127
+ datetime,
1128
+ ...this.customMetricsLabels,
1129
+ })
1130
+ }
1131
+ }
1132
+
1112
1133
  // Set alerts metrics.
1113
1134
  if (this.alertRules && this.alertRules[name]) {
1114
1135
  const rule = this.alertRules[name]
package/src/utils.ts CHANGED
@@ -20,7 +20,7 @@ import os, { networkInterfaces } from 'os'
20
20
  import path, { dirname } from 'path'
21
21
  import pidtree from 'pidtree'
22
22
  import pidusage from 'pidusage'
23
- import puppeteer, { Page } from 'puppeteer-core'
23
+ import puppeteer, { ImageFormat, Page } from 'puppeteer-core'
24
24
 
25
25
  import { Session } from './session'
26
26
 
@@ -255,7 +255,7 @@ export function startRandomActivateAudio(
255
255
  sessions: Map<number, Session>,
256
256
  randomAudioPeriod: number,
257
257
  randomAudioProbability: number,
258
- randomAudioRange: number,
258
+ randomAudioRange: string,
259
259
  ): void {
260
260
  if (randomActivateAudioRunning) return
261
261
  randomActivateAudioRunning = true
@@ -272,13 +272,13 @@ export function stopRandomActivateAudio(): void {
272
272
  * @param sessions The sessions Map
273
273
  * @param randomAudioPeriod If set, the function will be called in loop
274
274
  * @param randomAudioProbability The activation probability
275
- * @param randomAudioRange The number of pages to include into the automation
275
+ * @param randomAudioRange The page indexes to include into the automation
276
276
  */
277
277
  export async function randomActivateAudio(
278
278
  sessions: Map<number, Session>,
279
279
  randomAudioPeriod: number,
280
280
  randomAudioProbability: number,
281
- randomAudioRange: number,
281
+ randomAudioRange: string,
282
282
  ): Promise<void> {
283
283
  if (!randomAudioPeriod || !randomActivateAudioRunning) {
284
284
  return
@@ -287,13 +287,9 @@ export async function randomActivateAudio(
287
287
  let pages: (Page | null)[] = []
288
288
  for (const session of sessions.values()) {
289
289
  const sessionPages = [...session.pages.values()]
290
- if (randomAudioRange) {
291
- if (session.id > randomAudioRange) {
292
- break
293
- }
294
- sessionPages.splice(randomAudioRange - session.id)
290
+ if (enabledForSession(session.id, randomAudioRange)) {
291
+ pages = pages.concat(sessionPages)
295
292
  }
296
- pages = pages.concat(sessionPages)
297
293
  }
298
294
  // Remove pages with no audio tracks.
299
295
  for (const [i, page] of pages.entries()) {
@@ -1037,7 +1033,8 @@ export async function pageScreenshot(
1037
1033
  if (!element) {
1038
1034
  throw new Error(`pageScreenshot selector "${selector}" not found`)
1039
1035
  }
1040
- await element.screenshot({ path: filePath })
1036
+ const path = filePath as `${string}.${ImageFormat}`
1037
+ await element.screenshot({ path })
1041
1038
  } catch (err) {
1042
1039
  log.error(`pageScreenshot error: ${(err as Error).message}`)
1043
1040
  } finally {
package/src/vmaf.ts CHANGED
@@ -464,7 +464,12 @@ export async function runVmaf(
464
464
  const sender = path.basename(referencePath).replace('.ivf', '')
465
465
  const receiver = path.basename(degradedPath).replace('.ivf', '').split('_recv-by_')[1]
466
466
 
467
- const { frameRate: refFrameRate, frames: refFrames } = await parseIvf(referencePath, false)
467
+ const {
468
+ width: refWidth,
469
+ height: refHeight,
470
+ frameRate: refFrameRate,
471
+ frames: refFrames,
472
+ } = await parseIvf(referencePath, false)
468
473
  const {
469
474
  width: degWidth,
470
475
  height: degHeight,
@@ -517,13 +522,20 @@ export async function runVmaf(
517
522
  -i ${referencePath} \
518
523
  `
519
524
 
525
+ let cropWidth = 0
526
+ const refFactor = refWidth / refHeight
527
+ const degFactor = degWidth / degHeight
528
+ if (refFactor > degFactor) {
529
+ cropWidth = Math.round(degFactor * refHeight)
530
+ }
531
+
520
532
  const filter = `\
521
533
  [0:v]\
522
- scale=w=-1:h=${degHeight}:flags=bicubic,\
534
+ scale=w=-1:h=${refHeight}:flags=bicubic,${cropWidth ? `crop=w=${cropWidth}:x=(iw-${cropWidth})/2,` : ''}\
523
535
  ${cropFilter(crop.deg, 0, ',')}\
524
536
  ${splitFilter(['deg_vmaf', 'deg_psnr', preview ? 'deg_preview' : ''])};\
525
537
  [1:v]\
526
- scale=w=-1:h=${degHeight}:flags=bicubic,crop=w=${degWidth}:x=(iw-${degWidth})/2,\
538
+ scale=w=-1:h=${refHeight}:flags=bicubic,\
527
539
  ${cropFilter(crop.ref, 0, ',')}\
528
540
  ${splitFilter(['ref_vmaf', 'ref_psnr', preview ? 'ref_preview' : ''])};\
529
541
  [deg_vmaf][ref_vmaf]libvmaf=model='path=/usr/share/model/vmaf_v0.6.1.json':log_fmt=json:log_path=${vmafLogPath}:n_subsample=1:n_threads=${cpus}:shortest=1[vmaf];\
@@ -575,8 +587,19 @@ ${splitFilter(['ref_vmaf', 'ref_psnr', preview ? 'ref_preview' : ''])};\
575
587
  }
576
588
 
577
589
  async function writeGraph(vmafLogPath: string) {
590
+ const {
591
+ CategoryScale,
592
+ Chart,
593
+ LinearScale,
594
+ LineController,
595
+ LineElement,
596
+ PointElement,
597
+ Legend,
598
+ Title,
599
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
600
+ } = require('chart.js')
578
601
  // eslint-disable-next-line @typescript-eslint/no-require-imports
579
- const { ChartJSNodeCanvas } = require('chartjs-node-canvas')
602
+ const { Canvas } = require('skia-canvas')
580
603
 
581
604
  const vmafLog = JSON.parse(await fs.promises.readFile(vmafLogPath, 'utf-8')) as {
582
605
  frames: {
@@ -613,13 +636,9 @@ async function writeGraph(vmafLogPath: string) {
613
636
  )
614
637
  .map(d => ({ x: d.x, y: d.y / d.count }))
615
638
 
616
- const chartJSNodeCanvas = new ChartJSNodeCanvas({
617
- width: 1280,
618
- height: 720,
619
- backgroundColour: 'white',
620
- })
621
-
622
- const buffer = await chartJSNodeCanvas.renderToBuffer({
639
+ Chart.register([CategoryScale, LineController, LineElement, LinearScale, PointElement, Legend, Title])
640
+ const canvas = new Canvas(1280, 720)
641
+ const chart = new Chart(canvas, {
623
642
  type: 'line',
624
643
  data: {
625
644
  labels: data.map(d => d.x),
@@ -640,7 +659,7 @@ async function writeGraph(vmafLogPath: string) {
640
659
  plugins: {
641
660
  title: {
642
661
  display: true,
643
- text: path.basename(vmafLogPath).replace('.vmaf.json', '').replace(/_/g, ' '),
662
+ text: path.basename(path.dirname(vmafLogPath)).replace(/_/g, ' '),
644
663
  },
645
664
  },
646
665
  scales: {
@@ -651,8 +670,8 @@ async function writeGraph(vmafLogPath: string) {
651
670
  },
652
671
  },
653
672
  })
654
-
655
- await fs.promises.writeFile(fpath, buffer)
673
+ await canvas.saveAs(fpath, { format: 'png', matte: 'white' })
674
+ chart.destroy()
656
675
  }
657
676
 
658
677
  interface Crop {