Bug 1271304 - Measure the tabs/window open events and max number of tab/window per subsession. r=Gijs, r=gfritzsche, data-review=bsmedberg
authorAlessio Placitelli <alessio.placitelli@gmail.com>
Thu, 12 May 2016 09:30:00 +0200
changeset 345404 63eeb207ec658de26ec797a9f85a9b1daa7ad158
parent 345403 fba169c6ead923420d03932778ef5d88cbf2f0d7
child 345405 9de4826f4e6b23b1ab16aad29d3d8d6690331101
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs, gfritzsche
bugs1271304
milestone50.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 1271304 - Measure the tabs/window open events and max number of tab/window per subsession. r=Gijs, r=gfritzsche, data-review=bsmedberg MozReview-Commit-ID: 6vYfoEb8VvB
browser/components/nsBrowserGlue.js
browser/modules/BrowserUsageTelemetry.jsm
browser/modules/moz.build
toolkit/components/telemetry/Scalars.yaml
toolkit/components/telemetry/TelemetrySession.jsm
toolkit/components/telemetry/docs/main-ping.rst
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -97,16 +97,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource:///modules/Feeds.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SelfSupportBackend",
                                   "resource:///modules/SelfSupportBackend.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
                                   "resource:///modules/sessionstore/SessionStore.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUsageTelemetry",
+                                  "resource:///modules/BrowserUsageTelemetry.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
                                   "resource:///modules/BrowserUITelemetry.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
                                   "resource://gre/modules/AsyncShutdown.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerParent",
                                   "resource://gre/modules/LoginManagerParent.jsm");
