Bug 668392 - Include enabled addons + persona in telemetry r=Mossop
authorTaras Glek <tglek@mozilla.com>
Thu, 11 Aug 2011 14:19:40 -0700
changeset 74263 fdffde7c3b14935e8d7701f15738ebf45bd8d7e2
parent 74262 4b4b359e77e48709bdd71a03cc6318b7d4fc232d
child 74264 792eb2aff31526cd7b168441130d36da03ee4cae
push id1149
push usertglek@mozilla.com
push dateThu, 11 Aug 2011 21:20:06 +0000
treeherdermozilla-inbound@792eb2aff315 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMossop
bugs668392
milestone8.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 668392 - Include enabled addons + persona in telemetry r=Mossop
toolkit/components/telemetry/TelemetryPing.js
toolkit/components/telemetry/tests/unit/test_TelemetryPing.js
toolkit/mozapps/extensions/XPIProvider.jsm
--- a/toolkit/components/telemetry/TelemetryPing.js
+++ b/toolkit/components/telemetry/TelemetryPing.js
@@ -37,16 +37,17 @@
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/LightweightThemeManager.jsm");
 
 // When modifying the payload in incompatible ways, please bump this version number
 const PAYLOAD_VERSION = 1;
 
 const PREF_SERVER = "toolkit.telemetry.server";
 const PREF_ENABLED = "toolkit.telemetry.enabled";
 // Do not gather data more than once a minute
 const TELEMETRY_INTERVAL = 60;
