From 0abf384b9084c544e8edc64d1f478733764aafc7 Mon Sep 17 00:00:00 2001 From: Alessio Placitelli Date: Wed, 25 Feb 2015 23:54:32 +0100 Subject: [PATCH] Bug 1122048 - Add generic preferences collection and change detection to Telemetry Environment. r=gfritzsche --- testing/profiles/prefs_general.js | 5 + .../telemetry/TelemetryEnvironment.jsm | 122 +++++++++++++++++- .../tests/unit/test_TelemetryEnvironment.js | 68 ++++++++++ 3 files changed, 194 insertions(+), 1 deletion(-) diff --git a/testing/profiles/prefs_general.js b/testing/profiles/prefs_general.js index 5ea26aff699b..83d57dbf6c06 100644 --- a/testing/profiles/prefs_general.js +++ b/testing/profiles/prefs_general.js @@ -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. diff --git a/toolkit/components/telemetry/TelemetryEnvironment.jsm b/toolkit/components/telemetry/TelemetryEnvironment.jsm index 28fb5fc1713b..35fde555e1b5 100644 --- a/toolkit/components/telemetry/TelemetryEnvironment.jsm +++ b/toolkit/components/telemetry/TelemetryEnvironment.jsm @@ -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 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) { diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js b/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js index 22f582a86ea6..f9a50ec02f14 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js @@ -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(); });