Bug 998989 - save as telemetry ping file for upload by telemetry. r=vladan
authorJan-Ivar Bruaroey <jib@mozilla.com>
Thu, 10 Jul 2014 12:33:13 -0400
changeset 193551 f0e6af66a4cf4ae4180cb7385d9736504d2c1b15
parent 193550 b336c6598bf14dc67ccc9c2d8989964adcbde5a8
child 193552 ff281663a545ba282d171e1b6709d2c23b83153c
push id27123
push userryanvm@gmail.com
push dateFri, 11 Jul 2014 20:35:05 +0000
treeherdermozilla-central@84bd8d9f4256 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvladan
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 - save as telemetry ping file for upload by telemetry. r=vladan
browser/components/loop/MozLoopService.jsm
browser/components/loop/MozLoopWorker.js
browser/components/loop/moz.build
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -4,16 +4,17 @@
 
 "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");
+Cu.import("resource://gre/modules/osfile.jsm", this);
 
 this.EXPORTED_SYMBOLS = ["MozLoopService"];
 
 XPCOMUtils.defineLazyModuleGetter(this, "console",
   "resource://gre/modules/devtools/Console.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "injectLoopAPI",
   "resource:///modules/loop/MozLoopAPI.jsm");
@@ -47,17 +48,16 @@ XPCOMUtils.defineLazyServiceGetter(this,
  *
  * 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
@@ -309,75 +309,77 @@ let MozLoopServiceInternal = {
         map[key] = {};
       map[key][property] = string.value;
     }
 
     return this._localizedStrings = map;
   },
 
   /**
-   * Uploads telemetry logs to telemetryServer
+   * Saves loop logs to the saved-telemetry-pings folder.
    *
    * @param {Object} pc The peerConnection in question.
    */
-  uploadTelemetry: function(window, pc) {
-    if (!this.telemetryUri) {
-      return;
-    }
+  stageForTelemetryUpload: function(window, pc) {
     window.WebrtcGlobalInformation.getAllStats(allStats => {
       let internalFormat = allStats.reports[0]; // filtered on pc.id
       window.WebrtcGlobalInformation.getLogging('', logs => {
+        let report = convertToRTCStatsReport(internalFormat);
+        let logStr = "";
+        logs.forEach(s => { logStr += s + "\n"; });
 
         // We have stats and logs.
-        // Prepare payload from https://wiki.mozilla.org/Loop/Telemetry
+
+        // Create worker job. ping = saved telemetry ping file header + payload
+        //
+        // Prepare payload according to 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);
+        let directory = OS.Path.join(OS.Constants.Path.profileDir,
+                                     "saved-telemetry-pings");
+        let job = {
+          directory: directory,
+          filename: uuid + ".json",
+          ping: {
+            reason: "loop",
+            slug: uuid,
+            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: logStr
+            }
           }
         };
-        xhr.send(JSON.stringify(payload));
-        console.log("Uploading telemetry logs to " + url);
+
+        // Send job to worker to do saving to
+        // disk for pickup by telemetry on next startup, which then uploads it.
+
+        let worker = new ChromeWorker("MozLoopWorker.js");
+        worker.onmessage = function(e) {
+          console.log(e.data.ok ?
+            "Successfully staged loop report for telemetry upload." :
+            ("Failed to stage loop report. Error: " + e.data.fail));
+        }
+        worker.postMessage(job);
       });
     }, pc.id);
   },
 
   /**
    * Opens the chat window
    *
    * @param {Object} contentWindow The window to open the chat window in, may
@@ -415,17 +417,20 @@ let MozLoopServiceInternal = {
         let onPCLifecycleChange = (pc, winID, type) => {
           if (winID != ourID) {
             return;
           }
           if (type == "iceconnectionstatechange") {
             switch(pc.iceConnectionState) {
               case "failed":
               case "disconnected":
-                this.uploadTelemetry(window, pc);
+                if (Services.telemetry.canSend ||
+                    Services.prefs.getBoolPref("toolkit.telemetry.test")) {
+                  this.stageForTelemetryUpload(window, pc);
+                }
                 break;
             }
           }
         };
 
         let pc_static = new window.mozRTCPeerConnectionStatic();
         pc_static.registerPeerConnectionLifecycleCallback(onPCLifecycleChange);
       }.bind(this), true);
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/MozLoopWorker.js
@@ -0,0 +1,54 @@
+/* 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/. */
+
+/**
+ * A worker dedicated to loop-report sanitation and writing for MozLoopService.
+ */
+
+"use strict";
+
+importScripts("resource://gre/modules/osfile.jsm");
+
+let File = OS.File;
+let Encoder = new TextEncoder();
+let Counter = 0;
+
+const MAX_LOOP_LOGS = 5;
+/**
+ * Communications with the controller.
+ *
+ * Accepts messages:
+ * { path: filepath, ping: data }
+ *
+ * Sends messages:
+ * { ok: true }
+ * { fail: serialized_form_of_OS.File.Error }
+ */
+
+onmessage = function(e) {
+  if (++Counter > MAX_LOOP_LOGS) {
+    postMessage({
+      fail: "Maximum " + MAX_LOOP_LOGS + "loop reports reached for this session"
+    });
+    return;
+  }
+
+  let directory = e.data.directory;
+  let filename = e.data.filename;
+  let ping = e.data.ping;
+
+  let pingStr = JSON.stringify(ping);
+
+  // Save to disk
+  let array = Encoder.encode(pingStr);
+  try {
+    File.makeDir(directory,
+                 { unixMode: OS.Constants.S_IRWXU, ignoreExisting: true });
+    File.writeAtomic(OS.Path.join(directory, filename), array);
+    postMessage({ ok: true });
+  } catch (ex if ex instanceof File.Error) {
+    // Instances of OS.File.Error know how to serialize themselves
+    postMessage({fail: File.Error.toMsg(ex)});
+  }
+};
--- a/browser/components/loop/moz.build
+++ b/browser/components/loop/moz.build
@@ -9,9 +9,10 @@ JAR_MANIFESTS += ['jar.mn']
 JS_MODULES_PATH = 'modules/loop'
 
 XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
 
 EXTRA_JS_MODULES += [
     'MozLoopAPI.jsm',
     'MozLoopPushHandler.jsm',
     'MozLoopService.jsm',
+    'MozLoopWorker.js',
 ]