Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log webrtc stats and events #1179

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@mdi/font": "^7.0.96",
"@mdi/js": "^7.2.96",
"@sentry/vue": "^8.20.0",
"@peermetrics/webrtc-stats": "^5.7.1",
"@types/hammerjs": "^2.0.45",
"@vueuse/core": "9.8.1",
"@vueuse/math": "^10.1.2",
Expand Down
2 changes: 1 addition & 1 deletion src/composables/webRTC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class WebRTCManager {
private connected = ref(false)
private consumerId: string | undefined
private streamName: string | undefined
private session: Session | undefined
public session: Session | undefined
private rtcConfiguration: RTCConfiguration
private selectedICEIPs: string[] = []
private selectedICEProtocols: string[] = []
Expand Down
2 changes: 1 addition & 1 deletion src/libs/webrtc/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class Session {
public status: string
private ended: boolean
private signaller: Signaller
private peerConnection: RTCPeerConnection
public peerConnection: RTCPeerConnection
private availableICEIPs: string[]
private selectedICEIPs: string[]
private selectedICEProtocols: string[]
Expand Down
90 changes: 89 additions & 1 deletion src/stores/omniscientLogger.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { WebRTCStats } from '@peermetrics/webrtc-stats'
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { ref, watch } from 'vue'

import { WebRTCStatsEvent, WebRTCVideoStat } from '@/types/video'

import { useVideoStore } from './video'

export const webrtcStats = new WebRTCStats({ getStatsInterval: 250 })

export const useOmniscientLoggerStore = defineStore('omniscient-logger', () => {
const videoStore = useVideoStore()

Expand Down Expand Up @@ -79,4 +84,87 @@ export const useOmniscientLoggerStore = defineStore('omniscient-logger', () => {
})
}
fpsMeter()

// Routine to log the WebRTC statistics

// Monitor the active streams to add the connections to the WebRTC statistics
watch(videoStore.activeStreams, (streams) => {
Object.keys(streams).forEach((streamName) => {
const session = streams[streamName]?.webRtcManager.session
if (!session || !session.peerConnection) return
if (webrtcStats.peersToMonitor[session.consumerId]) return
webrtcStats.addConnection({
pc: session.peerConnection, // RTCPeerConnection instance
peerId: session.consumerId, // any string that helps you identify this peer,
connectionId: session.id, // optional, an id that you can use to keep track of this connection
remote: false, // optional, override the global remote flag
})
})
})

// Track the WebRTC statistics, warn about changes in cumulative values and log the average values
const historyLength = 30 // Number of samples to keep in the history
const cumulativeKeys: WebRTCVideoStat[] = ['packetsLost', 'totalFreezesDuration', 'framesDropped'] // Keys that have cumulative values
const averageKeys: WebRTCVideoStat[] = ['packetRate', 'jitter'] // Keys that have average values
const storedKeys = [...cumulativeKeys, ...averageKeys] // Keys to store in the history

const webrtcStatsAverageLogDelay = 1000
let lastWebrtcStatsAverageLog = new Date()
const webRtcStatsHistory = ref<{ [id in string]: { [stat in string]: (number | string)[] } }>({})

webrtcStats.on('stats', (ev: WebRTCStatsEvent) => {
try {
const videoData = ev.data.video.inbound[0]
if (videoData === undefined) return

// Initialize the peer's statistics if they do not exist
if (webRtcStatsHistory.value[ev.peerId] === undefined) webRtcStatsHistory.value[ev.peerId] = {}

storedKeys.forEach((key) => {
// Initialize the key array if it does not exist
if (webRtcStatsHistory.value[ev.peerId][key] === undefined) webRtcStatsHistory.value[ev.peerId][key] = []

webRtcStatsHistory.value[ev.peerId][key].push(videoData[key])

// Keep only the last 'historyLength' samples
const keyArray = webRtcStatsHistory.value[ev.peerId][key]
keyArray.splice(0, keyArray.length - historyLength)
webRtcStatsHistory.value[ev.peerId][key] = keyArray
})

// Warn about changes in cumulative values
cumulativeKeys.forEach((key) => {
const keyArray = webRtcStatsHistory.value[ev.peerId][key]
if (keyArray.length < 2) return

const lastValue = keyArray[keyArray.length - 1]
const prevValue = keyArray[keyArray.length - 2]

if (typeof lastValue !== 'number' || typeof prevValue !== 'number') return

if (lastValue > prevValue) {
console.warn(`Cumulative value '${key}' increased for peer '${ev.peerId}': ${lastValue.toFixed(2)}.`)
}
})

// Log the average values recursively
if (new Date().getTime() - lastWebrtcStatsAverageLog.getTime() > webrtcStatsAverageLogDelay) {
averageKeys.forEach((key) => {
const keyArray = webRtcStatsHistory.value[ev.peerId][key]
if (keyArray.find((value) => typeof value !== 'number')) return
const average = (keyArray as number[]).reduce((a, b) => a + b, 0) / keyArray.length
console.debug(`Average value '${key}' for peer '${ev.peerId}': ${average.toFixed(4)}.`)
})
lastWebrtcStatsAverageLog = new Date()
}
} catch (error) {
console.error('Error while logging WebRTC statistics:', error)
}
})

return {
streamsFrameRateHistory,
appFrameRateHistory,
webRtcStatsHistory,
}
})
1 change: 1 addition & 0 deletions src/types/shims.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ declare module 'gamepad.js'
declare module 'vuetify'
declare module 'vuetify/lib/components'
declare module 'vuetify/lib/directives'
declare module '@peermetrics/webrtc-stats'

declare module 'vue-virtual-scroller' {
import Vue, { ComponentOptions, PluginObject, Component } from 'vue'
Expand Down
61 changes: 61 additions & 0 deletions src/types/video.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,64 @@ export const getBlobExtensionContainer = (blob: Blob): VideoExtensionContainer |
}
return undefined
}

export type WebRTCVideoStats = {
id: string
timestamp: number
type: string
codecId: string
kind: string
mediaType: string
ssrc: number
transportId: string
jitter: number
packetsLost: number
packetsReceived: number
bytesReceived: number
firCount: number
frameHeight: number
frameWidth: number
framesAssembledFromMultiplePackets: number
framesDecoded: number
framesDropped: number
framesPerSecond: number
framesReceived: number
freezeCount: number
headerBytesReceived: number
jitterBufferDelay: number
jitterBufferEmittedCount: number
jitterBufferMinimumDelay: number
jitterBufferTargetDelay: number
keyFramesDecoded: number
lastPacketReceivedTimestamp: number
mid: string
nackCount: number
pauseCount: number
pliCount: number
remoteId: string
totalAssemblyTime: number
totalDecodeTime: number
totalFreezesDuration: number
totalInterFrameDelay: number
totalPausesDuration: number
totalProcessingDelay: number
totalSquaredInterFrameDelay: number
trackIdentifier: string
clockRate: number
mimeType: string
payloadType: number
bitrate: number
packetRate: number
}
export type WebRTCVideoStat = keyof WebRTCVideoStats

export type WebRTCStatsEvent = {
peerId: string
data: {
video: {
inbound: {
[index: number]: WebRTCVideoStats
}
}
}
}
Loading