Bug 1338522 - Add telemetry for UI responsiveness during import of profile data from another browser. r=francois,Gijs
authorDão Gottwald <dao@mozilla.com>
Sat, 18 Feb 2017 00:01:50 +0100
changeset 343764 23391448faad86360d6eba8269146ba2a00ddee9
parent 343763 259591bde104bef38510868164bc1f32055fbdfc
child 343765 0e1e91d221373947551b268ad3dde6d90ff0465e
push id87200
push usercbook@mozilla.com
push dateMon, 20 Feb 2017 13:09:46 +0000
treeherdermozilla-inbound@55cdebd17264 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfrancois, Gijs
bugs1338522
milestone54.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 1338522 - Add telemetry for UI responsiveness during import of profile data from another browser. r=francois,Gijs MozReview-Commit-ID: Cmh0rhZEPDo
browser/components/migration/MigrationUtils.jsm
toolkit/components/telemetry/Histograms.json
toolkit/modules/ResponsivenessMonitor.jsm
toolkit/modules/moz.build
--- a/browser/components/migration/MigrationUtils.jsm
+++ b/browser/components/migration/MigrationUtils.jsm
@@ -23,16 +23,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils",
                                   "resource://gre/modules/BookmarkHTMLUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
                                   "resource://gre/modules/LoginHelper.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                   "resource://gre/modules/PromiseUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ResponsivenessMonitor",
+                                  "resource://gre/modules/ResponsivenessMonitor.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
                                   "resource://gre/modules/Sqlite.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
                                   "resource://gre/modules/TelemetryStopwatch.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
                                   "resource://gre/modules/WindowsRegistry.jsm");
 
 var gMigrators = null;
@@ -207,17 +209,17 @@ this.MigratorPrototype = {
     let resources = this._getMaybeCachedResources(aProfile);
     if (!resources) {
       return [];
     }
     let types = resources.map(r => r.type);
     return types.reduce((a, b) => { a |= b; return a }, 0);
   },
 
