ソースを参照

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().
dev1
bgrozev 8年前
コミット
515374c8d3

+ 38
- 25
JitsiConference.js ファイルの表示

2
 import { Strophe } from 'strophe.js';
2
 import { Strophe } from 'strophe.js';
3
 
3
 
4
 import {
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
     ICE_ESTABLISHMENT_DURATION_DIFF,
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
 } from './service/statistics/AnalyticsEvents';
14
 } from './service/statistics/AnalyticsEvents';
13
 import AvgRTPStatsReporter from './modules/statistics/AvgRTPStatsReporter';
15
 import AvgRTPStatsReporter from './modules/statistics/AvgRTPStatsReporter';
14
 import ComponentsVersions from './modules/version/ComponentsVersions';
16
 import ComponentsVersions from './modules/version/ComponentsVersions';
1452
     this.jvbJingleSession = jingleSession;
1454
     this.jvbJingleSession = jingleSession;
1453
     this.room.connectionTimes['session.initiate'] = now;
1455
     this.room.connectionTimes['session.initiate'] = now;
1454
 
1456
 
1455
-    // Log "session.restart"
1456
     if (this.wasStopped) {
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
     try {
1468
     try {
1466
         jingleSession.initialize(false /* initiator */, this.room, this.rtc);
1469
         jingleSession.initialize(false /* initiator */, this.room, this.rtc);
1467
     } catch (error) {
1470
     } catch (error) {
1586
 
1589
 
1587
 /**
1590
 /**
1588
  * Handles the call ended event.
1591
  * Handles the call ended event.
1592
+ * XXX is this due to the remote side terminating the Jingle session?
1593
+ *
1589
  * @param {JingleSessionPC} jingleSession the jingle session which has been
1594
  * @param {JingleSessionPC} jingleSession the jingle session which has been
1590
  * terminated.
1595
  * terminated.
1591
  * @param {String} reasonCondition the Jingle reason condition.
1596
  * @param {String} reasonCondition the Jingle reason condition.
1602
     if (jingleSession === this.jvbJingleSession) {
1607
     if (jingleSession === this.jvbJingleSession) {
1603
         this.wasStopped = true;
1608
         this.wasStopped = true;
1604
 
1609
 
1605
-        Statistics.sendEventToAll(SESSION_TERMINATE);
1610
+        Statistics.sendAnalytics(
1611
+            createJingleEvent(ACTION_JINGLE_TERMINATE, { p2p: false }));
1606
 
1612
 
1607
         // Stop the stats
1613
         // Stop the stats
1608
         if (this.statistics) {
1614
         if (this.statistics) {
2087
         // and "bad" connection
2093
         // and "bad" connection
2088
         Statistics.analytics.addPermanentProperties({ p2pFailed: true });
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
         this._stopP2PSession('connectivity-error', 'ICE FAILED');
2105
         this._stopP2PSession('connectivity-error', 'ICE FAILED');
2095
     }
2106
     }
2238
         const establishmentDurationDiff
2249
         const establishmentDurationDiff
2239
             = this.p2pEstablishmentDuration - this.jvbEstablishmentDuration;
2250
             = this.p2pEstablishmentDuration - this.jvbEstablishmentDuration;
2240
 
2251
 
2241
-        Statistics.analytics.sendEvent(
2252
+        Statistics.sendAnalytics(
2242
             ICE_ESTABLISHMENT_DURATION_DIFF,
2253
             ICE_ESTABLISHMENT_DURATION_DIFF,
2243
-            { 'value': establishmentDurationDiff });
2254
+            { value: establishmentDurationDiff });
2244
     }
2255
     }
2245
 
2256
 
2246
     if (done) {
2257
     if (done) {
2258
         logger.info('Not removing remote JVB tracks - no session yet');
2269
         logger.info('Not removing remote JVB tracks - no session yet');
2259
     }
2270
     }
2260
 
2271
 
2261
-    // Add remote tracks
2262
     this._addRemoteP2PTracks();
2272
     this._addRemoteP2PTracks();
2263
 
2273
 
2264
     // Stop media transfer over the JVB connection
2274
     // Stop media transfer over the JVB connection
2266
         this._suspendMediaTransferForJvbConnection();
2276
         this._suspendMediaTransferForJvbConnection();
2267
     }
2277
     }
2268
 
2278
 
2269
-    // Start remote stats
2270
     logger.info('Starting remote stats with p2p connection');
2279
     logger.info('Starting remote stats with p2p connection');
2271
     this.statistics.startRemoteStats(this.p2pJingleSession.peerconnection);
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
 
2553
 
2542
         // Log that there will be a switch back to the JVB connection
2554
         // Log that there will be a switch back to the JVB connection
2543
         if (this.p2pJingleSession.isInitiator && peerCount > 1) {
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
         this._stopP2PSession();
2559
         this._stopP2PSession();
2547
     }
2560
     }

+ 37
- 36
JitsiConferenceEventManager.js ファイルの表示

2
 import { Strophe } from 'strophe.js';
2
 import { Strophe } from 'strophe.js';
3
 
3
 
4
 import {
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
 } from './service/statistics/AnalyticsEvents';
11
 } from './service/statistics/AnalyticsEvents';
14
 import AuthenticationEvents
12
 import AuthenticationEvents
15
     from './service/authentication/AuthenticationEvents';
13
     from './service/authentication/AuthenticationEvents';
51
                 track.isMuted(),
49
                 track.isMuted(),
52
                 track.getType());
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
     });
75
     });
86
 
76
 
87
     chatRoom.addListener(XMPPEvents.AUDIO_MUTED_BY_FOCUS,
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
             // set isMutedByFocus when setAudioMute Promise ends
84
             // set isMutedByFocus when setAudioMute Promise ends
92
-            conference.rtc.setAudioMute(value).then(
85
+            conference.rtc.setAudioMute(true).then(
93
                 () => {
86
                 () => {
94
                     conference.isMutedByFocus = true;
87
                     conference.isMutedByFocus = true;
95
                 },
88
                 },
110
         () => {
103
         () => {
111
             this.conference.isJvbConnectionInterrupted = false;
104
             this.conference.isJvbConnectionInterrupted = false;
112
 
105
 
106
+            // TODO: Move all of the 'connectionTimes' logic to its own module.
113
             Object.keys(chatRoom.connectionTimes).forEach(key => {
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
             Object.keys(chatRoom.xmpp.connectionTimes).forEach(key => {
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
         JitsiConferenceErrors.VIDEOBRIDGE_NOT_AVAILABLE);
152
         JitsiConferenceErrors.VIDEOBRIDGE_NOT_AVAILABLE);
153
     chatRoom.addListener(
153
     chatRoom.addListener(
154
         XMPPEvents.BRIDGE_DOWN,
154
         XMPPEvents.BRIDGE_DOWN,
155
-        () => Statistics.analytics.sendEvent(BRIDGE_DOWN));
155
+        () => Statistics.sendAnalytics(createBridgeDownEvent()));
156
 
156
 
157
     this.chatRoomForwarder.forward(XMPPEvents.RESERVATION_ERROR,
157
     this.chatRoomForwarder.forward(XMPPEvents.RESERVATION_ERROR,
158
         JitsiConferenceEvents.CONFERENCE_FAILED,
158
         JitsiConferenceEvents.CONFERENCE_FAILED,
190
 
190
 
191
     chatRoom.addListener(XMPPEvents.FOCUS_LEFT,
191
     chatRoom.addListener(XMPPEvents.FOCUS_LEFT,
192
         () => {
192
         () => {
193
-            Statistics.analytics.sendEvent(FOCUS_LEFT);
193
+            Statistics.sendAnalytics(createFocusLeftEvent());
194
             conference.eventEmitter.emit(
194
             conference.eventEmitter.emit(
195
                 JitsiConferenceEvents.CONFERENCE_FAILED,
195
                 JitsiConferenceEvents.CONFERENCE_FAILED,
196
                 JitsiConferenceErrors.FOCUS_LEFT);
196
                 JitsiConferenceErrors.FOCUS_LEFT);
197
         });
197
         });
198
 
198
 
199
-    const eventLogHandler
200
-        = reason => Statistics.sendEventToAll(`${CONFERENCE_ERROR_}.${reason}`);
201
-
202
     chatRoom.addListener(XMPPEvents.SESSION_ACCEPT_TIMEOUT,
199
     chatRoom.addListener(XMPPEvents.SESSION_ACCEPT_TIMEOUT,
203
         jingleSession => {
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
     this.chatRoomForwarder.forward(XMPPEvents.RECORDER_STATE_CHANGED,
207
     this.chatRoomForwarder.forward(XMPPEvents.RECORDER_STATE_CHANGED,
474
 
472
 
475
     rtc.addListener(RTCEvents.DATA_CHANNEL_OPEN, () => {
473
     rtc.addListener(RTCEvents.DATA_CHANNEL_OPEN, () => {
476
         const now = window.performance.now();
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
         conference.eventEmitter.emit(JitsiConferenceEvents.DATA_CHANNEL_OPENED);
483
         conference.eventEmitter.emit(JitsiConferenceEvents.DATA_CHANNEL_OPENED);
483
     });
484
     });

+ 2
- 0
JitsiConferenceEvents.js ファイルの表示

48
 /**
48
 /**
49
  * Indicates that the connection to the conference has been interrupted for some
49
  * Indicates that the connection to the conference has been interrupted for some
50
  * reason.
50
  * reason.
51
+ * XXX This is currently fired when the *ICE* connection is interrupted.
51
  */
52
  */
52
 export const CONNECTION_INTERRUPTED = 'conference.connectionInterrupted';
53
 export const CONNECTION_INTERRUPTED = 'conference.connectionInterrupted';
53
 
54
 
54
 /**
55
 /**
55
  * Indicates that the connection to the conference has been restored.
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
 export const CONNECTION_RESTORED = 'conference.connectionRestored';
59
 export const CONNECTION_RESTORED = 'conference.connectionRestored';
58
 
60
 

+ 14
- 9
JitsiConnection.js ファイルの表示

1
 import {
1
 import {
2
-    CONNECTION_DISCONNECTED_,
3
-    CONNECTION_FAILED_
2
+    CONNECTION_DISCONNECTED as ANALYTICS_CONNECTION_DISCONNECTED,
3
+    createConnectionFailedEvent
4
 } from './service/statistics/AnalyticsEvents';
4
 } from './service/statistics/AnalyticsEvents';
5
 import JitsiConference from './JitsiConference';
5
 import JitsiConference from './JitsiConference';
6
 import * as JitsiConnectionEvents from './JitsiConnectionEvents';
6
 import * as JitsiConnectionEvents from './JitsiConnectionEvents';
25
 
25
 
26
     this.addEventListener(JitsiConnectionEvents.CONNECTION_FAILED,
26
     this.addEventListener(JitsiConnectionEvents.CONNECTION_FAILED,
27
         (errType, msg) => {
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
     this.addEventListener(JitsiConnectionEvents.CONNECTION_DISCONNECTED,
32
     this.addEventListener(JitsiConnectionEvents.CONNECTION_DISCONNECTED,
35
             // we can see disconnects from normal tab closing of the browser
34
             // we can see disconnects from normal tab closing of the browser
36
             // and then there are no msgs, but we want to log only disconnects
35
             // and then there are no msgs, but we want to log only disconnects
37
             // when there is real error
36
             // when there is real error
37
+            // XXX Do we need the difference in handling between the log and
38
+            // analytics event here?
38
             if (msg) {
39
             if (msg) {
39
-                Statistics.analytics.sendEvent(
40
-                    `${CONNECTION_DISCONNECTED_}.${msg}`);
40
+                Statistics.sendAnalytics(
41
+                    ANALYTICS_CONNECTION_DISCONNECTED,
42
+                    { message: msg });
41
             }
43
             }
42
             Statistics.sendLog(
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 ファイルの表示

1
 /* global __filename */
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
 import AuthUtil from './modules/util/AuthUtil';
4
 import AuthUtil from './modules/util/AuthUtil';
11
 import * as ConnectionQualityEvents
5
 import * as ConnectionQualityEvents
12
     from './service/connectivity/ConnectionQualityEvents';
6
     from './service/connectivity/ConnectionQualityEvents';
34
 import RTCBrowserType from './modules/RTC/RTCBrowserType';
28
 import RTCBrowserType from './modules/RTC/RTCBrowserType';
35
 import RTCUIHelper from './modules/RTC/RTCUIHelper';
29
 import RTCUIHelper from './modules/RTC/RTCUIHelper';
36
 import ScriptUtil from './modules/util/ScriptUtil';
30
 import ScriptUtil from './modules/util/ScriptUtil';
37
-import Settings from './modules/settings/Settings';
38
 import Statistics from './modules/statistics/statistics';
31
 import Statistics from './modules/statistics/statistics';
39
 import * as VideoSIPGWConstants from './modules/videosipgw/VideoSIPGWConstants';
32
 import * as VideoSIPGWConstants from './modules/videosipgw/VideoSIPGWConstants';
40
 
33
 
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
                 window.connectionTimes['obtainPermissions.end']
275
                 window.connectionTimes['obtainPermissions.end']
283
                     = window.performance.now();
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
                 if (!RTC.options.disableAudioLevels) {
283
                 if (!RTC.options.disableAudioLevels) {
291
                     for (let i = 0; i < tracks.length; i++) {
284
                     for (let i = 0; i < tracks.length; i++) {
334
                             'Retry createLocalTracks with resolution',
327
                             'Retry createLocalTracks with resolution',
335
                             newResolution);
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
                         return this.createLocalTracks(options);
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
                     // User cancelled action is not really an error, so only
344
                     // User cancelled action is not really an error, so only
348
                     // log it as an event to avoid having conference classified
345
                     // log it as an event to avoid having conference classified
349
                     // as partially failed
346
                     // as partially failed
353
                     };
350
                     };
354
 
351
 
355
                     Statistics.sendLog(JSON.stringify(logObject));
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
                     // logs not found devices with just application log to cs
361
                     // logs not found devices with just application log to cs
360
                     const logObject = {
362
                     const logObject = {
361
                         id: 'usermedia_missing_device',
363
                         id: 'usermedia_missing_device',
363
                     };
365
                     };
364
 
366
 
365
                     Statistics.sendLog(JSON.stringify(logObject));
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
                 } else {
376
                 } else {
370
                     // Report gUM failed to the stats
377
                     // Report gUM failed to the stats
371
                     Statistics.sendGetUserMediaFailed(error);
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
                 window.connectionTimes['obtainPermissions.end']
388
                 window.connectionTimes['obtainPermissions.end']
466
 
473
 
467
     /* eslint-enable max-params */
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
      * Represents a hub/namespace for utility functionality which may be of
477
      * Represents a hub/namespace for utility functionality which may be of
479
      * interest to lib-jitsi-meet clients.
478
      * interest to lib-jitsi-meet clients.

+ 2
- 2
JitsiTrackEvents.js ファイルの表示

33
 export const TRACK_VIDEOTYPE_CHANGED = 'track.videoTypeChanged';
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
 export const NO_DATA_FROM_SOURCE = 'track.no_data_from_source';
39
 export const NO_DATA_FROM_SOURCE = 'track.no_data_from_source';

+ 14
- 10
modules/RTC/JitsiLocalTrack.js ファイルの表示

19
 import RTCEvents from '../../service/RTC/RTCEvents';
19
 import RTCEvents from '../../service/RTC/RTCEvents';
20
 import VideoType from '../../service/RTC/VideoType';
20
 import VideoType from '../../service/RTC/VideoType';
21
 import {
21
 import {
22
-    _NO_DATA_FROM_SOURCE,
23
-    _TRACK_UNMUTE
22
+    TRACK_UNMUTED,
23
+    createNoDataFromSourceEvent
24
 } from '../../service/statistics/AnalyticsEvents';
24
 } from '../../service/statistics/AnalyticsEvents';
25
 import Statistics from '../statistics/statistics';
25
 import Statistics from '../statistics/statistics';
26
 
26
 
32
  */
32
  */
33
 export default class JitsiLocalTrack extends JitsiTrack {
33
 export default class JitsiLocalTrack extends JitsiTrack {
34
     /**
34
     /**
35
-     * Constructs new JitsiLocalTrack instanse.
35
+     * Constructs new JitsiLocalTrack instance.
36
      *
36
      *
37
      * @constructor
37
      * @constructor
38
      * @param {Object} trackInfo
38
      * @param {Object} trackInfo
202
                         = setTimeout(_onNoDataFromSourceError, 3000);
202
                         = setTimeout(_onNoDataFromSourceError, 3000);
203
                     this._setHandler('track_unmute', () => {
203
                     this._setHandler('track_unmute', () => {
204
                         this._clearNoDataFromSourceMuteResources();
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
      */
245
      */
242
     _fireNoDataFromSourceEvent() {
246
     _fireNoDataFromSourceEvent() {
243
         this.emit(NO_DATA_FROM_SOURCE);
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
         if (this.isAudioTrack()) {
252
         if (this.isAudioTrack()) {
250
             log.isReceivingData = this._isReceivingData();
253
             log.isReceivingData = this._isReceivingData();
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
      * @returns {boolean} true if an issue is detected and false otherwise
721
      * @returns {boolean} true if an issue is detected and false otherwise
718
      */
722
      */

+ 11
- 7
modules/RTC/JitsiRemoteTrack.js ファイルの表示

1
-import { _TTFM_ } from '../../service/statistics/AnalyticsEvents';
1
+import { createTtfmEvent } from '../../service/statistics/AnalyticsEvents';
2
 import JitsiTrack from './JitsiTrack';
2
 import JitsiTrack from './JitsiTrack';
3
 import * as JitsiTrackEvents from '../../JitsiTrackEvents';
3
 import * as JitsiTrackEvents from '../../JitsiTrackEvents';
4
 import RTCBrowserType from './RTCBrowserType';
4
 import RTCBrowserType from './RTCBrowserType';
223
         const gumDuration
223
         const gumDuration
224
             = !isNaN(gumEnd) && !isNaN(gumStart) ? gumEnd - gumStart : 0;
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
         const ttfm = now
228
         const ttfm = now
227
             - (this.conference.getConnectionTimes()['session.initiate']
229
             - (this.conference.getConnectionTimes()['session.initiate']
228
-            - this.conference.getConnectionTimes()['muc.joined'])
230
+                - this.conference.getConnectionTimes()['muc.joined'])
229
             - gumDuration;
231
             - gumDuration;
230
 
232
 
231
         this.conference.getConnectionTimes()[`${type}.ttfm`] = ttfm;
233
         this.conference.getConnectionTimes()[`${type}.ttfm`] = ttfm;
232
         console.log(`(TIME) TTFM ${type}:\t`, ttfm);
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 ファイルの表示

9
           webkitRTCPeerConnection,
9
           webkitRTCPeerConnection,
10
           webkitURL
10
           webkitURL
11
 */
11
 */
12
-import { DEVICE_LIST } from '../../service/statistics/AnalyticsEvents';
12
+import { AVAILABLE_DEVICE } from '../../service/statistics/AnalyticsEvents';
13
 import CameraFacingMode from '../../service/RTC/CameraFacingMode';
13
 import CameraFacingMode from '../../service/RTC/CameraFacingMode';
14
 import EventEmitter from 'events';
14
 import EventEmitter from 'events';
15
 import { getLogger } from 'jitsi-meet-logger';
15
 import { getLogger } from 'jitsi-meet-logger';
547
  * @returns {void}
547
  * @returns {void}
548
  */
548
  */
549
 function sendDeviceListToAnalytics(deviceList) {
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 ファイルの表示

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
 import RTCBrowserType from '../RTC/RTCBrowserType';
8
 import RTCBrowserType from '../RTC/RTCBrowserType';
2
 import Settings from '../settings/Settings';
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
     constructor() {
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
 export default new AnalyticsAdapter();
327
 export default new AnalyticsAdapter();

+ 46
- 43
modules/statistics/AvgRTPStatsReporter.js ファイルの表示

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

+ 5
- 5
modules/statistics/CallStats.js ファイルの表示

465
      *
465
      *
466
      * @param {string} conferenceID the conference ID for which the feedback
466
      * @param {string} conferenceID the conference ID for which the feedback
467
      * will be reported.
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
      * user feedback
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
         if (CallStats.backend) {
473
         if (CallStats.backend) {
474
             CallStats.backend.sendUserFeedback(
474
             CallStats.backend.sendUserFeedback(
475
                 conferenceID, {
475
                 conferenceID, {
476
                     userID: CallStats.userID,
476
                     userID: CallStats.userID,
477
-                    overall: overallFeedback,
478
-                    comment: detailedFeedback
477
+                    overall,
478
+                    comment
479
                 });
479
                 });
480
         } else {
480
         } else {
481
             logger.error('Failed to submit feedback to CallStats - no backend');
481
             logger.error('Failed to submit feedback to CallStats - no backend');

+ 48
- 22
modules/statistics/statistics.js ファイルの表示

1
 import EventEmitter from 'events';
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
 import analytics from './AnalyticsAdapter';
4
 import analytics from './AnalyticsAdapter';
8
 import CallStats from './CallStats';
5
 import CallStats from './CallStats';
9
 import LocalStats from './LocalStatsCollector';
6
 import LocalStats from './LocalStatsCollector';
470
     if (instance) {
467
     if (instance) {
471
         instance.sendIceConnectionFailedEvent();
468
         instance.sendIceConnectionFailedEvent();
472
     }
469
     }
473
-    Statistics.analytics.sendEvent(ICE_FAILED);
474
 };
470
 };
475
 
471
 
476
 /**
472
 /**
682
 /**
678
 /**
683
  * Sends the given feedback through CallStats.
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
     Statistics.analytics.sendEvent(
686
     Statistics.analytics.sendEvent(
691
         FEEDBACK,
687
         FEEDBACK,
692
         {
688
         {
693
-            value: overall,
694
-            detailed
689
+            rating: overall,
690
+            comment
695
         });
691
         });
696
 };
692
 };
697
 
693
 
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 ファイルの表示

1325
     }
1325
     }
1326
 
1326
 
1327
     /**
1327
     /**
1328
-     *
1328
+     * TODO: Document
1329
      * @param iq
1329
      * @param iq
1330
      */
1330
      */
1331
     onMute(iq) {
1331
     onMute(iq) {
1334
         if (from !== this.focusMucJid) {
1334
         if (from !== this.focusMucJid) {
1335
             logger.warn('Ignored mute from non focus peer');
1335
             logger.warn('Ignored mute from non focus peer');
1336
 
1336
 
1337
-            return false;
1337
+            return;
1338
         }
1338
         }
1339
         const mute = $(iq).find('mute');
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 ファイルの表示

1
 /* global __filename, $ */
1
 /* global __filename, $ */
2
 
2
 
3
 import {
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
 } from '../../service/statistics/AnalyticsEvents';
6
 } from '../../service/statistics/AnalyticsEvents';
9
 import async from 'async';
7
 import async from 'async';
10
 import { getLogger } from 'jitsi-meet-logger';
8
 import { getLogger } from 'jitsi-meet-logger';
243
         this.lasticecandidate = false;
241
         this.lasticecandidate = false;
244
 
242
 
245
         // True if reconnect is in progress
243
         // True if reconnect is in progress
246
-        this.isreconnect = false;
244
+        this.isReconnect = false;
247
 
245
 
248
         // Set to true if the connection was ever stable
246
         // Set to true if the connection was ever stable
249
         this.wasstable = false;
247
         this.wasstable = false;
318
                 }
316
                 }
319
             } else if (!this._gatheringReported) {
317
             } else if (!this._gatheringReported) {
320
                 // End of gathering
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
                 this._gatheringReported = true;
327
                 this._gatheringReported = true;
329
             }
328
             }
330
             this.sendIceCandidate(candidate);
329
             this.sendIceCandidate(candidate);
373
                 `(TIME) ICE ${this.peerconnection.iceConnectionState}`
372
                 `(TIME) ICE ${this.peerconnection.iceConnectionState}`
374
                     + ` P2P? ${this.isP2P}:\t`,
373
                     + ` P2P? ${this.isP2P}:\t`,
375
                 now);
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
             this.room.eventEmitter.emit(
386
             this.room.eventEmitter.emit(
383
                 XMPPEvents.ICE_CONNECTION_STATE_CHANGED,
387
                 XMPPEvents.ICE_CONNECTION_STATE_CHANGED,
384
                 this,
388
                 this,
391
                 // Informs interested parties that the connection has been
395
                 // Informs interested parties that the connection has been
392
                 // restored.
396
                 // restored.
393
                 if (this.peerconnection.signalingState === 'stable') {
397
                 if (this.peerconnection.signalingState === 'stable') {
394
-                    if (this.isreconnect) {
398
+                    if (this.isReconnect) {
395
                         this.room.eventEmitter.emit(
399
                         this.room.eventEmitter.emit(
396
                             XMPPEvents.CONNECTION_RESTORED, this);
400
                             XMPPEvents.CONNECTION_RESTORED, this);
397
                     }
401
                     }
398
                 }
402
                 }
399
 
403
 
400
                 if (!this.wasConnected && this.wasstable) {
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
                     // Switch between ICE gathering and ICE checking whichever
415
                     // Switch between ICE gathering and ICE checking whichever
417
 
422
 
418
                     this.establishmentDuration = now - iceStarted;
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
                     this.wasConnected = true;
434
                     this.wasConnected = true;
426
                     this.room.eventEmitter.emit(
435
                     this.room.eventEmitter.emit(
427
                         XMPPEvents.CONNECTION_ESTABLISHED, this);
436
                         XMPPEvents.CONNECTION_ESTABLISHED, this);
428
                 }
437
                 }
429
-                this.isreconnect = false;
438
+                this.isReconnect = false;
430
                 break;
439
                 break;
431
             case 'disconnected':
440
             case 'disconnected':
432
                 if (this.closed) {
441
                 if (this.closed) {
433
                     break;
442
                     break;
434
                 }
443
                 }
435
-                this.isreconnect = true;
444
+                this.isReconnect = true;
436
 
445
 
437
                 // Informs interested parties that the connection has been
446
                 // Informs interested parties that the connection has been
438
                 // interrupted.
447
                 // interrupted.

+ 3
- 1
modules/xmpp/strophe.emuc.js ファイルの表示

158
     }
158
     }
159
 
159
 
160
     /**
160
     /**
161
-     *
161
+     * TODO: Document
162
      * @param iq
162
      * @param iq
163
      */
163
      */
164
     onMute(iq) {
164
     onMute(iq) {
165
         const from = iq.getAttribute('from');
165
         const from = iq.getAttribute('from');
166
         const room = this.rooms[Strophe.getBareJidFromJid(from)];
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
         if (!room) {
170
         if (!room) {
169
             return;
171
             return;
170
         }
172
         }

+ 20
- 16
modules/xmpp/strophe.jingle.js ファイルの表示

1
 /* global $, __filename */
1
 /* global $, __filename */
2
 
2
 
3
 import {
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
 } from '../../service/statistics/AnalyticsEvents';
7
 } from '../../service/statistics/AnalyticsEvents';
8
 import { getLogger } from 'jitsi-meet-logger';
8
 import { getLogger } from 'jitsi-meet-logger';
9
 import { $iq, Strophe } from 'strophe.js';
9
 import { $iq, Strophe } from 'strophe.js';
128
         }
128
         }
129
         const now = window.performance.now();
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
         // see http://xmpp.org/extensions/xep-0166.html#concepts-session
136
         // see http://xmpp.org/extensions/xep-0166.html#concepts-session
132
 
137
 
133
         switch (action) {
138
         switch (action) {
145
                     videoMuted === 'true');
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
             logger.info(
153
             logger.info(
154
                 `Marking session from ${fromJid
154
                 `Marking session from ${fromJid
155
                 } as ${isP2P ? '' : '*not*'} P2P`);
155
                 } as ${isP2P ? '' : '*not*'} P2P`);
169
 
169
 
170
             this.eventEmitter.emit(XMPPEvents.CALL_INCOMING,
170
             this.eventEmitter.emit(XMPPEvents.CALL_INCOMING,
171
                 sess, $(iq).find('>jingle'), now);
171
                 sess, $(iq).find('>jingle'), now);
172
-            Statistics.analytics.sendEvent(
173
-                SESSION_INITIATE_RECEIVED, { value: now });
174
             break;
172
             break;
175
         }
173
         }
176
         case 'session-accept': {
174
         case 'session-accept': {
204
         }
202
         }
205
         case 'transport-replace':
203
         case 'transport-replace':
206
             logger.info('(TIME) Start transport replace', now);
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
             sess.replaceTransport($(iq).find('>jingle'), () => {
212
             sess.replaceTransport($(iq).find('>jingle'), () => {
212
                 const successTime = window.performance.now();
213
                 const successTime = window.performance.now();
213
 
214
 
214
                 logger.info('(TIME) Transport replace success!', successTime);
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
             }, error => {
222
             }, error => {
219
                 GlobalOnErrorHandler.callErrorHandler(error);
223
                 GlobalOnErrorHandler.callErrorHandler(error);
220
                 logger.error('Transport replace failed', error);
224
                 logger.error('Transport replace failed', error);

+ 305
- 337
service/statistics/AnalyticsEvents.js ファイルの表示

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
  * TODO: document
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
+};

読み込み中…
キャンセル
保存