Bug 1046234 - Add more DevTools Telemetry measures (display size etc.) r=pbrosset, r=gijs
authorMichael Ratcliffe <mratcliffe@mozilla.com>
Fri, 13 Mar 2015 11:52:45 +0000
changeset 251870 ec213bf54bcfdff4719f2b717fb75dbde8b98575
parent 251869 ad7229b4a3d454ef48f1e98214e8a630f998b758
child 251871 cda55565ed35629f720ecd5e66786a5fc8fd5146
push id7860
push userjlund@mozilla.com
push dateMon, 30 Mar 2015 18:46:02 +0000
treeherdermozilla-aurora@8ac636cd51f3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspbrosset, gijs
bugs1046234
milestone39.0a1
Bug 1046234 - Add more DevTools Telemetry measures (display size etc.) r=pbrosset, r=gijs
browser/devtools/framework/gDevTools.jsm
browser/devtools/framework/toolbox.js
toolkit/components/telemetry/Histograms.json
toolkit/xre/nsAppRunner.cpp
xpcom/system/nsIXULRuntime.idl
--- a/browser/devtools/framework/gDevTools.jsm
+++ b/browser/devtools/framework/gDevTools.jsm
@@ -22,16 +22,23 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource:///modules/CustomizableUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
                                   "resource://gre/modules/devtools/dbg-server.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
                                   "resource://gre/modules/devtools/dbg-client.jsm");
 
 const EventEmitter = devtools.require("devtools/toolkit/event-emitter");
+const Telemetry = devtools.require("devtools/shared/telemetry");
+
+const TABS_OPEN_PEAK_HISTOGRAM = "DEVTOOLS_TABS_OPEN_PEAK_LINEAR";
+const TABS_OPEN_AVG_HISTOGRAM = "DEVTOOLS_TABS_OPEN_AVERAGE_LINEAR";
+const TABS_PINNED_PEAK_HISTOGRAM = "DEVTOOLS_TABS_PINNED_PEAK_EXPONENTIAL";
+const TABS_PINNED_AVG_HISTOGRAM = "DEVTOOLS_TABS_PINNED_AVERAGE_EXPONENTIAL";
+
 const FORBIDDEN_IDS = new Set(["toolbox", ""]);
 const MAX_ORDINAL = 99;
 
 const bundle = Services.strings.createBundle("chrome://browser/locale/devtools/toolbox.properties");
 
 /**
  * DevTools is a class that represents a set of developer tools, it holds a
  * set of tools and keeps track of open toolboxes in the browser.
@@ -462,16 +469,33 @@ DevTools.prototype = {
   closeToolbox: function DT_closeToolbox(target) {
     let toolbox = this._toolboxes.get(target);
     if (toolbox == null) {
       return promise.resolve(false);
     }
     return toolbox.destroy().then(() => true);
   },
 
+  _pingTelemetry: function() {
+    let mean = function(arr) {
+      if (arr.length === 0) {
+        return 0;
+      }
+
+      let total = arr.reduce((a, b) => a + b);
+      return Math.ceil(total / arr.length);
+    };
+
+    let tabStats = gDevToolsBrowser._tabStats;
+    this._telemetry.log(TABS_OPEN_PEAK_HISTOGRAM, tabStats.peakOpen);
+    this._telemetry.log(TABS_OPEN_AVG_HISTOGRAM, mean(tabStats.histOpen));
+    this._telemetry.log(TABS_PINNED_PEAK_HISTOGRAM, tabStats.peakPinned);
+    this._telemetry.log(TABS_PINNED_AVG_HISTOGRAM, mean(tabStats.histPinned));
+  },
+
   /**
    * Called to tear down a tools provider.
    */
   _teardown: function DT_teardown() {
     for (let [target, toolbox] of this._toolboxes) {
       toolbox.destroy();
     }
   },
