Bug 1122048 - Add generic preferences collection and change detection to Telemetry Environment. r=gfritzsche

This commit is contained in:
Alessio Placitelli 2015-02-25 23:54:32 +01:00
parent c971625f12
commit bd4cfd087b
3 changed files with 194 additions and 1 deletions

View File

@ -230,6 +230,11 @@ user_pref('browser.tiles.reportURL', 'http://%(server)s/tests/robocop/robocop_ti
// We want to collect telemetry, but we don't want to send in the results.
user_pref('toolkit.telemetry.server', 'https://%(server)s/telemetry-dummy/');
// A couple of preferences with default values to test that telemetry preference
// watching is working.
user_pref('toolkit.telemetry.test.pref1', true);
user_pref('toolkit.telemetry.test.pref2', false);
// We don't want to hit the real Firefox Accounts server for tests. We don't
// actually need a functioning FxA server, so just set it to something that
// resolves and accepts requests, even if they all fail.

View File

@ -13,6 +13,7 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
const LOGGER_NAME = "Toolkit.Telemetry";
@ -25,6 +26,14 @@ this.TelemetryEnvironment = {
_collectTask: null,
_doNotify: false,
// Policy to use when saving preferences. Exported for using them in tests.
RECORD_PREF_STATE: 1, // Don't record the preference value
RECORD_PREF_VALUE: 2, // We only record user-set prefs.
// A map of watched preferences which trigger an Environment change when modified.
// Every entry contains a recording policy (RECORD_PREF_STATE or RECORD_PREF_VALUE).
_watchedPrefs: null,
/**
* Initialize TelemetryEnvironment.
*/
@ -37,6 +46,7 @@ this.TelemetryEnvironment = {
this._log = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, "TelemetryEnvironment::");
this._log.trace("init");
this._shutdown = false;
this._startWatchingPrefs();
},
/**
@ -51,6 +61,7 @@ this.TelemetryEnvironment = {
this._log.trace("shutdown");
this._shutdown = true;
this._stopWatchingPrefs();
this._changeListeners.clear();
yield this._collectTask;
}),
@ -82,6 +93,97 @@ this.TelemetryEnvironment = {
this._changeListeners.delete(name);
},
/**
* Only used in tests, set the preferences to watch.
* @param aPreferences A map of preferences names and their recording policy.
*/
_watchPreferences: function (aPreferences) {
if (this._watchedPrefs) {
this._stopWatchingPrefs();
}
this._watchedPrefs = aPreferences;
this._startWatchingPrefs();
},
/**
* Get an object containing the values for the watched preferences. Depending on the
* policy, the value for a preference or whether it was changed by user is reported.
*
* @return An object containing the preferences values.
*/
_getPrefData: function () {
if (!this._watchedPrefs) {
return {};
}
let prefData = {};
for (let pref in this._watchedPrefs) {
// We only want to record preferences if they are non-default.
if (!Preferences.isSet(pref)) {
continue;
}
// Check the policy for the preference and decide if we need to store its value
// or whether it changed from the default value.
let prefValue = undefined;
if (this._watchedPrefs[pref] == this.RECORD_PREF_STATE) {
prefValue = null;
} else {
prefValue = Preferences.get(pref, null);
}
prefData[pref] = prefValue;
}
return prefData;
},
/**
* Start watching the preferences.
*/
_startWatchingPrefs: function () {
this._log.trace("_startWatchingPrefs - " + this._watchedPrefs);
if (!this._watchedPrefs) {
return;
}
for (let pref in this._watchedPrefs) {
Preferences.observe(pref, this._onPrefChanged, this);
}
},
/**
* Do not receive any more change notifications for the preferences.
*/
_stopWatchingPrefs: function () {
this._log.trace("_stopWatchingPrefs");
if (!this._watchedPrefs) {
return;
}
for (let pref in this._watchedPrefs) {
Preferences.ignore(pref, this._onPrefChanged, this);
}
this._watchedPrefs = null;
},
_onPrefChanged: function () {
this._log.trace("_onPrefChanged");
this._onEnvironmentChange("pref-changed");
},
/**
* Get the settings data in object form.
* @return Object containing the settings.
*/
_getSettings: function () {
return {
"userPrefs": this._getPrefData(),
};
},
/**
* Get the environment data in object form.
* @return Promise<Object> Resolved with the data on success, otherwise rejected.
@ -105,7 +207,25 @@ this.TelemetryEnvironment = {
_doGetEnvironmentData: Task.async(function* () {
this._log.trace("getEnvironmentData");
return {};
// Define the data collection function for each section.
let sections = {
"settings": () => this._getSettings(),
};
let data = {};
// Recover from exceptions in the collection functions and populate the data
// object. We want to recover so that, in the worst-case, we only lose some data
// sections instead of all.
for (let s in sections) {
try {
data[s] = sections[s]();
} catch (e) {
this._log.error("getEnvironmentData - There was an exception collecting " + s, e);
}
}
return data;
}),
_onEnvironmentChange: function (what) {

View File

@ -4,6 +4,8 @@
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/TelemetryEnvironment.jsm", this);
Cu.import("resource://gre/modules/Preferences.jsm", this);
Cu.import("resource://gre/modules/PromiseUtils.jsm", this);
function run_test() {
do_test_pending();
@ -65,6 +67,72 @@ add_task(function* test_changeNotify() {
}
});
add_task(function* test_prefWatchPolicies() {
const PREF_TEST_1 = "toolkit.telemetry.test.pref_new";
const PREF_TEST_2 = "toolkit.telemetry.test.pref1";
const PREF_TEST_3 = "toolkit.telemetry.test.pref2";
const expectedValue = "some-test-value";
let prefsToWatch = {};
prefsToWatch[PREF_TEST_1] = TelemetryEnvironment.RECORD_PREF_VALUE;
prefsToWatch[PREF_TEST_2] = TelemetryEnvironment.RECORD_PREF_STATE;
prefsToWatch[PREF_TEST_3] = TelemetryEnvironment.RECORD_PREF_STATE;
yield TelemetryEnvironment.init();
// Set the Environment preferences to watch.
TelemetryEnvironment._watchPreferences(prefsToWatch);
let deferred = PromiseUtils.defer();
TelemetryEnvironment.registerChangeListener("testWatchPrefs", deferred.resolve);
// Trigger a change in the watched preferences.
Preferences.set(PREF_TEST_1, expectedValue);
Preferences.set(PREF_TEST_2, false);
yield deferred.promise;
// Unregister the listener.
TelemetryEnvironment.unregisterChangeListener("testWatchPrefs");
// Check environment contains the correct data.
let environmentData = yield TelemetryEnvironment.getEnvironmentData();
let userPrefs = environmentData.settings.userPrefs;
Assert.equal(userPrefs[PREF_TEST_1], expectedValue,
"Environment contains the correct preference value.");
Assert.equal(userPrefs[PREF_TEST_2], null,
"Report that the pref was user set and has no value.");
Assert.ok(!(PREF_TEST_3 in userPrefs),
"Do not report if preference not user set.");
yield TelemetryEnvironment.shutdown();
});
add_task(function* test_prefWatch_prefReset() {
const PREF_TEST = "toolkit.telemetry.test.pref1";
let prefsToWatch = {};
prefsToWatch[PREF_TEST] = TelemetryEnvironment.RECORD_PREF_STATE;
// Set the preference to a non-default value.
Preferences.set(PREF_TEST, false);
yield TelemetryEnvironment.init();
// Set the Environment preferences to watch.
TelemetryEnvironment._watchPreferences(prefsToWatch);
let deferred = PromiseUtils.defer();
TelemetryEnvironment.registerChangeListener("testWatchPrefs_reset", deferred.resolve);
// Trigger a change in the watched preferences.
Preferences.reset(PREF_TEST);
yield deferred.promise;
// Unregister the listener.
TelemetryEnvironment.unregisterChangeListener("testWatchPrefs_reset");
yield TelemetryEnvironment.shutdown();
});
add_task(function*() {
do_test_finished();
});