Bug 998989 - upload telemetry logs on Loop ICE failure. r=smaug, r=abr
authorJan-Ivar Bruaroey <jib@mozilla.com>
Mon, 09 Jun 2014 18:14:14 -0400
changeset 214346 b336c6598bf14dc67ccc9c2d8989964adcbde5a8
parent 214345 fb2ab91ddd34477e3e00eabee861935e3d5c6b5f
child 214347 f0e6af66a4cf4ae4180cb7385d9736504d2c1b15
push id3857
push userraliiev@mozilla.com
push dateTue, 02 Sep 2014 16:39:23 +0000
treeherdermozilla-beta@5638b907b505 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, abr
bugs998989
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 998989 - upload telemetry logs on Loop ICE failure. r=smaug, r=abr
browser/components/loop/MozLoopService.jsm
dom/media/PeerConnection.js
dom/media/RTCStatsReport.jsm
dom/media/moz.build
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -4,23 +4,28 @@
 
 "use strict";
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
-let console = (Cu.import("resource://gre/modules/devtools/Console.jsm", {})).console;
 
 this.EXPORTED_SYMBOLS = ["MozLoopService"];
 
+XPCOMUtils.defineLazyModuleGetter(this, "console",
+  "resource://gre/modules/devtools/Console.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "injectLoopAPI",
   "resource:///modules/loop/MozLoopAPI.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "convertToRTCStatsReport",
+  "resource://gre/modules/media/RTCStatsReport.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "Chat", "resource:///modules/Chat.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
                                   "resource://services-common/utils.js");
 
 XPCOMUtils.defineLazyModuleGetter(this, "CryptoUtils",
                                   "resource://services-crypto/utils.js");
 
@@ -28,26 +33,31 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://services-common/hawkclient.js");
 
 XPCOMUtils.defineLazyModuleGetter(this, "deriveHawkCredentials",
                                   "resource://services-common/hawkrequest.js");
 
 XPCOMUtils.defineLazyModuleGetter(this, "MozLoopPushHandler",
                                   "resource:///modules/loop/MozLoopPushHandler.jsm");
 
+XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
+                                   "@mozilla.org/uuid-generator;1",
+                                   "nsIUUIDGenerator");
+
 /**
  * Internal helper methods and state
  *
  * The registration is a two-part process. First we need to connect to
  * and register with the push server. Then we need to take the result of that
  * and register with the Loop server.
  */
 let MozLoopServiceInternal = {
   // The uri of the Loop server.
   loopServerUri: Services.prefs.getCharPref("loop.server"),
+  telemetryUri: Services.prefs.getCharPref("loop.telemetryURL"),
 
   // The current deferred for the registration process. This is set if in progress
   // or the registration was successful. This is null if a registration attempt was
   // unsuccessful.
   _registeredDeferred: null,
 
   /**
    * The initial delay for push registration. This ensures we don't start
@@ -299,25 +309,88 @@ let MozLoopServiceInternal = {
         map[key] = {};
       map[key][property] = string.value;
     }
 
     return this._localizedStrings = map;
   },
 
   /**
+   * Uploads telemetry logs to telemetryServer
+   *
+   * @param {Object} pc The peerConnection in question.
+   */
+  uploadTelemetry: function(window, pc) {
+    if (!this.telemetryUri) {
+      return;
+    }
+    window.WebrtcGlobalInformation.getAllStats(allStats => {
+      let internalFormat = allStats.reports[0]; // filtered on pc.id
+      window.WebrtcGlobalInformation.getLogging('', logs => {
+
+        // We have stats and logs.
+        // Prepare payload from https://wiki.mozilla.org/Loop/Telemetry
+
+        let ai = Services.appinfo;
+        let report = convertToRTCStatsReport(internalFormat);
+
+        let payload = {
+          ver: 1,
+          info: {
+            appUpdateChannel: ai.defaultUpdateChannel,
+            appBuildID: ai.appBuildID,
+            appName: ai.name,
+            appVersion: ai.version,
+            reason: "loop",
+            OS: ai.OS,
+            version: Services.sysinfo.getProperty("version")
+          },
+          report: "ice failure",
+          connectionstate: pc.iceConnectionState,
+          stats: report,
+          localSdp: internalFormat.localSdp,
+          remoteSdp: internalFormat.remoteSdp,
+          log: ""
+        };
+        logs.forEach(s => { payload.log += s + "\n"; });
+
+        let uuid = uuidgen.generateUUID().toString();
+        uuid = uuid.substr(1,uuid.length-2); // remove uuid curly braces
+
+        let url = this.telemetryUri;
+        url += ((url.substr(-1) == "/")? "":"/") + uuid + "/loop/" +
+                ai.OS + "/" + ai.version + "/" + ai.defaultUpdateChannel + "/" +
+                ai.appBuildID;
+
+        // Send payload.
+        //TODO: gzip!
+
+        let xhr = new window.XMLHttpRequest();
+        xhr.open("POST", url, true);
+        xhr.setRequestHeader("Content-Type", 'application/json');
+        xhr.onreadystatechange = function() {
+          if (xhr.readyState == 4 && xhr.status == 200) {
+            console.log("Failed to upload telemetry logs: " + xhr.responseText);
+          }
+        };
+        xhr.send(JSON.stringify(payload));
+        console.log("Uploading telemetry logs to " + url);
+      });
+    }, pc.id);
+  },
+
+  /**
    * Opens the chat window
    *
    * @param {Object} contentWindow The window to open the chat window in, may
    *                               be null.
    * @param {String} title The title of the chat window.
    * @param {String} url The page to load in the chat window.
-   * @param {String} mode May be "minimized" or undefined.
    */
-  openChatWindow: function(contentWindow, title, url, mode) {
+  openChatWindow: function(contentWindow, title, url) {
     // So I guess the origin is the loop server!?
     let origin = this.loopServerUri;
     url = url.spec || url;
 
     let callback = chatbox => {
       // We need to use DOMContentLoaded as otherwise the injection will happen
       // in about:blank and then get lost.
       // Sadly we can't use chatbox.promiseChatLoaded() as promise chaining
@@ -327,18 +400,40 @@ let MozLoopServiceInternal = {
         return;
       }
 
       chatbox.addEventListener("DOMContentLoaded", function loaded(event) {
         if (event.target != chatbox.contentDocument) {
           return;
         }
         chatbox.removeEventListener("DOMContentLoaded", loaded, true);
-        injectLoopAPI(chatbox.contentWindow);
-      }, true);
+
+        let window = chatbox.contentWindow;
+        injectLoopAPI(window);
+
+        let ourID = window.QueryInterface(Ci.nsIInterfaceRequestor)
+            .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
+
+        let onPCLifecycleChange = (pc, winID, type) => {
+          if (winID != ourID) {
+            return;
+          }
+          if (type == "iceconnectionstatechange") {
+            switch(pc.iceConnectionState) {
+              case "failed":
+              case "disconnected":
+                this.uploadTelemetry(window, pc);
+                break;
+            }
+          }
+        };
+
+        let pc_static = new window.mozRTCPeerConnectionStatic();
+        pc_static.registerPeerConnectionLifecycleCallback(onPCLifecycleChange);
+      }.bind(this), true);
     };
 
     Chat.open(contentWindow, origin, title, url, undefined, undefined, callback);
   }
 };
 
 /**
  * Public API
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -6,16 +6,18 @@
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PeerConnectionIdp",
   "resource://gre/modules/media/PeerConnectionIdp.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "convertToRTCStatsReport",
+  "resource://gre/modules/media/RTCStatsReport.jsm");
 
 const PC_CONTRACT = "@mozilla.org/dom/peerconnection;1";
 const PC_OBS_CONTRACT = "@mozilla.org/dom/peerconnectionobserver;1";
 const PC_ICE_CONTRACT = "@mozilla.org/dom/rtcicecandidate;1";
 const PC_SESSION_CONTRACT = "@mozilla.org/dom/rtcsessiondescription;1";
 const PC_MANAGER_CONTRACT = "@mozilla.org/dom/peerconnectionmanager;1";
 const PC_STATS_CONTRACT = "@mozilla.org/dom/rtcstatsreport;1";
 const PC_IDENTITY_CONTRACT = "@mozilla.org/dom/rtcidentityassertion;1";
@@ -182,26 +184,17 @@ function RTCStatsReport(win, dict) {
   function appendStats(stats, report) {
     stats.forEach(function(stat) {
         report[stat.id] = stat;
       });
   }
 
   this._win = win;
   this._pcid = dict.pcid;
-  this._report = {};
-  appendStats(dict.inboundRTPStreamStats, this._report);
-  appendStats(dict.outboundRTPStreamStats, this._report);
-  appendStats(dict.mediaStreamTrackStats, this._report);
-  appendStats(dict.mediaStreamStats, this._report);
-  appendStats(dict.transportStats, this._report);
-  appendStats(dict.iceComponentStats, this._report);
-  appendStats(dict.iceCandidatePairStats, this._report);
-  appendStats(dict.iceCandidateStats, this._report);
-  appendStats(dict.codecStats, this._report);
+  this._report = convertToRTCStatsReport(dict);
 }
 RTCStatsReport.prototype = {
   classDescription: "RTCStatsReport",
   classID: PC_STATS_CID,
   contractID: PC_STATS_CONTRACT,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
 
   // TODO: Change to use webidl getters once available (Bug 952122)
new file mode 100644
--- /dev/null
+++ b/dom/media/RTCStatsReport.jsm
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["convertToRTCStatsReport"];
+
+function convertToRTCStatsReport(dict) {
+  function appendStats(stats, report) {
+    stats.forEach(function(stat) {
+        report[stat.id] = stat;
+      });
+  }
+  let report = {};
+  appendStats(dict.inboundRTPStreamStats, report);
+  appendStats(dict.outboundRTPStreamStats, report);
+  appendStats(dict.mediaStreamTrackStats, report);
+  appendStats(dict.mediaStreamStats, report);
+  appendStats(dict.transportStats, report);
+  appendStats(dict.iceComponentStats, report);
+  appendStats(dict.iceCandidatePairStats, report);
+  appendStats(dict.iceCandidateStats, report);
+  appendStats(dict.codecStats, report);
+  return report;
+}
+
+this.convertToRTCStatsReport = convertToRTCStatsReport;
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -45,16 +45,17 @@ EXTRA_COMPONENTS += [
     'PeerConnection.manifest',
 ]
 
 JS_MODULES_PATH = 'modules/media'
 
 EXTRA_JS_MODULES += [
     'IdpProxy.jsm',
     'PeerConnectionIdp.jsm',
+    'RTCStatsReport.jsm',
 ]
 
 if CONFIG['MOZ_B2G']:
     EXPORTS.mozilla += [
         'MediaPermissionGonk.h',
     ]
     SOURCES += [
         'MediaPermissionGonk.cpp',