@@ -482,16 +506,19 @@ DevTools.prototype = {
   destroy: function() {
     Services.obs.removeObserver(this.destroy, "quit-application");
     Services.obs.removeObserver(this._teardown, "devtools-unloaded");
 
     for (let [key, tool] of this.getToolDefinitionMap()) {
       this.unregisterTool(key, true);
     }
 
+    this._pingTelemetry();
+    this._telemetry = null;
+
     // Cleaning down the toolboxes: i.e.
     //   for (let [target, toolbox] of this._toolboxes) toolbox.destroy();
     // Is taken care of by the gDevToolsBrowser.forgetBrowserWindow
   },
 
   /**
    * Iterator that yields each of the toolboxes.
    */
@@ -517,16 +544,23 @@ this.gDevTools = gDevTools;
  */
 let gDevToolsBrowser = {
   /**
    * A record of the windows whose menus we altered, so we can undo the changes
    * as the window is closed
    */
   _trackedBrowserWindows: new Set(),
 
+  _tabStats: {
+    peakOpen: 0,
+    peakPinned: 0,
+    histOpen: [],
+    histPinned: []
+  },
+
   /**
    * This function is for the benefit of Tools:DevToolbox in
    * browser/base/content/browser-sets.inc and should not be used outside
    * of there
    */
   toggleToolboxCommand: function(gBrowser) {
     let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
     let toolbox = gDevTools.getToolbox(target);
@@ -814,19 +848,22 @@ let gDevToolsBrowser = {
     gDevToolsBrowser._trackedBrowserWindows.add(win);
     gDevToolsBrowser._addAllToolsToMenu(win.document);
 
     if (this._isFirebugInstalled()) {
       let broadcaster = win.document.getElementById("devtoolsMenuBroadcaster_DevToolbox");
       broadcaster.removeAttribute("key");
     }
 
-    let tabContainer = win.document.getElementById("tabbrowser-tabs")
-    tabContainer.addEventListener("TabSelect",
-                                  gDevToolsBrowser._updateMenuCheckbox, false);
+    let tabContainer = win.document.getElementById("tabbrowser-tabs");
+    tabContainer.addEventListener("TabSelect", this, false);
+    tabContainer.addEventListener("TabOpen", this, false);
+    tabContainer.addEventListener("TabClose", this, false);
+    tabContainer.addEventListener("TabPinned", this, false);
+    tabContainer.addEventListener("TabUnpinned", this, false);
   },
 
   /**
    * Add a <key> to <keyset id="devtoolsKeyset">.
    * Appending a <key> element is not always enough. The <keyset> needs
    * to be detached and reattached to make sure the <key> is taken into
    * account (see bug 832984).
    *
@@ -1248,19 +1285,50 @@ let gDevToolsBrowser = {
 
     // Destroy toolboxes for closed window
     for (let [target, toolbox] of gDevTools._toolboxes) {
       if (toolbox.frame && toolbox.frame.ownerDocument.defaultView == win) {
         toolbox.destroy();
       }
     }
 
-    let tabContainer = win.document.getElementById("tabbrowser-tabs")
-    tabContainer.removeEventListener("TabSelect",
-                                     gDevToolsBrowser._updateMenuCheckbox, false);
+    let tabContainer = win.document.getElementById("tabbrowser-tabs");
+    tabContainer.removeEventListener("TabSelect", this, false);
+    tabContainer.removeEventListener("TabOpen", this, false);
+    tabContainer.removeEventListener("TabClose", this, false);
+    tabContainer.removeEventListener("TabPinned", this, false);
+    tabContainer.removeEventListener("TabUnpinned", this, false);
+  },
+
+  handleEvent: function(event) {
+    switch (event.type) {
+      case "TabOpen":
+      case "TabClose":
+      case "TabPinned":
+      case "TabUnpinned":
+        let open = 0;
+        let pinned = 0;
+
+        for (let win of this._trackedBrowserWindows) {
+          let tabContainer = win.gBrowser.tabContainer;
+          let numPinnedTabs = tabContainer.tabbrowser._numPinnedTabs;
+          let numTabs = tabContainer.itemCount - numPinnedTabs;
+
+          open += numTabs;
+          pinned += numPinnedTabs;
+        }
+
+        this._tabStats.histOpen.push(open);
+        this._tabStats.histPinned.push(pinned);
+        this._tabStats.peakOpen = Math.max(open, this._tabStats.peakOpen);
+        this._tabStats.peakPinned = Math.max(pinned, this._tabStats.peakPinned);
+      break;
+      case "TabSelect":
+        gDevToolsBrowser._updateMenuCheckbox();
+    }
   },
 
   /**
    * All browser windows have been closed, tidy up remaining objects.
    */
   destroy: function() {
     gDevTools.off("toolbox-ready", gDevToolsBrowser._connectToProfiler);
     Services.prefs.removeObserver("devtools.", gDevToolsBrowser);
--- a/browser/devtools/framework/toolbox.js
+++ b/browser/devtools/framework/toolbox.js
@@ -5,16 +5,19 @@
 "use strict";
 
 const MAX_ORDINAL = 99;
 const ZOOM_PREF = "devtools.toolbox.zoomValue";
 const SPLITCONSOLE_ENABLED_PREF = "devtools.toolbox.splitconsoleEnabled";
 const SPLITCONSOLE_HEIGHT_PREF = "devtools.toolbox.splitconsoleHeight";
 const MIN_ZOOM = 0.5;
 const MAX_ZOOM = 2;
+const OS_HISTOGRAM = "DEVTOOLS_OS_ENUMERATED_PER_USER";
+const OS_IS_64_BITS = "DEVTOOLS_OS_IS_64_BITS_PER_USER";
+const SCREENSIZE_HISTOGRAM = "DEVTOOLS_SCREEN_RESOLUTION_ENUMERATED_PER_USER";
 
 let {Cc, Ci, Cu} = require("chrome");
 let {Promise: promise} = require("resource://gre/modules/Promise.jsm");
 let EventEmitter = require("devtools/toolkit/event-emitter");
 let Telemetry = require("devtools/shared/telemetry");
 let {getHighlighterUtils} = require("devtools/framework/toolbox-highlighter-utils");
 let HUDService = require("devtools/webconsole/hudservice");
 let {showDoorhanger} = require("devtools/shared/doorhanger");
@@ -44,16 +47,29 @@ loader.lazyGetter(this, "toolboxStrings"
     }
   };
 });
 
 loader.lazyGetter(this, "Selection", () => require("devtools/framework/selection").Selection);
 loader.lazyGetter(this, "InspectorFront", () => require("devtools/server/actors/inspector").InspectorFront);
 loader.lazyRequireGetter(this, "DevToolsUtils", "devtools/toolkit/DevToolsUtils");
 
+XPCOMUtils.defineLazyGetter(this, "screenManager", () => {
+  return Cc["@mozilla.org/gfx/screenmanager;1"].getService(Ci.nsIScreenManager);
+});
+
+XPCOMUtils.defineLazyGetter(this, "oscpu", () => {
+  return Cc["@mozilla.org/network/protocol;1?name=http"]
+           .getService(Ci.nsIHttpProtocolHandler).oscpu;
+});
+
+XPCOMUtils.defineLazyGetter(this, "is64Bit", () => {
+  return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo).is64Bit;
+});
+
 // White-list buttons that can be toggled to prevent adding prefs for
 // addons that have manually inserted toolbarbuttons into DOM.
 // (By default, supported target is only local tab)
 const ToolboxButtons = [
   { id: "command-button-pick",
     isTargetSupported: target =>
       target.getTrait("highlightable")
   },
@@ -329,17 +345,17 @@ Toolbox.prototype = {
         this.webconsolePanel = this.doc.querySelector("#toolbox-panel-webconsole");
         this.webconsolePanel.height =
           Services.prefs.getIntPref(SPLITCONSOLE_HEIGHT_PREF);
         this.webconsolePanel.addEventListener("resize",
           this._saveSplitConsoleHeight);
 
         let buttonsPromise = this._buildButtons();
 
-        this._telemetry.toolOpened("toolbox");
+        this._pingTelemetry();
 
         this.selectTool(this._defaultToolId).then(panel => {
 
           // Wait until the original tool is selected so that the split
           // console input will receive focus.
           let splitConsolePromise = promise.resolve();
           if (Services.prefs.getBoolPref(SPLITCONSOLE_ENABLED_PREF)) {
             splitConsolePromise = this.openSplitConsole();
@@ -354,25 +370,35 @@ Toolbox.prototype = {
             deferred.resolve();
           }, deferred.reject);
         });
       };
 
       // Load the toolbox-level actor fronts and utilities now
       this._target.makeRemote().then(() => {
         iframe.setAttribute("src", this._URL);
-        iframe.setAttribute("aria-label", toolboxStrings("toolbox.label"))
+        iframe.setAttribute("aria-label", toolboxStrings("toolbox.label"));
         let domHelper = new DOMHelpers(iframe.contentWindow);
         domHelper.onceDOMReady(domReady);
       });
 
       return deferred.promise;
     }).then(null, console.error.bind(console));
   },
 
