Bug 1007710 - Add information about the currently-active experiment to the telemetry ping, r=gfritzsche+vladan
authorBenjamin Smedberg <benjamin@smedbergs.us>
Thu, 15 May 2014 09:11:01 -0400
changeset 183209 28e4d5e18cf40ef6b6cf2ebe00856540d90be663
parent 183208 f692d3faf83ca87890f2d1f966d603b4db646447
child 183210 d1269bdb70e2891cb92cd5f0754d942c70ff29bb
push idunknown
push userunknown
push dateunknown
reviewersgfritzsche
bugs1007710
milestone32.0a1
Bug 1007710 - Add information about the currently-active experiment to the telemetry ping, r=gfritzsche+vladan
browser/experiments/Experiments.jsm
browser/experiments/test/xpcshell/test_api.js
browser/experiments/test/xpcshell/test_telemetry.js
toolkit/components/telemetry/TelemetryPing.jsm
--- a/browser/experiments/Experiments.jsm
+++ b/browser/experiments/Experiments.jsm
@@ -989,16 +989,27 @@ Experiments.Experiments.prototype = {
 
       experiments.set(id, entry);
     }
 
     this._experiments = experiments;
     this._dirty = true;
   },
 
+  getActiveExperimentID: function() {
+    if (!this._experiments) {
+      return null;
+    }
+    let e = this._getActiveExperiment();
+    if (!e) {
+      return null;
+    }
+    return e.id;
+  },
+
   _getActiveExperiment: function () {
     let enabled = [experiment for ([,experiment] of this._experiments) if (experiment._enabled)];
 
     if (enabled.length == 1) {
       return enabled[0];
     }
 
     if (enabled.length > 1) {
--- a/browser/experiments/test/xpcshell/test_api.js
+++ b/browser/experiments/test/xpcshell/test_api.js
@@ -158,31 +158,37 @@ add_task(function* test_getExperiments()
 
   let now = baseDate;
   gTimerScheduleOffset = -1;
   defineNow(gPolicy, now);
 
   yield experiments.updateManifest();
   Assert.equal(observerFireCount, 0,
                "Experiments observer should not have been called yet.");
+  Assert.equal(experiments.getActiveExperimentID(), null,
+               "getActiveExperimentID should return null");
+
   let list = yield experiments.getExperiments();
   Assert.equal(list.length, 0, "Experiment list should be empty.");
   let addons = yield getExperimentAddons();
   Assert.equal(addons.length, 0, "Precondition: No experiment add-ons are installed.");
 
   // Trigger update, clock set for experiment 1 to start.
 
   now = futureDate(startDate1, 5 * MS_IN_ONE_DAY);
   gTimerScheduleOffset = -1;
   defineNow(gPolicy, now);
 
   yield experiments.updateManifest();
   Assert.equal(observerFireCount, ++expectedObserverFireCount,
                "Experiments observer should have been called.");
 
+  Assert.equal(experiments.getActiveExperimentID(), EXPERIMENT1_ID,
+               "getActiveExperimentID should return the active experiment1");
+
   list = yield experiments.getExperiments();
   Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
   addons = yield getExperimentAddons();
   Assert.equal(addons.length, 1, "An experiment add-on was installed.");
 
   experimentListData[1].active = true;
   experimentListData[1].endDate = now.getTime() + 10 * MS_IN_ONE_DAY;
   for (let k of Object.keys(experimentListData[1])) {
@@ -198,16 +204,19 @@ add_task(function* test_getExperiments()
   now = futureDate(endDate1, 1000);
   gTimerScheduleOffset = -1;
   defineNow(gPolicy, now);
 
   yield experiments.updateManifest();
   Assert.equal(observerFireCount, ++expectedObserverFireCount,
                "Experiments observer should have been called.");
 
+  Assert.equal(experiments.getActiveExperimentID(), null,
+               "getActiveExperimentID should return null again");
+
   list = yield experiments.getExperiments();
   Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
   addons = yield getExperimentAddons();
   Assert.equal(addons.length, 0, "The experiment add-on should be uninstalled.");
 
   experimentListData[1].active = false;
   experimentListData[1].endDate = now.getTime();
   for (let k of Object.keys(experimentListData[1])) {
@@ -224,16 +233,19 @@ add_task(function* test_getExperiments()
   now = startDate2;
   gTimerScheduleOffset = -1;
   defineNow(gPolicy, now);
 
   yield experiments.notify();
   Assert.equal(observerFireCount, ++expectedObserverFireCount,
                "Experiments observer should have been called.");
 
+  Assert.equal(experiments.getActiveExperimentID(), EXPERIMENT2_ID,
+               "getActiveExperimentID should return the active experiment2");
+
   list = yield experiments.getExperiments();
   Assert.equal(list.length, 2, "Experiment list should have 2 entries now.");
   addons = yield getExperimentAddons();
   Assert.equal(addons.length, 1, "An experiment add-on is installed.");
 
   experimentListData[0].active = true;
   experimentListData[0].endDate = now.getTime() + 10 * MS_IN_ONE_DAY;
   for (let i=0; i<experimentListData.length; ++i) {
@@ -251,16 +263,19 @@ add_task(function* test_getExperiments()
 
   now = futureDate(startDate2, 10 * MS_IN_ONE_DAY + 1000);
   gTimerScheduleOffset = -1;
   defineNow(gPolicy, now);
   yield experiments.notify();
   Assert.equal(observerFireCount, ++expectedObserverFireCount,
                "Experiments observer should have been called.");
 
+  Assert.equal(experiments.getActiveExperimentID(), null,
+               "getActiveExperimentID should return null again2");
+
   list = yield experiments.getExperiments();
   Assert.equal(list.length, 2, "Experiment list should have 2 entries now.");
   addons = yield getExperimentAddons();
   Assert.equal(addons.length, 0, "No experiments add-ons are installed.");
 
   experimentListData[0].active = false;
   experimentListData[0].endDate = now.getTime();
   for (let i=0; i<experimentListData.length; ++i) {
@@ -272,16 +287,59 @@ add_task(function* test_getExperiments()
   }
 
   // Cleanup.
 
   Services.obs.removeObserver(observer, OBSERVER_TOPIC);
   yield testCleanup(experiments);
 });
 
+add_task(function* test_getActiveExperimentID() {
+  // Check that getActiveExperimentID returns the correct result even
+  // after .uninit()
+
+  // Dates the following tests are based on.
+
+  let baseDate   = new Date(2014, 5, 1, 12);
+  let startDate1 = futureDate(baseDate,  50 * MS_IN_ONE_DAY);
+  let endDate1   = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
+
+  gManifestObject = {
+    "version": 1,
+    experiments: [
+      {
+        id:               EXPERIMENT1_ID,
+        xpiURL:           gDataRoot + EXPERIMENT1_XPI_NAME,
+        xpiHash:          EXPERIMENT1_XPI_SHA1,
+        startTime:        dateToSeconds(startDate1),
+        endTime:          dateToSeconds(endDate1),
+        maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+        appName:          ["XPCShell"],
+        channel:          ["nightly"],
+      },
+    ],
+  };
+
+  let now = futureDate(startDate1, 5 * MS_IN_ONE_DAY);
+  gTimerScheduleOffset = -1;
+  defineNow(gPolicy, now);
+
+  let experiments = new Experiments.Experiments(gPolicy);
+  yield experiments.updateManifest();
+
+  Assert.equal(experiments.getActiveExperimentID(), EXPERIMENT1_ID,
+               "getActiveExperimentID should return the active experiment1");
+
+  yield experiments.uninit();
+  Assert.equal(experiments.getActiveExperimentID(), EXPERIMENT1_ID,
+               "getActiveExperimentID should return the active experiment1 after uninit()");
+
+  yield testCleanup(experiments);
+});
+
 // Test that we handle the experiments addon already being
 // installed properly.
 // We should just pave over them.
 
 add_task(function* test_addonAlreadyInstalled() {
   const OBSERVER_TOPIC = "experiments-changed";
   let observerFireCount = 0;
   let expectedObserverFireCount = 0;
--- a/browser/experiments/test/xpcshell/test_telemetry.js
+++ b/browser/experiments/test/xpcshell/test_telemetry.js
@@ -1,16 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 Cu.import("resource://testing-common/httpd.js");
 Cu.import("resource://gre/modules/TelemetryLog.jsm");
-Cu.import("resource://gre/modules/TelemetryPing.jsm");
 let bsp = Cu.import("resource:///modules/experiments/Experiments.jsm");
 
 
 const FILE_MANIFEST            = "experiments.manifest";
 const MANIFEST_HANDLER         = "manifests/handler";
 
 const SEC_IN_ONE_DAY  = 24 * 60 * 60;
 const MS_IN_ONE_DAY   = SEC_IN_ONE_DAY * 1000;
@@ -101,16 +100,19 @@ add_task(function* test_setup() {
   });
 
   yield removeCacheFile();
 });
 
 // Test basic starting and stopping of experiments.
 
 add_task(function* test_telemetryBasics() {
+  // Check TelemetryLog instead of TelemetryPing.getPayload().log because
+  // TelemetryPing gets Experiments.instance() and side-effects log entries.
+
   const OBSERVER_TOPIC = "experiments-changed";
   let observerFireCount = 0;
   let expectedLogLength = 0;
 
   // Dates the following tests are based on.
 
   let baseDate   = new Date(2014, 5, 1, 12);
   let startDate1 = futureDate(baseDate,  50 * MS_IN_ONE_DAY);
@@ -169,17 +171,17 @@ add_task(function* test_telemetryBasics(
   let now = baseDate;
   defineNow(gPolicy, now);
 
   yield experiments.updateManifest();
   let list = yield experiments.getExperiments();
   Assert.equal(list.length, 0, "Experiment list should be empty.");
 
   expectedLogLength += 2;
-  let log = TelemetryPing.getPayload().log;
+  let log = TelemetryLog.entries();
   do_print("Telemetry log: " + JSON.stringify(log));
   Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
   checkEvent(log[log.length-2], TLOG.ACTIVATION_KEY,
              [TLOG.ACTIVATION.REJECTED, EXPERIMENT1_ID, "startTime"]);
   checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
              [TLOG.ACTIVATION.REJECTED, EXPERIMENT2_ID, "startTime"]);
 
   // Trigger update, clock set for experiment 1 to start.
@@ -187,32 +189,32 @@ add_task(function* test_telemetryBasics(
   now = futureDate(startDate1, 5 * MS_IN_ONE_DAY);
   defineNow(gPolicy, now);
 
   yield experiments.updateManifest();
   list = yield experiments.getExperiments();
   Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
 
   expectedLogLength += 1;
-  log = TelemetryPing.getPayload().log;
-  Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
+  log = TelemetryLog.entries();
+  Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries. Got " + log.toSource());
   checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
              [TLOG.ACTIVATION.ACTIVATED, EXPERIMENT1_ID]);
 
   // Trigger update, clock set for experiment 1 to stop.
 
   now = futureDate(endDate1, 1000);
   defineNow(gPolicy, now);
 
   yield experiments.updateManifest();
   list = yield experiments.getExperiments();
   Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
 
   expectedLogLength += 2;
-  log = TelemetryPing.getPayload().log;
+  log = TelemetryLog.entries();
   Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
   checkEvent(log[log.length-2], TLOG.TERMINATION_KEY,
              [TLOG.TERMINATION.EXPIRED, EXPERIMENT1_ID]);
   checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
              [TLOG.ACTIVATION.REJECTED, EXPERIMENT2_ID, "startTime"]);
 
   // Trigger update, clock set for experiment 2 to start with invalid hash.
 
@@ -220,113 +222,113 @@ add_task(function* test_telemetryBasics(
   defineNow(gPolicy, now);
   gManifestObject.experiments[1].xpiHash = "sha1:0000000000000000000000000000000000000000";
 
   yield experiments.updateManifest();
   list = yield experiments.getExperiments();
   Assert.equal(list.length, 1, "Experiment list should have 1 entries.");
 
   expectedLogLength += 1;
-  log = TelemetryPing.getPayload().log;
+  log = TelemetryLog.entries();
   Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
   checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
              [TLOG.ACTIVATION.INSTALL_FAILURE, EXPERIMENT2_ID]);
 
   // Trigger update, clock set for experiment 2 to properly start now.
 
   now = futureDate(now, MS_IN_ONE_DAY);
   defineNow(gPolicy, now);
   gManifestObject.experiments[1].xpiHash = EXPERIMENT2_XPI_SHA1;
 
   yield experiments.updateManifest();
   list = yield experiments.getExperiments();
   Assert.equal(list.length, 2, "Experiment list should have 2 entries.");
 
   expectedLogLength += 1;
-  log = TelemetryPing.getPayload().log;
+  log = TelemetryLog.entries();
   Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
   checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
              [TLOG.ACTIVATION.ACTIVATED, EXPERIMENT2_ID]);
 
   // Fake user uninstall of experiment via add-on manager.
 
   now = futureDate(now, MS_IN_ONE_DAY);
   defineNow(gPolicy, now);
 
   yield experiments.disableExperiment(TLOG.TERMINATION.ADDON_UNINSTALLED);
   list = yield experiments.getExperiments();
   Assert.equal(list.length, 2, "Experiment list should have 2 entries.");
 
   expectedLogLength += 1;
-  log = TelemetryPing.getPayload().log;
+  log = TelemetryLog.entries();
   Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
   checkEvent(log[log.length-1], TLOG.TERMINATION_KEY,
              [TLOG.TERMINATION.ADDON_UNINSTALLED, EXPERIMENT2_ID]);
 
   // Trigger update with experiment 1a ready to start.
 
   now = futureDate(now, MS_IN_ONE_DAY);
   defineNow(gPolicy, now);
   gManifestObject.experiments[0].id      = EXPERIMENT3_ID;
   gManifestObject.experiments[0].endTime = dateToSeconds(futureDate(now, 50 * MS_IN_ONE_DAY));
 
   yield experiments.updateManifest();
   list = yield experiments.getExperiments();
   Assert.equal(list.length, 3, "Experiment list should have 3 entries.");
 
   expectedLogLength += 1;
-  log = TelemetryPing.getPayload().log;
+  log = TelemetryLog.entries();
   Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
   checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
              [TLOG.ACTIVATION.ACTIVATED, EXPERIMENT3_ID]);
 
   // Trigger disable of an experiment via the API.
 
   now = futureDate(now, MS_IN_ONE_DAY);
   defineNow(gPolicy, now);
 
   yield experiments.disableExperiment(TLOG.TERMINATION.FROM_API);
   list = yield experiments.getExperiments();
   Assert.equal(list.length, 3, "Experiment list should have 3 entries.");
 
   expectedLogLength += 1;
-  log = TelemetryPing.getPayload().log;
+  log = TelemetryLog.entries();
   Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
   checkEvent(log[log.length-1], TLOG.TERMINATION_KEY,
              [TLOG.TERMINATION.FROM_API, EXPERIMENT3_ID]);
 
   // Trigger update with experiment 1a ready to start.
 
   now = futureDate(now, MS_IN_ONE_DAY);
   defineNow(gPolicy, now);
   gManifestObject.experiments[0].id      = EXPERIMENT4_ID;
   gManifestObject.experiments[0].endTime = dateToSeconds(futureDate(now, 50 * MS_IN_ONE_DAY));
 
   yield experiments.updateManifest();
   list = yield experiments.getExperiments();
   Assert.equal(list.length, 4, "Experiment list should have 4 entries.");
 
   expectedLogLength += 1;
-  log = TelemetryPing.getPayload().log;
+  log = TelemetryLog.entries();
   Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
   checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
              [TLOG.ACTIVATION.ACTIVATED, EXPERIMENT4_ID]);
 
   // Trigger experiment termination by something other than expiry via the manifest.
 
   now = futureDate(now, MS_IN_ONE_DAY);
   defineNow(gPolicy, now);
   gManifestObject.experiments[0].os = "Plan9";
 
   yield experiments.updateManifest();
   list = yield experiments.getExperiments();
   Assert.equal(list.length, 4, "Experiment list should have 4 entries.");
 
   expectedLogLength += 1;
-  log = TelemetryPing.getPayload().log;
+  log = TelemetryLog.entries();
   Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
   checkEvent(log[log.length-1], TLOG.TERMINATION_KEY,
              [TLOG.TERMINATION.RECHECK, EXPERIMENT4_ID, "os"]);
 
   // Cleanup.
 
   yield experiments.uninit();
   yield removeCacheFile();
--- a/toolkit/components/telemetry/TelemetryPing.jsm
+++ b/toolkit/components/telemetry/TelemetryPing.jsm
@@ -527,16 +527,27 @@ let Impl = {
 
     if (this._addons)
       ret.addons = this._addons;
 
     let flashVersion = this.getFlashVersion();
     if (flashVersion)
       ret.flashVersion = flashVersion;
 
+    try {
+      let scope = {};
+      Cu.import("resource:///modules/experiments/Experiments.jsm", scope);
+      let activeExperiment = scope.Experiments.instance().getActiveExperimentID();
+      if (activeExperiment) {
+        ret.activeExperiment = activeExperiment;
+      }
+    } catch(e) {
+      // If this is not Firefox, the import will fail.
+    }
+
     return ret;
   },
 
   /**
    * Pull values from about:memory into corresponding histograms
    */
   gatherMemory: function gatherMemory() {
     let mgr;