Procházet zdrojové kódy

Analytics refactoring (#676)

* wip: initial version of the new AnalyticsAdapter.

* ref: Restructures the ICE duration and state change events.

* ref: Restructures the JitsiLocalTrack events.

* ref: Restructures the TTFM events.

* ref: Updates the user feedback event.

* ref: Restructures the _CONNECTION_TIMES_ and TTFM events.

* ref: Restructures the BRIDGE_DOWN and NO_DATA_FROM_SOURCE events.

* ref: Restructures the FOCUS_LEFT event.

* ref: Restructures the DATA_CHANNEL_OPEN event.

* ref: Removes the ICE_FAILED event (it is a duplicate of a state change event).

* ref: Restructures the device list events.

Uses one event per device, since the new format does not allow non-atomic attributes.

* fix: Does not obey "unmute" commands from the focus.

* ref: Restructures the "remotely muted" event.

* ref: Restructures the CONFERENCE_ERROR events.

* ref: Removes the CONNECTION_INTERRUPTED event

We can use ICE_STATE_CHANGED instead.

* ref: Renames isreconnect to isReconnect.

* ref: Removes the CONNECTION_RESTORED event. Use ICE state changes instead.

* ref: Restructures the p2p events.

* ref: Restructures the jingle events.

* ref: Restructures the RTP statistics event.

* ref: Restructures the CONNECTION_FAILED and DISCONNECTED events.

* ref: Restructures the getUserMedia analytics events.

* ref: Cleans up AnalyticsEvents and restructures some of the events.

* fix: Adds error logs to the analytics adapter.

* ref: Refactor Statistics.sendEventToAll

Renames to sendEventAndLog, supports the object-based API, uses the
function where appropriate.

* fix: Addresses PR feedback.

* fix: Addresses Lyubomir's feedback.

* ref: Remove unused functions, adds documentation.

* feat: Adds a Statistics.sendAnalytics shortcut.

* ref: Uses the conference name as the default containerId.

* fix: Adrdesses Lenny's feedback.

* fix: Addresses more feedback.

* fix: Uses 'operational' as the default event type.

* doc: Updates the documentation.

* fix: Fixes adding of permanent properties.

* ref: Uses consistent naming for events' attributes.

Uses "_" as a separator instead of camel case or ".".

* feat: Adds the conference name as a permanent property automatically.

* ref: Don't expose Setting.machineId.

* fix: Adds a "p2p" attribute to jingle events.

* ref: Uses "action" instead of "name".

* ref: Uses underscore in events' attribute names.

* ref: Logs a message to the logger/console

instead of callstats in sendAnalyticsAndLog().
master
bgrozev před 7 roky
rodič
revize
515374c8d3

+ 38
- 25
JitsiConference.js Zobrazit soubor

@@ -2,13 +2,15 @@
2 2
 import { Strophe } from 'strophe.js';
3 3
 
4 4
 import {
5
+    ACTION_JINGLE_RESTART,
6
+    ACTION_JINGLE_SI_RECEIVED,
7
+    ACTION_JINGLE_TERMINATE,
8
+    ACTION_P2P_ESTABLISHED,
9
+    ACTION_P2P_FAILED,
10
+    ACTION_P2P_SWITCH_TO_JVB,
5 11
     ICE_ESTABLISHMENT_DURATION_DIFF,
6
-    P2P_ESTABLISHED,
7
-    P2P_FAILED,
8
-    P2P_SWITCH_TO_JVB,
9
-    SESSION_INITIATE,
10
-    SESSION_RESTART,
11
-    SESSION_TERMINATE
12
+    createJingleEvent,
13
+    createP2PEvent
12 14
 } from './service/statistics/AnalyticsEvents';
13 15
 import AvgRTPStatsReporter from './modules/statistics/AvgRTPStatsReporter';
14 16
 import ComponentsVersions from './modules/version/ComponentsVersions';
@@ -1452,16 +1454,17 @@ JitsiConference.prototype._acceptJvbIncomingCall = function(
1452 1454
     this.jvbJingleSession = jingleSession;
1453 1455
     this.room.connectionTimes['session.initiate'] = now;
1454 1456
 
1455
-    // Log "session.restart"
1456 1457
     if (this.wasStopped) {
1457
-        Statistics.sendEventToAll(SESSION_RESTART);
1458
+        Statistics.sendAnalyticsAndLog(
1459
+            createJingleEvent(ACTION_JINGLE_RESTART, { p2p: false }));
1458 1460
     }
1459 1461
 
1460
-    Statistics.analytics.sendEvent(
1461
-        SESSION_INITIATE,
1462
+    Statistics.sendAnalytics(createJingleEvent(
1463
+        ACTION_JINGLE_SI_RECEIVED,
1462 1464
         {
1463
-            value: now - this.room.connectionTimes['muc.joined']
1464
-        });
1465
+            p2p: false,
1466
+            value: now
1467
+        }));
1465 1468
     try {
1466 1469
         jingleSession.initialize(false /* initiator */, this.room, this.rtc);
1467 1470
     } catch (error) {
@@ -1586,6 +1589,8 @@ JitsiConference.prototype._rejectIncomingCall = function(
1586 1589
 
1587 1590
 /**
1588 1591
  * Handles the call ended event.
1592
+ * XXX is this due to the remote side terminating the Jingle session?
1593
+ *
1589 1594
  * @param {JingleSessionPC} jingleSession the jingle session which has been
1590 1595
  * terminated.
1591 1596
  * @param {String} reasonCondition the Jingle reason condition.
@@ -1602,7 +1607,8 @@ JitsiConference.prototype.onCallEnded = function(
1602 1607
     if (jingleSession === this.jvbJingleSession) {
1603 1608
         this.wasStopped = true;
1604 1609
 
1605
-        Statistics.sendEventToAll(SESSION_TERMINATE);
1610
+        Statistics.sendAnalytics(
1611
+            createJingleEvent(ACTION_JINGLE_TERMINATE, { p2p: false }));
1606 1612
 
1607 1613
         // Stop the stats
1608 1614
         if (this.statistics) {
@@ -2087,9 +2093,14 @@ JitsiConference.prototype._onIceConnectionFailed = function(session) {
2087 2093
         // and "bad" connection
2088 2094
         Statistics.analytics.addPermanentProperties({ p2pFailed: true });
2089 2095
 
2090
-        // Log analytics event, but only for the initiator to not count it twice
2091
-        if (this.p2pJingleSession && this.p2pJingleSession.isInitiator) {
2092
-            Statistics.sendEventToAll(P2P_FAILED);
2096
+        if (this.p2pJingleSession) {
2097
+            Statistics.sendAnalyticsAndLog(
2098
+                createP2PEvent(
2099
+                    ACTION_P2P_FAILED,
2100
+                    {
2101
+                        initiator: this.p2pJingleSession.isInitiator
2102
+                    }));
2103
+
2093 2104
         }
2094 2105
         this._stopP2PSession('connectivity-error', 'ICE FAILED');
2095 2106
     }
@@ -2238,9 +2249,9 @@ JitsiConference.prototype._onIceConnectionEstablished = function(
2238 2249
         const establishmentDurationDiff
2239 2250
             = this.p2pEstablishmentDuration - this.jvbEstablishmentDuration;
2240 2251
 
2241
-        Statistics.analytics.sendEvent(
2252
+        Statistics.sendAnalytics(
2242 2253
             ICE_ESTABLISHMENT_DURATION_DIFF,
2243
-            { 'value': establishmentDurationDiff });
2254
+            { value: establishmentDurationDiff });
2244 2255
     }
2245 2256
 
2246 2257
     if (done) {
@@ -2258,7 +2269,6 @@ JitsiConference.prototype._onIceConnectionEstablished = function(
2258 2269
         logger.info('Not removing remote JVB tracks - no session yet');
2259 2270
     }
2260 2271
 
2261
-    // Add remote tracks
2262 2272
     this._addRemoteP2PTracks();
2263 2273
 
2264 2274
     // Stop media transfer over the JVB connection
@@ -2266,14 +2276,16 @@ JitsiConference.prototype._onIceConnectionEstablished = function(
2266 2276
         this._suspendMediaTransferForJvbConnection();
2267 2277
     }
2268 2278
 
2269
-    // Start remote stats
2270 2279
     logger.info('Starting remote stats with p2p connection');
2271 2280
     this.statistics.startRemoteStats(this.p2pJingleSession.peerconnection);
2272 2281
 
2273
-    // Log the P2P established event
2274
-    if (this.p2pJingleSession.isInitiator) {
2275
-        Statistics.sendEventToAll(P2P_ESTABLISHED);
2276
-    }
2282
+    Statistics.sendAnalyticsAndLog(
2283
+        createP2PEvent(
2284
+            ACTION_P2P_ESTABLISHED,
2285
+            {
2286
+                initiator: this.p2pJingleSession.isInitiator
2287
+            }));
2288
+
2277 2289
 };
2278 2290
 
2279 2291
 /**
@@ -2541,7 +2553,8 @@ JitsiConference.prototype._maybeStartOrStopP2P = function(userLeftEvent) {
2541 2553
 
2542 2554
         // Log that there will be a switch back to the JVB connection
2543 2555
         if (this.p2pJingleSession.isInitiator && peerCount > 1) {
2544
-            Statistics.sendEventToAll(P2P_SWITCH_TO_JVB);
2556
+            Statistics.sendAnalyticsAndLog(
2557
+                createP2PEvent(ACTION_P2P_SWITCH_TO_JVB));
2545 2558
         }
2546 2559
         this._stopP2PSession();
2547 2560
     }

+ 37
- 36
JitsiConferenceEventManager.js Zobrazit soubor

@@ -2,14 +2,12 @@
2 2
 import { Strophe } from 'strophe.js';
3 3
 
4 4
 import {
5
-    _CONNECTION_TIMES_,
6
-    BRIDGE_DOWN,
7
-    CONFERENCE_ERROR_,
8
-    CONNECTION_INTERRUPTED,
9
-    CONNECTION_RESTORED,
10
-    DATA_CHANNEL_OPEN,
11
-    FOCUS_LEFT,
12
-    REMOTELY_MUTED
5
+    ACTION_JINGLE_SA_TIMEOUT,
6
+    createBridgeDownEvent,
7
+    createConnectionStageReachedEvent,
8
+    createFocusLeftEvent,
9
+    createJingleEvent,
10
+    createRemotelyMutedEvent
13 11
 } from './service/statistics/AnalyticsEvents';
14 12
 import AuthenticationEvents
15 13
     from './service/authentication/AuthenticationEvents';
@@ -51,14 +49,6 @@ export default function JitsiConferenceEventManager(conference) {
51 49
                 track.isMuted(),
52 50
                 track.getType());
53 51
         });
54
-    conference.on(
55
-        JitsiConferenceEvents.CONNECTION_INTERRUPTED,
56
-        Statistics.sendEventToAll.bind(
57
-            Statistics, CONNECTION_INTERRUPTED));
58
-    conference.on(
59
-        JitsiConferenceEvents.CONNECTION_RESTORED,
60
-        Statistics.sendEventToAll.bind(
61
-            Statistics, CONNECTION_RESTORED));
62 52
 }
63 53
 
64 54
 /**
@@ -85,11 +75,14 @@ JitsiConferenceEventManager.prototype.setupChatRoomListeners = function() {
85 75
     });
86 76
 
87 77
     chatRoom.addListener(XMPPEvents.AUDIO_MUTED_BY_FOCUS,
88
-        value => {
89
-            Statistics.analytics.sendEvent(REMOTELY_MUTED);
78
+        () => {
79
+            // TODO: Add a way to differentiate between commands which caused
80
+            // us to mute and those that did not change our state (i.e. we were
81
+            // already muted).
82
+            Statistics.sendAnalytics(createRemotelyMutedEvent());
90 83
 
91 84
             // set isMutedByFocus when setAudioMute Promise ends
92
-            conference.rtc.setAudioMute(value).then(
85
+            conference.rtc.setAudioMute(true).then(
93 86
                 () => {
94 87
                     conference.isMutedByFocus = true;
95 88
                 },
@@ -110,17 +103,24 @@ JitsiConferenceEventManager.prototype.setupChatRoomListeners = function() {
110 103
         () => {
111 104
             this.conference.isJvbConnectionInterrupted = false;
112 105
 
106
+            // TODO: Move all of the 'connectionTimes' logic to its own module.
113 107
             Object.keys(chatRoom.connectionTimes).forEach(key => {
114
-                const value = chatRoom.connectionTimes[key];
115
-                const eventName = `conference.${_CONNECTION_TIMES_}${key}`;
108
+                const event
109
+                    = createConnectionStageReachedEvent(
110
+                        `conference_${key}`,
111
+                        { value: chatRoom.connectionTimes[key] });
116 112
 
117
-                Statistics.analytics.sendEvent(eventName, { value });
113
+                Statistics.sendAnalytics(event);
118 114
             });
115
+
116
+            // TODO: Move all of the 'connectionTimes' logic to its own module.
119 117
             Object.keys(chatRoom.xmpp.connectionTimes).forEach(key => {
120
-                const value = chatRoom.xmpp.connectionTimes[key];
121
-                const eventName = `xmpp.${_CONNECTION_TIMES_}${key}`;
118
+                const event
119
+                    = createConnectionStageReachedEvent(
120
+                        `xmpp_${key}`,
121
+                        { value: chatRoom.xmpp.connectionTimes[key] });
122 122
 
123
-                Statistics.analytics.sendEvent(eventName, { value });
123
+                Statistics.sendAnalytics(event);
124 124
             });
125 125
         });
126 126
 
@@ -152,7 +152,7 @@ JitsiConferenceEventManager.prototype.setupChatRoomListeners = function() {
152 152
         JitsiConferenceErrors.VIDEOBRIDGE_NOT_AVAILABLE);
153 153
     chatRoom.addListener(
154 154
         XMPPEvents.BRIDGE_DOWN,
155
-        () => Statistics.analytics.sendEvent(BRIDGE_DOWN));
155
+        () => Statistics.sendAnalytics(createBridgeDownEvent()));
156 156
 
157 157
     this.chatRoomForwarder.forward(XMPPEvents.RESERVATION_ERROR,
158 158
         JitsiConferenceEvents.CONFERENCE_FAILED,
@@ -190,20 +190,18 @@ JitsiConferenceEventManager.prototype.setupChatRoomListeners = function() {
190 190
 
191 191
     chatRoom.addListener(XMPPEvents.FOCUS_LEFT,
192 192
         () => {
193
-            Statistics.analytics.sendEvent(FOCUS_LEFT);
193
+            Statistics.sendAnalytics(createFocusLeftEvent());
194 194
             conference.eventEmitter.emit(
195 195
                 JitsiConferenceEvents.CONFERENCE_FAILED,
196 196
                 JitsiConferenceErrors.FOCUS_LEFT);
197 197
         });
198 198
 
199
-    const eventLogHandler
200
-        = reason => Statistics.sendEventToAll(`${CONFERENCE_ERROR_}.${reason}`);
201
-
202 199
     chatRoom.addListener(XMPPEvents.SESSION_ACCEPT_TIMEOUT,
203 200
         jingleSession => {
204
-            eventLogHandler(
205
-                jingleSession.isP2P
206
-                    ? 'p2pSessionAcceptTimeout' : 'sessionAcceptTimeout');
201
+            Statistics.sendAnalyticsAndLog(
202
+                createJingleEvent(
203
+                    ACTION_JINGLE_SA_TIMEOUT,
204
+                    { p2p: jingleSession.isP2P }));
207 205
         });
208 206
 
209 207
     this.chatRoomForwarder.forward(XMPPEvents.RECORDER_STATE_CHANGED,
@@ -474,10 +472,13 @@ JitsiConferenceEventManager.prototype.setupRTCListeners = function() {
474 472
 
475 473
     rtc.addListener(RTCEvents.DATA_CHANNEL_OPEN, () => {
476 474
         const now = window.performance.now();
475
+        const key = 'data.channel.opened';
477 476
 
478
-        logger.log('(TIME) data channel opened ', now);
479
-        conference.room.connectionTimes['data.channel.opened'] = now;
480
-        Statistics.analytics.sendEvent(DATA_CHANNEL_OPEN, { value: now });
477
+        // TODO: Move all of the 'connectionTimes' logic to its own module.
478
+        logger.log(`(TIME) ${key}`, now);
479
+        conference.room.connectionTimes[key] = now;
480
+        Statistics.sendAnalytics(
481
+            createConnectionStageReachedEvent(key, { value: now }));
481 482
 
482 483
         conference.eventEmitter.emit(JitsiConferenceEvents.DATA_CHANNEL_OPENED);
483 484
     });

+ 2
- 0
JitsiConferenceEvents.js Zobrazit soubor

@@ -48,11 +48,13 @@ export const CONFERENCE_LEFT = 'conference.left';
48 48
 /**
49 49
  * Indicates that the connection to the conference has been interrupted for some
50 50
  * reason.
51
+ * XXX This is currently fired when the *ICE* connection is interrupted.
51 52
  */
52 53
 export const CONNECTION_INTERRUPTED = 'conference.connectionInterrupted';
53 54
 
54 55
 /**
55 56
  * Indicates that the connection to the conference has been restored.
57
+ * XXX This is currently fired when the *ICE* connection is restored.
56 58
  */
57 59
 export const CONNECTION_RESTORED = 'conference.connectionRestored';
58 60
 

+ 14
- 9
JitsiConnection.js Zobrazit soubor

@@ -1,6 +1,6 @@
1 1
 import {
2
-    CONNECTION_DISCONNECTED_,
3
-    CONNECTION_FAILED_
2
+    CONNECTION_DISCONNECTED as ANALYTICS_CONNECTION_DISCONNECTED,
3
+    createConnectionFailedEvent
4 4
 } from './service/statistics/AnalyticsEvents';
5 5
 import JitsiConference from './JitsiConference';
6 6
 import * as JitsiConnectionEvents from './JitsiConnectionEvents';
@@ -25,9 +25,8 @@ export default function JitsiConnection(appID, token, options) {
25 25
 
26 26
     this.addEventListener(JitsiConnectionEvents.CONNECTION_FAILED,
27 27
         (errType, msg) => {
28
-            Statistics.sendEventToAll(
29
-                `${CONNECTION_FAILED_}.${errType}`,
30
-                { label: msg });
28
+            Statistics.sendAnalyticsAndLog(
29
+                createConnectionFailedEvent(errType, msg));
31 30
         });
32 31
 
33 32
     this.addEventListener(JitsiConnectionEvents.CONNECTION_DISCONNECTED,
@@ -35,13 +34,19 @@ export default function JitsiConnection(appID, token, options) {
35 34
             // we can see disconnects from normal tab closing of the browser
36 35
             // and then there are no msgs, but we want to log only disconnects
37 36
             // when there is real error
37
+            // XXX Do we need the difference in handling between the log and
38
+            // analytics event here?
38 39
             if (msg) {
39
-                Statistics.analytics.sendEvent(
40
-                    `${CONNECTION_DISCONNECTED_}.${msg}`);
40
+                Statistics.sendAnalytics(
41
+                    ANALYTICS_CONNECTION_DISCONNECTED,
42
+                    { message: msg });
41 43
             }
42 44
             Statistics.sendLog(
43
-                JSON.stringify({ id: 'connection.disconnected',
44
-                    msg }));
45
+                JSON.stringify(
46
+                    {
47
+                        id: ANALYTICS_CONNECTION_DISCONNECTED,
48
+                        msg
49
+                    }));
45 50
         });
46 51
 }
47 52
 

+ 55
- 56
JitsiMeetJS.js Zobrazit soubor

@@ -1,12 +1,6 @@
1 1
 /* global __filename */
2 2
 
3
-import {
4
-    GET_USER_MEDIA_DEVICE_NOT_FOUND_,
5
-    GET_USER_MEDIA_FAIL_,
6
-    GET_USER_MEDIA_FAILED_,
7
-    GET_USER_MEDIA_SUCCESS_,
8
-    GET_USER_MEDIA_USER_CANCEL_
9
-} from './service/statistics/AnalyticsEvents';
3
+import { createGetUserMediaEvent } from './service/statistics/AnalyticsEvents';
10 4
 import AuthUtil from './modules/util/AuthUtil';
11 5
 import * as ConnectionQualityEvents
12 6
     from './service/connectivity/ConnectionQualityEvents';
@@ -34,7 +28,6 @@ import RTC from './modules/RTC/RTC';
34 28
 import RTCBrowserType from './modules/RTC/RTCBrowserType';
35 29
 import RTCUIHelper from './modules/RTC/RTCUIHelper';
36 30
 import ScriptUtil from './modules/util/ScriptUtil';
37
-import Settings from './modules/settings/Settings';
38 31
 import Statistics from './modules/statistics/statistics';
39 32
 import * as VideoSIPGWConstants from './modules/videosipgw/VideoSIPGWConstants';
40 33
 
@@ -69,28 +62,28 @@ function getLowerResolution(resolution) {
69 62
 }
70 63
 
71 64
 /**
72
- * Checks the available devices in options and concatenate the data to the
73
- * name, which will be used as analytics event name. Adds resolution for the
74
- * devices.
75
- * @param name name of event
76
- * @param options gum options
77
- * @returns {*}
65
+ * Extracts from an 'options' objects with a specific format
66
+ * (TODO what IS the format?) the attributes which are to be logged in analytics
67
+ * events.
68
+ *
69
+ * @param options gum options (???)
70
+ * @returns {*} the attributes to attach to analytics events.
78 71
  */
79
-function addDeviceTypeToAnalyticsEvent(name, options) {
80
-    let ret = name;
81
-
82
-    if (options.devices.indexOf('audio') !== -1) {
83
-        ret += '.audio';
84
-    }
85
-    if (options.devices.indexOf('desktop') !== -1) {
86
-        ret += '.desktop';
87
-    }
88
-    if (options.devices.indexOf('video') !== -1) {
89
-        // we have video add resolution
90
-        ret += `.video.${options.resolution}`;
72
+function getAnalyticsAttributesFromOptions(options) {
73
+    const attributes = {
74
+        'audio_requested':
75
+            options.devices.includes('audio'),
76
+        'video_requested':
77
+            options.devices.includes('video'),
78
+        'screen_sharing_requested':
79
+            options.devices.includes('desktop')
80
+    };
81
+
82
+    if (attributes.video_requested) {
83
+        attributes.resolution = options.resolution;
91 84
     }
92 85
 
93
-    return ret;
86
+    return attributes;
94 87
 }
95 88
 
96 89
 /**
@@ -282,10 +275,10 @@ export default {
282 275
                 window.connectionTimes['obtainPermissions.end']
283 276
                     = window.performance.now();
284 277
 
285
-                Statistics.analytics.sendEvent(
286
-                    addDeviceTypeToAnalyticsEvent(
287
-                        GET_USER_MEDIA_SUCCESS_, options),
288
-                    { value: options });
278
+                Statistics.sendAnalytics(
279
+                    createGetUserMediaEvent(
280
+                        'success',
281
+                        getAnalyticsAttributesFromOptions(options)));
289 282
 
290 283
                 if (!RTC.options.disableAudioLevels) {
291 284
                     for (let i = 0; i < tracks.length; i++) {
@@ -334,16 +327,20 @@ export default {
334 327
                             'Retry createLocalTracks with resolution',
335 328
                             newResolution);
336 329
 
337
-                        Statistics.analytics.sendEvent(
338
-                            `${GET_USER_MEDIA_FAIL_}.resolution.${
339
-                                oldResolution}`);
330
+                        Statistics.sendAnalytics(createGetUserMediaEvent(
331
+                            'warning',
332
+                            {
333
+                                'old_resolution': oldResolution,
334
+                                'new_resolution': newResolution,
335
+                                reason: 'unsupported resolution'
336
+                            }));
340 337
 
341 338
                         return this.createLocalTracks(options);
342 339
                     }
343 340
                 }
344 341
 
345
-                if (JitsiTrackErrors.CHROME_EXTENSION_USER_CANCELED
346
-                        === error.name) {
342
+                if (error.name
343
+                        === JitsiTrackErrors.CHROME_EXTENSION_USER_CANCELED) {
347 344
                     // User cancelled action is not really an error, so only
348 345
                     // log it as an event to avoid having conference classified
349 346
                     // as partially failed
@@ -353,9 +350,14 @@ export default {
353 350
                     };
354 351
 
355 352
                     Statistics.sendLog(JSON.stringify(logObject));
356
-                    Statistics.analytics.sendEvent(
357
-                        `${GET_USER_MEDIA_USER_CANCEL_}.extensionInstall`);
358
-                } else if (JitsiTrackErrors.NOT_FOUND === error.name) {
353
+
354
+                    Statistics.sendAnalytics(
355
+                        createGetUserMediaEvent(
356
+                            'warning',
357
+                            {
358
+                                reason: 'extension install user canceled'
359
+                            }));
360
+                } else if (error.name === JitsiTrackErrors.NOT_FOUND) {
359 361
                     // logs not found devices with just application log to cs
360 362
                     const logObject = {
361 363
                         id: 'usermedia_missing_device',
@@ -363,19 +365,24 @@ export default {
363 365
                     };
364 366
 
365 367
                     Statistics.sendLog(JSON.stringify(logObject));
366
-                    Statistics.analytics.sendEvent(
367
-                        `${GET_USER_MEDIA_DEVICE_NOT_FOUND_}.${
368
-                            error.gum.devices.join('.')}`);
368
+
369
+                    const attributes
370
+                        = getAnalyticsAttributesFromOptions(options);
371
+
372
+                    attributes.reason = 'device not found';
373
+                    attributes.devices = error.gum.devices.join('.');
374
+                    Statistics.sendAnalytics(
375
+                        createGetUserMediaEvent('error', attributes));
369 376
                 } else {
370 377
                     // Report gUM failed to the stats
371 378
                     Statistics.sendGetUserMediaFailed(error);
372
-                    const eventName
373
-                        = addDeviceTypeToAnalyticsEvent(
374
-                            GET_USER_MEDIA_FAILED_, options);
375 379
 
376
-                    Statistics.analytics.sendEvent(
377
-                        `${eventName}.${error.name}`,
378
-                        { value: options });
380
+                    const attributes
381
+                        = getAnalyticsAttributesFromOptions(options);
382
+
383
+                    attributes.reason = error.name;
384
+                    Statistics.sendAnalytics(
385
+                        createGetUserMediaEvent('error', attributes));
379 386
                 }
380 387
 
381 388
                 window.connectionTimes['obtainPermissions.end']
@@ -466,14 +473,6 @@ export default {
466 473
 
467 474
     /* eslint-enable max-params */
468 475
 
469
-    /**
470
-     * Returns current machine id saved from the local storage.
471
-     * @returns {string} the machine id
472
-     */
473
-    getMachineId() {
474
-        return Settings.machineId;
475
-    },
476
-
477 476
     /**
478 477
      * Represents a hub/namespace for utility functionality which may be of
479 478
      * interest to lib-jitsi-meet clients.

+ 2
- 2
JitsiTrackEvents.js Zobrazit soubor

@@ -33,7 +33,7 @@ export const TRACK_MUTE_CHANGED = 'track.trackMuteChanged';
33 33
 export const TRACK_VIDEOTYPE_CHANGED = 'track.videoTypeChanged';
34 34
 
35 35
 /**
36
- * Indicates that the track is no receiving any data without reason(the
37
- * stream was stopped, etc)
36
+ * Indicates that the track is not receiving any data even though we expect it
37
+ * to receive data (i.e. the stream is not stopped).
38 38
  */
39 39
 export const NO_DATA_FROM_SOURCE = 'track.no_data_from_source';

+ 14
- 10
modules/RTC/JitsiLocalTrack.js Zobrazit soubor

@@ -19,8 +19,8 @@ import * as MediaType from '../../service/RTC/MediaType';
19 19
 import RTCEvents from '../../service/RTC/RTCEvents';
20 20
 import VideoType from '../../service/RTC/VideoType';
21 21
 import {
22
-    _NO_DATA_FROM_SOURCE,
23
-    _TRACK_UNMUTE
22
+    TRACK_UNMUTED,
23
+    createNoDataFromSourceEvent
24 24
 } from '../../service/statistics/AnalyticsEvents';
25 25
 import Statistics from '../statistics/statistics';
26 26
 
@@ -32,7 +32,7 @@ const logger = getLogger(__filename);
32 32
  */
33 33
 export default class JitsiLocalTrack extends JitsiTrack {
34 34
     /**
35
-     * Constructs new JitsiLocalTrack instanse.
35
+     * Constructs new JitsiLocalTrack instance.
36 36
      *
37 37
      * @constructor
38 38
      * @param {Object} trackInfo
@@ -202,9 +202,13 @@ export default class JitsiLocalTrack extends JitsiTrack {
202 202
                         = setTimeout(_onNoDataFromSourceError, 3000);
203 203
                     this._setHandler('track_unmute', () => {
204 204
                         this._clearNoDataFromSourceMuteResources();
205
-                        Statistics.sendEventToAll(
206
-                            `${this.getType()}.${_TRACK_UNMUTE}`,
207
-                            { value: window.performance.now() - now });
205
+                        Statistics.sendAnalyticsAndLog(
206
+                            TRACK_UNMUTED,
207
+                            {
208
+                                'media_type': this.getType(),
209
+                                'track_type': 'local',
210
+                                value: window.performance.now() - now
211
+                            });
208 212
                     });
209 213
                 }
210 214
             });
@@ -241,10 +245,9 @@ export default class JitsiLocalTrack extends JitsiTrack {
241 245
      */
242 246
     _fireNoDataFromSourceEvent() {
243 247
         this.emit(NO_DATA_FROM_SOURCE);
244
-        const eventName = `${this.getType()}.${_NO_DATA_FROM_SOURCE}`;
245 248
 
246
-        Statistics.analytics.sendEvent(eventName);
247
-        const log = { name: eventName };
249
+        Statistics.sendAnalytics(createNoDataFromSourceEvent(this.getType()));
250
+        const log = { name: NO_DATA_FROM_SOURCE };
248 251
 
249 252
         if (this.isAudioTrack()) {
250 253
             log.isReceivingData = this._isReceivingData();
@@ -712,7 +715,8 @@ export default class JitsiLocalTrack extends JitsiTrack {
712 715
     }
713 716
 
714 717
     /**
715
-     * Detects camera issues on ended and mute events from MediaStreamTrack.
718
+     * Detects camera issues, i.e. returns true if we expect this track to be
719
+     * receiving data from its source, but it isn't receiving data.
716 720
      *
717 721
      * @returns {boolean} true if an issue is detected and false otherwise
718 722
      */

+ 11
- 7
modules/RTC/JitsiRemoteTrack.js Zobrazit soubor

@@ -1,4 +1,4 @@
1
-import { _TTFM_ } from '../../service/statistics/AnalyticsEvents';
1
+import { createTtfmEvent } from '../../service/statistics/AnalyticsEvents';
2 2
 import JitsiTrack from './JitsiTrack';
3 3
 import * as JitsiTrackEvents from '../../JitsiTrackEvents';
4 4
 import RTCBrowserType from './RTCBrowserType';
@@ -223,19 +223,23 @@ export default class JitsiRemoteTrack extends JitsiTrack {
223 223
         const gumDuration
224 224
             = !isNaN(gumEnd) && !isNaN(gumStart) ? gumEnd - gumStart : 0;
225 225
 
226
+        // Subtract the muc.joined-to-session-initiate duration because jicofo
227
+        // waits until there are 2 participants to start Jingle sessions.
226 228
         const ttfm = now
227 229
             - (this.conference.getConnectionTimes()['session.initiate']
228
-            - this.conference.getConnectionTimes()['muc.joined'])
230
+                - this.conference.getConnectionTimes()['muc.joined'])
229 231
             - gumDuration;
230 232
 
231 233
         this.conference.getConnectionTimes()[`${type}.ttfm`] = ttfm;
232 234
         console.log(`(TIME) TTFM ${type}:\t`, ttfm);
233
-        let eventName = `${type}.${_TTFM_}`;
234 235
 
235
-        if (this.hasBeenMuted) {
236
-            eventName += '.muted';
237
-        }
238
-        Statistics.analytics.sendEvent(eventName, { value: ttfm });
236
+        Statistics.sendAnalytics(createTtfmEvent(
237
+            {
238
+                'media_type': type,
239
+                muted: this.hasBeenMuted,
240
+                value: ttfm
241
+            }));
242
+
239 243
     }
240 244
 
241 245
     /**

+ 23
- 13
modules/RTC/RTCUtils.js Zobrazit soubor

@@ -9,7 +9,7 @@
9 9
           webkitRTCPeerConnection,
10 10
           webkitURL
11 11
 */
12
-import { DEVICE_LIST } from '../../service/statistics/AnalyticsEvents';
12
+import { AVAILABLE_DEVICE } from '../../service/statistics/AnalyticsEvents';
13 13
 import CameraFacingMode from '../../service/RTC/CameraFacingMode';
14 14
 import EventEmitter from 'events';
15 15
 import { getLogger } from 'jitsi-meet-logger';
@@ -547,19 +547,29 @@ function pollForAvailableMediaDevices() {
547 547
  * @returns {void}
548 548
  */
549 549
 function sendDeviceListToAnalytics(deviceList) {
550
-    const devicesPropsArray
551
-        = deviceList.map(
552
-            ({ deviceId, groupId, kind, label }) => {
553
-                // Filter the props of the device object.
554
-                return {
555
-                    deviceId,
556
-                    groupId,
557
-                    kind,
558
-                    label
559
-                };
560
-            });
550
+    const audioInputDeviceCount
551
+        = deviceList.filter(d => d.kind === 'audioinput').length;
552
+    const audioOutputDeviceCount
553
+        = deviceList.filter(d => d.kind === 'audiooutput').length;
554
+    const videoInputDeviceCount
555
+        = deviceList.filter(d => d.kind === 'videoinput').length;
556
+    const videoOutputDeviceCount
557
+        = deviceList.filter(d => d.kind === 'videooutput').length;
558
+
559
+    deviceList.forEach(device => {
560
+        const attributes = {
561
+            'audio_input_device_count': audioInputDeviceCount,
562
+            'audio_output_device_count': audioOutputDeviceCount,
563
+            'video_input_device_count': videoInputDeviceCount,
564
+            'video_output_device_count': videoOutputDeviceCount,
565
+            'device_id': device.deviceId,
566
+            'device_group_id': device.groupId,
567
+            'device_kind': device.kind,
568
+            'device_label': device.label
569
+        };
561 570
 
562
-    Statistics.analytics.sendEvent(DEVICE_LIST, { devices: devicesPropsArray });
571
+        Statistics.sendAnalytics(AVAILABLE_DEVICE, attributes);
572
+    });
563 573
 }
564 574
 
565 575
 /**

+ 278
- 81
modules/statistics/AnalyticsAdapter.js Zobrazit soubor

@@ -1,130 +1,327 @@
1
+import {
2
+    TYPE_OPERATIONAL,
3
+    TYPE_PAGE,
4
+    TYPE_TRACK,
5
+    TYPE_UI
6
+} from '../../service/statistics/AnalyticsEvents';
7
+import { getLogger } from 'jitsi-meet-logger';
1 8
 import RTCBrowserType from '../RTC/RTCBrowserType';
2 9
 import Settings from '../settings/Settings';
3 10
 
4
-/**
5
- * Interface for analytics handlers.
6
- */
7
-class AnalyticsAbstract {
8
-    /**
9
-     *
10
-     */
11
-    sendEvent() {} // eslint-disable-line no-empty-function
12
-}
11
+const MAX_CACHE_SIZE = 100;
12
+
13
+// eslist-disable-line no-undef
14
+const logger = getLogger(__filename);
13 15
 
14 16
 /**
15
- * Handler that caches all the events.
16
- * @extends AnalyticsAbstract
17
+ * This class provides an API to lib-jitsi-meet and its users for sending
18
+ * analytics events. It serves as a bridge to different backend implementations
19
+ * ("analytics handlers") and a cache for events attempted to be sent before
20
+ * the analytics handlers were enabled.
21
+ *
22
+ * The API is designed to be an easy replacement for the previous version of
23
+ * this adapter, and is meant to be extended with more convenience methods.
24
+ *
25
+ *
26
+ * The API calls are translated to objects with the following structure, which
27
+ * are then passed to the sendEvent(event) function of the underlying handlers:
28
+ *
29
+ * {
30
+ *    type,
31
+ *
32
+ *    action,
33
+ *    actionSubject,
34
+ *    actionSubjectId,
35
+ *    attributes,
36
+ *    categories,
37
+ *    containerId,
38
+ *    containerType,
39
+ *    name,
40
+ *    objectId,
41
+ *    objectType,
42
+ *    source,
43
+ *    tags
44
+ * }
45
+ *
46
+ * The 'type' is one of 'operational', 'page', 'track' or 'ui', and some of the
47
+ * other properties are considered required according to the type.
48
+ *
49
+ * For events with type 'page', the required properties are: name.
50
+ *
51
+ * For events with type 'operational' and 'ui', the required properties are:
52
+ * action, actionSubject, source
53
+ *
54
+ * For events with type 'page', the required properties are:
55
+ * action, actionSubject, source, containerType, containerId, objectType,
56
+ * objectId
17 57
  */
18
-class CacheAnalytics extends AnalyticsAbstract {
58
+class AnalyticsAdapter {
19 59
     /**
20
-     *
60
+     * Creates new AnalyticsAdapter instance.
21 61
      */
22 62
     constructor() {
23
-        super();
63
+        /**
64
+         * Whether this AnalyticsAdapter has been disposed of or not. Once this
65
+         * is set to true, the AnalyticsAdapter is disabled and does not accept
66
+         * any more events, and it can not be re-enabled.
67
+         * @type {boolean}
68
+         */
69
+        this.disposed = false;
70
+
71
+        /**
72
+         * The set of handlers to which events will be sent.
73
+         * @type {Set<any>}
74
+         */
75
+        this.analyticsHandlers = new Set();
76
+
77
+        /**
78
+         * The cache of events which are not sent yet. The cache is enabled
79
+         * while this field is truthy, and disabled otherwise.
80
+         * @type {Array}
81
+         */
82
+        this.cache = [];
83
+
84
+        /**
85
+         * Map of properties that will be added to every event. Note that the
86
+         * keys will be prefixed with "permanent.".
87
+         */
88
+        this.permanentProperties = {};
89
+
90
+        /**
91
+         * The name of the conference that this AnalyticsAdapter is associated
92
+         * with.
93
+         * @type {null}
94
+         */
95
+        this.conferenceName = '';
24 96
 
25
-        // some events may happen before init or implementation script download
26
-        // in this case we accumulate them in this array and send them on init
27
-        this.eventCache = [];
97
+        this.addPermanentProperties({
98
+            'callstats_name': Settings.callStatsUserName,
99
+            'user_agent': navigator.userAgent,
100
+            'browser_name': RTCBrowserType.getBrowserName()
101
+        });
28 102
     }
29 103
 
30 104
     /**
31
-     * Cache analytics event.
32
-     * @param {String} eventName the name of the event
33
-     * @param {Object} data can be any JSON object
105
+     * Dispose analytics. Clears all handlers.
34 106
      */
35
-    sendEvent(eventName, data = {}) {
36
-        this.eventCache.push({
37
-            eventName,
38
-            data
39
-        });
107
+    dispose() {
108
+        this.setAnalyticsHandlers([]);
109
+        this.disposed = true;
40 110
     }
41 111
 
42 112
     /**
43
-     * Clears the cached events.
44
-     * @returns {Array} with the cached events.
113
+     * Sets the handlers that are going to be used to send analytics. Sends any
114
+     * cached events.
115
+     * @param {Array} handlers the handlers
45 116
      */
46
-    drainCachedEvents() {
47
-        const eventCacheCopy = this.eventCache.slice();
117
+    setAnalyticsHandlers(handlers) {
118
+        if (this.disposed) {
119
+            return;
120
+        }
48 121
 
49
-        this.eventCache = [];
122
+        this.analyticsHandlers = new Set(handlers);
123
+
124
+        // Note that we disable the cache even if the set of handlers is empty.
125
+        const cache = this.cache;
50 126
 
51
-        return eventCacheCopy;
127
+        this.cache = null;
128
+        if (cache) {
129
+            cache.forEach(event => this._sendEvent(event));
130
+        }
52 131
     }
53 132
 
54
-}
133
+    /**
134
+     * Adds a set of permanent properties to this this AnalyticsAdapter.
135
+     * Permanent properties will be added as "attributes" to events sent to
136
+     * the underlying "analytics handlers", and their keys will be prefixed
137
+     * by "permanent_", i.e. adding a permanent property {key: "value"} will
138
+     * result in {"permanent_key": "value"} object to be added to the
139
+     * "attributes" field of events.
140
+     *
141
+     * @param {Object} properties the properties to add
142
+     */
143
+    addPermanentProperties(properties) {
144
+        for (const property in properties) {
145
+            if (properties.hasOwnProperty(property)) {
146
+                this.permanentProperties[`permanent_${property}`]
147
+                    = properties[property];
148
+            }
149
+        }
150
+    }
55 151
 
56
-const cacheAnalytics = new CacheAnalytics();
152
+    /**
153
+     * Sets the name of the conference that this AnalyticsAdapter is associated
154
+     * with.
155
+     * @param name the name to set.
156
+     */
157
+    setConferenceName(name) {
158
+        this.conferenceName = name;
159
+        this.addPermanentProperties({ 'conference_name': name });
160
+    }
57 161
 
58
-/**
59
- * This class will store and manage the handlers that are going to be used.
60
- */
61
-class AnalyticsAdapter {
62 162
     /**
63
-     * Creates new AnalyticsAdapter instance.
163
+     * Sends an event with a given name and given properties. The first
164
+     * parameter is either a string or an object. If it is a string, it is used
165
+     * as the event name and the second parameter is used at the attributes to
166
+     * attach to the event. If it is an object, it represents the whole event,
167
+     * including any desired attributes, and the second parameter is ignored.
168
+     *
169
+     * @param {String|Object} eventName either a string to be used as the name
170
+     * of the event, or an event object. If an event object is passed, the
171
+     * properties parameters is ignored.
172
+     * @param {Object} properties the properties/attributes to attach to the
173
+     * event, if eventName is a string.
64 174
      */
65
-    constructor() {
66
-        this.disposed = false;
67
-        this.analyticsHandlers = new Set();
175
+    sendEvent(eventName, properties = {}) {
176
+        let event = null;
68 177
 
69
-        /**
70
-         * Map of properties that will be added to every event
71
-         */
72
-        this.permanentProperties = {
73
-            callstatsname: Settings.callStatsUserName,
74
-            userAgent: navigator.userAgent,
75
-            browserName: RTCBrowserType.getBrowserName()
76
-        };
178
+        if (typeof eventName === 'string') {
179
+            event = {
180
+                type: TYPE_OPERATIONAL,
181
+                action: eventName,
182
+                actionSubject: eventName,
183
+                source: eventName,
184
+                attributes: properties
185
+            };
186
+        } else if (typeof eventName === 'object') {
187
+            event = eventName;
188
+        }
189
+
190
+        if (!this._verifyRequiredFields(event)) {
191
+            logger.error(
192
+                `Dropping a mis-formatted event: ${JSON.stringify(event)}`);
193
+
194
+            return;
195
+        }
77 196
 
78
-        this.analyticsHandlers.add(cacheAnalytics);
197
+        this._sendEvent(event);
79 198
     }
80 199
 
81 200
     /**
82
-     * Sends analytics event.
83
-     * @param {String} eventName the name of the event
84
-     * @param {Object} data can be any JSON object
201
+     * Checks whether an event has all of the required fields set, and tries
202
+     * to fill in some of the missing fields with reasonable default values.
203
+     * Returns true if after this operation the event has all of the required
204
+     * fields set, and false otherwise (if some of the required fields were not
205
+     * set and the attempt to fill them in with a default failed).
206
+     *
207
+     * @param event the event object.
208
+     * @return {boolean} true if the event (after the call to this function)
209
+     * contains all of the required fields, and false otherwise.
210
+     * @private
85 211
      */
86
-    sendEvent(eventName, data = {}) {
87
-        const modifiedData = Object.assign({}, this.permanentProperties, data);
88
-
89
-        this.analyticsHandlers.forEach(
90
-            analytics =>
91
-                analytics.sendEvent(
92
-                    eventName,
93
-                    analytics === cacheAnalytics ? data : modifiedData
94
-                )
95
-        );
212
+    _verifyRequiredFields(event) {
213
+        if (!event) {
214
+            return false;
215
+        }
216
+
217
+        if (!event.type) {
218
+            event.type = TYPE_OPERATIONAL;
219
+        }
220
+
221
+        const type = event.type;
222
+
223
+        if (type !== TYPE_OPERATIONAL && type !== TYPE_PAGE
224
+            && type !== TYPE_UI && type !== TYPE_TRACK) {
225
+            logger.error(`Unknown event type: ${type}`);
226
+
227
+            return false;
228
+        }
229
+
230
+        if (type === TYPE_PAGE) {
231
+            return Boolean(event.name);
232
+        }
233
+
234
+        // Try to set some reasonable default values in case some of the
235
+        // parameters required by the handler API are missing.
236
+        event.action = event.action || event.name || event.actionSubject;
237
+        event.actionSubject = event.actionSubject || event.name || event.action;
238
+        event.source = event.source || event.name || event.action
239
+            || event.actionSubject;
240
+
241
+        if (!event.action || !event.actionSubject || !event.source) {
242
+            logger.error(
243
+                'Required field missing (action, actionSubject or source)');
244
+
245
+            return false;
246
+        }
247
+
248
+        // Track events have additional required fields.
249
+        if (type === TYPE_TRACK) {
250
+            event.objectType = event.objectType || 'generic-object-type';
251
+            event.containerType = event.containerType || 'conference';
252
+            if (event.containerType === 'conference' && !event.containerId) {
253
+                event.containerId = this.conferenceName;
254
+            }
255
+
256
+
257
+            if (!event.objectType || !event.objectId
258
+                || !event.containerType || !event.containerId) {
259
+                logger.error(
260
+                    'Required field missing (containerId, containerType, '
261
+                        + 'objectId or objectType)');
262
+
263
+                return false;
264
+            }
265
+        }
266
+
267
+        return true;
96 268
     }
97 269
 
98 270
     /**
99
-     * Dispose analytics. Clears all handlers.
271
+     * Saves an event to the cache, if the cache is enabled.
272
+     * @param event the event to save.
273
+     * @returns {boolean} true if the event was saved, and false otherwise (i.e.
274
+     * if the cache was disabled).
275
+     * @private
100 276
      */
101
-    dispose() {
102
-        cacheAnalytics.drainCachedEvents();
103
-        this.analyticsHandlers.clear();
104
-        this.disposed = true;
277
+    _maybeCacheEvent(event) {
278
+        if (this.cache) {
279
+            this.cache.push(event);
280
+
281
+            // We limit the size of the cache, in case the user fails to ever
282
+            // set the analytics handlers.
283
+            if (this.cache.length > MAX_CACHE_SIZE) {
284
+                this.cache.splice(0, 1);
285
+            }
286
+
287
+            return true;
288
+        }
289
+
290
+        return false;
291
+
105 292
     }
106 293
 
107 294
     /**
108
-     * Sets the handlers that are going to be used to send analytics and send
109
-     * the cached events.
110
-     * @param {Array} handlers the handlers
295
+     *
296
+     * @param event
297
+     * @private
111 298
      */
112
-    setAnalyticsHandlers(handlers) {
113
-        if (this.disposed) {
114
-            return;
299
+    _sendEvent(event) {
300
+        if (this._maybeCacheEvent(event)) {
301
+            // The event was consumed by the cache.
302
+        } else {
303
+            // We append the permanent properties at the time we send the event,
304
+            // not at the time we receive it.
305
+            this._appendPermanentProperties(event);
306
+
307
+            this.analyticsHandlers.forEach(handler => handler.sendEvent(event));
115 308
         }
116
-        this.analyticsHandlers = new Set(handlers);
117
-        cacheAnalytics.drainCachedEvents().forEach(
118
-            ev => this.sendEvent(ev.eventName, ev.data));
119 309
     }
120 310
 
121 311
     /**
122
-     * Adds map of properties that will be added to every event.
123
-     * @param {Object} properties the map of properties
312
+     * Extends an event object with the configured permanent properties.
313
+     * @param event the event to extend with permanent properties.
314
+     * @private
124 315
      */
125
-    addPermanentProperties(properties) {
126
-        Object.assign(this.permanentProperties, properties);
316
+    _appendPermanentProperties(event) {
317
+        if (!event.attributes) {
318
+            event.attributes = {};
319
+        }
320
+
321
+        event.attributes
322
+            = Object.assign(event.attributes, this.permanentProperties);
127 323
     }
324
+
128 325
 }
129 326
 
130 327
 export default new AnalyticsAdapter();

+ 46
- 43
modules/statistics/AvgRTPStatsReporter.js Zobrazit soubor

@@ -1,6 +1,6 @@
1 1
 /* global __filename */
2 2
 
3
-import { AVG_RTP_STATS } from '../../service/statistics/AnalyticsEvents';
3
+import { createRtpStatsEvent } from '../../service/statistics/AnalyticsEvents';
4 4
 import { getLogger } from 'jitsi-meet-logger';
5 5
 import * as ConnectionQualityEvents
6 6
     from '../../service/connectivity/ConnectionQualityEvents';
@@ -61,10 +61,8 @@ class AverageStatReport {
61 61
      * @param {Object} report the analytics "data" object
62 62
      */
63 63
     appendReport(report) {
64
-        report[this.name] = {
65
-            value: this.calculate(),
66
-            samples: this.samples
67
-        };
64
+        report[`${this.name}_avg`] = this.calculate();
65
+        report[`${this.name}_samples`] = JSON.stringify(this.samples);
68 66
     }
69 67
 
70 68
     /**
@@ -117,7 +115,7 @@ class ConnectionAvgStats {
117 115
          * Average round trip time reported by the ICE candidate pair.
118 116
          * @type {AverageStatReport}
119 117
          */
120
-        this._avgRTT = new AverageStatReport('stat_avg_rtt');
118
+        this._avgRTT = new AverageStatReport('rtt');
121 119
 
122 120
         /**
123 121
          * Map stores average RTT to the JVB reported by remote participants.
@@ -197,16 +195,16 @@ class ConnectionAvgStats {
197 195
 
198 196
                 const batchReport = {
199 197
                     p2p: this.isP2P,
200
-                    size: conference.getParticipantCount()
198
+                    'conference_size': conference.getParticipantCount()
201 199
                 };
202 200
 
203 201
                 if (data.transport && data.transport.length) {
204 202
                     Object.assign(batchReport, {
205
-                        localCandidateType:
203
+                        'local_candidate_type':
206 204
                             data.transport[0].localCandidateType,
207
-                        remoteCandidateType:
205
+                        'remote_candidate_type':
208 206
                             data.transport[0].remoteCandidateType,
209
-                        transportType: data.transport[0].type
207
+                        'transport_type': data.transport[0].type
210 208
                     });
211 209
                 }
212 210
 
@@ -218,13 +216,9 @@ class ConnectionAvgStats {
218 216
                         ._avgRtpStatsReporter.jvbStatsMonitor._avgEnd2EndRTT;
219 217
 
220 218
                     if (!isNaN(jvbEnd2EndRTT)) {
221
-                        const avgRTTDiff
222
-                            = this._avgRTT.calculate() - jvbEnd2EndRTT;
223
-
224 219
                         // eslint-disable-next-line dot-notation
225
-                        batchReport['stat_avg_rtt_diff'] = {
226
-                            value: avgRTTDiff
227
-                        };
220
+                        batchReport['rtt_diff']
221
+                            = this._avgRTT.calculate() - jvbEnd2EndRTT;
228 222
                     }
229 223
                 } else {
230 224
                     // Report end to end RTT only for JVB.
@@ -235,13 +229,11 @@ class ConnectionAvgStats {
235 229
 
236 230
                     if (!isNaN(avgLocalRTT) && !isNaN(avgRemoteRTT)) {
237 231
                         // eslint-disable-next-line dot-notation
238
-                        batchReport['stat_avg_end2endrtt'] = {
239
-                            value: this._avgEnd2EndRTT
240
-                        };
232
+                        batchReport['end2end_rtt_avg'] = this._avgEnd2EndRTT;
241 233
                     }
242 234
                 }
243 235
 
244
-                Statistics.analytics.sendEvent(AVG_RTP_STATS, batchReport);
236
+                Statistics.sendAnalytics(createRtpStatsEvent(batchReport));
245 237
             }
246 238
 
247 239
             this._resetAvgStats();
@@ -377,82 +369,91 @@ export default class AvgRTPStatsReporter {
377 369
 
378 370
         /**
379 371
          * Average audio upload bitrate
372
+         * XXX What are the units?
380 373
          * @type {AverageStatReport}
381 374
          * @private
382 375
          */
383 376
         this._avgAudioBitrateUp
384
-            = new AverageStatReport('stat_avg_bitrate_audio_upload');
377
+            = new AverageStatReport('bitrate_audio_upload');
385 378
 
386 379
         /**
387 380
          * Average audio download bitrate
381
+         * XXX What are the units?
388 382
          * @type {AverageStatReport}
389 383
          * @private
390 384
          */
391 385
         this._avgAudioBitrateDown
392
-            = new AverageStatReport('stat_avg_bitrate_audio_download');
386
+            = new AverageStatReport('bitrate_audio_download');
393 387
 
394 388
         /**
395 389
          * Average video upload bitrate
390
+         * XXX What are the units?
396 391
          * @type {AverageStatReport}
397 392
          * @private
398 393
          */
399 394
         this._avgVideoBitrateUp
400
-            = new AverageStatReport('stat_avg_bitrate_video_upload');
395
+            = new AverageStatReport('bitrate_video_upload');
401 396
 
402 397
         /**
403 398
          * Average video download bitrate
399
+         * XXX What are the units?
404 400
          * @type {AverageStatReport}
405 401
          * @private
406 402
          */
407 403
         this._avgVideoBitrateDown
408
-            = new AverageStatReport('stat_avg_bitrate_video_download');
404
+            = new AverageStatReport('bitrate_video_download');
409 405
 
410 406
         /**
411 407
          * Average upload bandwidth
408
+         * XXX What are the units?
412 409
          * @type {AverageStatReport}
413 410
          * @private
414 411
          */
415 412
         this._avgBandwidthUp
416
-            = new AverageStatReport('stat_avg_bandwidth_upload');
413
+            = new AverageStatReport('bandwidth_upload');
417 414
 
418 415
         /**
419 416
          * Average download bandwidth
417
+         * XXX What are the units?
420 418
          * @type {AverageStatReport}
421 419
          * @private
422 420
          */
423 421
         this._avgBandwidthDown
424
-            = new AverageStatReport('stat_avg_bandwidth_download');
422
+            = new AverageStatReport('bandwidth_download');
425 423
 
426 424
         /**
427 425
          * Average total packet loss
426
+         * XXX What are the units?
428 427
          * @type {AverageStatReport}
429 428
          * @private
430 429
          */
431 430
         this._avgPacketLossTotal
432
-            = new AverageStatReport('stat_avg_packetloss_total');
431
+            = new AverageStatReport('packet_loss_total');
433 432
 
434 433
         /**
435 434
          * Average upload packet loss
435
+         * XXX What are the units?
436 436
          * @type {AverageStatReport}
437 437
          * @private
438 438
          */
439 439
         this._avgPacketLossUp
440
-            = new AverageStatReport('stat_avg_packetloss_upload');
440
+            = new AverageStatReport('packet_loss_upload');
441 441
 
442 442
         /**
443 443
          * Average download packet loss
444
+         * XXX What are the units?
444 445
          * @type {AverageStatReport}
445 446
          * @private
446 447
          */
447 448
         this._avgPacketLossDown
448
-            = new AverageStatReport('stat_avg_packetloss_download');
449
+            = new AverageStatReport('packet_loss_download');
449 450
 
450 451
         /**
451 452
          * Average FPS for remote videos
452 453
          * @type {AverageStatReport}
453 454
          * @private
454 455
          */
455
-        this._avgRemoteFPS = new AverageStatReport('stat_avg_framerate_remote');
456
+        this._avgRemoteFPS = new AverageStatReport('framerate_remote');
456 457
 
457 458
         /**
458 459
          * Average FPS for remote screen streaming videos (reported only if not
@@ -461,14 +462,14 @@ export default class AvgRTPStatsReporter {
461 462
          * @private
462 463
          */
463 464
         this._avgRemoteScreenFPS
464
-            = new AverageStatReport('stat_avg_framerate_screen_remote');
465
+            = new AverageStatReport('framerate_screen_remote');
465 466
 
466 467
         /**
467 468
          * Average FPS for local video (camera)
468 469
          * @type {AverageStatReport}
469 470
          * @private
470 471
          */
471
-        this._avgLocalFPS = new AverageStatReport('stat_avg_framerate_local');
472
+        this._avgLocalFPS = new AverageStatReport('framerate_local');
472 473
 
473 474
         /**
474 475
          * Average FPS for local screen streaming video (reported only if not
@@ -477,7 +478,7 @@ export default class AvgRTPStatsReporter {
477 478
          * @private
478 479
          */
479 480
         this._avgLocalScreenFPS
480
-            = new AverageStatReport('stat_avg_framerate_screen_local');
481
+            = new AverageStatReport('framerate_screen_local');
481 482
 
482 483
         /**
483 484
          * Average pixels for remote screen streaming videos (reported only if
@@ -486,7 +487,7 @@ export default class AvgRTPStatsReporter {
486 487
          * @private
487 488
          */
488 489
         this._avgRemoteCameraPixels
489
-            = new AverageStatReport('stat_avg_pixels_remote');
490
+            = new AverageStatReport('pixels_remote');
490 491
 
491 492
         /**
492 493
          * Average pixels for remote screen streaming videos (reported only if
@@ -495,7 +496,7 @@ export default class AvgRTPStatsReporter {
495 496
          * @private
496 497
          */
497 498
         this._avgRemoteScreenPixels
498
-            = new AverageStatReport('stat_avg_pixels_screen_remote');
499
+            = new AverageStatReport('pixels_screen_remote');
499 500
 
500 501
         /**
501 502
          * Average pixels for local video (camera)
@@ -503,7 +504,7 @@ export default class AvgRTPStatsReporter {
503 504
          * @private
504 505
          */
505 506
         this._avgLocalCameraPixels
506
-            = new AverageStatReport('stat_avg_pixels_local');
507
+            = new AverageStatReport('pixels_local');
507 508
 
508 509
         /**
509 510
          * Average pixels for local screen streaming video (reported only if not
@@ -512,7 +513,7 @@ export default class AvgRTPStatsReporter {
512 513
          * @private
513 514
          */
514 515
         this._avgLocalScreenPixels
515
-            = new AverageStatReport('stat_avg_pixels_screen_local');
516
+            = new AverageStatReport('pixels_screen_local');
516 517
 
517 518
         /**
518 519
          * Average connection quality as defined by
@@ -520,7 +521,7 @@ export default class AvgRTPStatsReporter {
520 521
          * @type {AverageStatReport}
521 522
          * @private
522 523
          */
523
-        this._avgCQ = new AverageStatReport('stat_avg_cq');
524
+        this._avgCQ = new AverageStatReport('connection_quality');
524 525
 
525 526
         this._onLocalStatsUpdated = data => this._calculateAvgStats(data);
526 527
         conference.on(
@@ -674,14 +675,16 @@ export default class AvgRTPStatsReporter {
674 675
 
675 676
             const batchReport = {
676 677
                 p2p: isP2P,
677
-                size: confSize
678
+                'conference_size': confSize
678 679
             };
679 680
 
680 681
             if (data.transport && data.transport.length) {
681 682
                 Object.assign(batchReport, {
682
-                    localCandidateType: data.transport[0].localCandidateType,
683
-                    remoteCandidateType: data.transport[0].remoteCandidateType,
684
-                    transportType: data.transport[0].type
683
+                    'local_candidate_type':
684
+                        data.transport[0].localCandidateType,
685
+                    'remote_candidate_type':
686
+                        data.transport[0].remoteCandidateType,
687
+                    'transport_type': data.transport[0].type
685 688
                 });
686 689
             }
687 690
 
@@ -719,7 +722,7 @@ export default class AvgRTPStatsReporter {
719 722
 
720 723
             this._avgCQ.appendReport(batchReport);
721 724
 
722
-            Statistics.analytics.sendEvent(AVG_RTP_STATS, batchReport);
725
+            Statistics.sendAnalytics(createRtpStatsEvent(batchReport));
723 726
 
724 727
             this._resetAvgStats();
725 728
         }

+ 5
- 5
modules/statistics/CallStats.js Zobrazit soubor

@@ -465,17 +465,17 @@ export default class CallStats {
465 465
      *
466 466
      * @param {string} conferenceID the conference ID for which the feedback
467 467
      * will be reported.
468
-     * @param overallFeedback an integer between 1 and 5 indicating the
468
+     * @param overall an integer between 1 and 5 indicating the
469 469
      * user feedback
470
-     * @param detailedFeedback detailed feedback from the user. Not yet used
470
+     * @param comment detailed feedback from the user.
471 471
      */
472
-    static sendFeedback(conferenceID, overallFeedback, detailedFeedback) {
472
+    static sendFeedback(conferenceID, overall, comment) {
473 473
         if (CallStats.backend) {
474 474
             CallStats.backend.sendUserFeedback(
475 475
                 conferenceID, {
476 476
                     userID: CallStats.userID,
477
-                    overall: overallFeedback,
478
-                    comment: detailedFeedback
477
+                    overall,
478
+                    comment
479 479
                 });
480 480
         } else {
481 481
             logger.error('Failed to submit feedback to CallStats - no backend');

+ 48
- 22
modules/statistics/statistics.js Zobrazit soubor

@@ -1,9 +1,6 @@
1 1
 import EventEmitter from 'events';
2 2
 
3
-import {
4
-    FEEDBACK,
5
-    ICE_FAILED
6
-} from '../../service/statistics/AnalyticsEvents';
3
+import { FEEDBACK } from '../../service/statistics/AnalyticsEvents';
7 4
 import analytics from './AnalyticsAdapter';
8 5
 import CallStats from './CallStats';
9 6
 import LocalStats from './LocalStatsCollector';
@@ -470,7 +467,6 @@ Statistics.prototype.sendIceConnectionFailedEvent = function(tpc) {
470 467
     if (instance) {
471 468
         instance.sendIceConnectionFailedEvent();
472 469
     }
473
-    Statistics.analytics.sendEvent(ICE_FAILED);
474 470
 };
475 471
 
476 472
 /**
@@ -682,16 +678,16 @@ Statistics.sendLog = function(m) {
682 678
 /**
683 679
  * Sends the given feedback through CallStats.
684 680
  *
685
- * @param overall an integer between 1 and 5 indicating the user feedback
686
- * @param detailed detailed feedback from the user. Not yet used
681
+ * @param overall an integer between 1 and 5 indicating the user's rating.
682
+ * @param comment the comment from the user.
687 683
  */
688
-Statistics.prototype.sendFeedback = function(overall, detailed) {
689
-    CallStats.sendFeedback(this._getCallStatsConfID(), overall, detailed);
684
+Statistics.prototype.sendFeedback = function(overall, comment) {
685
+    CallStats.sendFeedback(this._getCallStatsConfID(), overall, comment);
690 686
     Statistics.analytics.sendEvent(
691 687
         FEEDBACK,
692 688
         {
693
-            value: overall,
694
-            detailed
689
+            rating: overall,
690
+            comment
695 691
         });
696 692
 };
697 693
 
@@ -711,16 +707,46 @@ Statistics.reportGlobalError = function(error) {
711 707
 };
712 708
 
713 709
 /**
714
- * Sends event to analytics and callstats.
715
- * @param {string} eventName the event name.
716
- * @param {Object} data the data to be sent.
710
+ * Sends event to analytics and logs a message to the logger/console. Console
711
+ * messages might also be logged to callstats automatically.
712
+ *
713
+ * @param {string | Object} event the event name, or an object which
714
+ * represents the entire event.
715
+ * @param {Object} properties properties to attach to the event (if an event
716
+ * name as opposed to an event object is provided).
717 717
  */
718
-Statistics.sendEventToAll = function(eventName, data) {
719
-    this.analytics.sendEvent(eventName, data);
720
-    Statistics.sendLog(
721
-        JSON.stringify(
722
-            {
723
-                name: eventName,
724
-                data
725
-            }));
718
+Statistics.sendAnalyticsAndLog = function(event, properties = {}) {
719
+    if (!event) {
720
+        logger.warn('No event or event name given.');
721
+
722
+        return;
723
+    }
724
+
725
+    let eventToLog;
726
+
727
+    // Also support an API with a single object as an event.
728
+    if (typeof event === 'object') {
729
+        eventToLog = event;
730
+    } else {
731
+        eventToLog = {
732
+            name: event,
733
+            properties
734
+        };
735
+    }
736
+
737
+    logger.log(JSON.stringify(eventToLog));
738
+
739
+    // We do this last, because it may modify the object which is passed.
740
+    this.analytics.sendEvent(event, properties);
741
+};
742
+
743
+/**
744
+ * Sends event to analytics.
745
+ *
746
+ * @param {string | Object} eventName the event name, or an object which
747
+ * represents the entire event.
748
+ * @param {Object} properties properties to attach to the event
749
+ */
750
+Statistics.sendAnalytics = function(eventName, properties = {}) {
751
+    this.analytics.sendEvent(eventName, properties);
726 752
 };

+ 10
- 10
modules/xmpp/ChatRoom.js Zobrazit soubor

@@ -1325,7 +1325,7 @@ export default class ChatRoom extends Listenable {
1325 1325
     }
1326 1326
 
1327 1327
     /**
1328
-     *
1328
+     * TODO: Document
1329 1329
      * @param iq
1330 1330
      */
1331 1331
     onMute(iq) {
@@ -1334,19 +1334,19 @@ export default class ChatRoom extends Listenable {
1334 1334
         if (from !== this.focusMucJid) {
1335 1335
             logger.warn('Ignored mute from non focus peer');
1336 1336
 
1337
-            return false;
1337
+            return;
1338 1338
         }
1339 1339
         const mute = $(iq).find('mute');
1340 1340
 
1341
-        if (mute.length) {
1342
-            const doMuteAudio = mute.text() === 'true';
1343
-
1344
-            this.eventEmitter.emit(
1345
-                XMPPEvents.AUDIO_MUTED_BY_FOCUS,
1346
-                doMuteAudio);
1341
+        if (mute.length && mute.text() === 'true') {
1342
+            this.eventEmitter.emit(XMPPEvents.AUDIO_MUTED_BY_FOCUS);
1343
+        } else {
1344
+            // XXX Why do we support anything but muting? Why do we encode the
1345
+            // value in the text of the element? Why do we use a separate XML
1346
+            // namespace?
1347
+            logger.warn('Ignoring a mute request which does not explicitly '
1348
+                + 'specify a positive mute command.');
1347 1349
         }
1348
-
1349
-        return true;
1350 1350
     }
1351 1351
 
1352 1352
     /**

+ 37
- 28
modules/xmpp/JingleSessionPC.js Zobrazit soubor

@@ -1,10 +1,8 @@
1 1
 /* global __filename, $ */
2 2
 
3 3
 import {
4
-    _ICE_CHECKING_DURATION,
5
-    _ICE_CONNECTION_STATE_,
6
-    _ICE_ESTABLISHMENT_DURATION,
7
-    _ICE_GATHERING_DURATION
4
+    ICE_DURATION,
5
+    ICE_STATE_CHANGED
8 6
 } from '../../service/statistics/AnalyticsEvents';
9 7
 import async from 'async';
10 8
 import { getLogger } from 'jitsi-meet-logger';
@@ -243,7 +241,7 @@ export default class JingleSessionPC extends JingleSession {
243 241
         this.lasticecandidate = false;
244 242
 
245 243
         // True if reconnect is in progress
246
-        this.isreconnect = false;
244
+        this.isReconnect = false;
247 245
 
248 246
         // Set to true if the connection was ever stable
249 247
         this.wasstable = false;
@@ -318,13 +316,14 @@ export default class JingleSessionPC extends JingleSession {
318 316
                 }
319 317
             } else if (!this._gatheringReported) {
320 318
                 // End of gathering
321
-                let eventName = this.isP2P ? 'p2p.ice.' : 'ice.';
322
-
323
-                eventName += this.isInitiator ? 'initiator.' : 'responder.';
324
-                eventName += _ICE_GATHERING_DURATION;
325
-                Statistics.analytics.sendEvent(
326
-                    eventName,
327
-                    { value: now - this._gatheringStartedTimestamp });
319
+                Statistics.sendAnalytics(
320
+                    ICE_DURATION,
321
+                    {
322
+                        phase: 'gathering',
323
+                        value: now - this._gatheringStartedTimestamp,
324
+                        p2p: this.isP2P,
325
+                        initiator: this.isInitiator
326
+                    });
328 327
                 this._gatheringReported = true;
329 328
             }
330 329
             this.sendIceCandidate(candidate);
@@ -373,12 +372,17 @@ export default class JingleSessionPC extends JingleSession {
373 372
                 `(TIME) ICE ${this.peerconnection.iceConnectionState}`
374 373
                     + ` P2P? ${this.isP2P}:\t`,
375 374
                 now);
376
-            const iceConnectionEventName
377
-                = `${this.isP2P ? 'p2p.' : ''}${_ICE_CONNECTION_STATE_}.${
378
-                    this.peerconnection.iceConnectionState}`;
379 375
 
380
-            Statistics.analytics.sendEvent(
381
-                iceConnectionEventName, { value: now });
376
+            Statistics.sendAnalytics(
377
+                ICE_STATE_CHANGED,
378
+                {
379
+                    p2p: this.isP2P,
380
+                    state: this.peerconnection.iceConnectionState,
381
+                    'signaling_state': this.peerconnection.signalingState,
382
+                    reconnect: this.isReconnect,
383
+                    value: now
384
+                });
385
+
382 386
             this.room.eventEmitter.emit(
383 387
                 XMPPEvents.ICE_CONNECTION_STATE_CHANGED,
384 388
                 this,
@@ -391,20 +395,21 @@ export default class JingleSessionPC extends JingleSession {
391 395
                 // Informs interested parties that the connection has been
392 396
                 // restored.
393 397
                 if (this.peerconnection.signalingState === 'stable') {
394
-                    if (this.isreconnect) {
398
+                    if (this.isReconnect) {
395 399
                         this.room.eventEmitter.emit(
396 400
                             XMPPEvents.CONNECTION_RESTORED, this);
397 401
                     }
398 402
                 }
399 403
 
400 404
                 if (!this.wasConnected && this.wasstable) {
401
-                    let eventName = this.isP2P ? 'p2p.ice.' : 'ice.';
402 405
 
403
-                    eventName += this.isInitiator ? 'initiator.' : 'responder.';
404
-                    Statistics.analytics.sendEvent(
405
-                        `${eventName}${_ICE_CHECKING_DURATION}`,
406
+                    Statistics.sendAnalytics(
407
+                        ICE_DURATION,
406 408
                         {
407
-                            value: now - this._iceCheckingStartedTimestamp
409
+                            phase: 'checking',
410
+                            value: now - this._iceCheckingStartedTimestamp,
411
+                            p2p: this.isP2P,
412
+                            initiator: this.isInitiator
408 413
                         });
409 414
 
410 415
                     // Switch between ICE gathering and ICE checking whichever
@@ -417,22 +422,26 @@ export default class JingleSessionPC extends JingleSession {
417 422
 
418 423
                     this.establishmentDuration = now - iceStarted;
419 424
 
420
-                    Statistics.analytics.sendEvent(
421
-                        `${eventName}${_ICE_ESTABLISHMENT_DURATION}`,
425
+                    Statistics.sendAnalytics(
426
+                        ICE_DURATION,
422 427
                         {
423
-                            value: this.establishmentDuration
428
+                            phase: 'establishment',
429
+                            value: this.establishmentDuration,
430
+                            p2p: this.isP2P,
431
+                            initiator: this.isInitiator
424 432
                         });
433
+
425 434
                     this.wasConnected = true;
426 435
                     this.room.eventEmitter.emit(
427 436
                         XMPPEvents.CONNECTION_ESTABLISHED, this);
428 437
                 }
429
-                this.isreconnect = false;
438
+                this.isReconnect = false;
430 439
                 break;
431 440
             case 'disconnected':
432 441
                 if (this.closed) {
433 442
                     break;
434 443
                 }
435
-                this.isreconnect = true;
444
+                this.isReconnect = true;
436 445
 
437 446
                 // Informs interested parties that the connection has been
438 447
                 // interrupted.

+ 3
- 1
modules/xmpp/strophe.emuc.js Zobrazit soubor

@@ -158,13 +158,15 @@ class MucConnectionPlugin extends ConnectionPluginListenable {
158 158
     }
159 159
 
160 160
     /**
161
-     *
161
+     * TODO: Document
162 162
      * @param iq
163 163
      */
164 164
     onMute(iq) {
165 165
         const from = iq.getAttribute('from');
166 166
         const room = this.rooms[Strophe.getBareJidFromJid(from)];
167 167
 
168
+        // XXX What are the semantics of the return value? Why is it sometimes
169
+        // undefined and sometimes a boolean?
168 170
         if (!room) {
169 171
             return;
170 172
         }

+ 20
- 16
modules/xmpp/strophe.jingle.js Zobrazit soubor

@@ -1,9 +1,9 @@
1 1
 /* global $, __filename */
2 2
 
3 3
 import {
4
-    SESSION_INITIATE_RECEIVED,
5
-    TRANSPORT_REPLACE_START,
6
-    TRANSPORT_REPLACE_SUCCESS
4
+    ACTION_JINGLE_TR_RECEIVED,
5
+    ACTION_JINGLE_TR_SUCCESS,
6
+    createJingleEvent
7 7
 } from '../../service/statistics/AnalyticsEvents';
8 8
 import { getLogger } from 'jitsi-meet-logger';
9 9
 import { $iq, Strophe } from 'strophe.js';
@@ -128,6 +128,11 @@ class JingleConnectionPlugin extends ConnectionPlugin {
128 128
         }
129 129
         const now = window.performance.now();
130 130
 
131
+        // FIXME that should work most of the time, but we'd have to
132
+        // think how secure it is to assume that user with "focus"
133
+        // nickname is Jicofo.
134
+        const isP2P = Strophe.getResourceFromJid(fromJid) !== 'focus';
135
+
131 136
         // see http://xmpp.org/extensions/xep-0166.html#concepts-session
132 137
 
133 138
         switch (action) {
@@ -145,11 +150,6 @@ class JingleConnectionPlugin extends ConnectionPlugin {
145 150
                     videoMuted === 'true');
146 151
             }
147 152
 
148
-            // FIXME that should work most of the time, but we'd have to
149
-            // think how secure it is to assume that user with "focus"
150
-            // nickname is Jicofo.
151
-            const isP2P = Strophe.getResourceFromJid(fromJid) !== 'focus';
152
-
153 153
             logger.info(
154 154
                 `Marking session from ${fromJid
155 155
                 } as ${isP2P ? '' : '*not*'} P2P`);
@@ -169,8 +169,6 @@ class JingleConnectionPlugin extends ConnectionPlugin {
169 169
 
170 170
             this.eventEmitter.emit(XMPPEvents.CALL_INCOMING,
171 171
                 sess, $(iq).find('>jingle'), now);
172
-            Statistics.analytics.sendEvent(
173
-                SESSION_INITIATE_RECEIVED, { value: now });
174 172
             break;
175 173
         }
176 174
         case 'session-accept': {
@@ -204,17 +202,23 @@ class JingleConnectionPlugin extends ConnectionPlugin {
204 202
         }
205 203
         case 'transport-replace':
206 204
             logger.info('(TIME) Start transport replace', now);
207
-            Statistics.analytics.sendEvent(
208
-                TRANSPORT_REPLACE_START,
209
-                { value: now });
205
+            Statistics.sendAnalytics(createJingleEvent(
206
+                ACTION_JINGLE_TR_RECEIVED,
207
+                {
208
+                    p2p: isP2P,
209
+                    value: now
210
+                }));
210 211
 
211 212
             sess.replaceTransport($(iq).find('>jingle'), () => {
212 213
                 const successTime = window.performance.now();
213 214
 
214 215
                 logger.info('(TIME) Transport replace success!', successTime);
215
-                Statistics.analytics.sendEvent(
216
-                    TRANSPORT_REPLACE_SUCCESS,
217
-                    { value: successTime });
216
+                Statistics.sendAnalytics(createJingleEvent(
217
+                    ACTION_JINGLE_TR_SUCCESS,
218
+                    {
219
+                        p2p: isP2P,
220
+                        value: successTime
221
+                    }));
218 222
             }, error => {
219 223
                 GlobalOnErrorHandler.callErrorHandler(error);
220 224
                 logger.error('Transport replace failed', error);

+ 305
- 337
service/statistics/AnalyticsEvents.js Zobrazit soubor

@@ -1,447 +1,415 @@
1 1
 /**
2
- * Note that an event's own properties and its permanent properties are
3
- * are merged in one object. Because of this an event should never use
4
- * properties with names that are already used by permanent properties
5
- * (unless the intention is to override a permanent property). Here is a
6
- * (non-exhaustive) list of currently know permanent properties:
2
+ * This class exports constants and factory methods related to the analytics
3
+ * API provided by AnalyticsAdapter. In order for entries in a database to be
4
+ * somewhat easily traceable back to the code which produced them, events sent
5
+ * through analytics should be defined here.
7 6
  *
8
- * abtestSuspendVideo
9
- * browserName
10
- * callstatsname
11
- * crossRegion
12
- * forceJvb121
13
- * region
14
- * roomName
15
- * shard
16
- * size
17
- * userAgent
18
- * userRegion
7
+ * Since the AnalyticsAdapter API can be used in different ways, for some events
8
+ * it is more convenient to just define the event name as a constant. For other
9
+ * events a factory function is easier.
19 10
  *
20
- * The naming convention for the constants below uses "_" as a prefix or
21
- * suffix to indicate that known usage of the constant prepends or appends
22
- * a string to the name of the event.
23
- */
24
-
25
-/**
26
- * Properties: value
11
+ * A general approach for adding a new event:
12
+ * 1. Determine the event type: track, UI, page, or operational. If in doubt use
13
+ * operational.
14
+ * 2. Determine whether the event is related to other existing events, and
15
+ * which fields are desired to be set: name, action, actionSubject, source.
16
+ * 3. If the name is sufficient (the other fields are not important), use a
17
+ * constant. Otherwise use a factory function.
27 18
  *
28
- * TODO: document, reformat
19
+ * Note that the AnalyticsAdapter uses the events passed to its functions for
20
+ * its own purposes, and might modify them. Because of this factory functions
21
+ * should create new objects.
29 22
  *
30
- * Full event names (uncertain):
31
- * conference.muc.joined (???)
32
- * conference.sharingDesktop.start (???)
33
- * conference.sharingDesktop.stop (???)
34
- * xmpp.attached (???)
35
- * xmpp.attaching (???)
36
- * xmpp.connected (???)
37
- * xmpp.connecting (???)
38
- * xmpp.session-initiate (???)
39 23
  */
40
-export const _CONNECTION_TIMES_ = '';
41 24
 
42 25
 /**
43
- * TODO: document, reformat (group together with other ICE events)
44
- *
45
- * Known full event names:
46
- * ice.initiator.checksDuration
47
- * ice.responder.checksDuration
48
- * p2p.ice.initiator.checksDuration
49
- * p2p.ice.responder.checksDuration
50
- *
51
- * Properties: value
26
+ * The constant which identifies an event of type "operational".
27
+ * @type {string}
52 28
  */
53
-export const _ICE_CHECKING_DURATION = 'checksDuration';
29
+export const TYPE_OPERATIONAL = 'operational';
54 30
 
55 31
 /**
56
- * TODO: document, reformat
57
- *
58
- * Known full event names:
59
- * ice.checking
60
- * ice.closed
61
- * ice.completed
62
- * ice.connected
63
- * ice.disconnected
64
- * ice.failed
65
- * p2p.ice.checking
66
- * p2p.ice.closed
67
- * p2p.ice.completed
68
- * p2p.ice.connected
69
- * p2p.ice.disconnected
70
- * p2p.ice.failed
71
- *
72
- * Properties: value
32
+ * The constant which identifies an event of type "page".
33
+ * @type {string}
73 34
  */
74
-export const _ICE_CONNECTION_STATE_ = 'ice';
35
+export const TYPE_PAGE = 'page';
75 36
 
76 37
 /**
77
- * TODO: document, reformat (group together with other ICE events)
78
- *
79
- * Known full event names:
80
- * ice.initiator.establishmentDuration
81
- * ice.responder.establishmentDuration
82
- * p2p.ice.initiator.establishmentDuration
83
- * p2p.ice.responder.establishmentDuration
84
- *
85
- * Properties: value
38
+ * The constant which identifies an event of type "track".
39
+ * @type {string}
86 40
  */
87
-export const _ICE_ESTABLISHMENT_DURATION = 'establishmentDuration';
41
+export const TYPE_TRACK = 'track';
88 42
 
89 43
 /**
90
- * TODO: document, reformat
91
- *
92
- * Known full event names:
93
- * ice.initiator.gatheringDuration
94
- * ice.responder.gatheringDuration
95
- * p2p.ice.initiator.gatheringDuration
96
- * p2p.ice.responder.gatheringDuration
97
- *
98
- * Properties: value
44
+ * The constant which identifies an event of type "ui".
45
+ * @type {string}
99 46
  */
100
-export const _ICE_GATHERING_DURATION = 'gatheringDuration';
47
+export const TYPE_UI = 'ui';
101 48
 
102 49
 /**
103
- * TODO: document, reformat
104
- *
105
- * Known full event names:
106
- * audio.no_data_from_source
107
- * video.no_data_from_source
108
- */
109
-export const _NO_DATA_FROM_SOURCE = 'no_data_from_source';
110
-
111
-/**
112
- * TODO: document, reformat
113
- *
114
- * Known full event names:
115
- * audio.track_unmute
116
- * video.track_unmute
50
+ * The "action" value for Jingle events which indicates that the Jingle session
51
+ * was restarted (TODO: verify/fix the documentation)
52
+ * @type {string}
117 53
  */
118
-export const _TRACK_UNMUTE = 'track_unmute';
54
+export const ACTION_JINGLE_RESTART = 'restart';
119 55
 
120 56
 /**
121
- * TODO: document, reformat
122
- *
123
- * TTMF: Time To First Media
124
- *
125
- * Known full event names:
126
- * audio.ttfm
127
- * video.ttfm
128
- * audio.ttfm.muted
129
- * video.ttfm.muted
57
+ * The "action" value for Jingle events which indicates that a session-accept
58
+ * timed out (TODO: verify/fix the documentation)
59
+ * @type {string}
130 60
  */
131
-export const _TTFM_ = 'ttfm';
61
+export const ACTION_JINGLE_SA_TIMEOUT = 'session-accept.timeout';
132 62
 
133 63
 /**
134
- * All average RTP stats are currently reported under 1 event name, but with
135
- * different properties that allows to distinguish between a P2P call, a
136
- * call relayed through TURN or the JVB, and multiparty vs 1:1.
137
- * Example structure of an "avg.rtp.stats" analytics event:
138
- *
139
- * {
140
-     *   p2p: true,
141
-     *   conferenceSize: 2,
142
-     *   localCandidateType: "relay",
143
-     *   remoteCandidateType: "relay",
144
-     *   transportType: "udp",
145
-     *
146
-     *   "stat_avg_rtt": {
147
-     *     value: 200,
148
-     *     samples: [ 100, 200, 300 ]
149
-     *   },
150
-     *   "stat_avg_packetloss_total": {
151
-     *     value: 10,
152
-     *     samples: [ 5, 10, 15]
153
-     *   }
154
-     * }
155
- *
156
- * Note that the samples array is currently emitted for debug purposes only
157
- * and can be removed anytime soon from the structure.
158
- *
159
- * Also not all values are always present in "avg.rtp.stats", some of the
160
- * values are obtained and calculated as part of different process/event
161
- * pipe. For example {@link ConnectionAvgStats} instances are doing the
162
- * reports for each {@link TraceablePeerConnection} and work independently
163
- * from the main stats pipe.
64
+ * The "action" value for Jingle events which indicates that a session-initiate
65
+ * was received.
66
+ * @type {string}
164 67
  */
165
-export const AVG_RTP_STATS = 'avg.rtp.stats';
68
+export const ACTION_JINGLE_SI_RECEIVED = 'session-initiate.received';
166 69
 
167 70
 /**
168
- * Properties: none
169
- *
170
- * TODO: document, deprecate?
71
+ * A constant for the "terminate" action for Jingle events. TODO: verify/fix
72
+ * the documentation)
73
+ * @type {string}
171 74
  */
172
-export const BRIDGE_DOWN = 'conference.bridgeDown';
75
+export const ACTION_JINGLE_TERMINATE = 'terminate';
173 76
 
174 77
 /**
175
- * Properties: none
176
- *
177
- * Known full event names:
178
- * conference.error.p2pSessionAcceptTimeout
179
- * conference.error.sessionAcceptTimeout
180
- *
181
- * TODO: document, reformat
78
+ * The "action" value for Jingle events which indicates that a transport-replace
79
+ * was received.
80
+ * @type {string}
182 81
  */
183
-export const CONFERENCE_ERROR_ = 'conference.error';
82
+export const ACTION_JINGLE_TR_RECEIVED
83
+    = 'transport-replace.received';
184 84
 
185 85
 /**
186
- * Properties: none
187
- *
188
- * TODO: document
86
+ * The "action" value for Jingle events which indicates that a transport-replace
87
+ * succeeded (TODO: verify/fix the documentation)
88
+ * @type {string}
189 89
  */
190
-export const CONNECTION_INTERRUPTED = 'connection.interrupted';
90
+export const ACTION_JINGLE_TR_SUCCESS
91
+    = 'transport-replace.success';
191 92
 
192 93
 /**
193
- * Properties: none
194
- *
195
- * Known full event names: NONE
196
- *
197
- * TODO: document, reformat?, deprecate?
94
+ * The "action" value for P2P events which indicates that a connection was
95
+ * established (TODO: verify/fix the documentation)
96
+ * @type {string}
198 97
  */
199
-export const CONNECTION_DISCONNECTED_ = 'connection.disconnected';
98
+export const ACTION_P2P_ESTABLISHED = 'established';
200 99
 
201 100
 /**
202
- * Properties: label
203
- *
204
- * Known full event names:
205
- * connection.failed.connection.droppedError
206
- * connection.failed.connection.otherError
207
- * connection.failed.connection.passwordRequired
208
- *
209
- * TODO: document, reformat
101
+ * The "action" value for P2P events which indicates that something failed.
102
+ * @type {string}
210 103
  */
211
-export const CONNECTION_FAILED_ = 'connection.failed';
104
+export const ACTION_P2P_FAILED = 'failed';
212 105
 
213 106
 /**
214
- * Properties: none
215
- *
216
- * TODO: document
107
+ * The "action" value for P2P events which indicates that a switch to
108
+ * jitsi-videobridge happened.
109
+ * @type {string}
217 110
  */
218
-export const CONNECTION_RESTORED = 'connection.restored';
111
+export const ACTION_P2P_SWITCH_TO_JVB = 'switch.to.jvb';
219 112
 
220 113
 /**
221
- * Properties: value
114
+ * The name of an event which indicates an available device. We send one such
115
+ * event per available device once when the available devices are first known,
116
+ * and every time that they change
117
+ * @type {string}
222 118
  *
223
- * TODO: document, deprecate (is it the same as the one which is part of
224
- * CONNECTION_TIMES?)
225
- */
226
-export const DATA_CHANNEL_OPEN = 'conference.dataChannel.open';
227
-
228
-/**
229
- * TODO: document, reformat
119
+ * Properties:
120
+ *      audio_input_device_count: the number of audio input devices available at
121
+ *          the time the event was sent.
122
+ *      audio_output_device_count: the number of audio output devices available
123
+ *          at the time the event was sent.
124
+ *      video_input_device_count: the number of video input devices available at
125
+ *          the time the event was sent.
126
+ *      video_output_device_count: the number of video output devices available
127
+ *          at the time the event was sent.
128
+ *      device_id: an identifier of the device described in this event.
129
+ *      device_group_id:
130
+ *      device_kind: one of 'audioinput', 'audiooutput', 'videoinput' or
131
+ *          'videooutput'.
132
+ *      device_label: a string which describes the device.
230 133
  */
231
-export const DEVICE_LIST = 'devices.deviceList';
134
+export const AVAILABLE_DEVICE = 'available.device';
232 135
 
233 136
 /**
234
- * User feedback event.
235
- * Properties: value, detailed
137
+ * This appears to be fired only in certain cases when the XMPP connection
138
+ * disconnects (and it was intentional?). It is currently never observed to
139
+ * fire in production.
236 140
  *
237 141
  * TODO: document
238
- */
239
-export const FEEDBACK = 'feedback.rating';
240
-
241
-/**
242
- * Properties: none
243 142
  *
244
- * TODO: document
143
+ * Properties:
144
+ *      message: an error message
245 145
  */
246
-export const FOCUS_LEFT = 'conference.focusLeft';
146
+export const CONNECTION_DISCONNECTED = 'connection.disconnected';
247 147
 
248 148
 /**
249
- * Properties: none
250
- *
251
- * Known full event names:
252
- * getUserMedia.deviceNotFound.audio
253
- * getUserMedia.deviceNotFound.audio.video
254
- * getUserMedia.deviceNotFound.video
255
- * getUserMedia.deviceNotFound.screen
256
- *
257
- * TODO: document, reformat, merge with GET_USER_MEDIA_FAILED?
149
+ * Indicates that the user of the application provided feedback in terms of a
150
+ * rating (an integer from 1 to 5) and an optional comment.
151
+ * Properties:
152
+ *      value: the user's rating (an integer from 1 to 5)
153
+ *      comment: the user's comment
258 154
  */
259
-export const GET_USER_MEDIA_DEVICE_NOT_FOUND_
260
-    = 'getUserMedia.deviceNotFound';
155
+export const FEEDBACK = 'feedback';
261 156
 
262 157
 /**
263
- * Properties: none
158
+ * Indicates the duration of a particular phase of the ICE connectivity
159
+ * establishment.
264 160
  *
265
- * Known full event names:
266
- * getUserMedia.fail.resolution.180
267
- * getUserMedia.fail.resolution.360
268
- * getUserMedia.fail.resolution.640
269
- * getUserMedia.fail.resolution.720
270
- * getUserMedia.fail.resolution.960
271
- *
272
- * TODO: reformat, merge with GET_USER_MEDIA_FAILED
161
+ * Properties:
162
+ *      phase: the ICE phase (e.g. 'gathering', 'checking', 'establishment')
163
+ *      value: the duration in milliseconds.
164
+ *      p2p: whether the associated ICE connection is p2p or towards a
165
+ *          jitsi-videobridge
166
+ *      initiator: whether the local Jingle peer is the initiator or responder
167
+ *          in the Jingle session. XXX we probably actually care about the ICE
168
+ *          role (controlling vs controlled), and we assume that this correlates
169
+ *          with the Jingle initiator.
273 170
  */
274
-export const GET_USER_MEDIA_FAIL_ = 'getUserMedia.fail';
171
+export const ICE_DURATION = 'ice.duration';
275 172
 
276 173
 /**
277
- * Properties: value
174
+ * Indicates the difference in milliseconds between the ICE establishment time
175
+ * for the P2P and JVB connections (e.g. a value of 10 would indicate that the
176
+ * P2P connection took 10ms more than JVB connection to establish).
278 177
  *
279
- * Known full event names:
280
- * getUserMedia.failed.Error
281
- * getUserMedia.failed.TypeError
282
- * getUserMedia.failed.audio.TypeError
283
- * getUserMedia.failed.audio.gum.general
284
- * getUserMedia.failed.audio.gum.permission_denied
285
- * getUserMedia.failed.audio.track.no_data_from_source
286
- * getUserMedia.failed.audio.video.180.gum.general
287
- * getUserMedia.failed.audio.video.360.gum.general
288
- * getUserMedia.failed.audio.video.360.gum.permission_denied
289
- * getUserMedia.failed.audio.video.360.track.no_data_from_source
290
- * getUserMedia.failed.audio.video.720.TypeError
291
- * getUserMedia.failed.audio.video.720.gum.constraint_failed
292
- * getUserMedia.failed.audio.video.720.gum.general
293
- * getUserMedia.failed.audio.video.720.gum.permission_denied
294
- * getUserMedia.failed.audio.video.720.track.no_data_from_source
295
- * getUserMedia.failed.audio.video.960.gum.permission_denied
296
- * getUserMedia.failed.audio.video.undefined.gum.general
297
- * getUserMedia.failed.desktop.TypeError
298
- * getUserMedia.failed.desktop.gum.chrome_extension_generic_error
299
- * getUserMedia.failed.desktop.gum.chrome_extension_installation_error
300
- * getUserMedia.failed.desktop.gum.chrome_extension_user_gesture_required
301
- * getUserMedia.failed.desktop.gum.general
302
- * getUserMedia.failed.desktop.track.no_data_from_source
303
- * getUserMedia.failed.gum.chrome_extension_generic_error
304
- * getUserMedia.failed.gum.chrome_extension_installation_error
305
- * getUserMedia.failed.gum.constraint_failed
306
- * getUserMedia.failed.gum.firefox_extension_needed
307
- * getUserMedia.failed.gum.general
308
- * getUserMedia.failed.gum.permission_denied
309
- * getUserMedia.failed.undefined
310
- * getUserMedia.failed.video.360.gum.permission_denied
311
- * getUserMedia.failed.video.720.TypeError
312
- * getUserMedia.failed.video.720.gum.constraint_failed
313
- * getUserMedia.failed.video.720.gum.general
314
- * getUserMedia.failed.video.720.gum.permission_denied
315
- * getUserMedia.failed.video.720.track.no_data_from_source
316
- * getUserMedia.failed.video.undefined.TypeError
317
- * getUserMedia.failed.video.undefined.gum.general
318
- * getUserMedia.failed.video.undefined.track.no_data_from_source
178
+ * Properties:
179
+ *      value: the difference in establishment durations in milliseconds.
319 180
  *
320
- * TODO: reformat
321 181
  */
322
-export const GET_USER_MEDIA_FAILED_ = 'getUserMedia.failed';
182
+export const ICE_ESTABLISHMENT_DURATION_DIFF
183
+    = 'ice.establishment.duration.diff';
323 184
 
324 185
 /**
325
- * Properties: value
326
- *
327
- * Known full event names:
328
- * getUserMedia.success
329
- * getUserMedia.success.audio
330
- * getUserMedia.success.audio.video.180
331
- * getUserMedia.success.audio.video.300
332
- * getUserMedia.success.audio.video.360
333
- * getUserMedia.success.audio.video.720
334
- * getUserMedia.success.audio.video.960
335
- * getUserMedia.success.audio.video.undefined
336
- * getUserMedia.success.desktop
337
- * getUserMedia.success.video.180
338
- * getUserMedia.success.video.360
339
- * getUserMedia.success.video.720
340
- * getUserMedia.success.video.960
341
- * getUserMedia.success.video.undefined
186
+ * Indicates that the ICE state has changed.
342 187
  *
343
- * TODO: document, reformat
188
+ * Properties:
189
+ *      state: the ICE state which was entered (e.g. 'checking', 'connected',
190
+ *          'completed', etc).
191
+ *      value: the time in milliseconds (as reported by
192
+ *          window.performance.now()) that the state change occurred.
193
+ *      p2p: whether the associated ICE connection is p2p or towards a
194
+ *          jitsi-videobridge
195
+ *      signalingState: The signaling state of the associated PeerConnection
196
+ *      reconnect: whether the associated Jingle session is in the process of
197
+ *          reconnecting (or is it ICE? TODO: verify/fix the documentation)
344 198
  */
345
-export const GET_USER_MEDIA_SUCCESS_ = 'getUserMedia.success';
199
+export const ICE_STATE_CHANGED = 'ice.state.changed';
346 200
 
347 201
 /**
348
- * Properties: none
202
+ * Indicates that a track was unmuted (?).
349 203
  *
350
- * Known full event names:
351
- * getUserMedia.userCancel.extensionInstall
352
- *
353
- * TODO: document, reformat
204
+ * Properties:
205
+ *      mediaType: the media type of the local track ('audio' or 'video').
206
+ *      trackType: the type of the track ('local' or 'remote').
207
+ *      value: TODO: document
354 208
  */
355
-export const GET_USER_MEDIA_USER_CANCEL_ = 'getUserMedia.userCancel';
209
+export const TRACK_UNMUTED = 'track.unmuted';
356 210
 
357 211
 /**
358
- * Properties: value
359
- *
360
- * The "value" property contains the difference in milliseconds between
361
- * the ICE establishment time for the P2P and JVB connections (e.g. a value
362
- * of 10 would indicate that the P2P was 10ms slower than JVB).
212
+ * Creates an operational event which indicates that we have received a
213
+ * "bridge down" event from jicofo.
363 214
  */
364
-export const ICE_ESTABLISHMENT_DURATION_DIFF
365
-    = 'ice.establishmentDurationDiff';
215
+export const createBridgeDownEvent = function() {
216
+    const bridgeDown = 'bridge.down';
366 217
 
367
-/**
368
- * Properties: none
369
- *
370
- * TODO: document
371
- * TODO: do we need this in addition to _ICE_CONNECTION_STATE?
372
- */
373
-export const ICE_FAILED = 'connection.ice_failed';
218
+    return {
219
+        action: bridgeDown,
220
+        actionSubject: bridgeDown,
221
+        type: TYPE_OPERATIONAL
222
+    };
223
+};
374 224
 
375 225
 /**
376
- * Properties: none
377
- *
378
- * TODO: document
226
+ * Creates an event which indicates that the XMPP connection failed
227
+ * @param errorType TODO
228
+ * @param errorMessage TODO
379 229
  */
380
-export const P2P_ESTABLISHED = 'p2p.established';
230
+export const createConnectionFailedEvent = function(errorType, errorMessage) {
231
+    return {
232
+        type: TYPE_OPERATIONAL,
233
+        action: 'connection.failed',
234
+        attributes: {
235
+            'error_type': errorType,
236
+            'error_message': errorMessage
237
+        }
238
+    };
239
+};
381 240
 
382 241
 /**
383
- * Properties: none
242
+ * Creates an operational event which indicates that a particular connection
243
+ * stage was reached (i.e. the XMPP connection transitioned to the "connected"
244
+ * state).
384 245
  *
385
- * TODO: document
386
- */
387
-export const P2P_FAILED = 'p2p.failed';
388
-
389
-/**
390
- * Properties: none
246
+ * @param stage the stage which was reached
247
+ * @param attributes additional attributes for the event. This should be an
248
+ * object with a "value" property indicating a timestamp in milliseconds
249
+ * relative to the beginning of the document's lifetime.
391 250
  *
392
- * TODO: document
393 251
  */
394
-export const P2P_SWITCH_TO_JVB = 'p2p.switch_to_jvb';
252
+export const createConnectionStageReachedEvent = function(stage, attributes) {
253
+    const action = 'connection.stage.reached';
254
+
255
+    return {
256
+        action,
257
+        actionSubject: stage,
258
+        attributes,
259
+        source: action,
260
+        type: TYPE_OPERATIONAL
261
+    };
262
+};
395 263
 
396 264
 /**
397
- * Properties: none
398
- *
399
- * TODO: document
265
+ * Creates an event which indicates that the focus has left the MUC.
400 266
  */
401
-export const REMOTELY_MUTED = 'conference.remotelyMuted';
267
+export const createFocusLeftEvent = function() {
268
+    const action = 'focus.left';
269
+
270
+    return {
271
+        action,
272
+        actionSubject: action,
273
+        type: TYPE_OPERATIONAL
274
+    };
275
+};
402 276
 
403 277
 /**
404
- * Properties: value
405
- *
406
- * TODO: document
278
+ * Creates an event related to a getUserMedia call.
407 279
  *
408
- * The "value" property contains the delay in milliseconds between joining
409
- * the MUC and receiving a Jingle session-initiate from Jicofo (but not
410
- * P2P).
280
+ * @param action the type of the result that the event represents: 'error',
281
+ * 'success', 'warning', etc.
282
+ * @param attributes the attributes to attach to the event.
283
+ * @returns {{type: string, source: string, name: string}}
411 284
  */
412
-export const SESSION_INITIATE = 'session.initiate';
285
+export const createGetUserMediaEvent = function(action, attributes = {}) {
286
+    return {
287
+        type: TYPE_OPERATIONAL,
288
+        source: 'get.user.media',
289
+        action,
290
+        attributes
291
+    };
292
+};
413 293
 
414 294
 /**
415
- * Properties: value
416
- *
417
- * TODO: document
295
+ * Creates an event for a Jingle-related event.
296
+ * @param action the action of the event
297
+ * @param attributes attributes to add to the event.
418 298
  */
419
-export const SESSION_INITIATE_RECEIVED = 'xmpp.session-initiate';
299
+export const createJingleEvent = function(action, attributes = {}) {
300
+    return {
301
+        type: TYPE_OPERATIONAL,
302
+        action,
303
+        source: 'jingle',
304
+        attributes
305
+    };
306
+};
420 307
 
421 308
 /**
422
- * Properties: none
309
+ * Creates an event which indicates that a local track was not able to read
310
+ * data from its source (a camera or a microphone).
423 311
  *
424
- * TODO: document
312
+ * @param mediaType {String} the media type of the local track ('audio' or
313
+ * 'video').
425 314
  */
426
-export const SESSION_TERMINATE = 'session.terminate';
315
+export const createNoDataFromSourceEvent = function(mediaType) {
316
+    return {
317
+        attributes: { 'media_type': mediaType },
318
+        action: 'track.no.data.from.source',
319
+        type: TYPE_OPERATIONAL
320
+    };
321
+};
427 322
 
428 323
 /**
429
- * Properties: none
430
- *
431
- * TODO: document
324
+ * Creates an event for a p2p-related event.
325
+ * @param action the action of the event
326
+ * @param attributes attributes to add to the event.
432 327
  */
433
-export const SESSION_RESTART = 'session.restart';
328
+export const createP2PEvent = function(action, attributes = {}) {
329
+    return {
330
+        type: TYPE_OPERATIONAL,
331
+        action,
332
+        source: 'p2p',
333
+        attributes
334
+    };
335
+};
434 336
 
435 337
 /**
436
- * Properties: value
437
- *
438
- * TODO: document
338
+ * Indicates that we received a remote command to mute.
439 339
  */
440
-export const TRANSPORT_REPLACE_START = 'xmpp.transport-replace.start';
340
+export const createRemotelyMutedEvent = function() {
341
+    return {
342
+        type: TYPE_OPERATIONAL,
343
+        action: 'remotely.muted'
344
+    };
345
+};
441 346
 
442 347
 /**
443
- * Properties: value
348
+ * Creates an event which contains RTP statistics such as RTT and packet loss.
444 349
  *
445
- * TODO: document
446
- */
447
-export const TRANSPORT_REPLACE_SUCCESS = 'xmpp.transport-replace.success';
350
+ * All average RTP stats are currently reported under 1 event name, but with
351
+ * different properties that allows to distinguish between a P2P call, a
352
+ * call relayed through TURN or the JVB, and multiparty vs 1:1.
353
+ *
354
+ * The structure of the event is:
355
+ *
356
+ * {
357
+ *      p2p: true,
358
+ *      conferenceSize: 2,
359
+ *      localCandidateType: "relay",
360
+ *      remoteCandidateType: "relay",
361
+ *      transportType: "udp",
362
+ *
363
+ *      // Average RTT of 200ms
364
+ *      "rtt.avg": 200,
365
+ *      "rtt.samples": "[100, 200, 300]",
366
+ *
367
+ *      // Average packet loss of 10%
368
+ *      "packet.loss.avg": 10,
369
+ *      "packet.loss.samples": '[5, 10, 15]'
370
+ *
371
+ *      // Difference in milliseconds in the end-to-end RTT between p2p and jvb.
372
+ *      // The e2e RTT through jvb is 15ms shorter:
373
+ *      "rtt.diff": 15,
374
+ *
375
+ *      // End-to-end RTT through JVB is ms.
376
+ *      "end2end.rtt.avg" = 100
377
+ * }
378
+ *
379
+ * Note that the value of the "samples" properties are (JSON encoded) strings,
380
+ * and not JSON arrays, as events' attributes can not be nested. The samples are
381
+ * currently included for debug purposes only and can be removed anytime soon
382
+ * from the structure.
383
+ *
384
+ * Also note that not all of values are present in each event, as values are
385
+ * obtained and calculated as part of different process/event pipe. For example
386
+ * {@link ConnectionAvgStats} instances are doing the reports for each
387
+ * {@link TraceablePeerConnection} and work independently from the main stats
388
+ * pipe.
389
+ */
390
+export const createRtpStatsEvent = function(attributes) {
391
+    return {
392
+        type: TYPE_OPERATIONAL,
393
+        action: 'rtp.stats',
394
+        attributes
395
+    };
396
+};
397
+
398
+/**
399
+ * Creates an event which indicates the Time To First Media (TTFM).
400
+ * It is measured in milliseconds relative to the beginning of the document's
401
+ * lifetime (i.e. the origin used by window.performance.now()), and it excludes
402
+ * the following:
403
+ * 1. The delay due to getUserMedia()
404
+ * 2. The period between the MUC being joined and the reception of the Jingle
405
+ * session-initiate from jicofo. This is because jicofo will not start a Jingle
406
+ * session until there are at least 2 participants in the room.
407
+ *
408
+ * @param attributes the attributes to add to the event. Currently used fields:
409
+ *      mediaType: the media type of the local track ('audio' or 'video').
410
+ *      muted: whether the track has ever been muted (?)
411
+ *      value: the TTMF in milliseconds.
412
+ */
413
+export const createTtfmEvent = function(attributes) {
414
+    return createConnectionStageReachedEvent('ttfm', attributes);
415
+};

Načítá se…
Zrušit
Uložit