mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-03-01 05:48:26 +00:00
Bug 974024 - Add FHR recording of Telemetry Experiments activity; r=bsmedberg
--HG-- extra : rebase_source : 1e875e53da49c69194ee740898ff943d1801d1cf
This commit is contained in:
parent
8f8b2403d4
commit
beddc2399c
@ -6,6 +6,7 @@
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"Experiments",
|
||||
"ExperimentsProvider",
|
||||
];
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
@ -19,6 +20,7 @@ Cu.import("resource://gre/modules/Log.jsm");
|
||||
Cu.import("resource://gre/modules/Preferences.jsm");
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
Cu.import("resource://gre/modules/AsyncShutdown.jsm");
|
||||
Cu.import("resource://gre/modules/Metrics.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
|
||||
"resource://gre/modules/UpdateChannel.jsm");
|
||||
@ -388,6 +390,45 @@ Experiments.Experiments.prototype = {
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine whether another date has the same UTC day as now().
|
||||
*/
|
||||
_dateIsTodayUTC: function (d) {
|
||||
let now = this._policy.now();
|
||||
|
||||
return stripDateToMidnight(now).getTime() == stripDateToMidnight(d).getTime();
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtain the entry of the most recent active experiment that was active
|
||||
* today.
|
||||
*
|
||||
* If no experiment was active today, this resolves to nothing.
|
||||
*
|
||||
* Assumption: Only a single experiment can be active at a time.
|
||||
*
|
||||
* @return Promise<object>
|
||||
*/
|
||||
lastActiveToday: function () {
|
||||
return Task.spawn(function* getMostRecentActiveExperimentTask() {
|
||||
let experiments = yield this.getExperiments();
|
||||
|
||||
// Assumption: Ordered chronologically, descending, with active always
|
||||
// first.
|
||||
for (let experiment of experiments) {
|
||||
if (experiment.active) {
|
||||
return experiment;
|
||||
}
|
||||
|
||||
if (experiment.endDate && this._dateIsTodayUTC(experiment.endDate)) {
|
||||
return experiment;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch an updated list of experiments and trigger experiment updates.
|
||||
* Do only use when experiments are enabled.
|
||||
@ -1432,3 +1473,105 @@ Experiments.ExperimentEntry.prototype = {
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Strip a Date down to its UTC midnight.
|
||||
*
|
||||
* This will return a cloned Date object. The original is unchanged.
|
||||
*/
|
||||
let stripDateToMidnight = function (d) {
|
||||
let m = new Date(d);
|
||||
m.setUTCHours(0, 0, 0, 0);
|
||||
|
||||
return m;
|
||||
};
|
||||
|
||||
function ExperimentsLastActiveMeasurement1() {
|
||||
Metrics.Measurement.call(this);
|
||||
}
|
||||
|
||||
const FIELD_DAILY_LAST_TEXT = {type: Metrics.Storage.FIELD_DAILY_LAST_TEXT};
|
||||
|
||||
ExperimentsLastActiveMeasurement1.prototype = Object.freeze({
|
||||
__proto__: Metrics.Measurement.prototype,
|
||||
|
||||
name: "info",
|
||||
version: 1,
|
||||
|
||||
fields: {
|
||||
lastActive: FIELD_DAILY_LAST_TEXT,
|
||||
}
|
||||
});
|
||||
|
||||
this.ExperimentsProvider = function () {
|
||||
Metrics.Provider.call(this);
|
||||
|
||||
this._experiments = null;
|
||||
};
|
||||
|
||||
ExperimentsProvider.prototype = Object.freeze({
|
||||
__proto__: Metrics.Provider.prototype,
|
||||
|
||||
name: "org.mozilla.experiments",
|
||||
|
||||
measurementTypes: [
|
||||
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 () {
|
||||
for (let o of this._OBSERVERS) {
|
||||
Services.obs.removeObserver(this, o);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
observe: function (subject, topic, data) {
|
||||
switch (topic) {
|
||||
case OBSERVER_TOPIC:
|
||||
this.recordLastActiveExperiment();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
collectDailyData: function () {
|
||||
return this.recordLastActiveExperiment();
|
||||
},
|
||||
|
||||
recordLastActiveExperiment: function () {
|
||||
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: " +
|
||||
this._experiments._policy.now());
|
||||
return;
|
||||
}
|
||||
|
||||
this._log.info("Recording last active experiment: " + todayActive.id);
|
||||
yield m.setDailyLastText("lastActive", todayActive.id,
|
||||
this._experiments._policy.now());
|
||||
}.bind(this));
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -2,3 +2,7 @@ component {f7800463-3b97-47f9-9341-b7617e6d8d49} ExperimentsService.js
|
||||
contract @mozilla.org/browser/experiments-service;1 {f7800463-3b97-47f9-9341-b7617e6d8d49}
|
||||
category update-timer ExperimentsService @mozilla.org/browser/experiments-service;1,getService,experiments-update-timer,experiments.manifest.fetchIntervalSeconds,86400
|
||||
category profile-after-change ExperimentsService @mozilla.org/browser/experiments-service;1
|
||||
|
||||
category healthreport-js-provider-default ExperimentsProvider resource://gre/browser/modules/Experiments/Experiments.jsm
|
||||
|
||||
|
||||
|
@ -25,6 +25,35 @@ const EXPERIMENT2_ID = "test-experiment-2@tests.mozilla.org"
|
||||
const EXPERIMENT2_XPI_SHA1 = "sha1:81877991ec70360fb48db84c34a9b2da7aa41d6a";
|
||||
const EXPERIMENT2_XPI_NAME = "experiment-2.xpi";
|
||||
|
||||
const FAKE_EXPERIMENTS_1 = [
|
||||
{
|
||||
id: "id1",
|
||||
name: "experiment1",
|
||||
description: "experiment 1",
|
||||
active: true,
|
||||
detailUrl: "https://dummy/experiment1",
|
||||
},
|
||||
];
|
||||
|
||||
const FAKE_EXPERIMENTS_2 = [
|
||||
{
|
||||
id: "id2",
|
||||
name: "experiment2",
|
||||
description: "experiment 2",
|
||||
active: false,
|
||||
endDate: new Date(2014, 2, 11, 2, 4, 35, 42).getTime(),
|
||||
detailUrl: "https://dummy/experiment2",
|
||||
},
|
||||
{
|
||||
id: "id1",
|
||||
name: "experiment1",
|
||||
description: "experiment 1",
|
||||
active: false,
|
||||
endDate: new Date(2014, 2, 10, 0, 0, 0, 0).getTime(),
|
||||
detailURL: "https://dummy/experiment1",
|
||||
},
|
||||
];
|
||||
|
||||
let gAppInfo = null;
|
||||
|
||||
function getReporter(name, uri, inspected) {
|
||||
@ -129,3 +158,18 @@ function createAppInfo(options) {
|
||||
registrar.registerFactory(XULAPPINFO_CID, "XULAppInfo",
|
||||
XULAPPINFO_CONTRACTID, XULAppInfoFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the experiments on an Experiments with a new list.
|
||||
*
|
||||
* This monkeypatches getExperiments(). It doesn't monkeypatch the internal
|
||||
* experiments list. So its utility is not as great as it could be.
|
||||
*/
|
||||
function replaceExperiments(experiment, list) {
|
||||
Object.defineProperty(experiment, "getExperiments", {
|
||||
writable: true,
|
||||
value: () => {
|
||||
return Promise.resolve(list);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -264,6 +264,33 @@ add_task(function* test_getExperiments() {
|
||||
yield removeCacheFile();
|
||||
});
|
||||
|
||||
add_task(function* test_lastActiveToday() {
|
||||
let experiments = new Experiments.Experiments(gPolicy);
|
||||
|
||||
replaceExperiments(experiments, FAKE_EXPERIMENTS_1);
|
||||
|
||||
let e = yield experiments.getExperiments();
|
||||
Assert.equal(e.length, 1, "Monkeypatch successful.");
|
||||
Assert.equal(e[0].id, "id1", "ID looks sane");
|
||||
Assert.ok(e[0].active, "Experiment is active.");
|
||||
|
||||
let lastActive = yield experiments.lastActiveToday();
|
||||
Assert.equal(e[0], lastActive, "Last active object is expected.");
|
||||
|
||||
replaceExperiments(experiments, FAKE_EXPERIMENTS_2);
|
||||
e = yield experiments.getExperiments();
|
||||
Assert.equal(e.length, 2, "Monkeypatch successful.");
|
||||
|
||||
defineNow(gPolicy, e[0].endDate);
|
||||
|
||||
lastActive = yield experiments.lastActiveToday();
|
||||
Assert.ok(lastActive, "Have a last active experiment");
|
||||
Assert.equal(lastActive, e[0], "Last active object is expected.");
|
||||
|
||||
yield experiments.uninit();
|
||||
yield removeCacheFile();
|
||||
});
|
||||
|
||||
// Test explicitly disabling experiments.
|
||||
|
||||
add_task(function* test_disableExperiment() {
|
||||
@ -636,6 +663,9 @@ add_task(function* test_userDisabledAndUpdated() {
|
||||
Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
|
||||
Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
|
||||
Assert.equal(list[0].active, true, "Experiment 1 should be active.");
|
||||
let todayActive = yield experiments.lastActiveToday();
|
||||
Assert.ok(todayActive, "Last active for today reports a value.");
|
||||
Assert.equal(todayActive.id, list[0].id, "The entry is what we expect.");
|
||||
|
||||
// Explicitly disable an experiment.
|
||||
|
||||
@ -649,6 +679,9 @@ add_task(function* test_userDisabledAndUpdated() {
|
||||
Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
|
||||
Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
|
||||
Assert.equal(list[0].active, false, "Experiment should not be active anymore.");
|
||||
todayActive = yield experiments.lastActiveToday();
|
||||
Assert.ok(todayActive, "Last active for today still returns a value.");
|
||||
Assert.equal(todayActive.id, list[0].id, "The ID is still the same.");
|
||||
|
||||
// Trigger an update with a faked change for experiment 1.
|
||||
|
||||
@ -718,6 +751,9 @@ add_task(function* test_updateActiveExperiment() {
|
||||
let list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 0, "Experiment list should be empty.");
|
||||
|
||||
let todayActive = yield experiments.lastActiveToday();
|
||||
Assert.equal(todayActive, null, "No experiment active today.");
|
||||
|
||||
// Trigger update, clock set for the experiment to start.
|
||||
|
||||
now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
|
||||
@ -731,6 +767,9 @@ add_task(function* test_updateActiveExperiment() {
|
||||
Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
|
||||
Assert.equal(list[0].active, true, "Experiment 1 should be active.");
|
||||
Assert.equal(list[0].name, EXPERIMENT1_NAME, "Experiments name should match.");
|
||||
todayActive = yield experiments.lastActiveToday();
|
||||
Assert.ok(todayActive, "todayActive() returns a value.");
|
||||
Assert.equal(todayActive.id, list[0].id, "It returns the active experiment.");
|
||||
|
||||
// Trigger an update for the active experiment by changing it's hash (and xpi)
|
||||
// in the manifest.
|
||||
@ -748,6 +787,8 @@ add_task(function* test_updateActiveExperiment() {
|
||||
Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
|
||||
Assert.equal(list[0].active, true, "Experiment 1 should still be active.");
|
||||
Assert.equal(list[0].name, EXPERIMENT1A_NAME, "Experiments name should have been updated.");
|
||||
todayActive = yield experiments.lastActiveToday();
|
||||
Assert.equal(todayActive.id, list[0].id, "last active today is still sane.");
|
||||
|
||||
// Cleanup.
|
||||
|
||||
|
110
browser/experiments/test/xpcshell/test_healthreport.js
Normal file
110
browser/experiments/test/xpcshell/test_healthreport.js
Normal file
@ -0,0 +1,110 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://gre/modules/Metrics.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource:///modules/experiments/Experiments.jsm");
|
||||
Cu.import("resource://testing-common/services/healthreport/utils.jsm");
|
||||
Cu.import("resource://testing-common/services-common/logging.js");
|
||||
|
||||
function getStorageAndProvider(name) {
|
||||
return Task.spawn(function* get() {
|
||||
let storage = yield Metrics.Storage(name);
|
||||
let provider = new ExperimentsProvider();
|
||||
yield provider.init(storage);
|
||||
|
||||
return [storage, provider];
|
||||
});
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
do_get_profile();
|
||||
initTestLogging();
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function test_constructor() {
|
||||
let provider = new ExperimentsProvider();
|
||||
});
|
||||
|
||||
add_task(function* test_init() {
|
||||
let storage = yield Metrics.Storage("init");
|
||||
let provider = new ExperimentsProvider();
|
||||
yield provider.init(storage);
|
||||
yield provider.shutdown();
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
add_task(function* test_collect() {
|
||||
let [storage, provider] = yield getStorageAndProvider("no_active");
|
||||
|
||||
// Initial state should not report anything.
|
||||
yield provider.collectDailyData();
|
||||
let m = provider.getMeasurement("info", 1);
|
||||
let values = yield m.getValues();
|
||||
Assert.equal(values.days.size, 0, "Have no data if no experiments known.");
|
||||
|
||||
// An old experiment that ended today should be reported.
|
||||
replaceExperiments(provider._experiments, FAKE_EXPERIMENTS_2);
|
||||
let now = new Date(FAKE_EXPERIMENTS_2[0].endDate);
|
||||
defineNow(provider._experiments._policy, now.getTime());
|
||||
|
||||
yield provider.collectDailyData();
|
||||
values = yield m.getValues();
|
||||
Assert.equal(values.days.size, 1, "Have 1 day of data");
|
||||
Assert.ok(values.days.hasDay(now), "Has day the experiment ended.");
|
||||
let day = values.days.getDay(now);
|
||||
Assert.ok(day.has("lastActive"), "Has lastActive field.");
|
||||
Assert.equal(day.get("lastActive"), "id2", "Last active ID is sane.");
|
||||
|
||||
// Making an experiment active replaces the lastActive value.
|
||||
replaceExperiments(provider._experiments, FAKE_EXPERIMENTS_1);
|
||||
yield provider.collectDailyData();
|
||||
values = yield m.getValues();
|
||||
day = values.days.getDay(now);
|
||||
Assert.equal(day.get("lastActive"), "id1", "Last active ID is the active experiment.");
|
||||
|
||||
// And make sure the observer works.
|
||||
replaceExperiments(provider._experiments, FAKE_EXPERIMENTS_2);
|
||||
Services.obs.notifyObservers(null, "experiments-changed", null);
|
||||
// This may not wait long enough. It relies on the SQL insert happening
|
||||
// in the same tick as the observer notification.
|
||||
yield storage.enqueueOperation(function () {
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
values = yield m.getValues();
|
||||
day = values.days.getDay(now);
|
||||
Assert.equal(day.get("lastActive"), "id2", "Last active ID set by observer.");
|
||||
|
||||
yield provider.shutdown();
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
add_task(function* test_healthreporterJSON() {
|
||||
let reporter = yield getHealthReporter("healthreporterJSON");
|
||||
yield reporter.init();
|
||||
try {
|
||||
yield reporter._providerManager.registerProvider(new ExperimentsProvider());
|
||||
let experiments = Experiments.instance();
|
||||
defineNow(experiments._policy, Date.now());
|
||||
replaceExperiments(experiments, FAKE_EXPERIMENTS_1);
|
||||
yield reporter.collectMeasurements();
|
||||
|
||||
let payload = yield reporter.getJSONPayload(true);
|
||||
let today = reporter._formatDate(reporter._policy.now());
|
||||
|
||||
Assert.ok(today in payload.data.days, "Day in payload.");
|
||||
let day = payload.data.days[today];
|
||||
|
||||
Assert.ok("org.mozilla.experiments.info" in day, "Measurement present.");
|
||||
let m = day["org.mozilla.experiments.info"];
|
||||
Assert.ok("lastActive" in m, "lastActive field present.");
|
||||
Assert.equal(m["lastActive"], "id1", "Last active ID proper.");
|
||||
} finally {
|
||||
reporter._shutdown();
|
||||
}
|
||||
});
|
@ -12,3 +12,4 @@ support-files =
|
||||
[test_api.js]
|
||||
[test_conditions.js]
|
||||
[test_fetch.js]
|
||||
[test_healthreport.js]
|
||||
|
@ -245,6 +245,10 @@ Leading by example::
|
||||
"google": 1
|
||||
},
|
||||
"_v": "4"
|
||||
},
|
||||
"org.mozilla.experiment": {
|
||||
"lastActive": "some.experiment.id"
|
||||
"_v": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1461,3 +1465,29 @@ Example
|
||||
"version": "12.2.0"
|
||||
}
|
||||
|
||||
|
||||
|
||||
org.mozilla.experiments.info
|
||||
----------------------------------
|
||||
|
||||
Daily measurement reporting information about the Telemetry Experiments service.
|
||||
|
||||
Version 1
|
||||
^^^^^^^^^
|
||||
|
||||
Property:
|
||||
|
||||
lastActive
|
||||
ID of the final Telemetry Experiment that is active on a given day, if any.
|
||||
|
||||
|
||||
Example
|
||||
^^^^^^^
|
||||
|
||||
::
|
||||
|
||||
"org.mozilla.experiments.info": {
|
||||
"_v": 1,
|
||||
"lastActive": "some.experiment.id"
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user