Bug 1301131 - Disable e10s if the user sees the big tab spinner too much. r=mconley draft
authorFelipe Gomes <felipc@gmail.com>
Thu, 29 Sep 2016 17:50:56 -0300
changeset 419174 68e5b06603a91471abcc5eae8e45c8859bd41d0b
parent 417914 66a77b9bfe5dcacd50eccf85de7c0e7e15ce0ffd
child 532517 1345245fc011699bd04d23ff70731218d235fa4e
push id30869
push userfelipc@gmail.com
push dateThu, 29 Sep 2016 20:54:53 +0000
reviewersmconley
bugs1301131
milestone52.0a1
Bug 1301131 - Disable e10s if the user sees the big tab spinner too much. r=mconley MozReview-Commit-ID: Kg2YGBaQilE
browser/extensions/e10srollout/bootstrap.js
toolkit/components/telemetry/TelemetryEnvironment.jsm
--- a/browser/extensions/e10srollout/bootstrap.js
+++ b/browser/extensions/e10srollout/bootstrap.js
@@ -4,16 +4,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/UpdateUtils.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/TelemetryArchive.jsm");
+Cu.import("resource://gre/modules/TelemetryController.jsm");
 
  // The amount of people to be part of e10s
 const TEST_THRESHOLD = {
   "beta"    : 0.5,  // 50%
   "release" : 1.0,  // 100%
 };
 
 const ADDON_ROLLOUT_POLICY = {
@@ -25,16 +28,19 @@ const PREF_COHORT_SAMPLE       = "e10s.r
 const PREF_COHORT_NAME         = "e10s.rollout.cohort";
 const PREF_E10S_OPTED_IN       = "browser.tabs.remote.autostart";
 const PREF_E10S_FORCE_ENABLED  = "browser.tabs.remote.force-enable";
 const PREF_E10S_FORCE_DISABLED = "browser.tabs.remote.force-disable";
 const PREF_TOGGLE_E10S         = "browser.tabs.remote.autostart.2";
 const PREF_E10S_ADDON_POLICY   = "extensions.e10s.rollout.policy";
 const PREF_E10S_ADDON_BLOCKLIST = "extensions.e10s.rollout.blocklist";
 const PREF_E10S_HAS_NONEXEMPT_ADDON = "extensions.e10s.rollout.hasAddon";
+const PREF_DISABLED_FOR_SPINNERS = "e10s.rollout.disabledByLongSpinners";
+
+const LONG_SPINNER_HISTOGRAM   = "FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS";
 
 function startup() {
   // In theory we only need to run this once (on install()), but
   // it's better to also run it on every startup. If the user has
   // made manual changes to the prefs, this will keep the data
   // reported more accurate.
   // It's also fine (and preferred) to just do it here on startup
   // (instead of observing prefs), because e10s takes a restart
@@ -57,16 +63,18 @@ function defineCohort() {
   cohortDefinedOnThisSession = true;
 
   let updateChannel = UpdateUtils.getUpdateChannel(false);
   if (!(updateChannel in TEST_THRESHOLD)) {
     setCohort("unsupportedChannel");
     return;
   }
 
+  setUpSpinnerCheck();
+
   let addonPolicy = "unknown";
   if (updateChannel in ADDON_ROLLOUT_POLICY) {
     addonPolicy = ADDON_ROLLOUT_POLICY[updateChannel];
     Preferences.set(PREF_E10S_ADDON_POLICY, addonPolicy);
     // This is also the proper place to set the blocklist pref
     // in case it is necessary.
 
     // Tab Mix Plus exception tracked at bug 1185672.
@@ -165,20 +173,105 @@ function optedOut() {
 
 /* If this function returns a non-empty string, it
  * means that this particular user should be temporarily
  * disqualified due to some particular reason.
  * If a user shouldn't be disqualified, then an empty
  * string must be returned.
  */
 function getTemporaryDisqualification() {
+  if (Preferences.isSet(PREF_DISABLED_FOR_SPINNERS)) {
+    return "longspinner";
+  }
+
   let applicationLanguage =
     Cc["@mozilla.org/chrome/chrome-registry;1"]
       .getService(Ci.nsIXULChromeRegistry)
       .getSelectedLocale("global")
       .split("-")[0];
 
   if (applicationLanguage == "ru") {
     return "ru";
   }
 
   return "";
 }
+
+let performLongSpinnerCheck = Task.async(function*() {
+  if (!Services.appinfo.browserTabsRemoteAutostart) {
+    return;
+  }
+
+  const DAYS_OLD = 3;
+  let thresholdDate = new Date(Date.now() - (1000 * 60 * 60 * 24 * DAYS_OLD));
+
+  let allPingsInfo = yield TelemetryArchive.promiseArchivedPingList();
+
+  let recentPingsInfo = allPingsInfo.filter(ping => {
+    let pingDate = new Date(ping.timestampCreated);
+    return pingDate > thresholdDate;
+  });
+
+  let pingList = [];
+
+  for (let pingInfo of recentPingsInfo) {
+    pingList.push(yield TelemetryArchive.promiseArchivedPingById(pingInfo.id));
+  }
+
+  pingList.push(TelemetryController.getCurrentPingData(/* subsession = */ true));
+
+  let totalSessionTime = 0;
+  let totalSpinnerTime = 0;
+
+  for (let ping of pingList) {
+    try {
+      if (ping.type != "main") {
+        continue;
+      }
+
+      if (!ping.environment.settings.e10sEnabled) {
+        continue;
+      }
+
+      totalSessionTime = ping.payload.info.subsessionLength;
+
+      if (!(LONG_SPINNER_HISTOGRAM in ping.payload.histograms)) {
+        // The Histogram might not be defined in this ping if no data was recorded for it.
+        // In this case, we still add the session length because that was a valid session
+        // without a long spinner.
+        continue;
+      }
+
+      let histogram = ping.payload.histograms[LONG_SPINNER_HISTOGRAM];
+
+      for (spinnerTime of Object.keys(histogram.values)) {
+        // Only consider spinners that took more than 2 seconds.
+        // Note: the first bucket size that fits this criteria is
+        // 2297ms. And the largest bucket is 64000ms, meaning that
+        // any pause larger than that is only counted as a 64s pause.
+        // For reference, the bucket sizes are:
+        // 0, 1000, 2297, 5277, 12124, 27856, 64000
+        if (spinnerTime >= 2000) {
+          totalSpinnerTime += spinnerTime * histogram.values[spinnerTime];
+        }
+      }
+    }
+  } catch (e) { /* just in case there's a malformed ping, ignore it silently */ }
+
+  totalSpinnerTime /= 1000; // session time is in seconds, but spinner time in ms
+
+  const ACCEPTABLE_THRESHOLD = 20 / 3600; // 20 seconds per hour, per bug 1301131
+
+  if ((totalSpinnerTime / totalSessionTime) > ACCEPTABLE_THRESHOLD) {
+    Preferences.set(PREF_DISABLED_FOR_SPINNERS, true);
+  } else {
+    Preferences.reset(PREF_DISABLED_FOR_SPINNERS);
+  }
+});
+
+function setUpSpinnerCheck() {
+  let {setTimeout, setInterval} = Cu.import("resource://gre/modules/Timer.jsm");
+
+  // Perform an initial check after 5min (which should give good clearance from
+  // the startup process), and then a subsequent check every hour.
+  setTimeout(performLongSpinnerCheck, 1000 * 60 * 5);
+  setInterval(performLongSpinnerCheck, 1000 * 60 * 60);
+}
--- a/toolkit/components/telemetry/TelemetryEnvironment.jsm
+++ b/toolkit/components/telemetry/TelemetryEnvironment.jsm
@@ -137,16 +137,17 @@ const DEFAULT_ENVIRONMENT_PREFS = new Ma
   ["browser.zoom.full", {what: RECORD_PREF_VALUE}],
   ["devtools.chrome.enabled", {what: RECORD_PREF_VALUE}],
   ["devtools.debugger.enabled", {what: RECORD_PREF_VALUE}],
   ["devtools.debugger.remote-enabled", {what: RECORD_PREF_VALUE}],
   ["dom.ipc.plugins.asyncInit.enabled", {what: RECORD_PREF_VALUE}],
   ["dom.ipc.plugins.enabled", {what: RECORD_PREF_VALUE}],
   ["dom.ipc.processCount", {what: RECORD_PREF_VALUE, requiresRestart: true}],
   ["dom.max_script_run_time", {what: RECORD_PREF_VALUE}],
+  ["e10s.rollout.disabledByLongSpinners", {what: RECORD_PREF_VALUE}],
   ["experiments.manifest.uri", {what: RECORD_PREF_VALUE}],
   ["extensions.autoDisableScopes", {what: RECORD_PREF_VALUE}],
   ["extensions.enabledScopes", {what: RECORD_PREF_VALUE}],
   ["extensions.blocklist.enabled", {what: RECORD_PREF_VALUE}],
   ["extensions.blocklist.url", {what: RECORD_PREF_VALUE}],
   ["extensions.strictCompatibility", {what: RECORD_PREF_VALUE}],
   ["extensions.update.enabled", {what: RECORD_PREF_VALUE}],
   ["extensions.update.url", {what: RECORD_PREF_VALUE}],