Bug 993084 - Delay initialization of telemetry experiments if there is no active experiment. r=bsmedberg
authorGeorg Fritzsche <georg.fritzsche@googlemail.com>
Tue, 15 Apr 2014 18:12:26 +0200
changeset 197068 beb482c201c5ecbbef04aa2434b5d8263b66e695
parent 197067 b207a9c58fb6528ef583d80dce597864ad74a5b8
child 197069 7d9a7a53127539c0107634a67ba7c681ee83283a
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbsmedberg
bugs993084
milestone31.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 993084 - Delay initialization of telemetry experiments if there is no active experiment. r=bsmedberg To sync experiment state with the addon manager, we need to start it early when there is a active experiment. However, initializing the telemetry experiments system too early can lead to performance regressions, so we delay the initialization if we don't have an active experiment.
browser/experiments/Experiments.jsm
browser/experiments/ExperimentsService.js
--- a/browser/experiments/Experiments.jsm
+++ b/browser/experiments/Experiments.jsm
@@ -53,16 +53,17 @@ const OBSERVER_TOPIC            = "exper
 const MANIFEST_VERSION          = 1;
 const CACHE_VERSION             = 1;
 
 const KEEP_HISTORY_N_DAYS       = 180;
 const MIN_EXPERIMENT_ACTIVE_SECONDS = 60;
 
 const PREF_BRANCH               = "experiments.";
 const PREF_ENABLED              = "enabled"; // experiments.enabled
+const PREF_ACTIVE_EXPERIMENT    = "activeExperiment"; // whether we have an active experiment
 const PREF_LOGGING              = "logging";
 const PREF_LOGGING_LEVEL        = PREF_LOGGING + ".level"; // experiments.logging.level
 const PREF_LOGGING_DUMP         = PREF_LOGGING + ".dump"; // experiments.logging.dump
 const PREF_MANIFEST_URI         = "manifest.uri"; // experiments.logging.manifest.uri
 const PREF_MANIFEST_CHECKCERT   = "manifest.cert.checkAttributes"; // experiments.manifest.cert.checkAttributes
 const PREF_MANIFEST_REQUIREBUILTIN = "manifest.cert.requireBuiltin"; // experiments.manifest.cert.requireBuiltin
 const PREF_FORCE_SAMPLE = "force-sample-value"; // experiments.force-sample-value
 
@@ -970,16 +971,21 @@ Experiments.Experiments.prototype = {
 
       yield uninstallAddons(unknownAddons);
     }
 
     let activeExperiment = this._getActiveExperiment();
     let activeChanged = false;
     let now = this._policy.now();
 
