Skip to content

Commit

Permalink
video stats
Browse files Browse the repository at this point in the history
  • Loading branch information
vpalmisano committed Dec 9, 2024
1 parent 88f2d4d commit 12afa23
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 18 deletions.
49 changes: 31 additions & 18 deletions scripts/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,14 +242,7 @@ webrtcperf.unregisterServiceWorkers = () => {
}

webrtcperf.MeasuredStats = window.MeasuredStats = class {
constructor(
{ ttl, maxItems, secondsPerSample, storeId } = {
ttl: 0,
maxItems: 0,
secondsPerSample: 1,
storeId: '',
},
) {
constructor({ ttl = 0, maxItems = 0, secondsPerSample = 1, storeId = '' }) {
/** @type number */
this.ttl = ttl
/** @type number */
Expand All @@ -264,6 +257,8 @@ webrtcperf.MeasuredStats = window.MeasuredStats = class {
this.statsSum = 0
/** @type number */
this.statsCount = 0
this.statsMin = undefined
this.statsMax = undefined
// Restore from localStorage.
this.load()
}
Expand All @@ -279,6 +274,8 @@ webrtcperf.MeasuredStats = window.MeasuredStats = class {
stats: this.stats,
statsSum: this.statsSum,
statsCount: this.statsCount,
statsMin: this.statsMin,
statsMax: this.statsMax,
}),
)
} catch (err) {
Expand All @@ -293,10 +290,12 @@ webrtcperf.MeasuredStats = window.MeasuredStats = class {
try {
const data = localStorage.getItem(`webrtcperf-MeasuredStats-${this.storeId}`)
if (data) {
const { stats, statsSum, statsCount } = JSON.parse(data)
const { stats, statsSum, statsCount, statsMin, statsMax } = JSON.parse(data)
this.stats = stats
this.statsSum = statsSum
this.statsCount = statsCount
this.statsMin = statsMin
this.statsMax = statsMax
}
} catch (err) {
log(`MeasuredStats load error: ${err.message}`)
Expand All @@ -307,6 +306,8 @@ webrtcperf.MeasuredStats = window.MeasuredStats = class {
this.stats = []
this.statsSum = 0
this.statsCount = 0
this.statsMin = undefined
this.statsMax = undefined
this.store()
}

Expand Down Expand Up @@ -348,21 +349,25 @@ webrtcperf.MeasuredStats = window.MeasuredStats = class {
* @param {number} value
*/
push(timestamp, value) {
const last = this.stats[this.stats.length - 1]
if (last && timestamp - last.timestamp < this.secondsPerSample * 1000) {
last.value += value
last.count += 1
} else {
this.stats.push({ timestamp, value, count: 1 })
if (timestamp !== undefined && value !== undefined) {
const last = this.stats[this.stats.length - 1]
if (last && timestamp - last.timestamp < this.secondsPerSample * 1000) {
last.value += value
last.count += 1
} else {
this.stats.push({ timestamp, value, count: 1 })
}
this.statsSum += value
this.statsCount += 1
if (this.statsMin === undefined || value < this.statsMin) this.statsMin = value
if (this.statsMax === undefined || value > this.statsMax) this.statsMax = value
}
this.statsSum += value
this.statsCount += 1
this.purge()
}

/**
* mean
* @returns {number | undefined} mean value
* @returns {number | undefined} The mean value.
*/
mean() {
this.purge()
Expand All @@ -372,6 +377,14 @@ webrtcperf.MeasuredStats = window.MeasuredStats = class {
get size() {
return this.statsCount
}

get min() {
return this.statsMin
}

get max() {
return this.statsMax
}
}

/**
Expand Down
4 changes: 4 additions & 0 deletions scripts/peer-connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@ webrtcperf.Timer = class {
this.duration = 0
this.lastTime = 0
this.timer = null
this.startEvents = 0
this.stopEvents = 0
}

start() {
if (this.timer) return
this.lastTime = Date.now()
this.startEvents++
this.timer = setInterval(() => {
const now = Date.now()
this.duration += (now - this.lastTime) / 1000
Expand All @@ -35,6 +38,7 @@ webrtcperf.Timer = class {
this.duration += (Date.now() - this.lastTime) / 1000
this.lastTime = 0
}
this.stopEvents++
}
}
webrtcperf.OnOffTimer = class {
Expand Down
96 changes: 96 additions & 0 deletions scripts/video-stats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/* global webrtcperf */

webrtcperf.videoStats = {
collectedVideos: new Map(),
bufferedTime: new webrtcperf.MeasuredStats({ ttl: 15 }),
width: new webrtcperf.MeasuredStats({ ttl: 15 }),
height: new webrtcperf.MeasuredStats({ ttl: 15 }),
playingTime: new webrtcperf.MeasuredStats({ ttl: 15 }),
bufferingTime: new webrtcperf.MeasuredStats({ ttl: 15 }),
bufferingEvents: new webrtcperf.MeasuredStats({ ttl: 15 }),

scheduleNext(timeout = 2000) {
setTimeout(() => {
try {
this.update()
} catch (e) {
webrtcperf.log('VideoStats error', e)
}
}, timeout)
},
watchVideo(video) {
if (this.collectedVideos.has(video)) return
webrtcperf.log('VideoStats watchVideo', video)
const playingTimer = new webrtcperf.Timer()
const bufferingTimer = new webrtcperf.Timer()
this.collectedVideos.set(video, { playingTimer, bufferingTimer })
video.addEventListener('playing', () => {
playingTimer.start()
bufferingTimer.stop()
})
video.addEventListener('waiting', () => {
playingTimer.stop()
bufferingTimer.start()
})
},
update() {
const now = Date.now()
document.querySelectorAll('video').forEach(el => this.watchVideo(el))
const entries = Array.from(this.collectedVideos.entries()).filter(([video]) => !!video.src && !video.ended)
const arrayAverage = cb =>
entries.length
? entries.reduce((acc, entry) => {
return acc + cb(...entry)
}, 0) / entries.length
: undefined

this.bufferedTime.push(
now,
arrayAverage(video => {
if (video.buffered.length) {
return Math.max(video.buffered.end(video.buffered.length - 1) - video.currentTime, 0)
}
return 0
}),
)
this.width.push(
now,
arrayAverage(video => video.videoWidth),
)
this.height.push(
now,
arrayAverage(video => video.videoHeight),
)
this.playingTime.push(
now,
arrayAverage((video, stats) => stats.playingTimer.duration),
)
this.bufferingTime.push(
now,
arrayAverage((video, stats) => stats.bufferingTimer.duration),
)
this.bufferingEvents.push(
now,
arrayAverage((video, stats) => stats.bufferingTimer.startEvents),
)
this.scheduleNext()
},
collect() {
return {
bufferedTime: this.bufferedTime.mean(),
width: this.width.mean(),
height: this.height.mean(),
playingTime: this.playingTime.mean(),
bufferingTime: this.bufferingTime.mean(),
bufferingEvents: this.bufferingEvents.mean(),
}
},
}

window.collectVideoStats = () => webrtcperf.videoStats.collect()

document.addEventListener('DOMContentLoaded', () => {
if (webrtcperf.enabledForSession(window.PARAMS?.enableVideoStats)) {
webrtcperf.videoStats.scheduleNext()
}
})
7 changes: 7 additions & 0 deletions src/rtcstats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ export enum PageStatsNames {

cpuPressure = 'cpuPressure',

videoWidth = 'videoWidth',
videoHeight = 'videoHeight',
videoBufferedTime = 'videoBufferedTime',
videoPlayingTime = 'videoPlayingTime',
videoBufferingTime = 'videoBufferingTime',
videoBufferingEvents = 'videoBufferingEvents',

/** The throttle upload rate limitation. */
throttleUpRate = 'throttleUpRate',
/** The throttle upload delay. */
Expand Down
32 changes: 32 additions & 0 deletions src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ declare global {
let collectVideoEndToEndNetworkDelayStats: () => number
let collectCpuPressure: () => number
let collectCustomMetrics: () => Promise<Record<string, number | string>>
let collectVideoStats: () => {
width: number
height: number
bufferedTime: number
playingTime: number
bufferingTime: number
bufferingEvents: number
}
let getParticipantName: () => string
}

Expand Down Expand Up @@ -908,6 +916,7 @@ window.SERVER_USE_HTTPS = ${this.serverUseHttps};
'https://raw.githubusercontent.com/ggerganov/ggwave/master/bindings/javascript/ggwave.js',
'scripts/e2e-audio-stats.js',
'scripts/e2e-video-stats.js',
'scripts/video-stats.js',
'scripts/playout-delay-hint.js',
'scripts/save-tracks.js',
'scripts/pressure-stats.js',
Expand Down Expand Up @@ -1512,6 +1521,13 @@ window.SERVER_USE_HTTPS = ${this.serverUseHttps};
const pageMemory: Record<string, number> = {}
const cpuPressureStats: Record<string, number> = {}

const videoWidth: Record<string, number> = {}
const videoHeight: Record<string, number> = {}
const videoBufferedTime: Record<string, number> = {}
const videoPlayingTime: Record<string, number> = {}
const videoBufferingTime: Record<string, number> = {}
const videoBufferingEvents: Record<string, number> = {}

const throttleUpValuesRate: Record<string, number> = {}
const throttleUpValuesDelay: Record<string, number> = {}
const throttleUpValuesLoss: Record<string, number> = {}
Expand All @@ -1533,13 +1549,15 @@ window.SERVER_USE_HTTPS = ${this.serverUseHttps};
videoEndToEndDelay,
videoEndToEndNetworkDelay,
cpuPressure,
videoStats,
customMetrics,
} = await page.evaluate(async () => ({
peerConnectionStats: await collectPeerConnectionStats(),
audioEndToEndDelay: collectAudioEndToEndStats(),
videoEndToEndDelay: collectVideoEndToEndStats(),
videoEndToEndNetworkDelay: collectVideoEndToEndNetworkDelayStats(),
cpuPressure: collectCpuPressure(),
videoStats: collectVideoStats(),
customMetrics: 'collectCustomMetrics' in window ? collectCustomMetrics() : null,
}))
const { participantName } = peerConnectionStats
Expand Down Expand Up @@ -1600,6 +1618,14 @@ window.SERVER_USE_HTTPS = ${this.serverUseHttps};
}

if (cpuPressure !== undefined) cpuPressureStats[pageKey] = cpuPressure
if (videoStats) {
videoWidth[pageKey] = videoStats.width
videoHeight[pageKey] = videoStats.height
videoBufferedTime[pageKey] = videoStats.bufferedTime
videoPlayingTime[pageKey] = videoStats.playingTime
videoBufferingTime[pageKey] = videoStats.bufferingTime
videoBufferingEvents[pageKey] = videoStats.bufferingEvents
}

// Collect RTC stats.
for (const s of stats) {
Expand Down Expand Up @@ -1694,6 +1720,12 @@ window.SERVER_USE_HTTPS = ${this.serverUseHttps};
collectedStats.httpRecvBytes = httpRecvBytesStats
collectedStats.httpRecvLatency = httpRecvLatencyStats
collectedStats.cpuPressure = cpuPressureStats
collectedStats.videoWidth = videoWidth
collectedStats.videoHeight = videoHeight
collectedStats.videoBufferedTime = videoBufferedTime
collectedStats.videoPlayingTime = videoPlayingTime
collectedStats.videoBufferingTime = videoBufferingTime
collectedStats.videoBufferingEvents = videoBufferingEvents
collectedStats.pageCpu = pageCpu
collectedStats.pageMemory = pageMemory
collectedStats.throttleUpRate = throttleUpValuesRate
Expand Down

0 comments on commit 12afa23

Please sign in to comment.