Bug 1301131 - Disable e10s if the user sees the big tab spinner too much. r=mconley
MozReview-Commit-ID: Kg2YGBaQilE
--- 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}],