Bug 1301131 - Disable e10s if the user sees the big tab spinner too much. r=mconley
authorFelipe Gomes <felipc@gmail.com>
Fri, 30 Sep 2016 13:00:00 -0300
changeset 316089 3b3de5c48e284354e98549344cd8f7e6b20580d3
parent 316088 1a61d8bf5b3d8da8918dffd01702e0c58b51e4f3
child 316090 cdb240a81e9b63ef5f55dcdf14bb0f2c4b32316e
push id20637
push userphilringnalda@gmail.com
push dateSat, 01 Oct 2016 06:28:47 +0000
treeherderfx-team@87cd291d2db6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley
bugs1301131
milestone52.0a1
Bug 1301131 - Disable e10s if the user sees the big tab spinner too much. r=mconley MozReview-Commit-ID: 6RxJd8PaJhQ
browser/extensions/e10srollout/bootstrap.js
browser/extensions/e10srollout/install.rdf.in
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,27 +28,32 @@ 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
   // to take effect, so we keep the data based on how it was when
   // the session started.
   defineCohort();
+
+  setUpSpinnerCheck();
 }
 
 function install() {
   defineCohort();
 }
 
 let cohortDefinedOnThisSession = false;
 
@@ -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/browser/extensions/e10srollout/install.rdf.in
+++ b/browser/extensions/e10srollout/install.rdf.in
@@ -5,17 +5,17 @@
 
 #filter substitution
 
 <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
      xmlns:em="http://www.mozilla.org/2004/em-rdf#">
 
   <Description about="urn:mozilla:install-manifest">
     <em:id>e10srollout@mozilla.org</em:id>
-    <em:version>1.3</em:version>
+    <em:version>1.4</em:version>
     <em:type>2</em:type>
     <em:bootstrap>true</em:bootstrap>
     <em:multiprocessCompatible>true</em:multiprocessCompatible>
 
     <!-- Target Application this theme can install into,
         with minimum and maximum supported versions. -->
     <em:targetApplication>
       <Description>
--- a/toolkit/components/telemetry/TelemetryEnvironment.jsm
+++ b/toolkit/components/telemetry/TelemetryEnvironment.jsm
@@ -141,16 +141,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}],