+  _pingTelemetry: function() {
+    this._telemetry.toolOpened("toolbox");
+
+    this._telemetry.logOncePerBrowserVersion(OS_HISTOGRAM,
+                                             this._getOsCpu());
+    this._telemetry.logOncePerBrowserVersion(OS_IS_64_BITS, is64Bit ? 1 : 0);
+    this._telemetry.logOncePerBrowserVersion(SCREENSIZE_HISTOGRAM,
+                                             this._getScreenDimensions());
+  },
+
   /**
    * Because our panels are lazy loaded this is a good place to watch for
    * "pref-changed" events.
    * @param  {String} event
    *         The event type, "pref-changed".
    * @param  {Object} data
    *         {
    *           newValue: The new value
@@ -1570,16 +1596,51 @@ Toolbox.prototype = {
    * Get the toolbox's notification box
    *
    * @return The notification box element.
    */
   getNotificationBox: function() {
     return this.doc.getElementById("toolbox-notificationbox");
   },
 
+  _getScreenDimensions: function() {
+    let width = {};
+    let height = {};
+
+    screenManager.primaryScreen.GetRect({}, {}, width, height);
+    let dims = width.value + "x" + height.value;
+
+    if (width.value < 800 || height.value < 600) return 0;
+    if (dims === "800x600")   return 1;
+    if (dims === "1024x768")  return 2;
+    if (dims === "1280x800")  return 3;
+    if (dims === "1280x1024") return 4;
+    if (dims === "1366x768")  return 5;
+    if (dims === "1440x900")  return 6;
+    if (dims === "1920x1080") return 7;
+    if (dims === "2560×1440") return 8;
+    if (dims === "2560×1600") return 9;
+    if (dims === "2880x1800") return 10;
+    if (width.value > 2880 || height.value > 1800) return 12;
+
+    return 11; // Other dimension such as a VM.
+  },
+
+  _getOsCpu: function() {
+    if (oscpu.contains("NT 5.1") || oscpu.contains("NT 5.2")) return 0;
+    if (oscpu.contains("NT 6.0")) return 1;
+    if (oscpu.contains("NT 6.1")) return 2;
+    if (oscpu.contains("NT 6.2")) return 3;
+    if (oscpu.contains("NT 6.3")) return 4;
+    if (oscpu.contains("OS X"))   return 5;
+    if (oscpu.contains("Linux"))  return 6;
+
+    return 12; // Other OS.
+  },
+
   /**
    * Destroy the current host, and remove event listeners from its frame.
    *
    * @return {promise} to be resolved when the host is destroyed.
    */
   destroyHost: function() {
     // The host iframe's contentDocument may already be gone.
     if (this.doc) {
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -6541,16 +6541,62 @@
     "kind": "boolean",
     "description": "Was WebIDE's play button used during this runtime connection?"
   },
   "DEVTOOLS_WEBIDE_CONNECTION_DEBUG_USED": {
     "expires_in_version": "never",
     "kind": "boolean",
     "description": "Was WebIDE's debug button used during this runtime connection?"
   },
+  "DEVTOOLS_OS_ENUMERATED_PER_USER": {
+    "expires_in_version": "never",
+    "kind": "enumerated",
+    "n_values": 13,
+    "description": "OS of DevTools user (0:Windows XP, 1:Windows Vista, 2:Windows 7, 3:Windows 8, 4:Windows 8.1, 5:OSX, 6:Linux 7:reserved, 8:reserved, 9:reserved, 10:reserved, 11:reserved, 12:other)"
+  },
+  "DEVTOOLS_OS_IS_64_BITS_PER_USER": {
+    "expires_in_version": "never",
+    "kind": "enumerated",
+    "n_values": 3,
+    "description": "OS bit size of DevTools user (0:32bit, 1:64bit, 2:128bit)"
+    },
+  "DEVTOOLS_SCREEN_RESOLUTION_ENUMERATED_PER_USER": {
+    "expires_in_version": "never",
+    "kind": "enumerated",
+    "n_values": 13,
+    "description": "Screen resolution of DevTools user (0:lower, 1:800x600, 2:1024x768, 3:1280x800, 4:1280x1024, 5:1366x768, 6:1440x900, 7:1920x1080, 8:2560×1440, 9:2560×1600, 10:2880x1800, 11:other, 12:higher)"
+  },
+  "DEVTOOLS_TABS_OPEN_PEAK_LINEAR": {
+    "expires_in_version": "never",
+    "kind": "linear",
+    "high": "101",
+    "n_buckets": 100,
+    "description": "The peak number of open tabs in all windows for a session for devtools users."
+  },
+  "DEVTOOLS_TABS_OPEN_AVERAGE_EXPONENTIAL": {
+    "expires_in_version": "never",
+    "kind": "exponential",
+    "high": "101",
+    "n_buckets": "100",
+    "description": "The mean number of open tabs in all windows for a session for devtools users."
+  },
+  "DEVTOOLS_TABS_PINNED_PEAK_EXPONENTIAL": {
+    "expires_in_version": "never",
+    "kind": "exponential",
+    "high": "101",
+    "n_buckets": "100",
+    "description": "The peak number of pinned tabs (app tabs) in all windows for a session for devtools users."
+  },
+  "DEVTOOLS_TABS_PINNED_AVERAGE_LINEAR": {
+    "expires_in_version": "never",
+    "kind": "linear",
+    "high": "101",
+    "n_buckets": "100",
+    "description": "The mean number of pinned tabs (app tabs) in all windows for a session for devtools users."
+  },
   "BROWSER_IS_USER_DEFAULT": {
     "expires_in_version": "never",
     "kind": "boolean",
     "description": "The result of the startup default desktop browser check."
   },
   "BROWSER_IS_ASSIST_DEFAULT": {
     "expires_in_version": "never",
     "kind": "boolean",
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -905,16 +905,27 @@ nsXULAppInfo::GetAccessibilityIsUIA(bool
        ::GetModuleHandleW(L"uiautomationcore"))) {
     *aResult = true;
   }
 #endif
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsXULAppInfo::GetIs64Bit(bool* aResult)
+{
+#ifdef HAVE_64BIT_BUILD
+  *aResult = true;
+#else
+  *aResult = false;
+#endif
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsXULAppInfo::EnsureContentProcess()
 {
   if (XRE_GetProcessType() != GeckoProcessType_Default)
     return NS_ERROR_NOT_AVAILABLE;
 
   nsRefPtr<ContentParent> unused = ContentParent::GetNewOrUsedBrowserProcess();
   return NS_OK;
 }
--- a/xpcom/system/nsIXULRuntime.idl
+++ b/xpcom/system/nsIXULRuntime.idl
@@ -18,17 +18,17 @@ bool BrowserTabsRemoteAutostart();
 
 /**
  * Provides information about the XUL runtime.
  * @status UNSTABLE - This interface is not frozen and will probably change in
  *                    future releases. If you need this functionality to be
  *                    stable/frozen, please contact Benjamin Smedberg.
  */
 
-[scriptable, uuid(fb861ca6-426f-4edf-844e-bbabec9bbc1a)]
+[scriptable, uuid(5754b56e-f392-426d-aec0-3ba7c49aff32)]
 interface nsIXULRuntime : nsISupports
 {
   /**
    * Whether the application was launched in safe mode.
    */
   readonly attribute boolean inSafeMode;
 
   /**
@@ -105,16 +105,21 @@ interface nsIXULRuntime : nsISupports
 
   /**
    * Indicates if the active accessibility client is UIA.
    * DO NOT USE! This is temporary and will be removed.
    */
   readonly attribute boolean accessibilityIsUIA;
 
   /**
+   * Indicates whether the current Firefox build is 64-bit.
+   */
+  readonly attribute boolean is64Bit;
+
+  /**
    * Signal the apprunner to invalidate caches on the next restart.
    * This will cause components to be autoregistered and all
    * fastload data to be re-created.
    */
   void invalidateCachesOnRestart();
 
   /**
    * Starts a child process. This method is intented to pre-start a