+    if (!activeExperiment) {
+      // Avoid this pref staying out of sync if there were e.g. crashes.
+      gPrefs.set(PREF_ACTIVE_EXPERIMENT, false);
+    }
+
     if (activeExperiment) {
       this._pendingUninstall = activeExperiment._addonId;
       try {
         let wasStopped;
         if (this._terminateReason) {
           yield activeExperiment.stop(this._terminateReason);
           wasStopped = true;
         } else {
@@ -1479,16 +1485,17 @@ Experiments.ExperimentEntry.prototype = 
       let addons = yield installedExperimentAddons();
       if (addons.length > 0) {
         this._log.error("start() - there are already "
                         + addons.length + " experiment addons installed");
         yield uninstallAddons(addons);
       }
 
       yield this._installAddon();
+      gPrefs.set(PREF_ACTIVE_EXPERIMENT, true);
     }.bind(this));
   },
 
   // Async install of the addon for this experiment, part of the start task above.
   _installAddon: function* () {
     let deferred = Promise.defer();
 
     let install = yield addonInstallForURL(this._manifestData.xpiURL,
@@ -1599,16 +1606,18 @@ Experiments.ExperimentEntry.prototype = 
   stop: function (terminationKind, terminationReason) {
     this._log.trace("stop() - id=" + this.id + ", terminationKind=" + terminationKind);
     if (!this._enabled) {
       this._log.warning("stop() - experiment not enabled: " + id);
       return Promise.reject();
     }
 
     this._enabled = false;
+    gPrefs.set(PREF_ACTIVE_EXPERIMENT, false);
+
     let deferred = Promise.defer();
     let updateDates = () => {
       let now = this._policy.now();
       this._lastChangedDate = now;
       this._endDate = now;
     };
 
     this._getAddon().then((addon) => {
@@ -1846,18 +1855,16 @@ ExperimentsProvider.prototype = Object.f
     ExperimentsLastActiveMeasurement1,
   ],
 
   _OBSERVERS: [
     OBSERVER_TOPIC,
   ],
 
   postInit: function () {
-    this._experiments = Experiments.instance();
-
     for (let o of this._OBSERVERS) {
       Services.obs.addObserver(this, o, false);
     }
 
     return Promise.resolve();
   },
 
   onShutdown: function () {
@@ -1876,16 +1883,24 @@ ExperimentsProvider.prototype = Object.f
     }
   },
 
   collectDailyData: function () {
     return this.recordLastActiveExperiment();
   },
 
   recordLastActiveExperiment: function () {
+    if (!gExperimentsEnabled) {
+      return;
+    }
+
+    if (!this._experiments) {
+      this._experiments = Experiments.instance();
+    }
+
     let m = this.getMeasurement(ExperimentsLastActiveMeasurement1.prototype.name,
                                 ExperimentsLastActiveMeasurement1.prototype.version);
 
     return this.enqueueStorageOperation(() => {
       return Task.spawn(function* recordTask() {
         let todayActive = yield this._experiments.lastActiveToday();
         if (!todayActive) {
           this._log.info("No active experiment on this day: " +
--- a/browser/experiments/ExperimentsService.js
+++ b/browser/experiments/ExperimentsService.js
@@ -3,59 +3,96 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Experiments",
                                   "resource:///modules/experiments/Experiments.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
+                                  "resource://services-common/utils.js");
 
 const PREF_EXPERIMENTS_ENABLED  = "experiments.enabled";
+const PREF_ACTIVE_EXPERIMENT    = "experiments.activeExperiment"; // whether we have an active experiment
 const PREF_HEALTHREPORT_ENABLED = "datareporting.healthreport.service.enabled";
 const PREF_TELEMETRY_ENABLED    = "toolkit.telemetry.enabled";
+const DELAY_INIT_MS             = 30 * 1000;
+
+XPCOMUtils.defineLazyGetter(
+  this, "gPrefs", () => {
+    return new Preferences();
+  });
 
 XPCOMUtils.defineLazyGetter(
   this, "gExperimentsEnabled", () => {
-    try {
-      let prefs = Services.prefs;
-      return prefs.getBoolPref(PREF_EXPERIMENTS_ENABLED) &&
-             prefs.getBoolPref(PREF_TELEMETRY_ENABLED) &&
-             prefs.getBoolPref(PREF_HEALTHREPORT_ENABLED);
-    } catch (e) {
-      return false;
-    }
+    return gPrefs.get(PREF_EXPERIMENTS_ENABLED, false) &&
+           gPrefs.get(PREF_TELEMETRY_ENABLED, false) &&
+           gPrefs.get(PREF_HEALTHREPORT_ENABLED, false);
+  });
+
+XPCOMUtils.defineLazyGetter(
+  this, "gActiveExperiment", () => {
+    return gPrefs.get(PREF_ACTIVE_EXPERIMENT);
   });
 
 function ExperimentsService() {
+  this._initialized = false;
+  this._delayedInitTimer = null;
 }
 
 ExperimentsService.prototype = {
   classID: Components.ID("{f7800463-3b97-47f9-9341-b7617e6d8d49}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback, Ci.nsIObserver]),
 
   notify: function (timer) {
     if (!gExperimentsEnabled) {
       return;
     }
     if (OS.Constants.Path.profileDir === undefined) {
       throw Error("Update timer fired before profile was initialized?");
     }
     Experiments.instance().updateManifest();
   },
 
+  _delayedInit: function () {
+    if (!this._initialized) {
+      this._initialized = true;
+      Experiments.instance(); // for side effects
+    }
+  },
+
   observe: function (subject, topic, data) {
     switch (topic) {
       case "profile-after-change":
         if (gExperimentsEnabled) {
-          Experiments.instance(); // for side effects
+          Services.obs.addObserver(this, "quit-application", false);
+          Services.obs.addObserver(this, "sessionstore-state-finalized", false);
+
+          if (gActiveExperiment) {
+            this._initialized = true;
+            Experiments.instance(); // for side effects
+          }
+        }
+        break;
+      case "sessionstore-state-finalized":
+        if (!this._initialized) {
+          CommonUtils.namedTimer(this._delayedInit, DELAY_INIT_MS, this, "_delayedInitTimer");
+        }
+        break;
+      case "quit-application":
+        Services.obs.removeObserver(this, "quit-application");
+        Services.obs.removeObserver(this, "sessionstore-state-finalized");
+        if (this._delayedInitTimer) {
+          this._delayedInitTimer.clear();
         }
         break;
     }
   },
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ExperimentsService]);