-  getKey: function MP_getKey() {
+  getBrowserKey: function MP_getBrowserKey() {
     return this.contractID.match(/\=([^\=]+)$/)[1];
   },
 
   /**
    * DO NOT OVERRIDE - After deCOMing migration, the UI will just call
    * migrate for each resource.
    *
    * @see nsIBrowserProfileMigrator
@@ -232,50 +234,74 @@ this.MigratorPrototype = {
 
     // Used to periodically give back control to the main-thread loop.
     let unblockMainThread = function() {
       return new Promise(resolve => {
         Services.tm.mainThread.dispatch(resolve, Ci.nsIThread.DISPATCH_NORMAL);
       });
     };
 
-    let getHistogramForResourceType = resourceType => {
+    let getHistogramIdForResourceType = (resourceType, template) => {
       if (resourceType == MigrationUtils.resourceTypes.HISTORY) {
-        return "FX_MIGRATION_HISTORY_IMPORT_MS";
+        return template.replace("*", "HISTORY");
       }
       if (resourceType == MigrationUtils.resourceTypes.BOOKMARKS) {
-        return "FX_MIGRATION_BOOKMARKS_IMPORT_MS";
+        return template.replace("*", "BOOKMARKS");
       }
       if (resourceType == MigrationUtils.resourceTypes.PASSWORDS) {
-        return "FX_MIGRATION_LOGINS_IMPORT_MS";
+        return template.replace("*", "LOGINS");
       }
       return null;
     };
-    let maybeStartTelemetryStopwatch = (resourceType) => {
-      let histogram = getHistogramForResourceType(resourceType);
-      if (histogram) {
-        TelemetryStopwatch.startKeyed(histogram, this.getKey());
+
+    let browserKey = this.getBrowserKey();
+
+    let maybeStartTelemetryStopwatch = resourceType => {
+      let histogramId = getHistogramIdForResourceType(resourceType, "FX_MIGRATION_*_IMPORT_MS");
+      if (histogramId) {
+        TelemetryStopwatch.startKeyed(histogramId, browserKey);
       }
+      return histogramId;
     };
-    let maybeStopTelemetryStopwatch = (resourceType) => {
-      let histogram = getHistogramForResourceType(resourceType);
-      if (histogram) {
-        TelemetryStopwatch.finishKeyed(histogram, this.getKey());
+
+    let maybeStartResponsivenessMonitor = resourceType => {
+      let responsivenessMonitor;
+      let responsivenessHistogramId =
+        getHistogramIdForResourceType(resourceType, "FX_MIGRATION_*_JANK_MS");
+      if (responsivenessHistogramId) {
+        responsivenessMonitor = new ResponsivenessMonitor();
+      }
+      return {responsivenessMonitor, responsivenessHistogramId};
+    };
+
+    let maybeFinishResponsivenessMonitor = (responsivenessMonitor, histogramId) => {
+      if (responsivenessMonitor) {
+        let accumulatedDelay = responsivenessMonitor.finish();
+        if (histogramId) {
+          try {
+            Services.telemetry.getKeyedHistogramById(histogramId)
+                    .add(browserKey, accumulatedDelay);
+          } catch (ex) {
+            Cu.reportError(histogramId + ": " + ex);
+          }
+        }
       }
     };
 
     let collectQuantityTelemetry = () => {
-      try {
-        for (let resourceType of Object.keys(MigrationUtils._importQuantities)) {
-          let histogramId =
-            "FX_MIGRATION_" + resourceType.toUpperCase() + "_QUANTITY";
-          let histogram = Services.telemetry.getKeyedHistogramById(histogramId);
-          histogram.add(this.getKey(), MigrationUtils._importQuantities[resourceType]);
+      for (let resourceType of Object.keys(MigrationUtils._importQuantities)) {
+        let histogramId =
+          "FX_MIGRATION_" + resourceType.toUpperCase() + "_QUANTITY";
+        try {
+          Services.telemetry.getKeyedHistogramById(histogramId)
+                  .add(browserKey, MigrationUtils._importQuantities[resourceType]);
+        } catch (ex) {
+          Cu.reportError(histogramId + ": " + ex);
         }
-      } catch (ex) { /* Telemetry is exception-happy */ }
+      }
     };
 
     // Called either directly or through the bookmarks import callback.
     let doMigrate = Task.async(function*() {
       let resourcesGroupedByItems = new Map();
       resources.forEach(function(resource) {
         if (!resourcesGroupedByItems.has(resource.type)) {
           resourcesGroupedByItems.set(resource.type, new Set());
@@ -292,31 +318,38 @@ this.MigratorPrototype = {
 
       for (let resourceType of Object.keys(MigrationUtils._importQuantities)) {
         MigrationUtils._importQuantities[resourceType] = 0;
       }
       notify("Migration:Started");
       for (let [migrationType, itemResources] of resourcesGroupedByItems) {
         notify("Migration:ItemBeforeMigrate", migrationType);
 
-        maybeStartTelemetryStopwatch(migrationType);
+        let stopwatchHistogramId = maybeStartTelemetryStopwatch(migrationType);
+
+        let {responsivenessMonitor, responsivenessHistogramId} =
+          maybeStartResponsivenessMonitor(migrationType);
 
         let itemSuccess = false;
         for (let res of itemResources) {
           let completeDeferred = PromiseUtils.defer();
           let resourceDone = function(aSuccess) {
             itemResources.delete(res);
             itemSuccess |= aSuccess;
             if (itemResources.size == 0) {
               notify(itemSuccess ?
                      "Migration:ItemAfterMigrate" : "Migration:ItemError",
                      migrationType);
               resourcesGroupedByItems.delete(migrationType);
 
-              maybeStopTelemetryStopwatch(migrationType);
+              if (stopwatchHistogramId) {
+                TelemetryStopwatch.finishKeyed(stopwatchHistogramId, browserKey);
+              }
+
+              maybeFinishResponsivenessMonitor(responsivenessMonitor, responsivenessHistogramId);
 
               if (resourcesGroupedByItems.size == 0) {
                 collectQuantityTelemetry();
                 notify("Migration:Ended");
               }
             }
             completeDeferred.resolve();
           };
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -5109,16 +5109,49 @@
     "expires_in_version": "57",
     "kind": "exponential",
     "n_buckets": 70,
     "high": 100000,
     "releaseChannelCollection": "opt-out",
     "keyed": true,
     "description": "How long it took to import logins (passwords) from another browser, keyed by the name of the browser."
   },
+  "FX_MIGRATION_BOOKMARKS_JANK_MS": {
+    "bug_numbers": [1338522],
+    "alert_emails": ["dao@mozilla.com"],
+    "expires_in_version": "58",
+    "kind": "exponential",
+    "n_buckets": 20,
+    "high": 60000,
+    "releaseChannelCollection": "opt-out",
+    "keyed": true,
+    "description": "Accumulated timer delay (variance between when the timer was expected to fire and when it actually fired) in milliseconds as an indicator for decreased main-thread responsiveness while importing bookmarks from another browser, keyed by the name of the browser (see gAvailableMigratorKeys in MigrationUtils.jsm). The import is happening on a background thread and should ideally not affect the UI noticeably."
+  },
+  "FX_MIGRATION_HISTORY_JANK_MS": {
+    "bug_numbers": [1338522],
+    "alert_emails": ["dao@mozilla.com"],
+    "expires_in_version": "58",
+    "kind": "exponential",
+    "n_buckets": 20,
+    "high": 60000,
+    "releaseChannelCollection": "opt-out",
+    "keyed": true,
+    "description": "Accumulated timer delay (variance between when the timer was expected to fire and when it actually fired) in milliseconds as an indicator for decreased main-thread responsiveness while importing history from another browser, keyed by the name of the browser (see gAvailableMigratorKeys in MigrationUtils.jsm). The import is happening on a background thread and should ideally not affect the UI noticeably."
+  },
+  "FX_MIGRATION_LOGINS_JANK_MS": {
+    "bug_numbers": [1338522],
+    "alert_emails": ["dao@mozilla.com"],
+    "expires_in_version": "58",
+    "kind": "exponential",
+    "n_buckets": 20,
+    "high": 60000,
+    "releaseChannelCollection": "opt-out",
+    "keyed": true,
+    "description": "Accumulated timer delay (variance between when the timer was expected to fire and when it actually fired) in milliseconds as an indicator for decreased main-thread responsiveness while importing logins / passwords from another browser, keyed by the name of the browser (see gAvailableMigratorKeys in MigrationUtils.jsm). The import is happening on a background thread and should ideally not affect the UI noticeably."
+  },
   "FX_MIGRATION_BOOKMARKS_QUANTITY": {
     "bug_numbers": [1279501],
     "alert_emails": ["gijs@mozilla.com"],
     "expires_in_version": "57",
     "kind": "exponential",
     "n_buckets": 20,
     "high": 1000,
     "releaseChannelCollection": "opt-out",
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/ResponsivenessMonitor.jsm
@@ -0,0 +1,37 @@
+/* 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 = ["ResponsivenessMonitor"];
+
+const { classes: Cc, interfaces: Ci } = Components;
+
+function ResponsivenessMonitor(intervalMS = 100) {
+  this._intervalMS = intervalMS;
+  this._prevTimestamp = Date.now();
+  this._accumulatedDelay = 0;
+  this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+  this._timer.initWithCallback(this, this._intervalMS, Ci.nsITimer.TYPE_REPEATING_SLACK);
+}
+
+ResponsivenessMonitor.prototype = {
+  notify() {
+    let now = Date.now();
+    this._accumulatedDelay += Math.max(0, now - this._prevTimestamp - this._intervalMS);
+    this._prevTimestamp = now;
+  },
+
+  abort() {
+    if (this._timer) {
+      this._timer.cancel();
+      this._timer = null;
+    }
+  },
+
+  finish() {
+    this.abort();
+    return this._accumulatedDelay;
+  },
+};
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -73,16 +73,17 @@ EXTRA_JS_MODULES += [
     'PromiseUtils.jsm',
     'PropertyListUtils.jsm',
     'RemoteController.jsm',
     'RemoteFinder.jsm',
     'RemotePageManager.jsm',
     'RemoteSecurityUI.jsm',
     'RemoteWebProgress.jsm',
     'ResetProfile.jsm',
+    'ResponsivenessMonitor.jsm',
     'secondscreen/RokuApp.jsm',
     'secondscreen/SimpleServiceDiscovery.jsm',
     'SelectContentHelper.jsm',
     'SelectParentHelper.jsm',
     'ServiceRequest.jsm',
     'Services.jsm',
     'SessionRecorder.jsm',
     'sessionstore/FormData.jsm',