Sfoglia il codice sorgente

feat(rtcstats) early rtcstats initialization

dev0
Andrei Gavrilescu 8 mesi fa
parent
commit
ba5ac784ee
Nessun account collegato all'indirizzo email del committer

+ 4
- 0
JitsiConnection.js Vedi File

@@ -2,6 +2,7 @@ import { getLogger } from '@jitsi/logger';
2 2
 
3 3
 import JitsiConference from './JitsiConference';
4 4
 import * as JitsiConnectionEvents from './JitsiConnectionEvents';
5
+import RTCStats from './modules/RTCStats/RTCStats';
5 6
 import FeatureFlags from './modules/flags/FeatureFlags';
6 7
 import Statistics from './modules/statistics/statistics';
7 8
 import XMPP from './modules/xmpp/xmpp';
@@ -66,6 +67,9 @@ export default function JitsiConnection(appID, token, options) {
66 67
  * to be used.
67 68
  */
68 69
 JitsiConnection.prototype.connect = function(options = {}) {
70
+
71
+    RTCStats.startWithConnection(this);
72
+
69 73
     // if we get redirected, we set disableFocus to skip sending the conference request twice
70 74
     if (this.xmpp.moderator.targetUrl && !this.options.disableFocus && options.name) {
71 75
         this.xmpp.moderator.sendConferenceRequest(this.xmpp.getRoomJid(options.name))

+ 25
- 5
JitsiMeetJS.ts Vedi File

@@ -85,7 +85,8 @@ type desktopSharingSourceType = 'screen' | 'window';
85 85
 
86 86
 interface IJitsiMeetJSOptions {
87 87
     analytics?: {
88
-        rtcstatsStoreLogs: boolean;
88
+        rtcstatsLogFlushSizeBytes?: number;
89
+        rtcstatsStoreLogs?: boolean;
89 90
     };
90 91
     desktopSharingSources?: Array<desktopSharingSourceType>;
91 92
     enableAnalyticsLogging?: boolean;
@@ -151,6 +152,18 @@ export default {
151 152
     mediaDevices: JitsiMediaDevices as unknown,
152 153
     analytics: Statistics.analytics as unknown,
153 154
     init(options: IJitsiMeetJSOptions = {}) {
155
+        // Start capturing and sending logs to the rtcstats server as soon as possible.
156
+        const {
157
+            analytics: {
158
+                rtcstatsStoreLogs,
159
+                rtcstatsLogFlushSizeBytes
160
+            } = {}
161
+        } = options;
162
+
163
+        if (rtcstatsStoreLogs) {
164
+            Logger.addGlobalTransport(RTCStats.getDefaultLogCollector(rtcstatsLogFlushSizeBytes));
165
+        }
166
+
154 167
         // @ts-ignore
155 168
         logger.info(`This appears to be ${browser.getName()}, ver: ${browser.getVersion()}`);
156 169
 
@@ -169,10 +182,6 @@ export default {
169 182
             this.analytics.dispose();
170 183
         }
171 184
 
172
-        if (options.analytics?.rtcstatsStoreLogs) {
173
-            Logger.addGlobalTransport(RTCStats.getDefaultLogCollector());
174
-        }
175
-
176 185
         return RTC.init(options);
177 186
     },
178 187
 
@@ -205,6 +214,17 @@ export default {
205 214
      * Expose rtcstats to the public API.
206 215
      */
207 216
     rtcstats: {
217
+        /**
218
+         * Checks if the rtcstats trace is available.
219
+         * The trace is the abstraction for the underlying rtcstats websocket connection.
220
+         *
221
+         * @returns {boolean} <tt>true</tt> if the rtcstats trace is available or
222
+         * <tt>false</tt> otherwise.
223
+         */
224
+        isTraceAvailable() {
225
+            return RTCStats.isTraceAvailable();
226
+        },
227
+
208 228
         /**
209 229
          * Sends identity data to the rtcstats server. This data is used
210 230
          * to identify the specifics of a particular client, it can be any object

+ 1
- 1
modules/RTCStats/DefaulLogStorage.ts Vedi File

@@ -22,7 +22,7 @@ export default class DefaultLogStorage {
22 22
      * <tt>false</tt> otherwise.
23 23
      */
24 24
     isReady() {
25
-        return this.rtcStats._initialized;
25
+        return this.rtcStats.isTraceAvailable();
26 26
     }
27 27
 
28 28
     /**

+ 115
- 37
modules/RTCStats/RTCStats.ts Vedi File

@@ -3,6 +3,7 @@ import rtcstatsInit from '@jitsi/rtcstats/rtcstats';
3 3
 import traceInit from '@jitsi/rtcstats/trace-ws';
4 4
 
5 5
 import JitsiConference from '../../JitsiConference';
6
+import JitsiConnection from '../../JitsiConnection';
6 7
 import {
7 8
     BEFORE_STATISTICS_DISPOSED,
8 9
     CONFERENCE_CREATED_TIMESTAMP,
@@ -15,7 +16,7 @@ import EventEmitter from '../util/EventEmitter';
15 16
 
16 17
 import DefaultLogStorage from './DefaulLogStorage';
17 18
 import { RTC_STATS_PC_EVENT, RTC_STATS_WC_DISCONNECTED } from './RTCStatsEvents';
18
-import { IRTCStatsConfiguration } from './interfaces';
19
+import { IRTCStatsConfiguration, ITraceOptions } from './interfaces';
19 20
 
20 21
 const logger = getLogger('modules/RTCStats/RTCStats');
21 22
 
@@ -24,10 +25,11 @@ const logger = getLogger('modules/RTCStats/RTCStats');
24 25
  * ignored. Config and conference changes are handled by the start method.
25 26
  */
26 27
 class RTCStats {
27
-    private _initialized: boolean = false;
28
-    private _trace: any = null;
29 28
     public events: EventEmitter = new EventEmitter();
29
+    public _startedWithNewConnection: boolean = true;
30 30
     private _defaultLogCollector: any = null;
31
+    private _initialized: boolean = false;
32
+    private _trace: any = null;
31 33
 
32 34
     /**
33 35
      * RTCStats "proxies" WebRTC functions such as GUM and RTCPeerConnection by rewriting the global objects.
@@ -40,7 +42,6 @@ class RTCStats {
40 42
     init(initConfig: IRTCStatsConfiguration) {
41 43
         const {
42 44
             analytics: {
43
-                rtcstatsUseLegacy: useLegacy = false,
44 45
                 rtcstatsPollInterval: pollInterval = 10000,
45 46
                 rtcstatsSendSdp: sendSdp = false,
46 47
                 rtcstatsEnabled = false
@@ -57,7 +58,7 @@ class RTCStats {
57 58
         rtcstatsInit(
58 59
             { statsEntry: this.sendStatsEntry.bind(this) },
59 60
             { pollInterval,
60
-                useLegacy,
61
+                useLegacy: false,
61 62
                 sendSdp,
62 63
                 eventCallback: event => this.events.emit(RTC_STATS_PC_EVENT, event) }
63 64
         );
@@ -65,6 +66,69 @@ class RTCStats {
65 66
         this._initialized = true;
66 67
     }
67 68
 
69
+    isTraceAvailable() {
70
+        return this._trace !== null;
71
+    }
72
+
73
+    /**
74
+      * A JitsiConnection instance is created before the conference is joined, so even though
75
+     * we don't have any conference specific data yet, we can initialize the trace module and
76
+     * send any logs that might of otherwise be missed in case an error occurs between the connection
77
+     * and conference initialization.
78
+     *
79
+     * @param connection - The JitsiConnection instance.
80
+     * @returns {void}
81
+     */
82
+    startWithConnection(connection: JitsiConnection) {
83
+        const { options } = connection;
84
+        const name = options?.name ?? '';
85
+        const {
86
+            analytics: {
87
+                rtcstatsEndpoint: endpoint = '',
88
+                rtcstatsEnabled = false
89
+            } = {},
90
+        } = options;
91
+
92
+        // Even though we have options being passed to init we need to recheck it as some client (react-native)
93
+        // don't always re-initialize the module and could create multiple connections with different options.
94
+        if (!rtcstatsEnabled) return;
95
+
96
+        // If rtcstats proxy module is not initialized, do nothing (should never happen).
97
+        if (!this._initialized) {
98
+            logger.error('Calling startWithConnection before RTCStats proxy module is initialized.');
99
+
100
+            return;
101
+        }
102
+
103
+        const traceOptions: ITraceOptions = {
104
+            endpoint,
105
+            meetingFqn: name,
106
+            isBreakoutRoom: false
107
+        };
108
+
109
+        // Can't be a breakout room.
110
+        this._connectTrace(traceOptions);
111
+
112
+        this._defaultLogCollector?.flush();
113
+
114
+        this.sendIdentity({
115
+            confName: name,
116
+            ...options
117
+        });
118
+
119
+        // This module is tightly tied with the ljm JitsiConnection and JitsiConference flows, technically
120
+        // the connection isn't associated with a conference, but we still need to have some association for
121
+        // data that is logged before the conference is joined.
122
+        // In short the flow is as follows:
123
+        // 1. Connection is created.
124
+        // 2. The trace module is initialized and connected to the rtcstats server, so data starts being sent.
125
+        // 3. Conference is created.
126
+        // 4. If the trace wasn't already initialized from the connection creation, it will be initialized again.
127
+        // this will take care of the cases where the connection is created and then multiple conferences are
128
+        // sequentially joined and left, such as breakout rooms.
129
+        this._startedWithNewConnection = true;
130
+    }
131
+
68 132
     /**
69 133
      * When a conference is about to start, we need to reset the trace module, and initialize it with the
70 134
      * new conference's config. On a normal conference flow this wouldn't be necessary, as the whole page is
@@ -74,7 +138,7 @@ class RTCStats {
74 138
      * @param conference - JitsiConference instance that's about to start.
75 139
      * @returns {void}
76 140
      */
77
-    start(conference: JitsiConference) {
141
+    attachToConference(conference: JitsiConference) {
78 142
         const {
79 143
             options: {
80 144
                 config: confConfig = {},
@@ -86,8 +150,7 @@ class RTCStats {
86 150
         const {
87 151
             analytics: {
88 152
                 rtcstatsEnabled = false,
89
-                rtcstatsEndpoint: endpoint = '',
90
-                rtcstatsUseLegacy: useLegacy = false
153
+                rtcstatsEndpoint: endpoint = ''
91 154
             } = {}
92 155
         } = confConfig;
93 156
 
@@ -101,10 +164,6 @@ class RTCStats {
101 164
         // localId, this is the unique id that is used to track users throughout stats.
102 165
         const localId = Settings?.callStatsUserName ?? '';
103 166
 
104
-        // Reset the trace module in case it wasn't during the previous conference.
105
-        // Closing the underlying websocket connection and deleting the trace obj.
106
-        this.reset();
107
-
108 167
         // The new conference config might have rtcstats disabled, so we need to check again.
109 168
         if (!rtcstatsEnabled) {
110 169
             return;
@@ -112,34 +171,35 @@ class RTCStats {
112 171
 
113 172
         // If rtcstats proxy module is not initialized, do nothing.
114 173
         if (!this._initialized) {
115
-            logger.error('Calling start before RTCStats proxy module is initialized.');
174
+            logger.error('Calling attachToConference before RTCStats proxy module is initialized.');
116 175
 
117 176
             return;
118 177
         }
119 178
 
120
-        // Make an attempt to flush in case a lot of logs have been cached
121
-        this._defaultLogCollector?.flush();
122
-
123 179
         // When the conference is joined, we need to initialize the trace module with the new conference's config.
124 180
         // The trace module will then connect to the rtcstats server and send the identity data.
125 181
         conference.once(CONFERENCE_JOINED, () => {
126
-            const traceOptions = {
127
-                endpoint,
128
-                meetingFqn: confName,
129
-                onCloseCallback: event => this.events.emit(RTC_STATS_WC_DISCONNECTED, event),
130
-                useLegacy
131
-            };
132
-
133 182
             const isBreakoutRoom = Boolean(conference.getBreakoutRooms()?.isBreakoutRoom());
134 183
             const endpointId = conference.myUserId();
135 184
             const meetingUniqueId = conference.getMeetingUniqueId();
136 185
 
137
-            this._trace = traceInit(traceOptions);
138
-
139 186
             // Connect to the rtcstats server instance. Stats (data obtained from getstats) won't be send until the
140 187
             // connect successfully initializes, however calls to GUM are recorded in an internal buffer even if not
141 188
             // connected and sent once it is established.
142
-            this._trace.connect(isBreakoutRoom);
189
+            if (!this._startedWithNewConnection) {
190
+                const traceOptions = {
191
+                    endpoint,
192
+                    meetingFqn: confName,
193
+                    isBreakoutRoom
194
+                };
195
+
196
+                this._connectTrace(traceOptions);
197
+
198
+                // In cases where the conference was left but the connection was not closed,
199
+                // logs could get cached, so we flush them as soon as we get a chance after the
200
+                // conference is joined.
201
+                this._defaultLogCollector?.flush();
202
+            }
143 203
 
144 204
             const identityData = {
145 205
                 ...confConfig,
@@ -152,6 +212,8 @@ class RTCStats {
152 212
             };
153 213
 
154 214
             this.sendIdentity(identityData);
215
+            // Reset the flag, so that the next conference that is joined will have the trace module initialized, such as a breakout room.
216
+            this._startedWithNewConnection = false;
155 217
         });
156 218
 
157 219
         // Note, this will only be called for normal rooms, not breakout rooms.
@@ -173,6 +235,27 @@ class RTCStats {
173 235
         );
174 236
     }
175 237
 
238
+    /**
239
+     * Reset and connects the trace module to the s server.
240
+     *
241
+     * @param traceOptions - Options for the trace module.
242
+     * @returns {void}
243
+     */
244
+    _connectTrace(traceOptions: ITraceOptions) {
245
+
246
+        const traceOptionsComplete = {
247
+            ...traceOptions,
248
+            useLegacy: false,
249
+            onCloseCallback: event => this.events.emit(RTC_STATS_WC_DISCONNECTED, event)
250
+        };
251
+
252
+        const { isBreakoutRoom } = traceOptionsComplete;
253
+
254
+        this.reset();
255
+        this._trace = traceInit(traceOptionsComplete);
256
+        this._trace.connect(isBreakoutRoom);
257
+    }
258
+
176 259
     /**
177 260
      * Sends the identity data to the rtcstats server.
178 261
      *
@@ -192,7 +275,9 @@ class RTCStats {
192 275
      * @returns {void}
193 276
      */
194 277
     reset() {
195
-        this.clearDefaultLogCollector();
278
+        // If a trace is connected, flush the remaining logs before closing the connection,
279
+        // if the trace is not present and we flush the logs will be lost,
280
+        this._trace && this._defaultLogCollector?.flush();
196 281
         this._trace?.close();
197 282
         this._trace = null;
198 283
     }
@@ -211,22 +296,15 @@ class RTCStats {
211 296
     /**
212 297
      * Creates a new log collector with the default log storage.
213 298
      */
214
-    getDefaultLogCollector() {
299
+    getDefaultLogCollector(maxEntryLength) {
215 300
         if (!this._defaultLogCollector) {
216
-            this._defaultLogCollector = new Logger.LogCollector(new DefaultLogStorage(this));
301
+            // If undefined is passed  as maxEntryLength LogCollector will default to 10000 bytes
302
+            this._defaultLogCollector = new Logger.LogCollector(new DefaultLogStorage(this), { maxEntryLength });
217 303
             this._defaultLogCollector.start();
218 304
         }
219 305
 
220 306
         return this._defaultLogCollector;
221 307
     }
222
-
223
-    /**
224
-     * Clears the collector and stops it.
225
-     */
226
-    clearDefaultLogCollector() {
227
-        this._defaultLogCollector?.stop();
228
-        this._defaultLogCollector = null;
229
-    }
230 308
 }
231 309
 
232 310
 export default new RTCStats();

+ 6
- 1
modules/RTCStats/interfaces.ts Vedi File

@@ -6,6 +6,11 @@ export interface IRTCStatsConfiguration {
6 6
         rtcstatsPollInterval?: number;
7 7
         rtcstatsSendSdp?: boolean;
8 8
         rtcstatsStoreLogs?: boolean;
9
-        rtcstatsUseLegacy?: boolean;
10 9
     };
11 10
 }
11
+
12
+export interface ITraceOptions {
13
+    endpoint: string;
14
+    isBreakoutRoom: boolean;
15
+    meetingFqn: string;
16
+}

+ 1
- 1
modules/statistics/statistics.js Vedi File

@@ -69,7 +69,7 @@ export default function Statistics(conference, options) {
69 69
 
70 70
     Statistics.instances.add(this);
71 71
 
72
-    RTCStats.start(this.conference);
72
+    RTCStats.attachToConference(this.conference);
73 73
 
74 74
     // WatchRTC is not required to work for react native
75 75
     if (!browser.isReactNative()) {

Loading…
Annulla
Salva