@@ -216,16 +217,64 @@ TelemetryPing.prototype = {
     if (!h) {
       h = Telemetry.getHistogramById(id);
       this._histograms[name] = h;
     }
     h.add(val);
   },
 
   /**
+   * Descriptive metadata
+   * 
+   * @param  reason
+   *         The reason for the telemetry ping, this will be included in the
+   *         returned metadata,
+   * @return The metadata as a JS object
+   */
+  getMetadata: function getMetadata(reason) {
+    let ai = Services.appinfo;
+    let ret = {
+      reason: reason,
+      OS: ai.OS,
+      appID: ai.ID,
+      appVersion: ai.version,
+      appName: ai.name,
+      appBuildID: ai.appBuildID,
+      platformBuildID: ai.platformBuildID,
+    };
+
+    // sysinfo fields are not always available, get what we can.
+    let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2);
+    let fields = ["cpucount", "memsize", "arch", "version", "device", "manufacturer", "hardware"];
+    for each (let field in fields) {
+      let value;
+      try {
+        value = sysInfo.getProperty(field);
+      } catch (e) {
+        continue
+      }
+      if (field == "memsize") {
+        // Send RAM size in megabytes. Rounding because sysinfo doesn't
+        // always provide RAM in multiples of 1024.
+        value = Math.round(value / 1024 / 1024)
+      }
+      ret[field] = value
+    }
+
+    let theme = LightweightThemeManager.currentTheme;
+    if (theme)
+      ret.persona = theme.id;
+
+    if (this._addons)
+      ret.addons = this._addons;
+
+    return ret;
+  },
+
+  /**
    * Pull values from about:memory into corresponding histograms
    */
   gatherMemory: function gatherMemory() {
     let mgr;
     try {
       mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
             getService(Ci.nsIMemoryReporterManager);
     } catch (e) {
@@ -281,20 +330,21 @@ TelemetryPing.prototype = {
   /**
    * Send data to the server. Record success/send-time in histograms
    */
   send: function send(reason, server) {
     // populate histograms one last time
     this.gatherMemory();
     let payload = {
       ver: PAYLOAD_VERSION,
-      info: getMetadata(reason),
+      info: this.getMetadata(reason),
       simpleMeasurements: getSimpleMeasurements(),
       histograms: getHistograms()
     };
+
     let isTestPing = (reason == "test-ping");
     // Generate a unique id once per session so the server can cope with duplicate submissions.
     // Use a deterministic url for testing.
     if (!this._path)
       this._path = "/submit/telemetry/" + (isTestPing ? reason : generateUUID());
     
     let hping = Telemetry.getHistogramById("TELEMETRY_PING");
     let hsuccess = Telemetry.getHistogramById("TELEMETRY_SUCCESS");
@@ -390,16 +440,19 @@ TelemetryPing.prototype = {
   /**
    * This observer drives telemetry.
    */
   observe: function (aSubject, aTopic, aData) {
     // Allows to change the server for testing
     var server = this._server;
 
     switch (aTopic) {
+    case "Add-ons":
+      this._addons = aData;
+      break;
     case "profile-after-change":
       this.setup();
       break;
     case "profile-before-change":
       this.uninstall();
       break;
     case "idle":
       this.gatherMemory();
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryPing.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryPing.js
@@ -5,27 +5,29 @@
  * 
  * Telemetry code keeps histograms of past telemetry pings. The first
  * ping populates these histograms. One of those histograms is then
  * checked in the second request.
  */
 
 do_load_httpd_js();
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/LightweightThemeManager.jsm");
 
 const PATH = "/submit/telemetry/test-ping";
 const SERVER = "http://localhost:4444";
 const IGNORE_HISTOGRAM = "test::ignore_me";
 
 const BinaryInputStream = Components.Constructor(
   "@mozilla.org/binaryinputstream;1",
   "nsIBinaryInputStream",
   "setInputStream");
 
 var httpserver = new nsHttpServer();
+var gFinished = false;
 
 function telemetry_ping () {
   const TelemetryPing = Cc["@mozilla.org/base/telemetry-ping;1"].getService(Ci.nsIObserver);
   TelemetryPing.observe(null, "test-ping", SERVER);
 }
 
 function nonexistentServerObserver(aSubject, aTopic, aData) {
   Services.obs.removeObserver(nonexistentServerObserver, aTopic);
@@ -41,37 +43,22 @@ function nonexistentServerObserver(aSubj
 function telemetryObserver(aSubject, aTopic, aData) {
   Services.obs.removeObserver(telemetryObserver, aTopic);
   httpserver.registerPathHandler(PATH, checkHistograms);
   const Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
   Telemetry.newHistogram(IGNORE_HISTOGRAM, 1, 2, 3, Telemetry.HISTOGRAM_BOOLEAN);
   telemetry_ping();
 }
 
-function run_test() {
-  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
-  Services.obs.addObserver(nonexistentServerObserver, "telemetry-test-xhr-complete", false);
-  telemetry_ping();
-  // spin the event loop
-  do_test_pending();
-}
-
-function readBytesFromInputStream(inputStream, count) {
-  if (!count) {
-    count = inputStream.available();
-  }
-  return new BinaryInputStream(inputStream).readBytes(count);
-}
-
 function checkHistograms(request, response) {
   // do not need the http server anymore
   httpserver.stop(do_test_finished);
   let s = request.bodyInputStream
   let payload = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON)
-                                             .decode(readBytesFromInputStream(s))
+                                             .decodeFromStream(s, s.available())
 
   do_check_eq(request.getHeader("content-type"), "application/json; charset=UTF-8");
   do_check_true(payload.simpleMeasurements.uptime >= 0)
 
   // get rid of the non-deterministic field
   const expected_info = {
     reason: "test-ping",
     OS: "XPCShell", 
@@ -97,16 +84,17 @@ function checkHistograms(request, respon
     bucket_count: 3,
     histogram_type: 2,
     values: {0:1, 1:1, 2:0},
     sum: 1
   }
   let tc = payload.histograms[TELEMETRY_SUCCESS]
   do_check_eq(uneval(tc), 
               uneval(expected_tc));
+  gFinished = true;
 }
 
 // copied from toolkit/mozapps/extensions/test/xpcshell/head_addons.js
 const XULAPPINFO_CONTRACTID = "@mozilla.org/xre/app-info;1";
 const XULAPPINFO_CID = Components.ID("{c763b610-9d49-455a-bbd2-ede71682a1ac}");
 
 function createAppInfo(id, name, version, platformVersion) {
   gAppInfo = {
@@ -147,8 +135,39 @@ function createAppInfo(id, name, version
         throw Components.results.NS_ERROR_NO_AGGREGATION;
       return gAppInfo.QueryInterface(iid);
     }
   };
   var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
   registrar.registerFactory(XULAPPINFO_CID, "XULAppInfo",
                             XULAPPINFO_CONTRACTID, XULAppInfoFactory);
 }
+
+function dummyTheme(id) {
+  return {
+    id: id,
+    name: Math.random().toString(),
+    headerURL: "http://lwttest.invalid/a.png",
+    footerURL: "http://lwttest.invalid/b.png",
+    textcolor: Math.random().toString(),
+    accentcolor: Math.random().toString()
+  };
+}
+
+function run_test() {
+  // Addon manager needs a profile directory
+  do_get_profile();
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
+  // try to make LightweightThemeManager do stuff
+  let gInternalManager = Cc["@mozilla.org/addons/integration;1"]
+                         .getService(Ci.nsIObserver)
+                         .QueryInterface(Ci.nsITimerCallback);
+
+  gInternalManager.observe(null, "addons-startup", null);
+  LightweightThemeManager.currentTheme = dummyTheme("1234");
+
+  Services.obs.addObserver(nonexistentServerObserver, "telemetry-test-xhr-complete", false);
+  telemetry_ping();
+  // spin the event loop
+  do_test_pending();
+  // ensure that test runs to completion
+  do_register_cleanup(function () do_check_true(gFinished))
+ }
--- a/toolkit/mozapps/extensions/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/XPIProvider.jsm
@@ -1733,16 +1733,19 @@ var XPIProvider = {
     let data = this.enabledAddons;
     for (let id in this.bootstrappedAddons)
       data += (data ? "," : "") + id + ":" + this.bootstrappedAddons[id].version;
 
     try {
       Services.appinfo.annotateCrashReport("Add-ons", data);
     }
     catch (e) { }
+    
+    const TelemetryPing = Cc["@mozilla.org/base/telemetry-ping;1"].getService(Ci.nsIObserver);
+    TelemetryPing.observe(null, "Add-ons", data);
   },
 
   /**
    * Gets the add-on states for an install location.
    * This function may be expensive because of the recursiveLastModifiedTime call.
    *
    * @param  location
    *         The install location to retrieve the add-on states for