mirror of https://github.com/jitsi/jitsi-meet
feat(rtcstats): Integrate rtcstats (#6945)
* Integrate rtcstats * expcetion handling / clean up * order imports * config fix * remove mock amplitude handler * additional comments * lint fix * address code review * move rtcstats middleware * link to jitsi rtcstats package * address code review * address code review / add ws onclose handler * add display name / bump rtcstats version * resolve import errorpull/7330/head stable/jitsi-meet_4857
parent
11fd5363ce
commit
29805edd02
@ -0,0 +1,111 @@ |
||||
import rtcstatsInit from 'rtcstats/rtcstats'; |
||||
import traceInit from 'rtcstats/trace-ws'; |
||||
|
||||
import { |
||||
createRTCStatsTraceCloseEvent, |
||||
sendAnalytics |
||||
} from '../analytics'; |
||||
|
||||
import logger from './logger'; |
||||
|
||||
/** |
||||
* Filter out RTCPeerConnection that are created by callstats.io. |
||||
* |
||||
* @param {*} config - Config object sent to the PC c'tor. |
||||
* @returns {boolean} |
||||
*/ |
||||
function connectionFilter(config) { |
||||
if (config && config.iceServers[0] && config.iceServers[0].urls) { |
||||
for (const iceUrl of config.iceServers[0].urls) { |
||||
if (iceUrl.indexOf('taas.callstats.io') >= 0) { |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Class that controls the rtcstats flow, because it overwrites and proxies global function it should only be |
||||
* initialized once. |
||||
*/ |
||||
class RTCStats { |
||||
/** |
||||
* Initialize the rtcstats components. First off we initialize the trace, which is a wrapped websocket |
||||
* that does the actual communication with the server. Secondly, the rtcstats component is initialized, |
||||
* it overwrites GUM and PeerConnection global functions and adds a proxy over them used to capture stats. |
||||
* Note, lib-jitsi-meet takes references to these methods before initializing so the init method needs to be |
||||
* loaded before it does. |
||||
* |
||||
* @param {Object} options -. |
||||
* @param {string} options.rtcstatsEndpoint - The Amplitude app key required. |
||||
* @param {number} options.rtcstatsPollInterval - The getstats poll interval in ms. |
||||
* @returns {void} |
||||
*/ |
||||
init(options) { |
||||
this.handleTraceWSClose = this.handleTraceWSClose.bind(this); |
||||
this.trace = traceInit(options.rtcstatsEndpoint, this.handleTraceWSClose); |
||||
rtcstatsInit(this.trace, options.rtcstatsPollInterval, [ '' ], connectionFilter); |
||||
this.initialized = true; |
||||
} |
||||
|
||||
/** |
||||
* Check whether or not the RTCStats is initialized. |
||||
* |
||||
* @returns {boolean} |
||||
*/ |
||||
isInitialized() { |
||||
return this.initialized; |
||||
} |
||||
|
||||
/** |
||||
* Send identity data to rtcstats server, this will be reflected in the identity section of the stats dump. |
||||
* It can be generally used to send additional metadata that might be relevant such as amplitude user data |
||||
* or deployment specific information. |
||||
* |
||||
* @param {Object} identityData - Metadata object to send as identity. |
||||
* @returns {void} |
||||
*/ |
||||
sendIdentityData(identityData) { |
||||
this.trace && this.trace('identity', null, identityData); |
||||
} |
||||
|
||||
/** |
||||
* Connect to the rtcstats server instance. Stats (data obtained from getstats) won't be send until the |
||||
* connect successfully initializes, however calls to GUM are recorded in an internal buffer even if not |
||||
* connected and sent once it is established. |
||||
* |
||||
* @returns {void} |
||||
*/ |
||||
connect() { |
||||
this.trace && this.trace.connect(); |
||||
} |
||||
|
||||
/** |
||||
* Self explanatory; closes the web socked connection. |
||||
* Note, at the point of writing this documentation there was no method to reset the function overwrites, |
||||
* thus even if the websocket is closed the global function proxies are still active but send no data, |
||||
* this shouldn't influence the normal flow of the application. |
||||
* |
||||
* @returns {void} |
||||
*/ |
||||
close() { |
||||
this.trace && this.trace.close(); |
||||
} |
||||
|
||||
/** |
||||
* The way rtcstats is currently designed the ws wouldn't normally be closed by the application logic but rather |
||||
* by the page being closed/reloaded. Using this assumption any onclose event is most likely something abnormal |
||||
* that happened on the ws. We then track this in order to determine how many rtcstats connection were closed |
||||
* prematurely. |
||||
* |
||||
* @param {Object} closeEvent - Event sent by ws onclose. |
||||
* @returns {void} |
||||
*/ |
||||
handleTraceWSClose(closeEvent) { |
||||
logger.info('RTCStats trace ws closed', closeEvent); |
||||
|
||||
sendAnalytics(createRTCStatsTraceCloseEvent(closeEvent)); |
||||
} |
||||
} |
||||
|
||||
export default new RTCStats(); |
@ -0,0 +1 @@ |
||||
import './middleware'; |
@ -0,0 +1,5 @@ |
||||
// @flow
|
||||
|
||||
import { getLogger } from '../base/logging/functions'; |
||||
|
||||
export default getLogger('features/rtcstats'); |
@ -0,0 +1,79 @@ |
||||
// @flow
|
||||
|
||||
import { getAmplitudeIdentity } from '../analytics'; |
||||
import { |
||||
CONFERENCE_JOINED |
||||
} from '../base/conference'; |
||||
import { LIB_WILL_INIT } from '../base/lib-jitsi-meet'; |
||||
import { getLocalParticipant } from '../base/participants'; |
||||
import { MiddlewareRegistry } from '../base/redux'; |
||||
|
||||
import RTCStats from './RTCStats'; |
||||
import logger from './logger'; |
||||
|
||||
/** |
||||
* Middleware which intercepts lib-jitsi-meet initialization and conference join in order init the |
||||
* rtcstats-client. |
||||
* |
||||
* @param {Store} store - The redux store. |
||||
* @returns {Function} |
||||
*/ |
||||
MiddlewareRegistry.register(store => next => action => { |
||||
const state = store.getState(); |
||||
const config = state['features/base/config']; |
||||
const { analytics } = config; |
||||
|
||||
switch (action.type) { |
||||
case LIB_WILL_INIT: { |
||||
if (analytics.rtcstatsEndpoint) { |
||||
// RTCStats "proxies" WebRTC functions such as GUM and RTCPeerConnection by rewriting the global
|
||||
// window functions. Because lib-jitsi-meet uses references to those functions that are taken on
|
||||
// init, we need to add these proxies before it initializes, otherwise lib-jitsi-meet will use the
|
||||
// original non proxy versions of these functions.
|
||||
try { |
||||
// Default poll interval is 1000ms if not provided in the config.
|
||||
const pollInterval = analytics.rtcstatsPollInterval || 1000; |
||||
|
||||
// Initialize but don't connect to the rtcstats server wss, as it will start sending data for all
|
||||
// media calls made even before the conference started.
|
||||
RTCStats.init({ |
||||
rtcstatsEndpoint: analytics.rtcstatsEndpoint, |
||||
rtcstatsPollInterval: pollInterval |
||||
}); |
||||
} catch (error) { |
||||
logger.error('Failed to initialize RTCStats: ', error); |
||||
} |
||||
} |
||||
break; |
||||
} |
||||
case CONFERENCE_JOINED: { |
||||
if (analytics.rtcstatsEndpoint && RTCStats.isInitialized()) { |
||||
// Once the conference started connect to the rtcstats server and send data.
|
||||
try { |
||||
RTCStats.connect(); |
||||
|
||||
const localParticipant = getLocalParticipant(state); |
||||
|
||||
// The current implementation of rtcstats-server is configured to send data to amplitude, thus
|
||||
// we add identity specific information so we can corelate on the amplitude side. If amplitude is
|
||||
// not configured an empty object will be sent.
|
||||
// The current configuration of the conference is also sent as metadata to rtcstats server.
|
||||
// This is done in order to facilitate queries based on different conference configurations.
|
||||
// e.g. Find all RTCPeerConnections that connect to a specific shard or were created in a
|
||||
// conference with a specific version.
|
||||
RTCStats.sendIdentityData({ |
||||
...getAmplitudeIdentity(), |
||||
...config, |
||||
displayName: localParticipant?.name |
||||
}); |
||||
} catch (error) { |
||||
// If the connection failed do not impact jitsi-meet just silently fail.
|
||||
logger.error('RTCStats connect failed with: ', error); |
||||
} |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
|
||||
return next(action); |
||||
}); |
Loading…
Reference in new issue