| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379 |
- /* global __filename */
-
- import { getLogger } from 'jitsi-meet-logger';
- import * as ConnectionQualityEvents
- from '../../service/connectivity/ConnectionQualityEvents';
- import * as ConferenceEvents from '../../JitsiConferenceEvents';
- import RTCBrowserType from '../RTC/RTCBrowserType';
- import Statistics from './statistics';
-
- const logger = getLogger(__filename);
-
- /**
- * This will calculate an average for one, named stat and submit it to
- * the analytics module when requested. It automatically counts the samples.
- */
- class AverageStatReport {
- /**
- * Creates new <tt>AverageStatReport</tt> for given name.
- * @param {string} name that's the name of the event that will be reported
- * to the analytics module.
- */
- constructor(name) {
- this.name = name;
- this.count = 0;
- this.sum = 0;
- }
-
- /**
- * Adds the next value that will be included in the average when
- * {@link calculate} is called.
- * @param {number} nextValue
- */
- addNext(nextValue) {
- if (typeof nextValue !== 'number') {
- logger.error(
- `${this.name} - invalid value for idx: ${this.count}`,
- nextValue);
-
- return;
- }
- this.sum += nextValue;
- this.count += 1;
- }
-
- /**
- * Calculates an average for the samples collected using {@link addNext}.
- * @return {number|NaN} an average of all collected samples or <tt>NaN</tt>
- * if no samples were collected.
- */
- calculate() {
- return this.sum / this.count;
- }
-
- /**
- * Calculates an average and submit the report to the analytics module.
- * @param {boolean} isP2P indicates if the report is to be submitted for
- * the P2P connection (when conference is currently in the P2P mode). This
- * will add 'p2p.' prefix to the name of the event. All averages should be
- * cleared when the conference switches, between P2P and JVB modes.
- */
- report(isP2P) {
- Statistics.analytics.sendEvent(
- `${isP2P ? 'p2p.' : ''}${this.name}`,
- { value: this.calculate() });
- }
-
- /**
- * Clears all memory of any samples collected, so that new average can be
- * calculated using this instance.
- */
- reset() {
- this.sum = 0;
- this.count = 0;
- }
- }
-
- /**
- * Reports average RTP statistics values (arithmetic mean) to the analytics
- * module for things like bit rate, bandwidth, packet loss etc. It keeps track
- * of the P2P vs JVB conference modes and submits the values under different
- * namespaces (the events for P2P mode have 'p2p.' prefix). Every switch between
- * P2P mode resets the data collected so far and averages are calculated from
- * scratch.
- */
- export default class AvgRTPStatsReporter {
- /**
- * Creates new instance of <tt>AvgRTPStatsReporter</tt>
- * @param {JitsiConference} conference
- * @param {number} n the number of samples, before arithmetic mean is to be
- * calculated and values submitted to the analytics module.
- */
- constructor(conference, n) {
- /**
- * How many {@link ConnectionQualityEvents.LOCAL_STATS_UPDATED} samples
- * are to be included in arithmetic mean calculation.
- * @type {number}
- * @private
- */
- this._n = n;
-
- if (n > 0) {
- logger.info(`Avg RTP stats will be calculated every ${n} samples`);
- } else {
- logger.info('Avg RTP stats reports are disabled.');
-
- // Do not initialize
- return;
- }
-
- /**
- * The current sample index. Starts from 0 and goes up to {@link _n})
- * when analytics report will be submitted.
- * @type {number}
- * @private
- */
- this._sampleIdx = 0;
-
- /**
- * The conference for which stats will be collected and reported.
- * @type {JitsiConference}
- * @private
- */
- this._conference = conference;
-
- /**
- * Average upload bitrate
- * @type {AverageStatReport}
- * @private
- */
- this._avgBitrateUp = new AverageStatReport('stat.avg.bitrate.upload');
-
- /**
- * Average download bitrate
- * @type {AverageStatReport}
- * @private
- */
- this._avgBitrateDown
- = new AverageStatReport('stat.avg.bitrate.download');
-
- /**
- * Average upload bandwidth
- * @type {AverageStatReport}
- * @private
- */
- this._avgBandwidthUp
- = new AverageStatReport('stat.avg.bandwidth.upload');
-
- /**
- * Average download bandwidth
- * @type {AverageStatReport}
- * @private
- */
- this._avgBandwidthDown
- = new AverageStatReport('stat.avg.bandwidth.download');
-
- /**
- * Average total packet loss
- * @type {AverageStatReport}
- * @private
- */
- this._avgPacketLossTotal
- = new AverageStatReport('stat.avg.packetloss.total');
-
- /**
- * Average upload packet loss
- * @type {AverageStatReport}
- * @private
- */
- this._avgPacketLossUp
- = new AverageStatReport('stat.avg.packetloss.upload');
-
- /**
- * Average download packet loss
- * @type {AverageStatReport}
- * @private
- */
- this._avgPacketLossDown
- = new AverageStatReport('stat.avg.packetloss.download');
-
- /**
- * Average FPS for remote videos
- * @type {AverageStatReport}
- * @private
- */
- this._avgRemoteFPS = new AverageStatReport('stat.avg.framerate.remote');
-
- /**
- * Average FPS for local video
- * @type {AverageStatReport}
- * @private
- */
- this._avgLocalFPS = new AverageStatReport('stat.avg.framerate.local');
-
- /**
- * Average connection quality as defined by
- * the {@link ConnectionQuality} module.
- * @type {AverageStatReport}
- * @private
- */
- this._avgCQ = new AverageStatReport('stat.avg.cq');
-
- this._onLocalStatsUpdated = data => this._calculateAvgStats(data);
- conference.on(
- ConnectionQualityEvents.LOCAL_STATS_UPDATED,
- this._onLocalStatsUpdated);
-
- this._onP2PStatusChanged = () => {
- logger.debug('Resetting average stats calculation');
- this._resetAvgStats();
- };
- conference.on(
- ConferenceEvents.P2P_STATUS,
- this._onP2PStatusChanged);
- }
-
- /**
- * Processes next batch of stats reported on
- * {@link ConnectionQualityEvents.LOCAL_STATS_UPDATED}.
- * @param {go figure} data
- * @private
- */
- _calculateAvgStats(data) {
-
- const isP2P = this._conference.isP2PActive();
- const peerCount = this._conference.getParticipants().length;
-
- if (!isP2P && peerCount < 1) {
-
- // There's no point in collecting stats for a JVB conference of 1.
- // That happens for short period of time after everyone leaves
- // the room, until Jicofo terminates the session.
- return;
- }
-
- /* Uncomment to figure out stats structure
- for (const key in data) {
- if (data.hasOwnProperty(key)) {
- logger.info(`local stat ${key}: `, data[key]);
- }
- } */
-
- if (!data) {
- logger.error('No stats');
-
- return;
- }
-
- const bitrate = data.bitrate;
- const bandwidth = data.bandwidth;
- const packetLoss = data.packetLoss;
- const frameRate = data.framerate;
-
- if (!bitrate) {
- logger.error('No "bitrate"');
-
- return;
- } else if (!bandwidth) {
- logger.error('No "bandwidth"');
-
- return;
- } else if (!packetLoss) {
- logger.error('No "packetloss"');
-
- return;
- } else if (!frameRate) {
- logger.error('No "framerate"');
-
- return;
- }
-
- this._avgBitrateUp.addNext(bitrate.upload);
- this._avgBitrateDown.addNext(bitrate.download);
-
- if (RTCBrowserType.supportsBandwidthStatistics()) {
- this._avgBandwidthUp.addNext(bandwidth.upload);
- this._avgBandwidthDown.addNext(bandwidth.download);
- }
-
- this._avgPacketLossUp.addNext(packetLoss.upload);
- this._avgPacketLossDown.addNext(packetLoss.download);
- this._avgPacketLossTotal.addNext(packetLoss.total);
- this._avgCQ.addNext(data.connectionQuality);
-
- if (frameRate) {
- this._avgRemoteFPS.addNext(
- this._calculateAvgVideoFps(frameRate, false /* remote */));
- this._avgLocalFPS.addNext(
- this._calculateAvgVideoFps(frameRate, true /* local */));
- }
-
- this._sampleIdx += 1;
-
- if (this._sampleIdx >= this._n) {
- this._avgBitrateUp.report(isP2P);
- this._avgBitrateDown.report(isP2P);
- if (RTCBrowserType.supportsBandwidthStatistics()) {
- this._avgBandwidthUp.report(isP2P);
- this._avgBandwidthDown.report(isP2P);
- }
- this._avgPacketLossUp.report(isP2P);
- this._avgPacketLossDown.report(isP2P);
- this._avgPacketLossTotal.report(isP2P);
- this._avgRemoteFPS.report(isP2P);
- this._avgLocalFPS.report(isP2P);
- this._avgCQ.report(isP2P);
-
- this._resetAvgStats();
- }
- }
-
- /**
- * Calculates average FPS for the report
- * @param {go figure} frameRate
- * @param {boolean} isLocal if the average is to be calculated for the local
- * video or <tt>false</tt> if for remote videos.
- * @return {number|NaN} average FPS or <tt>NaN</tt> if there are no samples.
- * @private
- */
- _calculateAvgVideoFps(frameRate, isLocal) {
- let peerCount = 0;
- let subFrameAvg = 0;
- const myID = this._conference.myUserId();
-
- for (const peerID of Object.keys(frameRate)) {
- if (isLocal ? peerID === myID : peerID !== myID) {
- const videos = frameRate[peerID];
- const ssrcs = Object.keys(videos);
-
- if (ssrcs.length) {
- let peerAvg = 0;
-
- for (const ssrc of ssrcs) {
- peerAvg += parseInt(videos[ssrc], 10);
- }
-
- peerAvg /= ssrcs.length;
-
- subFrameAvg += peerAvg;
- peerCount += 1;
- }
- }
- }
-
- return subFrameAvg / peerCount;
- }
-
- /**
- * Reset cache of all averages and {@link _sampleIdx}.
- * @private
- */
- _resetAvgStats() {
- this._avgBitrateUp.reset();
- this._avgBitrateDown.reset();
- this._avgBandwidthUp.reset();
- this._avgBandwidthDown.reset();
- this._avgPacketLossUp.reset();
- this._avgPacketLossDown.reset();
- this._avgRemoteFPS.reset();
- this._avgLocalFPS.reset();
- this._avgCQ.reset();
- this._sampleIdx = 0;
- }
-
- /**
- * Unregisters all event listeners and stops working.
- */
- dispose() {
- if (this._onP2PStatusChanged) {
- this._conference.off(
- ConferenceEvents.P2P_STATUS,
- this._onP2PStatusChanged);
- }
- if (this._onLocalStatsUpdated) {
- this._conference.off(
- ConnectionQualityEvents.LOCAL_STATS_UPDATED,
- this._onLocalStatsUpdated);
- }
- }
- }
|