@@ -776,16 +779,17 @@ BrowserGlue.prototype = {
     DirectoryLinksProvider.init();
     NewTabUtils.init();
     NewTabUtils.links.addProvider(DirectoryLinksProvider);
     AboutNewTab.init();
 
     NewTabMessages.init();
 
     SessionStore.init();
+    BrowserUsageTelemetry.init();
     BrowserUITelemetry.init();
     ContentSearch.init();
     FormValidationHandler.init();
 
     ContentClick.init();
     RemotePrompt.init();
     Feeds.init();
     ContentPrefServiceParent.init();
@@ -1169,16 +1173,17 @@ BrowserGlue.prototype = {
     try {
       let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
                          .getService(Ci.nsIAppStartup);
       appStartup.trackStartupCrashEnd();
     } catch (e) {
       Cu.reportError("Could not end startup crash tracking in quit-application-granted: " + e);
     }
 
+    BrowserUsageTelemetry.uninit();
     SelfSupportBackend.uninit();
     NewTabMessages.uninit();
 
     CaptivePortalWatcher.uninit();
 
     AboutNewTab.uninit();
     webrtcUI.uninit();
     FormValidationHandler.uninit();
new file mode 100644
--- /dev/null
+++ b/browser/modules/BrowserUsageTelemetry.jsm
@@ -0,0 +1,175 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* 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 = ["BrowserUsageTelemetry"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+// Observed topic names.
+const WINDOWS_RESTORED_TOPIC = "sessionstore-windows-restored";
+const TELEMETRY_SUBSESSIONSPLIT_TOPIC = "internal-telemetry-after-subsession-split";
+const DOMWINDOW_OPENED_TOPIC = "domwindowopened";
+const DOMWINDOW_CLOSED_TOPIC = "domwindowclosed";
+
+// Probe names.
+const MAX_TAB_COUNT_SCALAR_NAME = "browser.engagement.max_concurrent_tab_count";
+const MAX_WINDOW_COUNT_SCALAR_NAME = "browser.engagement.max_concurrent_window_count";
+const TAB_OPEN_EVENT_COUNT_SCALAR_NAME = "browser.engagement.tab_open_event_count";
+const WINDOW_OPEN_EVENT_COUNT_SCALAR_NAME = "browser.engagement.window_open_event_count";
+
+function getOpenTabsAndWinsCounts() {
+  let tabCount = 0;
+  let winCount = 0;
+
+  let browserEnum = Services.wm.getEnumerator("navigator:browser");
+  while (browserEnum.hasMoreElements()) {
+    let win = browserEnum.getNext();
+    winCount++;
+    tabCount += win.gBrowser.tabs.length;
+  }
+
+  return { tabCount, winCount };
+}
+
+let BrowserUsageTelemetry = {
+  init() {
+    Services.obs.addObserver(this, WINDOWS_RESTORED_TOPIC, false);
+  },
+
+  /**
+   * Handle subsession splits in the parent process.
+   */
+  afterSubsessionSplit() {
+    // Scalars just got cleared due to a subsession split. We need to set the maximum
+    // concurrent tab and window counts so that they reflect the correct value for the
+    // new subsession.
+    const counts = getOpenTabsAndWinsCounts();
+    Services.telemetry.scalarSetMaximum(MAX_TAB_COUNT_SCALAR_NAME, counts.tabCount);
+    Services.telemetry.scalarSetMaximum(MAX_WINDOW_COUNT_SCALAR_NAME, counts.winCount);
+  },
+
+  uninit() {
+    Services.obs.removeObserver(this, DOMWINDOW_OPENED_TOPIC, false);
+    Services.obs.removeObserver(this, DOMWINDOW_CLOSED_TOPIC, false);
+    Services.obs.removeObserver(this, TELEMETRY_SUBSESSIONSPLIT_TOPIC, false);
+    Services.obs.removeObserver(this, WINDOWS_RESTORED_TOPIC, false);
+  },
+
+  observe(subject, topic, data) {
+    switch(topic) {
+      case WINDOWS_RESTORED_TOPIC:
+        this._setupAfterRestore();
+        break;
+      case DOMWINDOW_OPENED_TOPIC:
+        this._onWindowOpen(subject);
+        break;
+      case DOMWINDOW_CLOSED_TOPIC:
+        this._unregisterWindow(subject);
+        break;
+      case TELEMETRY_SUBSESSIONSPLIT_TOPIC:
+        this.afterSubsessionSplit();
+        break;
+    }
+  },
+
+  handleEvent(event) {
+    switch(event.type) {
+      case "TabOpen":
+        this._onTabOpen();
+        break;
+    }
+  },
+
+  /**
+   * This gets called shortly after the SessionStore has finished restoring
+   * windows and tabs. It counts the open tabs and adds listeners to all the
+   * windows.
+   */
+  _setupAfterRestore() {
+    // Make sure to catch new chrome windows and subsession splits.
+    Services.obs.addObserver(this, DOMWINDOW_OPENED_TOPIC, false);
+    Services.obs.addObserver(this, DOMWINDOW_CLOSED_TOPIC, false);
+    Services.obs.addObserver(this, TELEMETRY_SUBSESSIONSPLIT_TOPIC, false);
+
+    // Attach the tabopen handlers to the existing Windows.
+    let browserEnum = Services.wm.getEnumerator("navigator:browser");
+    while (browserEnum.hasMoreElements()) {
+      this._registerWindow(browserEnum.getNext());
+    }
+
+    // Get the initial tab and windows max counts.
+    const counts = getOpenTabsAndWinsCounts();
+    Services.telemetry.scalarSetMaximum(MAX_TAB_COUNT_SCALAR_NAME, counts.tabCount);
+    Services.telemetry.scalarSetMaximum(MAX_WINDOW_COUNT_SCALAR_NAME, counts.winCount);
+  },
+
+  /**
+   * Adds listeners to a single chrome window.
+   */
+  _registerWindow(win) {
+    win.addEventListener("TabOpen", this, true);
+  },
+
+  /**
+   * Removes listeners from a single chrome window.
+   */
+  _unregisterWindow(win) {
+    // Ignore non-browser windows.
+    if (!(win instanceof Ci.nsIDOMWindow) ||
+        win.document.documentElement.getAttribute("windowtype") != "navigator:browser") {
+      return;
+    }
+
+    win.removeEventListener("TabOpen", this, true);
+  },
+
+  /**
+   * Updates the tab counts.
+   * @param {Number} [newTabCount=0] The count of the opened tabs across all windows. This
+   *        is computed manually if not provided.
+   */
+  _onTabOpen(tabCount = 0) {
+    // Use the provided tab count if available. Otherwise, go on and compute it.
+    tabCount = tabCount || getOpenTabsAndWinsCounts().tabCount;
+    // Update the "tab opened" count and its maximum.
+    Services.telemetry.scalarAdd(TAB_OPEN_EVENT_COUNT_SCALAR_NAME, 1);
+    Services.telemetry.scalarSetMaximum(MAX_TAB_COUNT_SCALAR_NAME, tabCount);
+  },
+
+  /**
+   * Tracks the window count and registers the listeners for the tab count.
+   * @param{Object} win The window object.
+   */
+  _onWindowOpen(win) {
+    // Make sure to have a |nsIDOMWindow|.
+    if (!(win instanceof Ci.nsIDOMWindow)) {
+      return;
+    }
+
+    let onLoad = () => {
+      win.removeEventListener("load", onLoad, false);
+
+      // Ignore non browser windows.
+      if (win.document.documentElement.getAttribute("windowtype") != "navigator:browser") {
+        return;
+      }
+
+      this._registerWindow(win);
+      // Track the window open event and check the maximum.
+      const counts = getOpenTabsAndWinsCounts();
+      Services.telemetry.scalarAdd(WINDOW_OPEN_EVENT_COUNT_SCALAR_NAME, 1);
+      Services.telemetry.scalarSetMaximum(MAX_WINDOW_COUNT_SCALAR_NAME, counts.winCount);
+
+      // We won't receive the "TabOpen" event for the first tab within a new window.
+      // Account for that.
+      this._onTabOpen(counts.tabCount);
+    };
+    win.addEventListener("load", onLoad, false);
+  },
+};
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -9,16 +9,17 @@ XPCSHELL_TESTS_MANIFESTS += [
     'test/unit/social/xpcshell.ini',
     'test/xpcshell/xpcshell.ini',
 ]
 
 EXTRA_JS_MODULES += [
     'AboutHome.jsm',
     'AboutNewTab.jsm',
     'BrowserUITelemetry.jsm',
+    'BrowserUsageTelemetry.jsm',
     'CaptivePortalWatcher.jsm',
     'CastingApps.jsm',
     'Chat.jsm',
     'ContentClick.jsm',
     'ContentCrashHandlers.jsm',
     'ContentLinkHandler.jsm',
     'ContentObservers.jsm',
     'ContentSearch.jsm',
--- a/toolkit/components/telemetry/Scalars.yaml
+++ b/toolkit/components/telemetry/Scalars.yaml
@@ -65,8 +65,59 @@ telemetry.test:
     bug_numbers:
       - 1276190
     description: A testing scalar; not meant to be touched.
     expires: never
     kind: uint
     notification_emails:
       - telemetry-client-dev@mozilla.com
     release_channel_collection: opt-out
+
+# The following section contains the browser engagement scalars.
+browser.engagement:
+  max_concurrent_tab_count:
+    bug_numbers:
+      - 1271304
+    description: >
+      The count of maximum number of tabs open during a subsession,
+      across all windows, including tabs in private windows and restored
+      at startup.
+    expires: "55"
+    kind: uint
+    notification_emails:
+      - rweiss@mozilla.com
+    release_channel_collection: opt-out
+
+  tab_open_event_count:
+    bug_numbers:
+      - 1271304
+    description: >
+      The count of tab open events per subsession, across all windows, after the
+      session has been restored. This includes tab open events from private windows.
+    expires: "55"
+    kind: uint
+    notification_emails:
+      - rweiss@mozilla.com
+    release_channel_collection: opt-out
+
+  max_concurrent_window_count:
+    bug_numbers:
+      - 1271304
+    description: >
+      The count of maximum number of browser windows open during a subsession. This
+      includes private windows and the ones opened when starting the browser.
+    expires: "55"
+    kind: uint
+    notification_emails:
+      - rweiss@mozilla.com
+    release_channel_collection: opt-out
+
+  window_open_event_count:
+    bug_numbers:
+      - 1271304
+    description: >
+      The count of browser window open events per subsession, after the session
+      has been restored. The count includes the private windows.
+    expires: "55"
+    kind: uint
+    notification_emails:
+      - rweiss@mozilla.com
+    release_channel_collection: opt-out
--- a/toolkit/components/telemetry/TelemetrySession.jsm
+++ b/toolkit/components/telemetry/TelemetrySession.jsm
@@ -1346,16 +1346,20 @@ var Impl = {
       Telemetry.getHistogramById("TELEMETRY_ASSEMBLE_PAYLOAD_EXCEPTION").add(1);
       throw ex;
     } finally {
       if (!Utils.isContentProcess && clearSubsession) {
         this.startNewSubsession();
         // Persist session data to disk (don't wait until it completes).
         let sessionData = this._getSessionDataObject();
         TelemetryStorage.saveSessionData(sessionData);
+
+        // Notify that there was a subsession split in the parent process. This is an
+        // internal topic and is only meant for internal Telemetry usage.
+        Services.obs.notifyObservers(null, "internal-telemetry-after-subsession-split", null);
       }
     }
 
     return payload;
   },
 
   /**
    * Send data to the server. Record success/send-time in histograms
--- a/toolkit/components/telemetry/docs/main-ping.rst
+++ b/toolkit/components/telemetry/docs/main-ping.rst
@@ -10,16 +10,18 @@ This ping is triggered by different scen
 * ``aborted-session`` - this ping is regularly saved to disk (every 5 minutes), overwriting itself, and deleted at shutdown. If a previous aborted session ping is found at startup, it gets sent to the server. The first aborted-session ping is generated as soon as Telemetry starts
 * ``environment-change`` - the :doc:`environment` changed, so the session measurements got reset and a new subsession starts
 * ``shutdown`` - triggered when the browser session ends
 * ``daily`` - a session split triggered in 24h hour intervals at local midnight. If an ``environment-change`` ping is generated by the time it should be sent, the daily ping is rescheduled for the next midnight
 * ``saved-session`` - the *"classic"* Telemetry payload with measurements covering the whole browser session (only submitted for a transition period)
 
 Most reasons lead to a session split, initiating a new *subsession*. We reset important measurements for those subsessions.
 
+After a new subsession split, the ``internal-telemetry-after-subsession-split`` topic is notified to all the observers. *This is an internal topic and is only meant for internal Telemetry usage.*
+
 *Note:* ``saved-session`` is sent with a different ping type (``saved-session``, not ``main``), but otherwise has the same format as discussed here.
 
 Structure::
 
     {
       version: 4,
 
       info: {