diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in index ce6589d93584..bd224e4b66a6 100644 --- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -627,10 +627,6 @@ #endif @RESPATH@/components/servicesComponents.manifest @RESPATH@/components/cryptoComponents.manifest -#ifdef MOZ_SERVICES_HEALTHREPORT -@RESPATH@/components/HealthReportComponents.manifest -@RESPATH@/components/HealthReportService.js -#endif @RESPATH@/components/CaptivePortalDetectComponents.manifest @RESPATH@/components/captivedetect.js @RESPATH@/components/TelemetryStartup.js diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 8cee18fbbfb7..e2e1740c4ca1 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -499,7 +499,6 @@ @RESPATH@/components/nsPrompter.manifest @RESPATH@/components/nsPrompter.js #ifdef MOZ_SERVICES_HEALTHREPORT -@RESPATH@/components/HealthReportComponents.manifest @RESPATH@/browser/components/SelfSupportService.manifest @RESPATH@/browser/components/SelfSupportService.js #endif diff --git a/mobile/android/b2gdroid/installer/package-manifest.in b/mobile/android/b2gdroid/installer/package-manifest.in index 5a3b97bedd4f..4bfa7184ad4a 100644 --- a/mobile/android/b2gdroid/installer/package-manifest.in +++ b/mobile/android/b2gdroid/installer/package-manifest.in @@ -428,11 +428,6 @@ @BINPATH@/components/PeerConnection.manifest #endif -#ifdef MOZ_SERVICES_HEALTHREPORT -@BINPATH@/components/HealthReportComponents.manifest -@BINPATH@/components/HealthReportService.js -#endif - @BINPATH@/components/CaptivePortalDetectComponents.manifest @BINPATH@/components/captivedetect.js diff --git a/modules/libpref/greprefs.js b/modules/libpref/greprefs.js index e56527cd1262..d59110b6a6fd 100644 --- a/modules/libpref/greprefs.js +++ b/modules/libpref/greprefs.js @@ -7,6 +7,6 @@ #if MOZ_WIDGET_TOOLKIT == android #include ../../mobile/android/chrome/content/healthreport-prefs.js #else -#include ../../services/healthreport/healthreport-prefs.js +#include ../../toolkit/components/telemetry/healthreport-prefs.js #endif #endif diff --git a/services/healthreport/HealthReport.jsm b/services/healthreport/HealthReport.jsm deleted file mode 100644 index 39fafd783c6c..000000000000 --- a/services/healthreport/HealthReport.jsm +++ /dev/null @@ -1,43 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -this.EXPORTED_SYMBOLS = [ - "HealthReporter", - "AddonsProvider", - "AppInfoProvider", - "CrashesProvider", - "HealthReportProvider", - "HotfixProvider", - "Metrics", - "PlacesProvider", - "ProfileMetadataProvider", - "SearchesProvider", - "SessionsProvider", - "SysInfoProvider", -]; - -const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - -const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000; - -// We concatenate the JSMs together to eliminate compartment overhead. -// This is a giant hack until compartment overhead is no longer an -// issue. -#define MERGED_COMPARTMENT - -#include ../common/async.js -; -#include ../common/bagheeraclient.js -; -#include ../metrics/Metrics.jsm -; -#include healthreporter.jsm -; -#include profile.jsm -; -#include providers.jsm -; - diff --git a/services/healthreport/HealthReportComponents.manifest b/services/healthreport/HealthReportComponents.manifest deleted file mode 100644 index 56ed646cd961..000000000000 --- a/services/healthreport/HealthReportComponents.manifest +++ /dev/null @@ -1,16 +0,0 @@ -# Register Firefox Health Report providers. -category healthreport-js-provider-default AddonsProvider resource://gre/modules/HealthReport.jsm -category healthreport-js-provider-default AppInfoProvider resource://gre/modules/HealthReport.jsm -#ifdef MOZ_CRASHREPORTER -category healthreport-js-provider-default CrashesProvider resource://gre/modules/HealthReport.jsm -#endif -category healthreport-js-provider-default HealthReportProvider resource://gre/modules/HealthReport.jsm -category healthreport-js-provider-default HotfixProvider resource://gre/modules/HealthReport.jsm -category healthreport-js-provider-default PlacesProvider resource://gre/modules/HealthReport.jsm -category healthreport-js-provider-default ProfileMetadataProvider resource://gre/modules/HealthReport.jsm -category healthreport-js-provider-default SearchesProvider resource://gre/modules/HealthReport.jsm -category healthreport-js-provider-default SessionsProvider resource://gre/modules/HealthReport.jsm -category healthreport-js-provider-default SysInfoProvider resource://gre/modules/HealthReport.jsm - -# No Aurora or Beta providers yet; use the categories -# "healthreport-js-provider-aurora", "healthreport-js-provider-beta". diff --git a/services/healthreport/healthreporter.jsm b/services/healthreport/healthreporter.jsm deleted file mode 100644 index 9aaca06b97c9..000000000000 --- a/services/healthreport/healthreporter.jsm +++ /dev/null @@ -1,1505 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef MERGED_COMPARTMENT - -"use strict"; - -this.EXPORTED_SYMBOLS = ["HealthReporter"]; - -const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - -const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000; - -Cu.import("resource://gre/modules/Metrics.jsm"); -Cu.import("resource://services-common/async.js"); - -Cu.import("resource://services-common/bagheeraclient.js"); -#endif - -Cu.import("resource://gre/modules/Log.jsm"); -Cu.import("resource://services-common/utils.js"); -Cu.import("resource://gre/modules/ClientID.jsm"); -Cu.import("resource://gre/modules/Promise.jsm"); -Cu.import("resource://gre/modules/osfile.jsm"); -Cu.import("resource://gre/modules/Preferences.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/Task.jsm"); -Cu.import("resource://gre/modules/TelemetryStopwatch.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "TelemetryController", - "resource://gre/modules/TelemetryController.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils", - "resource://gre/modules/UpdateUtils.jsm"); - -// Oldest year to allow in date preferences. This module was implemented in -// 2012 and no dates older than that should be encountered. -const OLDEST_ALLOWED_YEAR = 2012; - -const DAYS_IN_PAYLOAD = 180; - -const DEFAULT_DATABASE_NAME = "healthreport.sqlite"; - -const TELEMETRY_INIT = "HEALTHREPORT_INIT_MS"; -const TELEMETRY_INIT_FIRSTRUN = "HEALTHREPORT_INIT_FIRSTRUN_MS"; -const TELEMETRY_DB_OPEN = "HEALTHREPORT_DB_OPEN_MS"; -const TELEMETRY_DB_OPEN_FIRSTRUN = "HEALTHREPORT_DB_OPEN_FIRSTRUN_MS"; -const TELEMETRY_GENERATE_PAYLOAD = "HEALTHREPORT_GENERATE_JSON_PAYLOAD_MS"; -const TELEMETRY_JSON_PAYLOAD_SERIALIZE = "HEALTHREPORT_JSON_PAYLOAD_SERIALIZE_MS"; -const TELEMETRY_PAYLOAD_SIZE_UNCOMPRESSED = "HEALTHREPORT_PAYLOAD_UNCOMPRESSED_BYTES"; -const TELEMETRY_PAYLOAD_SIZE_COMPRESSED = "HEALTHREPORT_PAYLOAD_COMPRESSED_BYTES"; -const TELEMETRY_UPLOAD = "HEALTHREPORT_UPLOAD_MS"; -const TELEMETRY_COLLECT_CONSTANT = "HEALTHREPORT_COLLECT_CONSTANT_DATA_MS"; -const TELEMETRY_COLLECT_DAILY = "HEALTHREPORT_COLLECT_DAILY_MS"; -const TELEMETRY_SHUTDOWN = "HEALTHREPORT_SHUTDOWN_MS"; -const TELEMETRY_COLLECT_CHECKPOINT = "HEALTHREPORT_POST_COLLECT_CHECKPOINT_MS"; - - -/** - * Helper type to assist with management of Health Reporter state. - * - * Instances are not meant to be created outside of a HealthReporter instance. - * - * There are two types of IDs associated with clients. - * - * Since the beginning of FHR, there has existed a per-upload ID: a UUID is - * generated at upload time and associated with the state before upload starts. - * That same upload includes a request to delete all other upload IDs known by - * the client. - * - * Per-upload IDs had the unintended side-effect of creating "orphaned" - * records/upload IDs on the server. So, a stable client identifer has been - * introduced. This client identifier is generated when it's missing and sent - * as part of every upload. - * - * There is a high chance we may remove upload IDs in the future. - */ -function HealthReporterState(reporter) { - this._reporter = reporter; - - let profD = OS.Constants.Path.profileDir; - - if (!profD || !profD.length) { - throw new Error("Could not obtain profile directory. OS.File not " + - "initialized properly?"); - } - - this._log = reporter._log; - - this._stateDir = OS.Path.join(profD, "healthreport"); - - // To facilitate testing. - let leaf = reporter._stateLeaf || "state.json"; - - this._filename = OS.Path.join(this._stateDir, leaf); - this._log.debug("Storing state in " + this._filename); - this._s = null; -} - -HealthReporterState.prototype = Object.freeze({ - /** - * Persistent string identifier associated with this client. - */ - get clientID() { - return this._s.clientID; - }, - - /** - * The version associated with the client ID. - */ - get clientIDVersion() { - return this._s.clientIDVersion; - }, - - get lastPingDate() { - return new Date(this._s.lastPingTime); - }, - - get lastSubmitID() { - return this._s.remoteIDs[0]; - }, - - get remoteIDs() { - return this._s.remoteIDs; - }, - - get _lastPayloadPath() { - return OS.Path.join(this._stateDir, "lastpayload.json"); - }, - - init: function () { - return Task.spawn(function* init() { - yield OS.File.makeDir(this._stateDir); - - let drsClientID = yield ClientID.getClientID(); - - let resetObjectState = function () { - this._s = { - // The payload version. This is bumped whenever there is a - // backwards-incompatible change. - v: 1, - // The persistent client identifier. - clientID: drsClientID, - // Denotes the mechanism used to generate the client identifier. - // 1: Random UUID. - clientIDVersion: 1, - // Upload IDs that might be on the server. - remoteIDs: [], - // When we last performed an uploaded. - lastPingTime: 0, - // Tracks whether we removed an outdated payload. - removedOutdatedLastpayload: false, - }; - }.bind(this); - - try { - this._s = yield CommonUtils.readJSON(this._filename); - } catch (ex if ex instanceof OS.File.Error && - ex.becauseNoSuchFile) { - this._log.warn("Saved state file does not exist."); - resetObjectState(); - } catch (ex) { - this._log.error("Exception when reading state from disk", ex); - resetObjectState(); - - // Don't save in case it goes away on next run. - } - - if (typeof(this._s) != "object") { - this._log.warn("Read state is not an object. Resetting state."); - resetObjectState(); - yield this.save(); - } - - if (this._s.v != 1) { - this._log.warn("Unknown version in state file: " + this._s.v); - resetObjectState(); - // We explicitly don't save here in the hopes an application re-upgrade - // comes along and fixes us. - } - - this._s.clientID = drsClientID; - - // Always look for preferences. This ensures that downgrades followed - // by reupgrades don't result in excessive data loss. - for (let promise of this._migratePrefs()) { - yield promise; - } - }.bind(this)); - }, - - save: function () { - this._log.info("Writing state file: " + this._filename); - return CommonUtils.writeJSON(this._s, this._filename); - }, - - addRemoteID: function (id) { - this._log.warn("Recording new remote ID: " + id); - this._s.remoteIDs.push(id); - return this.save(); - }, - - removeRemoteID: function (id) { - return this.removeRemoteIDs(id ? [id] : []); - }, - - removeRemoteIDs: function (ids) { - if (!ids || !ids.length) { - this._log.warn("No IDs passed for removal."); - return Promise.resolve(); - } - - this._log.warn("Removing documents from remote ID list: " + ids); - let filtered = this._s.remoteIDs.filter((x) => ids.indexOf(x) === -1); - - if (filtered.length == this._s.remoteIDs.length) { - return Promise.resolve(); - } - - this._s.remoteIDs = filtered; - return this.save(); - }, - - setLastPingDate: function (date) { - this._s.lastPingTime = date.getTime(); - - return this.save(); - }, - - updateLastPingAndRemoveRemoteID: function (date, id) { - return this.updateLastPingAndRemoveRemoteIDs(date, id ? [id] : []); - }, - - updateLastPingAndRemoveRemoteIDs: function (date, ids) { - if (!ids) { - return this.setLastPingDate(date); - } - - this._log.info("Recording last ping time and deleted remote document."); - this._s.lastPingTime = date.getTime(); - return this.removeRemoteIDs(ids); - }, - - _migratePrefs: function () { - let prefs = this._reporter._prefs; - - let lastID = prefs.get("lastSubmitID", null); - let lastPingDate = CommonUtils.getDatePref(prefs, "lastPingTime", - 0, this._log, OLDEST_ALLOWED_YEAR); - - // If we have state from prefs, migrate and save it to a file then clear - // out old prefs. - if (lastID || (lastPingDate && lastPingDate.getTime() > 0)) { - this._log.warn("Migrating saved state from preferences."); - - if (lastID) { - this._log.info("Migrating last saved ID: " + lastID); - this._s.remoteIDs.push(lastID); - } - - let ourLast = this.lastPingDate; - - if (lastPingDate && lastPingDate.getTime() > ourLast.getTime()) { - this._log.info("Migrating last ping time: " + lastPingDate); - this._s.lastPingTime = lastPingDate.getTime(); - } - - yield this.save(); - prefs.reset(["lastSubmitID", "lastPingTime"]); - } else { - this._log.debug("No prefs data found."); - } - }, -}); - -/** - * This is the abstract base class of `HealthReporter`. It exists so that - * we can sanely divide work on platforms where control of Firefox Health - * Report is outside of Gecko (e.g., Android). - */ -function AbstractHealthReporter(branch, policy, sessionRecorder) { - if (!branch.endsWith(".")) { - throw new Error("Branch must end with a period (.): " + branch); - } - - if (!policy) { - throw new Error("Must provide policy to HealthReporter constructor."); - } - - this._log = Log.repository.getLogger("Services.HealthReport.HealthReporter"); - this._log.info("Initializing health reporter instance against " + branch); - - this._branch = branch; - this._prefs = new Preferences(branch); - - this._policy = policy; - this.sessionRecorder = sessionRecorder; - - this._dbName = this._prefs.get("dbName") || DEFAULT_DATABASE_NAME; - - this._storage = null; - this._storageInProgress = false; - this._providerManager = null; - this._providerManagerInProgress = false; - this._initializeStarted = false; - this._initialized = false; - this._initializeHadError = false; - this._initializedDeferred = Promise.defer(); - this._shutdownRequested = false; - this._shutdownInitiated = false; - this._shutdownComplete = false; - this._deferredShutdown = Promise.defer(); - this._promiseShutdown = this._deferredShutdown.promise; - - this._errors = []; - - this._lastDailyDate = null; - - // Yes, this will probably run concurrently with remaining constructor work. - let hasFirstRun = this._prefs.get("service.firstRun", false); - this._initHistogram = hasFirstRun ? TELEMETRY_INIT : TELEMETRY_INIT_FIRSTRUN; - this._dbOpenHistogram = hasFirstRun ? TELEMETRY_DB_OPEN : TELEMETRY_DB_OPEN_FIRSTRUN; - - // This is set to the name for the provider that we are currently initializing, - // shutting down or collecting data from, if any. - // This is used for AsyncShutdownTimeout diagnostics. - this._currentProviderInShutdown = null; - this._currentProviderInInit = null; - this._currentProviderInCollect = null; -} - -AbstractHealthReporter.prototype = Object.freeze({ - QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]), - - /** - * Whether the service is fully initialized and running. - * - * If this is false, it is not safe to call most functions. - */ - get initialized() { - return this._initialized; - }, - - /** - * Initialize the instance. - * - * This must be called once after object construction or the instance is - * useless. - */ - init: function () { - if (this._initializeStarted) { - throw new Error("We have already started initialization."); - } - - this._initializeStarted = true; - - return Task.spawn(function*() { - TelemetryStopwatch.start(this._initHistogram, this); - - try { - yield this._state.init(); - - if (!this._state._s.removedOutdatedLastpayload) { - yield this._deleteOldLastPayload(); - this._state._s.removedOutdatedLastpayload = true; - // Normally we should save this to a file but it directly conflicts with - // the "application re-upgrade" decision in HealthReporterState::init() - // which specifically does not save the state to a file. - } - } catch (ex) { - this._log.error("Error deleting last payload", ex); - } - - // As soon as we have could have storage, we need to register cleanup or - // else bad things happen on shutdown. - Services.obs.addObserver(this, "quit-application", false); - - // The database needs to be shut down by the end of shutdown - // phase profileBeforeChange. - Metrics.Storage.shutdown.addBlocker("FHR: Flushing storage shutdown", - () => { - // Workaround bug 1017706 - // Apparently, in some cases, quit-application is not triggered - // (or is triggered after profile-before-change), so we need to - // make sure that `_initiateShutdown()` is triggered at least - // once. - this._initiateShutdown(); - return this._promiseShutdown; - }, - () => ({ - shutdownInitiated: this._shutdownInitiated, - initialized: this._initialized, - shutdownRequested: this._shutdownRequested, - initializeHadError: this._initializeHadError, - providerManagerInProgress: this._providerManagerInProgress, - storageInProgress: this._storageInProgress, - hasProviderManager: !!this._providerManager, - hasStorage: !!this._storage, - shutdownComplete: this._shutdownComplete, - currentProviderInShutdown: this._currentProviderInShutdown, - currentProviderInInit: this._currentProviderInInit, - currentProviderInCollect: this._currentProviderInCollect, - })); - - try { - this._storageInProgress = true; - TelemetryStopwatch.start(this._dbOpenHistogram, this); - let storage = yield Metrics.Storage(this._dbName); - TelemetryStopwatch.finish(this._dbOpenHistogram, this); - yield this._onStorageCreated(); - - delete this._dbOpenHistogram; - this._log.info("Storage initialized."); - this._storage = storage; - this._storageInProgress = false; - - if (this._shutdownRequested) { - this._initiateShutdown(); - return null; - } - - yield this._initializeProviderManager(); - yield this._onProviderManagerInitialized(); - this._initializedDeferred.resolve(); - return this.onInit(); - } catch (ex) { - yield this._onInitError(ex); - this._initializedDeferred.reject(ex); - } - }.bind(this)); - }, - - //---------------------------------------------------- - // SERVICE CONTROL FUNCTIONS - // - // You shouldn't need to call any of these externally. - //---------------------------------------------------- - - _onInitError: function (error) { - TelemetryStopwatch.cancel(this._initHistogram, this); - TelemetryStopwatch.cancel(this._dbOpenHistogram, this); - delete this._initHistogram; - delete this._dbOpenHistogram; - - this._recordError("Error during initialization", error); - this._initializeHadError = true; - this._initiateShutdown(); - return Promise.reject(error); - - // FUTURE consider poisoning prototype's functions so calls fail with a - // useful error message. - }, - - - /** - * Removes the outdated lastpaylaod.json and lastpayload.json.tmp files - * @see Bug #867902 - * @return a promise for when all the files have been deleted - */ - _deleteOldLastPayload: function () { - let paths = [this._state._lastPayloadPath, this._state._lastPayloadPath + ".tmp"]; - return Task.spawn(function removeAllFiles () { - for (let path of paths) { - try { - OS.File.remove(path); - } catch (ex) { - if (!ex.becauseNoSuchFile) { - this._log.error("Exception when removing outdated payload files", ex); - } - } - } - }.bind(this)); - }, - - _initializeProviderManager: Task.async(function* _initializeProviderManager() { - if (this._collector) { - throw new Error("Provider manager has already been initialized."); - } - - this._log.info("Initializing provider manager."); - this._providerManager = new Metrics.ProviderManager(this._storage); - this._providerManager.onProviderError = this._recordError.bind(this); - this._providerManager.onProviderInit = this._initProvider.bind(this); - this._providerManagerInProgress = true; - - let catString = this._prefs.get("service.providerCategories") || ""; - if (catString.length) { - for (let category of catString.split(",")) { - yield this._providerManager.registerProvidersFromCategoryManager(category, - providerName => this._currentProviderInInit = providerName); - } - this._currentProviderInInit = null; - } - }), - - _onProviderManagerInitialized: function () { - TelemetryStopwatch.finish(this._initHistogram, this); - delete this._initHistogram; - this._log.debug("Provider manager initialized."); - this._providerManagerInProgress = false; - - if (this._shutdownRequested) { - this._initiateShutdown(); - return; - } - - this._log.info("HealthReporter started."); - this._initialized = true; - Services.obs.addObserver(this, "idle-daily", false); - - // If upload is not enabled, ensure daily collection works. If upload - // is enabled, this will be performed as part of upload. - // - // This is important because it ensures about:healthreport contains - // longitudinal data even if upload is disabled. Having about:healthreport - // provide useful info even if upload is disabled was a core launch - // requirement. - // - // We do not catch changes to the backing pref. So, if the session lasts - // many days, we may fail to collect. However, most sessions are short and - // this code will likely be refactored as part of splitting up policy to - // serve Android. So, meh. - if (!this._policy.healthReportUploadEnabled) { - this._log.info("Upload not enabled. Scheduling daily collection."); - // Since the timer manager is a singleton and there could be multiple - // HealthReporter instances, we need to encode a unique identifier in - // the timer ID. - try { - let timerName = this._branch.replace(/\./g, "-") + "lastDailyCollection"; - let tm = Cc["@mozilla.org/updates/timer-manager;1"] - .getService(Ci.nsIUpdateTimerManager); - tm.registerTimer(timerName, this.collectMeasurements.bind(this), - 24 * 60 * 60); - } catch (ex) { - this._log.error("Error registering collection timer", ex); - } - } - - // Clean up caches and reduce memory usage. - this._storage.compact(); - }, - - // nsIObserver to handle shutdown. - observe: function (subject, topic, data) { - switch (topic) { - case "quit-application": - Services.obs.removeObserver(this, "quit-application"); - this._initiateShutdown(); - break; - - case "idle-daily": - this._performDailyMaintenance(); - break; - } - }, - - _initiateShutdown: function () { - // Ensure we only begin the main shutdown sequence once. - if (this._shutdownInitiated) { - this._log.warn("Shutdown has already been initiated. No-op."); - return; - } - - this._log.info("Request to shut down."); - - this._initialized = false; - this._shutdownRequested = true; - - if (this._initializeHadError) { - this._log.warn("Initialization had error. Shutting down immediately."); - } else { - if (this._providerManagerInProgress) { - this._log.warn("Provider manager is in progress of initializing. " + - "Waiting to finish."); - return; - } - - // If storage is in the process of initializing, we need to wait for it - // to finish before continuing. The initialization process will call us - // again once storage has initialized. - if (this._storageInProgress) { - this._log.warn("Storage is in progress of initializing. Waiting to finish."); - return; - } - } - - this._log.warn("Initiating main shutdown procedure."); - - // Everything from here must only be performed once or else race conditions - // could occur. - - TelemetryStopwatch.start(TELEMETRY_SHUTDOWN, this); - this._shutdownInitiated = true; - - // We may not have registered the observer yet. If not, this will - // throw. - try { - Services.obs.removeObserver(this, "idle-daily"); - } catch (ex) { } - - Task.spawn(function*() { - try { - if (this._providerManager) { - this._log.info("Shutting down provider manager."); - for (let provider of this._providerManager.providers) { - try { - this._log.info("Shutting down provider: " + provider.name); - this._currentProviderInShutdown = provider.name; - yield provider.shutdown(); - } catch (ex) { - this._log.warn("Error when shutting down provider", ex); - } - } - this._log.info("Provider manager shut down."); - this._providerManager = null; - this._currentProviderInShutdown = null; - this._onProviderManagerShutdown(); - } - if (this._storage) { - this._log.info("Shutting down storage."); - try { - yield this._storage.close(); - yield this._onStorageClose(); - } catch (error) { - this._log.warn("Error when closing storage", error); - } - this._storage = null; - } - - this._log.warn("Shutdown complete."); - this._shutdownComplete = true; - } finally { - this._deferredShutdown.resolve(); - TelemetryStopwatch.finish(TELEMETRY_SHUTDOWN, this); - } - }.bind(this)); - }, - - onInit: function() { - return this._initializedDeferred.promise; - }, - - _onStorageCreated: function() { - // Do nothing. - // This method provides a hook point for the test suite. - }, - - _onStorageClose: function() { - // Do nothing. - // This method provides a hook point for the test suite. - }, - - _onProviderManagerShutdown: function() { - // Do nothing. - // This method provides a hook point for the test suite. - }, - - /** - * Convenience method to shut down the instance. - * - * This should *not* be called outside of tests. - */ - _shutdown: function () { - this._initiateShutdown(); - return this._promiseShutdown; - }, - - _performDailyMaintenance: function () { - this._log.info("Request to perform daily maintenance."); - - if (!this._initialized) { - return; - } - - let now = new Date(); - let cutoff = new Date(now.getTime() - MILLISECONDS_PER_DAY * (DAYS_IN_PAYLOAD - 1)); - - // The operation is enqueued and put in a transaction by the storage module. - this._storage.pruneDataBefore(cutoff); - }, - - //-------------------- - // Provider Management - //-------------------- - - /** - * Obtain a provider from its name. - * - * This will only return providers that are currently initialized. If - * a provider is lazy initialized (like pull-only providers) this - * will likely not return anything. - */ - getProvider: function (name) { - if (!this._providerManager) { - return null; - } - - return this._providerManager.getProvider(name); - }, - - _initProvider: function (provider) { - provider.healthReporter = this; - }, - - /** - * Record an exception for reporting in the payload. - * - * A side effect is the exception is logged. - * - * Note that callers need to be extra sensitive about ensuring personal - * or otherwise private details do not leak into this. All of the user data - * on the stack in FHR code should be limited to data we were collecting with - * the intent to submit. So, it is covered under the user's consent to use - * the feature. - * - * @param message - * (string) Human readable message describing error. - * @param ex - * (Error) The error that should be captured. - */ - _recordError: function (message, ex) { - let recordMessage = message; - let logMessage = message; - - if (ex) { - recordMessage += ": " + Log.exceptionStr(ex); - logMessage += ": " + Log.exceptionStr(ex); - } - - // Scrub out potentially identifying information from strings that could - // make the payload. - let appData = Services.dirsvc.get("UAppData", Ci.nsIFile); - let profile = Services.dirsvc.get("ProfD", Ci.nsIFile); - - let appDataURI = Services.io.newFileURI(appData); - let profileURI = Services.io.newFileURI(profile); - - // Order of operation is important here. We do the URI before the path version - // because the path may be a subset of the URI. We also have to check for the case - // where UAppData is underneath the profile directory (or vice-versa) so we - // don't substitute incomplete strings. - - // Return a /g regex that matches the provided string exactly. - function regexify(s) { - return new RegExp(s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"), "g"); - } - - function replace(uri, path, thing) { - // Try is because .spec can throw on invalid URI. - try { - recordMessage = recordMessage.replace(regexify(uri.spec), "<" + thing + "URI>"); - } catch (ex) { } - - recordMessage = recordMessage.replace(regexify(path), "<" + thing + "Path>"); - } - - if (appData.path.includes(profile.path)) { - replace(appDataURI, appData.path, 'AppData'); - replace(profileURI, profile.path, 'Profile'); - } else { - replace(profileURI, profile.path, 'Profile'); - replace(appDataURI, appData.path, 'AppData'); - } - - this._log.warn(logMessage); - this._errors.push(recordMessage); - }, - - /** - * Collect all measurements for all registered providers. - */ - collectMeasurements: function () { - if (!this._initialized) { - return Promise.reject(new Error("Not initialized.")); - } - - return Task.spawn(function doCollection() { - yield this._providerManager.ensurePullOnlyProvidersRegistered(); - - try { - TelemetryStopwatch.start(TELEMETRY_COLLECT_CONSTANT, this); - yield this._providerManager.collectConstantData(name => this._currentProviderInCollect = name); - this._currentProviderInCollect = null; - TelemetryStopwatch.finish(TELEMETRY_COLLECT_CONSTANT, this); - } catch (ex) { - TelemetryStopwatch.cancel(TELEMETRY_COLLECT_CONSTANT, this); - this._log.warn("Error collecting constant data", ex); - } - - // Daily data is collected if it hasn't yet been collected this - // application session or if it has been more than a day since the - // last collection. This means that providers could see many calls to - // collectDailyData per calendar day. However, this collection API - // makes no guarantees about limits. The alternative would involve - // recording state. The simpler implementation prevails for now. - if (!this._lastDailyDate || - Date.now() - this._lastDailyDate > MILLISECONDS_PER_DAY) { - - try { - TelemetryStopwatch.start(TELEMETRY_COLLECT_DAILY, this); - this._lastDailyDate = new Date(); - yield this._providerManager.collectDailyData(name => this._currentProviderInCollect = name); - this._currentProviderInCollect = null; - TelemetryStopwatch.finish(TELEMETRY_COLLECT_DAILY, this); - } catch (ex) { - TelemetryStopwatch.cancel(TELEMETRY_COLLECT_DAILY, this); - this._log.warn("Error collecting daily data from providers", ex); - } - } - - yield this._providerManager.ensurePullOnlyProvidersUnregistered(); - - // Flush gathered data to disk. This will incur an fsync. But, if - // there is ever a time we want to persist data to disk, it's - // after a massive collection. - try { - TelemetryStopwatch.start(TELEMETRY_COLLECT_CHECKPOINT, this); - yield this._storage.checkpoint(); - TelemetryStopwatch.finish(TELEMETRY_COLLECT_CHECKPOINT, this); - } catch (ex) { - TelemetryStopwatch.cancel(TELEMETRY_COLLECT_CHECKPOINT, this); - throw ex; - } - - throw new Task.Result(); - }.bind(this)); - }, - - /** - * Helper function to perform data collection and obtain the JSON payload. - * - * If you are looking for an up-to-date snapshot of FHR data that pulls in - * new data since the last upload, this is how you should obtain it. - * - * @param asObject - * (bool) Whether to resolve an object or JSON-encoded string of that - * object (the default). - * - * @return Promise - */ - collectAndObtainJSONPayload: function (asObject=false) { - if (!this._initialized) { - return Promise.reject(new Error("Not initialized.")); - } - - return Task.spawn(function collectAndObtain() { - yield this._storage.setAutoCheckpoint(0); - yield this._providerManager.ensurePullOnlyProvidersRegistered(); - - let payload; - let error; - - try { - yield this.collectMeasurements(); - payload = yield this.getJSONPayload(asObject); - } catch (ex) { - error = ex; - this._collectException("Error collecting and/or retrieving JSON payload", - ex); - } finally { - yield this._providerManager.ensurePullOnlyProvidersUnregistered(); - yield this._storage.setAutoCheckpoint(1); - - if (error) { - throw error; - } - } - - // We hold off throwing to ensure that behavior between finally - // and generators and throwing is sane. - throw new Task.Result(payload); - }.bind(this)); - }, - - - /** - * Obtain the JSON payload for currently-collected data. - * - * The payload only contains data that has been recorded to FHR. Some - * providers may have newer data available. If you want to ensure you - * have all available data, call `collectAndObtainJSONPayload` - * instead. - * - * @param asObject - * (bool) Whether to return an object or JSON encoding of that - * object (the default). - * - * @return Promise - */ - getJSONPayload: function (asObject=false) { - TelemetryStopwatch.start(TELEMETRY_GENERATE_PAYLOAD, this); - let deferred = Promise.defer(); - - Task.spawn(this._getJSONPayload.bind(this, this._now(), asObject)).then( - function onResult(result) { - TelemetryStopwatch.finish(TELEMETRY_GENERATE_PAYLOAD, this); - deferred.resolve(result); - }.bind(this), - function onError(error) { - TelemetryStopwatch.cancel(TELEMETRY_GENERATE_PAYLOAD, this); - deferred.reject(error); - }.bind(this) - ); - - return deferred.promise; - }, - - _getJSONPayload: function (now, asObject=false) { - let pingDateString = this._formatDate(now); - this._log.info("Producing JSON payload for " + pingDateString); - - // May not be present if we are generating as a result of init error. - if (this._providerManager) { - yield this._providerManager.ensurePullOnlyProvidersRegistered(); - } - - let o = { - version: 2, - clientID: this._state.clientID, - clientIDVersion: this._state.clientIDVersion, - thisPingDate: pingDateString, - geckoAppInfo: this.obtainAppInfo(this._log), - data: {last: {}, days: {}}, - }; - - let outputDataDays = o.data.days; - - // Guard here in case we don't track this (e.g., on Android). - let lastPingDate = this.lastPingDate; - if (lastPingDate && lastPingDate.getTime() > 0) { - o.lastPingDate = this._formatDate(lastPingDate); - } - - // We can still generate a payload even if we're not initialized. - // This is to facilitate error upload on init failure. - if (this._initialized) { - for (let provider of this._providerManager.providers) { - let providerName = provider.name; - - let providerEntry = { - measurements: {}, - }; - - // Measurement name to recorded version. - let lastVersions = {}; - // Day string to mapping of measurement name to recorded version. - let dayVersions = {}; - - for (let [measurementKey, measurement] of provider.measurements) { - let name = providerName + "." + measurement.name; - let version = measurement.version; - - let serializer; - try { - // The measurement is responsible for returning a serializer which - // is aware of the measurement version. - serializer = measurement.serializer(measurement.SERIALIZE_JSON); - } catch (ex) { - this._recordError("Error obtaining serializer for measurement: " + - name, ex); - continue; - } - - let data; - try { - data = yield measurement.getValues(); - } catch (ex) { - this._recordError("Error obtaining data for measurement: " + name, - ex); - continue; - } - - if (data.singular.size) { - try { - let serialized = serializer.singular(data.singular); - if (serialized) { - // Only replace the existing data if there is no data or if our - // version is newer than the old one. - if (!(name in o.data.last) || version > lastVersions[name]) { - o.data.last[name] = serialized; - lastVersions[name] = version; - } - } - } catch (ex) { - this._recordError("Error serializing singular data: " + name, - ex); - continue; - } - } - - let dataDays = data.days; - for (let i = 0; i < DAYS_IN_PAYLOAD; i++) { - let date = new Date(now.getTime() - i * MILLISECONDS_PER_DAY); - if (!dataDays.hasDay(date)) { - continue; - } - let dateFormatted = this._formatDate(date); - - try { - let serialized = serializer.daily(dataDays.getDay(date)); - if (!serialized) { - continue; - } - - if (!(dateFormatted in outputDataDays)) { - outputDataDays[dateFormatted] = {}; - } - - // This needs to be separate because dayVersions is provider - // specific and gets blown away in a loop while outputDataDays - // is persistent. - if (!(dateFormatted in dayVersions)) { - dayVersions[dateFormatted] = {}; - } - - if (!(name in outputDataDays[dateFormatted]) || - version > dayVersions[dateFormatted][name]) { - outputDataDays[dateFormatted][name] = serialized; - dayVersions[dateFormatted][name] = version; - } - } catch (ex) { - this._recordError("Error populating data for day: " + name, ex); - continue; - } - } - } - } - } else { - o.notInitialized = 1; - this._log.warn("Not initialized. Sending report with only error info."); - } - - if (this._errors.length) { - o.errors = this._errors.slice(0, 20); - } - - if (this._initialized) { - this._storage.compact(); - } - - if (!asObject) { - TelemetryStopwatch.start(TELEMETRY_JSON_PAYLOAD_SERIALIZE, this); - o = JSON.stringify(o); - TelemetryStopwatch.finish(TELEMETRY_JSON_PAYLOAD_SERIALIZE, this); - } - - if (this._providerManager) { - yield this._providerManager.ensurePullOnlyProvidersUnregistered(); - } - - throw new Task.Result(o); - }, - - _now: function _now() { - return new Date(); - }, - - // These are stolen from AppInfoProvider. - appInfoVersion: 1, - appInfoFields: { - // From nsIXULAppInfo. - vendor: "vendor", - name: "name", - id: "ID", - version: "version", - appBuildID: "appBuildID", - platformVersion: "platformVersion", - platformBuildID: "platformBuildID", - - // From nsIXULRuntime. - os: "OS", - xpcomabi: "XPCOMABI", - }, - - /** - * Statically return a bundle of app info data, a subset of that produced by - * AppInfoProvider._populateConstants. This allows us to more usefully handle - * payloads that, due to error, contain no data. - * - * Returns a very sparse object if Services.appinfo is unavailable. - */ - obtainAppInfo: function () { - let out = {"_v": this.appInfoVersion}; - try { - let ai = Services.appinfo; - for (let [k, v] in Iterator(this.appInfoFields)) { - out[k] = ai[v]; - } - } catch (ex) { - this._log.warn("Could not obtain Services.appinfo", ex); - } - - try { - out["updateChannel"] = UpdateUtils.UpdateChannel; - } catch (ex) { - this._log.warn("Could not obtain update channel", ex); - } - - return out; - }, -}); - -/** - * HealthReporter and its abstract superclass coordinate collection and - * submission of health report metrics. - * - * This is the main type for Firefox Health Report on desktop. It glues all the - * lower-level components (such as collection and submission) together. - * - * An instance of this type is created as an XPCOM service. See - * DataReportingService.js and - * DataReporting.manifest/HealthReportComponents.manifest. - * - * It is theoretically possible to have multiple instances of this running - * in the application. For example, this type may one day handle submission - * of telemetry data as well. However, there is some moderate coupling between - * this type and *the* Firefox Health Report (e.g., the policy). This could - * be abstracted if needed. - * - * Note that `AbstractHealthReporter` exists to allow for Firefox Health Report - * to be more easily implemented on platforms where a separate controlling - * layer is responsible for payload upload and deletion. - * - * IMPLEMENTATION NOTES - * ==================== - * - * These notes apply to the combination of `HealthReporter` and - * `AbstractHealthReporter`. - * - * Initialization and shutdown are somewhat complicated and worth explaining - * in extra detail. - * - * The complexity is driven by the requirements of SQLite connection management. - * Once you have a SQLite connection, it isn't enough to just let the - * application shut down. If there is an open connection or if there are - * outstanding SQL statements come XPCOM shutdown time, Storage will assert. - * On debug builds you will crash. On release builds you will get a shutdown - * hang. This must be avoided! - * - * During initialization, the second we create a SQLite connection (via - * Metrics.Storage) we register observers for application shutdown. The - * "quit-application" notification initiates our shutdown procedure. The - * subsequent "profile-do-change" notification ensures it has completed. - * - * The handler for "profile-do-change" may result in event loop spinning. This - * is because of race conditions between our shutdown code and application - * shutdown. - * - * All of our shutdown routines are async. There is the potential that these - * async functions will not complete before XPCOM shutdown. If they don't - * finish in time, we could get assertions in Storage. Our solution is to - * initiate storage early in the shutdown cycle ("quit-application"). - * Hopefully all the async operations have completed by the time we reach - * "profile-do-change." If so, great. If not, we spin the event loop until - * they have completed, avoiding potential race conditions. - * - * @param branch - * (string) The preferences branch to use for state storage. The value - * must end with a period (.). - * - * @param policy - * (HealthReportPolicy) Policy driving execution of HealthReporter. - */ -this.HealthReporter = function (branch, policy, stateLeaf=null) { - this._stateLeaf = stateLeaf; - this._uploadInProgress = false; - - AbstractHealthReporter.call(this, branch, policy, TelemetryController.getSessionRecorder()); - - if (!this.serverURI) { - throw new Error("No server URI defined. Did you forget to define the pref?"); - } - - if (!this.serverNamespace) { - throw new Error("No server namespace defined. Did you forget a pref?"); - } - - this._state = new HealthReporterState(this); -} - -this.HealthReporter.prototype = Object.freeze({ - __proto__: AbstractHealthReporter.prototype, - - QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]), - - get lastSubmitID() { - return this._state.lastSubmitID; - }, - - /** - * When we last successfully submitted data to the server. - * - * This is sent as part of the upload. This is redundant with similar data - * in the policy because we like the modules to be loosely coupled and the - * similar data in the policy is only used for forensic purposes. - */ - get lastPingDate() { - return this._state.lastPingDate; - }, - - /** - * The base URI of the document server to which to submit data. - * - * This is typically a Bagheera server instance. It is the URI up to but not - * including the version prefix. e.g. https://data.metrics.mozilla.com/ - */ - get serverURI() { - return this._prefs.get("documentServerURI", null); - }, - - set serverURI(value) { - if (!value) { - throw new Error("serverURI must have a value."); - } - - if (typeof(value) != "string") { - throw new Error("serverURI must be a string: " + value); - } - - this._prefs.set("documentServerURI", value); - }, - - /** - * The namespace on the document server to which we will be submitting data. - */ - get serverNamespace() { - return this._prefs.get("documentServerNamespace", "metrics"); - }, - - set serverNamespace(value) { - if (!value) { - throw new Error("serverNamespace must have a value."); - } - - if (typeof(value) != "string") { - throw new Error("serverNamespace must be a string: " + value); - } - - this._prefs.set("documentServerNamespace", value); - }, - - /** - * Whether this instance will upload data to a server. - */ - get willUploadData() { - return this._policy.userNotifiedOfCurrentPolicy && - this._policy.healthReportUploadEnabled; - }, - - /** - * Whether remote data is currently stored. - * - * @return bool - */ - haveRemoteData: function () { - return !!this._state.lastSubmitID; - }, - - /** - * Called to initiate a data upload. - * - * The passed argument is a `DataSubmissionRequest` from policy.jsm. - */ - requestDataUpload: function (request) { - if (!this._initialized) { - return Promise.reject(new Error("Not initialized.")); - } - - return Task.spawn(function doUpload() { - yield this._providerManager.ensurePullOnlyProvidersRegistered(); - try { - yield this.collectMeasurements(); - try { - yield this._uploadData(request); - } catch (ex) { - this._onSubmitDataRequestFailure(ex); - } - } finally { - yield this._providerManager.ensurePullOnlyProvidersUnregistered(); - } - }.bind(this)); - }, - - /** - * Request that server data be deleted. - * - * If deletion is scheduled to occur immediately, a promise will be returned - * that will be fulfilled when the deletion attempt finishes. Otherwise, - * callers should poll haveRemoteData() to determine when remote data is - * deleted. - */ - requestDeleteRemoteData: function (reason) { - if (!this.haveRemoteData()) { - return; - } - - return this._policy.deleteRemoteData(reason); - }, - - /** - * Override default handler to incur an upload describing the error. - */ - _onInitError: function (error) { - // Need to capture this before we call the parent else it's always - // set. - let inShutdown = this._shutdownRequested; - let result; - - try { - result = AbstractHealthReporter.prototype._onInitError.call(this, error); - } catch (ex) { - this._log.error("Error when calling _onInitError", ex); - } - - // This bypasses a lot of the checks in policy, such as respect for - // backoff. We should arguably not do this. However, reporting - // startup errors is important. And, they should not occur with much - // frequency in the wild. So, it shouldn't be too big of a deal. - if (!inShutdown && - this._policy.healthReportUploadEnabled && - this._policy.ensureUserNotified()) { - // We don't care about what happens to this request. It's best - // effort. - let request = { - onNoDataAvailable: function () {}, - onSubmissionSuccess: function () {}, - onSubmissionFailureSoft: function () {}, - onSubmissionFailureHard: function () {}, - onUploadInProgress: function () {}, - }; - - this._uploadData(request); - } - - return result; - }, - - _onBagheeraResult: function (request, isDelete, date, result) { - this._log.debug("Received Bagheera result."); - - return Task.spawn(function onBagheeraResult() { - let hrProvider = this.getProvider("org.mozilla.healthreport"); - - if (!result.transportSuccess) { - // The built-in provider may not be initialized if this instance failed - // to initialize fully. - if (hrProvider && !isDelete) { - try { - hrProvider.recordEvent("uploadTransportFailure", date); - } catch (ex) { - this._log.error("Error recording upload transport failure", ex); - } - } - - request.onSubmissionFailureSoft("Network transport error."); - throw new Task.Result(false); - } - - if (!result.serverSuccess) { - if (hrProvider && !isDelete) { - try { - hrProvider.recordEvent("uploadServerFailure", date); - } catch (ex) { - this._log.error("Error recording server failure", ex); - } - } - - request.onSubmissionFailureHard("Server failure."); - throw new Task.Result(false); - } - - if (hrProvider && !isDelete) { - try { - hrProvider.recordEvent("uploadSuccess", date); - } catch (ex) { - this._log.error("Error recording upload success", ex); - } - } - - if (isDelete) { - this._log.warn("Marking delete as successful."); - yield this._state.removeRemoteIDs([result.id]); - } else { - this._log.warn("Marking upload as successful."); - yield this._state.updateLastPingAndRemoveRemoteIDs(date, result.deleteIDs); - } - - request.onSubmissionSuccess(this._now()); - - throw new Task.Result(true); - }.bind(this)); - }, - - _onSubmitDataRequestFailure: function (error) { - this._log.error("Error processing request to submit data", error); - }, - - _formatDate: function (date) { - // Why, oh, why doesn't JS have a strftime() equivalent? - return date.toISOString().substr(0, 10); - }, - - _uploadData: function (request) { - // Under ideal circumstances, clients should never race to this - // function. However, server logs have observed behavior where - // racing to this function could be a cause. So, this lock was - // instituted. - if (this._uploadInProgress) { - this._log.warn("Upload requested but upload already in progress."); - let provider = this.getProvider("org.mozilla.healthreport"); - let promise = provider.recordEvent("uploadAlreadyInProgress"); - request.onUploadInProgress("Upload already in progress."); - return promise; - } - - let id = CommonUtils.generateUUID(); - - this._log.info("Uploading data to server: " + this.serverURI + " " + - this.serverNamespace + ":" + id); - let client = new BagheeraClient(this.serverURI); - let now = this._now(); - - return Task.spawn(function doUpload() { - try { - // The test for upload locking monkeypatches getJSONPayload. - // If the next two lines change, be sure to verify the test is - // accurate! - this._uploadInProgress = true; - let payload = yield this.getJSONPayload(); - - let histogram = Services.telemetry.getHistogramById(TELEMETRY_PAYLOAD_SIZE_UNCOMPRESSED); - histogram.add(payload.length); - - let lastID = this.lastSubmitID; - yield this._state.addRemoteID(id); - - let hrProvider = this.getProvider("org.mozilla.healthreport"); - if (hrProvider) { - let event = lastID ? "continuationUploadAttempt" - : "firstDocumentUploadAttempt"; - try { - hrProvider.recordEvent(event, now); - } catch (ex) { - this._log.error("Error when recording upload attempt", ex); - } - } - - TelemetryStopwatch.start(TELEMETRY_UPLOAD, this); - let result; - try { - let options = { - deleteIDs: this._state.remoteIDs.filter((x) => { return x != id; }), - telemetryCompressed: TELEMETRY_PAYLOAD_SIZE_COMPRESSED, - }; - result = yield client.uploadJSON(this.serverNamespace, id, payload, - options); - TelemetryStopwatch.finish(TELEMETRY_UPLOAD, this); - } catch (ex) { - TelemetryStopwatch.cancel(TELEMETRY_UPLOAD, this); - if (hrProvider) { - try { - hrProvider.recordEvent("uploadClientFailure", now); - } catch (ex) { - this._log.error("Error when recording client failure", ex); - } - } - throw ex; - } - - yield this._onBagheeraResult(request, false, now, result); - } finally { - this._uploadInProgress = false; - } - }.bind(this)); - }, - - /** - * Request deletion of remote data. - * - * @param request - * (DataSubmissionRequest) Tracks progress of this request. - */ - deleteRemoteData: function (request) { - if (!this._state.lastSubmitID) { - this._log.info("Received request to delete remote data but no data stored."); - request.onNoDataAvailable(); - return; - } - - this._log.warn("Deleting remote data."); - let client = new BagheeraClient(this.serverURI); - - return Task.spawn(function* doDelete() { - try { - let result = yield client.deleteDocument(this.serverNamespace, - this.lastSubmitID); - yield this._onBagheeraResult(request, true, this._now(), result); - } catch (ex) { - this._log.error("Error processing request to delete data", ex); - } - }.bind(this)); - }, -}); - diff --git a/services/healthreport/modules-testing/utils.jsm b/services/healthreport/modules-testing/utils.jsm deleted file mode 100644 index dacf92d52f77..000000000000 --- a/services/healthreport/modules-testing/utils.jsm +++ /dev/null @@ -1,198 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -this.EXPORTED_SYMBOLS = [ - "getAppInfo", - "updateAppInfo", - "createFakeCrash", - "InspectedHealthReporter", - "getHealthReporter", -]; - - -const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components; - -Cu.import("resource://gre/modules/Preferences.jsm"); -Cu.import("resource://gre/modules/Promise.jsm"); -Cu.import("resource://gre/modules/FileUtils.jsm"); -Cu.import("resource://gre/modules/osfile.jsm"); -Cu.import("resource://gre/modules/Task.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/services-common/utils.js"); -Cu.import("resource://gre/modules/services/healthreport/healthreporter.jsm"); -Cu.import("resource://gre/modules/services/healthreport/providers.jsm"); - - -var APP_INFO = { - vendor: "Mozilla", - name: "xpcshell", - ID: "xpcshell@tests.mozilla.org", - version: "1", - appBuildID: "20121107", - platformVersion: "p-ver", - platformBuildID: "20121106", - inSafeMode: false, - logConsoleErrors: true, - OS: "XPCShell", - XPCOMABI: "noarch-spidermonkey", - QueryInterface: XPCOMUtils.generateQI([Ci.nsIXULAppInfo, Ci.nsIXULRuntime]), - invalidateCachesOnRestart: function() {}, -}; - - -/** - * Obtain a reference to the current object used to define XULAppInfo. - */ -this.getAppInfo = function () { return APP_INFO; } - -/** - * Update the current application info. - * - * If the argument is defined, it will be the object used. Else, APP_INFO is - * used. - * - * To change the current XULAppInfo, simply call this function. If there was - * a previously registered app info object, it will be unloaded and replaced. - */ -this.updateAppInfo = function (obj) { - obj = obj || APP_INFO; - APP_INFO = obj; - - let id = Components.ID("{fbfae60b-64a4-44ef-a911-08ceb70b9f31}"); - let cid = "@mozilla.org/xre/app-info;1"; - let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); - - // Unregister an existing factory if one exists. - try { - let existing = Components.manager.getClassObjectByContractID(cid, Ci.nsIFactory); - registrar.unregisterFactory(id, existing); - } catch (ex) {} - - let factory = { - createInstance: function (outer, iid) { - if (outer != null) { - throw Cr.NS_ERROR_NO_AGGREGATION; - } - - return obj.QueryInterface(iid); - }, - }; - - registrar.registerFactory(id, "XULAppInfo", cid, factory); -}; - -/** - * Creates a fake crash in the Crash Reports directory. - * - * Currently, we just create a dummy file. A more robust implementation would - * create something that actually resembles a crash report file. - * - * This is very similar to code in crashreporter/tests/browser/head.js. - * - * FUTURE consolidate code in a shared JSM. - */ -this.createFakeCrash = function (submitted=false, date=new Date()) { - let id = CommonUtils.generateUUID(); - let filename; - - let paths = ["Crash Reports"]; - let mode; - - if (submitted) { - paths.push("submitted"); - filename = "bp-" + id + ".txt"; - mode = OS.Constants.libc.S_IRUSR | OS.Constants.libc.S_IWUSR | - OS.Constants.libc.S_IRGRP | OS.Constants.libc.S_IROTH; - } else { - paths.push("pending"); - filename = id + ".dmp"; - mode = OS.Constants.libc.S_IRUSR | OS.Constants.libc.S_IWUSR; - } - - paths.push(filename); - - let file = FileUtils.getFile("UAppData", paths, true); - file.create(file.NORMAL_FILE_TYPE, mode); - file.lastModifiedTime = date.getTime(); - dump("Created fake crash: " + id + "\n"); - - return id; -}; - - -/** - * A HealthReporter that is probed with various callbacks and counters. - * - * The purpose of this type is to aid testing of startup and shutdown. - */ -this.InspectedHealthReporter = function (branch, policy, stateLeaf) { - HealthReporter.call(this, branch, policy, stateLeaf); - - this.onStorageCreated = null; - this.onProviderManagerInitialized = null; - this.providerManagerShutdownCount = 0; - this.storageCloseCount = 0; -} - -InspectedHealthReporter.prototype = { - __proto__: HealthReporter.prototype, - - _onStorageCreated: function (storage) { - if (this.onStorageCreated) { - this.onStorageCreated(storage); - } - - return HealthReporter.prototype._onStorageCreated.call(this, storage); - }, - - _initializeProviderManager: Task.async(function* () { - yield HealthReporter.prototype._initializeProviderManager.call(this); - - if (this.onInitializeProviderManagerFinished) { - this.onInitializeProviderManagerFinished(); - } - }), - - _onProviderManagerInitialized: function () { - if (this.onProviderManagerInitialized) { - this.onProviderManagerInitialized(); - } - - return HealthReporter.prototype._onProviderManagerInitialized.call(this); - }, - - _onProviderManagerShutdown: function () { - this.providerManagerShutdownCount++; - - return HealthReporter.prototype._onProviderManagerShutdown.call(this); - }, - - _onStorageClose: function () { - this.storageCloseCount++; - - return HealthReporter.prototype._onStorageClose.call(this); - }, -}; - -const DUMMY_URI="http://localhost:62013/"; - -this.getHealthReporter = function (name, uri=DUMMY_URI, inspected=false) { - let branch = "healthreport.testing." + name + "."; - - let prefs = new Preferences(branch + "healthreport."); - prefs.set("documentServerURI", uri); - prefs.set("dbName", name); - - let reporter; - - let policyPrefs = new Preferences(branch + "policy."); - let policy = {}; - let type = inspected ? InspectedHealthReporter : HealthReporter; - reporter = new type(branch + "healthreport.", policy, - "state-" + name + ".json"); - - return reporter; -}; diff --git a/services/healthreport/moz.build b/services/healthreport/moz.build deleted file mode 100644 index dccdd1c2db6f..000000000000 --- a/services/healthreport/moz.build +++ /dev/null @@ -1,27 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -SPHINX_TREES['healthreport'] = 'docs' - -XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini'] - -EXTRA_PP_COMPONENTS += [ - 'HealthReportComponents.manifest', -] - -EXTRA_PP_JS_MODULES += [ - 'HealthReport.jsm', -] - -EXTRA_PP_JS_MODULES.services.healthreport += [ - 'healthreporter.jsm', - 'profile.jsm', - 'providers.jsm', -] - -TESTING_JS_MODULES.services.healthreport += [ - 'modules-testing/utils.jsm', -] diff --git a/services/healthreport/profile.jsm b/services/healthreport/profile.jsm deleted file mode 100644 index 85c887b2c442..000000000000 --- a/services/healthreport/profile.jsm +++ /dev/null @@ -1,124 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef MERGED_COMPARTMENT - -"use strict"; - -this.EXPORTED_SYMBOLS = ["ProfileMetadataProvider"]; - -const {utils: Cu, classes: Cc, interfaces: Ci} = Components; - -const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000; - -Cu.import("resource://gre/modules/Metrics.jsm"); - -#endif - -const DEFAULT_PROFILE_MEASUREMENT_NAME = "age"; -const DEFAULT_PROFILE_MEASUREMENT_VERSION = 2; -const REQUIRED_UINT32_TYPE = {type: "TYPE_UINT32"}; - -Cu.import("resource://gre/modules/Promise.jsm"); -Cu.import("resource://gre/modules/osfile.jsm") -Cu.import("resource://gre/modules/Task.jsm"); -Cu.import("resource://gre/modules/Log.jsm"); -Cu.import("resource://gre/modules/ProfileAge.jsm"); - -/** - * Measurements pertaining to the user's profile. - */ -// This is "version 1" of the metadata measurement - it must remain, but -// it's currently unused - see bug 1063714 comment 12 for why. -function ProfileMetadataMeasurement() { - Metrics.Measurement.call(this); -} -ProfileMetadataMeasurement.prototype = { - __proto__: Metrics.Measurement.prototype, - - name: DEFAULT_PROFILE_MEASUREMENT_NAME, - version: 1, - - fields: { - // Profile creation date. Number of days since Unix epoch. - profileCreation: {type: Metrics.Storage.FIELD_LAST_NUMERIC}, - }, -}; - -// This is the current measurement - it adds the profileReset value. -function ProfileMetadataMeasurement2() { - Metrics.Measurement.call(this); -} -ProfileMetadataMeasurement2.prototype = { - __proto__: Metrics.Measurement.prototype, - - name: DEFAULT_PROFILE_MEASUREMENT_NAME, - version: DEFAULT_PROFILE_MEASUREMENT_VERSION, - - fields: { - // Profile creation date. Number of days since Unix epoch. - profileCreation: {type: Metrics.Storage.FIELD_LAST_NUMERIC}, - // Profile reset date. Number of days since Unix epoch. - profileReset: {type: Metrics.Storage.FIELD_LAST_NUMERIC}, - }, -}; - -/** - * Turn a millisecond timestamp into a day timestamp. - * - * @param msec a number of milliseconds since epoch. - * @return the number of whole days denoted by the input. - */ -function truncate(msec) { - return Math.floor(msec / MILLISECONDS_PER_DAY); -} - -/** - * A Metrics.Provider for profile metadata, such as profile creation and - * reset time. - */ -this.ProfileMetadataProvider = function() { - Metrics.Provider.call(this); -} -this.ProfileMetadataProvider.prototype = { - __proto__: Metrics.Provider.prototype, - - name: "org.mozilla.profile", - - measurementTypes: [ProfileMetadataMeasurement2], - - pullOnly: true, - - getProfileDays: Task.async(function* () { - let result = {}; - let accessor = new ProfileAge(null, this._log); - - let created = yield accessor.created; - result["profileCreation"] = truncate(created); - let reset = yield accessor.reset; - if (reset) { - result["profileReset"] = truncate(reset); - } - return result; - }), - - collectConstantData: function () { - let m = this.getMeasurement(DEFAULT_PROFILE_MEASUREMENT_NAME, - DEFAULT_PROFILE_MEASUREMENT_VERSION); - - return Task.spawn(function* collectConstants() { - let days = yield this.getProfileDays(); - - yield this.enqueueStorageOperation(function storeDays() { - return Task.spawn(function* () { - yield m.setLastNumeric("profileCreation", days["profileCreation"]); - if (days["profileReset"]) { - yield m.setLastNumeric("profileReset", days["profileReset"]); - } - }); - }); - }.bind(this)); - }, -}; - diff --git a/services/healthreport/providers.jsm b/services/healthreport/providers.jsm deleted file mode 100644 index 6fb782e639b0..000000000000 --- a/services/healthreport/providers.jsm +++ /dev/null @@ -1,1792 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * This file contains metrics data providers for the Firefox Health - * Report. Ideally each provider in this file exists in separate modules - * and lives close to the code it is querying. However, because of the - * overhead of JS compartments (which are created for each module), we - * currently have all the code in one file. When the overhead of - * compartments reaches a reasonable level, this file should be split - * up. - */ - -#ifndef MERGED_COMPARTMENT - -"use strict"; - -this.EXPORTED_SYMBOLS = [ - "AddonsProvider", - "AppInfoProvider", -#ifdef MOZ_CRASHREPORTER - "CrashesProvider", -#endif - "HealthReportProvider", - "HotfixProvider", - "PlacesProvider", - "SearchesProvider", - "SessionsProvider", - "SysInfoProvider", -]; - -const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - -Cu.import("resource://gre/modules/Metrics.jsm"); - -#endif - -Cu.import("resource://gre/modules/Promise.jsm"); -Cu.import("resource://gre/modules/osfile.jsm"); -Cu.import("resource://gre/modules/Preferences.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/Task.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://services-common/utils.js"); - -XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", - "resource://gre/modules/AddonManager.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils", - "resource://gre/modules/UpdateUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "PlacesDBUtils", - "resource://gre/modules/PlacesDBUtils.jsm"); - - -const LAST_NUMERIC_FIELD = {type: Metrics.Storage.FIELD_LAST_NUMERIC}; -const LAST_TEXT_FIELD = {type: Metrics.Storage.FIELD_LAST_TEXT}; -const DAILY_DISCRETE_NUMERIC_FIELD = {type: Metrics.Storage.FIELD_DAILY_DISCRETE_NUMERIC}; -const DAILY_LAST_NUMERIC_FIELD = {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC}; -const DAILY_LAST_TEXT_FIELD = {type: Metrics.Storage.FIELD_DAILY_LAST_TEXT}; -const DAILY_COUNTER_FIELD = {type: Metrics.Storage.FIELD_DAILY_COUNTER}; - -const TELEMETRY_PREF = "toolkit.telemetry.enabled"; -const SEARCH_COHORT_PREF = "browser.search.cohort"; - -function isTelemetryEnabled(prefs) { - return prefs.get(TELEMETRY_PREF, false); -} - -/** - * Represents basic application state. - * - * This is roughly a union of nsIXULAppInfo, nsIXULRuntime, with a few extra - * pieces thrown in. - */ -function AppInfoMeasurement() { - Metrics.Measurement.call(this); -} - -AppInfoMeasurement.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "appinfo", - version: 2, - - fields: { - vendor: LAST_TEXT_FIELD, - name: LAST_TEXT_FIELD, - id: LAST_TEXT_FIELD, - version: LAST_TEXT_FIELD, - appBuildID: LAST_TEXT_FIELD, - platformVersion: LAST_TEXT_FIELD, - platformBuildID: LAST_TEXT_FIELD, - os: LAST_TEXT_FIELD, - xpcomabi: LAST_TEXT_FIELD, - updateChannel: LAST_TEXT_FIELD, - distributionID: LAST_TEXT_FIELD, - distributionVersion: LAST_TEXT_FIELD, - hotfixVersion: LAST_TEXT_FIELD, - locale: LAST_TEXT_FIELD, - isDefaultBrowser: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC}, - isTelemetryEnabled: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC}, - isBlocklistEnabled: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC}, - }, -}); - -/** - * Legacy version of app info before Telemetry was added. - * - * The "last" fields have all been removed. We only report the longitudinal - * field. - */ -function AppInfoMeasurement1() { - Metrics.Measurement.call(this); -} - -AppInfoMeasurement1.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "appinfo", - version: 1, - - fields: { - isDefaultBrowser: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC}, - }, -}); - - -function AppVersionMeasurement1() { - Metrics.Measurement.call(this); -} - -AppVersionMeasurement1.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "versions", - version: 1, - - fields: { - version: {type: Metrics.Storage.FIELD_DAILY_DISCRETE_TEXT}, - }, -}); - -// Version 2 added the build ID. -function AppVersionMeasurement2() { - Metrics.Measurement.call(this); -} - -AppVersionMeasurement2.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "versions", - version: 2, - - fields: { - appVersion: {type: Metrics.Storage.FIELD_DAILY_DISCRETE_TEXT}, - platformVersion: {type: Metrics.Storage.FIELD_DAILY_DISCRETE_TEXT}, - appBuildID: {type: Metrics.Storage.FIELD_DAILY_DISCRETE_TEXT}, - platformBuildID: {type: Metrics.Storage.FIELD_DAILY_DISCRETE_TEXT}, - }, -}); - -/** - * Holds data on the application update functionality. - */ -function AppUpdateMeasurement1() { - Metrics.Measurement.call(this); -} - -AppUpdateMeasurement1.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "update", - version: 1, - - fields: { - enabled: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC}, - autoDownload: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC}, - }, -}); - -this.AppInfoProvider = function AppInfoProvider() { - Metrics.Provider.call(this); - - this._prefs = new Preferences({defaultBranch: null}); -} -AppInfoProvider.prototype = Object.freeze({ - __proto__: Metrics.Provider.prototype, - - name: "org.mozilla.appInfo", - - measurementTypes: [ - AppInfoMeasurement, - AppInfoMeasurement1, - AppUpdateMeasurement1, - AppVersionMeasurement1, - AppVersionMeasurement2, - ], - - pullOnly: true, - - appInfoFields: { - // From nsIXULAppInfo. - vendor: "vendor", - name: "name", - id: "ID", - version: "version", - appBuildID: "appBuildID", - platformVersion: "platformVersion", - platformBuildID: "platformBuildID", - - // From nsIXULRuntime. - os: "OS", - xpcomabi: "XPCOMABI", - }, - - postInit: function () { - return Task.spawn(this._postInit.bind(this)); - }, - - _postInit: function () { - let recordEmptyAppInfo = function () { - this._setCurrentAppVersion(""); - this._setCurrentPlatformVersion(""); - this._setCurrentAppBuildID(""); - return this._setCurrentPlatformBuildID(""); - }.bind(this); - - // Services.appInfo should always be defined for any reasonably behaving - // Gecko app. If it isn't, we insert a empty string sentinel value. - let ai; - try { - ai = Services.appinfo; - } catch (ex) { - this._log.error("Could not obtain Services.appinfo", ex); - yield recordEmptyAppInfo(); - return; - } - - if (!ai) { - this._log.error("Services.appinfo is unavailable."); - yield recordEmptyAppInfo(); - return; - } - - let currentAppVersion = ai.version; - let currentPlatformVersion = ai.platformVersion; - let currentAppBuildID = ai.appBuildID; - let currentPlatformBuildID = ai.platformBuildID; - - // State's name doesn't contain "app" for historical compatibility. - let lastAppVersion = yield this.getState("lastVersion"); - let lastPlatformVersion = yield this.getState("lastPlatformVersion"); - let lastAppBuildID = yield this.getState("lastAppBuildID"); - let lastPlatformBuildID = yield this.getState("lastPlatformBuildID"); - - if (currentAppVersion != lastAppVersion) { - yield this._setCurrentAppVersion(currentAppVersion); - } - - if (currentPlatformVersion != lastPlatformVersion) { - yield this._setCurrentPlatformVersion(currentPlatformVersion); - } - - if (currentAppBuildID != lastAppBuildID) { - yield this._setCurrentAppBuildID(currentAppBuildID); - } - - if (currentPlatformBuildID != lastPlatformBuildID) { - yield this._setCurrentPlatformBuildID(currentPlatformBuildID); - } - }, - - _setCurrentAppVersion: function (version) { - this._log.info("Recording new application version: " + version); - let m = this.getMeasurement("versions", 2); - m.addDailyDiscreteText("appVersion", version); - - // "app" not encoded in key for historical compatibility. - return this.setState("lastVersion", version); - }, - - _setCurrentPlatformVersion: function (version) { - this._log.info("Recording new platform version: " + version); - let m = this.getMeasurement("versions", 2); - m.addDailyDiscreteText("platformVersion", version); - return this.setState("lastPlatformVersion", version); - }, - - _setCurrentAppBuildID: function (build) { - this._log.info("Recording new application build ID: " + build); - let m = this.getMeasurement("versions", 2); - m.addDailyDiscreteText("appBuildID", build); - return this.setState("lastAppBuildID", build); - }, - - _setCurrentPlatformBuildID: function (build) { - this._log.info("Recording new platform build ID: " + build); - let m = this.getMeasurement("versions", 2); - m.addDailyDiscreteText("platformBuildID", build); - return this.setState("lastPlatformBuildID", build); - }, - - - collectConstantData: function () { - return this.storage.enqueueTransaction(this._populateConstants.bind(this)); - }, - - _populateConstants: function () { - let m = this.getMeasurement(AppInfoMeasurement.prototype.name, - AppInfoMeasurement.prototype.version); - - let ai; - try { - ai = Services.appinfo; - } catch (ex) { - this._log.warn("Could not obtain Services.appinfo", ex); - throw ex; - } - - if (!ai) { - this._log.warn("Services.appinfo is unavailable."); - throw ex; - } - - for (let [k, v] in Iterator(this.appInfoFields)) { - try { - yield m.setLastText(k, ai[v]); - } catch (ex) { - this._log.warn("Error obtaining Services.appinfo." + v); - } - } - - try { - yield m.setLastText("updateChannel", UpdateUtils.UpdateChannel); - } catch (ex) { - this._log.warn("Could not obtain update channel", ex); - } - - yield m.setLastText("distributionID", this._prefs.get("distribution.id", "")); - yield m.setLastText("distributionVersion", this._prefs.get("distribution.version", "")); - yield m.setLastText("hotfixVersion", this._prefs.get("extensions.hotfix.lastVersion", "")); - - try { - let locale = Cc["@mozilla.org/chrome/chrome-registry;1"] - .getService(Ci.nsIXULChromeRegistry) - .getSelectedLocale("global"); - yield m.setLastText("locale", locale); - } catch (ex) { - this._log.warn("Could not obtain application locale", ex); - } - - // FUTURE this should be retrieved periodically or at upload time. - yield this._recordIsTelemetryEnabled(m); - yield this._recordIsBlocklistEnabled(m); - yield this._recordDefaultBrowser(m); - }, - - _recordIsTelemetryEnabled: function (m) { - let enabled = isTelemetryEnabled(this._prefs); - this._log.debug("Recording telemetry enabled (" + TELEMETRY_PREF + "): " + enabled); - yield m.setDailyLastNumeric("isTelemetryEnabled", enabled ? 1 : 0); - }, - - _recordIsBlocklistEnabled: function (m) { - let enabled = this._prefs.get("extensions.blocklist.enabled", false); - this._log.debug("Recording blocklist enabled: " + enabled); - yield m.setDailyLastNumeric("isBlocklistEnabled", enabled ? 1 : 0); - }, - - _recordDefaultBrowser: function (m) { - let shellService; - try { - shellService = Cc["@mozilla.org/browser/shell-service;1"] - .getService(Ci.nsIShellService); - } catch (ex) { - this._log.warn("Could not obtain shell service", ex); - } - - let isDefault = -1; - - if (shellService) { - try { - // This uses the same set of flags used by the pref pane. - isDefault = shellService.isDefaultBrowser(false, true) ? 1 : 0; - } catch (ex) { - this._log.warn("Could not determine if default browser", ex); - } - } - - return m.setDailyLastNumeric("isDefaultBrowser", isDefault); - }, - - collectDailyData: function () { - return this.storage.enqueueTransaction(function getDaily() { - let m = this.getMeasurement(AppUpdateMeasurement1.prototype.name, - AppUpdateMeasurement1.prototype.version); - - let enabled = this._prefs.get("app.update.enabled", false); - yield m.setDailyLastNumeric("enabled", enabled ? 1 : 0); - - let auto = this._prefs.get("app.update.auto", false); - yield m.setDailyLastNumeric("autoDownload", auto ? 1 : 0); - }.bind(this)); - }, -}); - - -function SysInfoMeasurement() { - Metrics.Measurement.call(this); -} - -SysInfoMeasurement.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "sysinfo", - version: 2, - - fields: { - cpuCount: {type: Metrics.Storage.FIELD_LAST_NUMERIC}, - memoryMB: {type: Metrics.Storage.FIELD_LAST_NUMERIC}, - manufacturer: LAST_TEXT_FIELD, - device: LAST_TEXT_FIELD, - hardware: LAST_TEXT_FIELD, - name: LAST_TEXT_FIELD, - version: LAST_TEXT_FIELD, - architecture: LAST_TEXT_FIELD, - isWow64: LAST_NUMERIC_FIELD, - }, -}); - - -this.SysInfoProvider = function SysInfoProvider() { - Metrics.Provider.call(this); -}; - -SysInfoProvider.prototype = Object.freeze({ - __proto__: Metrics.Provider.prototype, - - name: "org.mozilla.sysinfo", - - measurementTypes: [SysInfoMeasurement], - - pullOnly: true, - - sysInfoFields: { - cpucount: "cpuCount", - memsize: "memoryMB", - manufacturer: "manufacturer", - device: "device", - hardware: "hardware", - name: "name", - version: "version", - arch: "architecture", - isWow64: "isWow64", - }, - - collectConstantData: function () { - return this.storage.enqueueTransaction(this._populateConstants.bind(this)); - }, - - _populateConstants: function () { - let m = this.getMeasurement(SysInfoMeasurement.prototype.name, - SysInfoMeasurement.prototype.version); - - let si = Cc["@mozilla.org/system-info;1"] - .getService(Ci.nsIPropertyBag2); - - for (let [k, v] in Iterator(this.sysInfoFields)) { - try { - if (!si.hasKey(k)) { - this._log.debug("Property not available: " + k); - continue; - } - - let value = si.getProperty(k); - let method = "setLastText"; - - if (["cpucount", "memsize"].indexOf(k) != -1) { - let converted = parseInt(value, 10); - if (Number.isNaN(converted)) { - continue; - } - - value = converted; - method = "setLastNumeric"; - } - - switch (k) { - case "memsize": - // Round memory to mebibytes. - value = Math.round(value / 1048576); - break; - case "isWow64": - // Property is only present on Windows. hasKey() skipping from - // above ensures undefined or null doesn't creep in here. - value = value ? 1 : 0; - method = "setLastNumeric"; - break; - } - - yield m[method](v, value); - } catch (ex) { - this._log.warn("Error obtaining system info field: " + k, ex); - } - } - }, -}); - - -/** - * Holds information about the current/active session. - * - * The fields within the current session are moved to daily session fields when - * the application is shut down. - * - * This measurement is backed by the SessionRecorder, not the database. - */ -function CurrentSessionMeasurement() { - Metrics.Measurement.call(this); -} - -CurrentSessionMeasurement.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "current", - version: 3, - - // Storage is in preferences. - fields: {}, - - /** - * All data is stored in prefs, so we have a custom implementation. - */ - getValues: function () { - let sessions = this.provider.healthReporter.sessionRecorder; - - let fields = new Map(); - let now = new Date(); - fields.set("startDay", [now, Metrics.dateToDays(sessions.startDate)]); - fields.set("activeTicks", [now, sessions.activeTicks]); - fields.set("totalTime", [now, sessions.totalTime]); - fields.set("main", [now, sessions.main]); - fields.set("firstPaint", [now, sessions.firstPaint]); - fields.set("sessionRestored", [now, sessions.sessionRestored]); - - return CommonUtils.laterTickResolvingPromise({ - days: new Metrics.DailyValues(), - singular: fields, - }); - }, - - _serializeJSONSingular: function (data) { - let result = {"_v": this.version}; - - for (let [field, value] of data) { - result[field] = value[1]; - } - - return result; - }, -}); - -/** - * Records a history of all application sessions. - */ -function PreviousSessionsMeasurement() { - Metrics.Measurement.call(this); -} - -PreviousSessionsMeasurement.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "previous", - version: 3, - - fields: { - // Milliseconds of sessions that were properly shut down. - cleanActiveTicks: DAILY_DISCRETE_NUMERIC_FIELD, - cleanTotalTime: DAILY_DISCRETE_NUMERIC_FIELD, - - // Milliseconds of sessions that were not properly shut down. - abortedActiveTicks: DAILY_DISCRETE_NUMERIC_FIELD, - abortedTotalTime: DAILY_DISCRETE_NUMERIC_FIELD, - - // Startup times in milliseconds. - main: DAILY_DISCRETE_NUMERIC_FIELD, - firstPaint: DAILY_DISCRETE_NUMERIC_FIELD, - sessionRestored: DAILY_DISCRETE_NUMERIC_FIELD, - }, -}); - - -/** - * Records information about the current browser session. - * - * A browser session is defined as an application/process lifetime. We - * start a new session when the application starts (essentially when - * this provider is instantiated) and end the session on shutdown. - * - * As the application runs, we record basic information about the - * "activity" of the session. Activity is defined by the presence of - * physical input into the browser (key press, mouse click, touch, etc). - * - * We differentiate between regular sessions and "aborted" sessions. An - * aborted session is one that does not end expectedly. This is often the - * result of a crash. We detect aborted sessions by storing the current - * session separate from completed sessions. We normally move the - * current session to completed sessions on application shutdown. If a - * current session is present on application startup, that means that - * the previous session was aborted. - */ -this.SessionsProvider = function () { - Metrics.Provider.call(this); -}; - -SessionsProvider.prototype = Object.freeze({ - __proto__: Metrics.Provider.prototype, - - name: "org.mozilla.appSessions", - - measurementTypes: [CurrentSessionMeasurement, PreviousSessionsMeasurement], - - pullOnly: true, - - collectConstantData: function () { - let previous = this.getMeasurement("previous", 3); - - return this.storage.enqueueTransaction(this._recordAndPruneSessions.bind(this)); - }, - - _recordAndPruneSessions: function () { - this._log.info("Moving previous sessions from session recorder to storage."); - let recorder = this.healthReporter.sessionRecorder; - let sessions = recorder.getPreviousSessions(); - this._log.debug("Found " + Object.keys(sessions).length + " previous sessions."); - - let daily = this.getMeasurement("previous", 3); - - // Please note the coupling here between the session recorder and our state. - // If the pruned index or the current index of the session recorder is ever - // deleted or reset to 0, our stored state of a later index would mean that - // new sessions would never be captured by this provider until the session - // recorder index catches up to our last session ID. This should not happen - // under normal circumstances, so we don't worry too much about it. We - // should, however, consider this as part of implementing bug 841561. - let lastRecordedSession = yield this.getState("lastSession"); - if (lastRecordedSession === null) { - lastRecordedSession = -1; - } - this._log.debug("The last recorded session was #" + lastRecordedSession); - - for (let [index, session] in Iterator(sessions)) { - if (index <= lastRecordedSession) { - this._log.warn("Already recorded session " + index + ". Did the last " + - "session crash or have an issue saving the prefs file?"); - continue; - } - - let type = session.clean ? "clean" : "aborted"; - let date = session.startDate; - yield daily.addDailyDiscreteNumeric(type + "ActiveTicks", session.activeTicks, date); - yield daily.addDailyDiscreteNumeric(type + "TotalTime", session.totalTime, date); - - for (let field of ["main", "firstPaint", "sessionRestored"]) { - yield daily.addDailyDiscreteNumeric(field, session[field], date); - } - - lastRecordedSession = index; - } - - yield this.setState("lastSession", "" + lastRecordedSession); - recorder.pruneOldSessions(new Date()); - }, -}); - -/** - * Stores the set of active addons in storage. - * - * We do things a little differently than most other measurements. Because - * addons are difficult to shoehorn into distinct fields, we simply store a - * JSON blob in storage in a text field. - */ -function ActiveAddonsMeasurement() { - Metrics.Measurement.call(this); - - this._serializers = {}; - this._serializers[this.SERIALIZE_JSON] = { - singular: this._serializeJSONSingular.bind(this), - // We don't need a daily serializer because we have none of this data. - }; -} - -ActiveAddonsMeasurement.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "addons", - version: 2, - - fields: { - addons: LAST_TEXT_FIELD, - }, - - _serializeJSONSingular: function (data) { - if (!data.has("addons")) { - this._log.warn("Don't have addons info. Weird."); - return null; - } - - // Exceptions are caught in the caller. - let result = JSON.parse(data.get("addons")[1]); - result._v = this.version; - return result; - }, -}); - -/** - * Stores the set of active plugins in storage. - * - * This stores the data in a JSON blob in a text field similar to the - * ActiveAddonsMeasurement. - */ -function ActivePluginsMeasurement() { - Metrics.Measurement.call(this); - - this._serializers = {}; - this._serializers[this.SERIALIZE_JSON] = { - singular: this._serializeJSONSingular.bind(this), - // We don't need a daily serializer because we have none of this data. - }; -} - -ActivePluginsMeasurement.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "plugins", - version: 1, - - fields: { - plugins: LAST_TEXT_FIELD, - }, - - _serializeJSONSingular: function (data) { - if (!data.has("plugins")) { - this._log.warn("Don't have plugins info. Weird."); - return null; - } - - // Exceptions are caught in the caller. - let result = JSON.parse(data.get("plugins")[1]); - result._v = this.version; - return result; - }, -}); - -function ActiveGMPluginsMeasurement() { - Metrics.Measurement.call(this); - - this._serializers = {}; - this._serializers[this.SERIALIZE_JSON] = { - singular: this._serializeJSONSingular.bind(this), - }; -} - -ActiveGMPluginsMeasurement.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "gm-plugins", - version: 1, - - fields: { - "gm-plugins": LAST_TEXT_FIELD, - }, - - _serializeJSONSingular: function (data) { - if (!data.has("gm-plugins")) { - this._log.warn("Don't have GM plugins info. Weird."); - return null; - } - - let result = JSON.parse(data.get("gm-plugins")[1]); - result._v = this.version; - return result; - }, -}); - -function AddonCountsMeasurement() { - Metrics.Measurement.call(this); -} - -AddonCountsMeasurement.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "counts", - version: 2, - - fields: { - theme: DAILY_LAST_NUMERIC_FIELD, - lwtheme: DAILY_LAST_NUMERIC_FIELD, - plugin: DAILY_LAST_NUMERIC_FIELD, - extension: DAILY_LAST_NUMERIC_FIELD, - service: DAILY_LAST_NUMERIC_FIELD, - }, -}); - - -/** - * Legacy version of addons counts before services was added. - */ -function AddonCountsMeasurement1() { - Metrics.Measurement.call(this); -} - -AddonCountsMeasurement1.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "counts", - version: 1, - - fields: { - theme: DAILY_LAST_NUMERIC_FIELD, - lwtheme: DAILY_LAST_NUMERIC_FIELD, - plugin: DAILY_LAST_NUMERIC_FIELD, - extension: DAILY_LAST_NUMERIC_FIELD, - }, -}); - - -this.AddonsProvider = function () { - Metrics.Provider.call(this); - - this._prefs = new Preferences({defaultBranch: null}); -}; - -AddonsProvider.prototype = Object.freeze({ - __proto__: Metrics.Provider.prototype, - - // Whenever these AddonListener callbacks are called, we repopulate - // and store the set of addons. Note that these events will only fire - // for restartless add-ons. For actions that require a restart, we - // will catch the change after restart. The alternative is a lot of - // state tracking here, which isn't desirable. - ADDON_LISTENER_CALLBACKS: [ - "onEnabled", - "onDisabled", - "onInstalled", - "onUninstalled", - ], - - // Add-on types for which full details are uploaded in the - // ActiveAddonsMeasurement. All other types are ignored. - FULL_DETAIL_TYPES: [ - "extension", - "service", - ], - - name: "org.mozilla.addons", - - measurementTypes: [ - ActiveAddonsMeasurement, - ActivePluginsMeasurement, - ActiveGMPluginsMeasurement, - AddonCountsMeasurement1, - AddonCountsMeasurement, - ], - - postInit: function () { - let listener = {}; - - for (let method of this.ADDON_LISTENER_CALLBACKS) { - listener[method] = this._collectAndStoreAddons.bind(this); - } - - this._listener = listener; - AddonManager.addAddonListener(this._listener); - - return CommonUtils.laterTickResolvingPromise(); - }, - - onShutdown: function () { - AddonManager.removeAddonListener(this._listener); - this._listener = null; - - return CommonUtils.laterTickResolvingPromise(); - }, - - collectConstantData: function () { - return this._collectAndStoreAddons(); - }, - - _collectAndStoreAddons: function () { - let deferred = Promise.defer(); - - AddonManager.getAllAddons(function onAllAddons(allAddons) { - let data; - let addonsField; - let pluginsField; - let gmPluginsField; - try { - data = this._createDataStructure(allAddons); - addonsField = JSON.stringify(data.addons); - pluginsField = JSON.stringify(data.plugins); - gmPluginsField = JSON.stringify(data.gmPlugins); - } catch (ex) { - this._log.warn("Exception when populating add-ons data structure", ex); - deferred.reject(ex); - return; - } - - let now = new Date(); - let addons = this.getMeasurement("addons", 2); - let plugins = this.getMeasurement("plugins", 1); - let gmPlugins = this.getMeasurement("gm-plugins", 1); - let counts = this.getMeasurement(AddonCountsMeasurement.prototype.name, - AddonCountsMeasurement.prototype.version); - - this.enqueueStorageOperation(function storageAddons() { - for (let type in data.counts) { - try { - counts.fieldID(type); - } catch (ex) { - this._log.warn("Add-on type without field: " + type); - continue; - } - - counts.setDailyLastNumeric(type, data.counts[type], now); - } - - return addons.setLastText("addons", addonsField).then( - function onSuccess() { - return plugins.setLastText("plugins", pluginsField).then( - function onSuccess() { - return gmPlugins.setLastText("gm-plugins", gmPluginsField).then( - function onSuccess() { - deferred.resolve(); - }, - function onError(error) { - deferred.reject(error); - }); - }, - function onError(error) { deferred.reject(error); } - ); - }, - function onError(error) { deferred.reject(error); } - ); - }.bind(this)); - }.bind(this)); - - return deferred.promise; - }, - - COPY_ADDON_FIELDS: [ - "userDisabled", - "appDisabled", - "name", - "version", - "type", - "scope", - "description", - "foreignInstall", - "hasBinaryComponents", - ], - - COPY_PLUGIN_FIELDS: [ - "name", - "version", - "description", - "blocklisted", - "disabled", - "clicktoplay", - ], - - _createDataStructure: function (addons) { - let data = { - addons: {}, - plugins: {}, - gmPlugins: {}, - counts: {} - }; - - for (let addon of addons) { - let type = addon.type; - - // We count plugins separately below. - if (addon.type == "plugin") { - if (addon.isGMPlugin) { - data.gmPlugins[addon.id] = { - version: addon.version, - userDisabled: addon.userDisabled, - applyBackgroundUpdates: addon.applyBackgroundUpdates, - }; - } - continue; - } - - data.counts[type] = (data.counts[type] || 0) + 1; - - if (this.FULL_DETAIL_TYPES.indexOf(addon.type) == -1) { - continue; - } - - let obj = {}; - for (let field of this.COPY_ADDON_FIELDS) { - obj[field] = addon[field]; - } - - if (addon.installDate) { - obj.installDay = this._dateToDays(addon.installDate); - } - - if (addon.updateDate) { - obj.updateDay = this._dateToDays(addon.updateDate); - } - - data.addons[addon.id] = obj; - } - - let pluginTags = Cc["@mozilla.org/plugin/host;1"]. - getService(Ci.nsIPluginHost). - getPluginTags({}); - - for (let tag of pluginTags) { - let obj = { - mimeTypes: tag.getMimeTypes({}), - }; - - for (let field of this.COPY_PLUGIN_FIELDS) { - obj[field] = tag[field]; - } - - // Plugins need to have a filename and a name, so this can't be empty. - let id = tag.filename + ":" + tag.name + ":" + tag.version + ":" - + tag.description; - data.plugins[id] = obj; - } - - data.counts["plugin"] = pluginTags.length; - - return data; - }, -}); - -#ifdef MOZ_CRASHREPORTER - -function DailyCrashesMeasurement1() { - Metrics.Measurement.call(this); -} - -DailyCrashesMeasurement1.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "crashes", - version: 1, - - fields: { - pending: DAILY_COUNTER_FIELD, - submitted: DAILY_COUNTER_FIELD, - }, -}); - -function DailyCrashesMeasurement2() { - Metrics.Measurement.call(this); -} - -DailyCrashesMeasurement2.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "crashes", - version: 2, - - fields: { - mainCrash: DAILY_LAST_NUMERIC_FIELD, - }, -}); - -function DailyCrashesMeasurement3() { - Metrics.Measurement.call(this); -} - -DailyCrashesMeasurement3.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "crashes", - version: 3, - - fields: { - "main-crash": DAILY_LAST_NUMERIC_FIELD, - "main-hang": DAILY_LAST_NUMERIC_FIELD, - "content-crash": DAILY_LAST_NUMERIC_FIELD, - "content-hang": DAILY_LAST_NUMERIC_FIELD, - "plugin-crash": DAILY_LAST_NUMERIC_FIELD, - "plugin-hang": DAILY_LAST_NUMERIC_FIELD, - }, -}); - -function DailyCrashesMeasurement4() { - Metrics.Measurement.call(this); -} - -DailyCrashesMeasurement4.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "crashes", - version: 4, - - fields: { - "main-crash": DAILY_LAST_NUMERIC_FIELD, - "main-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD, - "main-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD, - "main-hang": DAILY_LAST_NUMERIC_FIELD, - "main-hang-submission-succeeded": DAILY_LAST_NUMERIC_FIELD, - "main-hang-submission-failed": DAILY_LAST_NUMERIC_FIELD, - "content-crash": DAILY_LAST_NUMERIC_FIELD, - "content-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD, - "content-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD, - "content-hang": DAILY_LAST_NUMERIC_FIELD, - "content-hang-submission-succeeded": DAILY_LAST_NUMERIC_FIELD, - "content-hang-submission-failed": DAILY_LAST_NUMERIC_FIELD, - "plugin-crash": DAILY_LAST_NUMERIC_FIELD, - "plugin-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD, - "plugin-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD, - "plugin-hang": DAILY_LAST_NUMERIC_FIELD, - "plugin-hang-submission-succeeded": DAILY_LAST_NUMERIC_FIELD, - "plugin-hang-submission-failed": DAILY_LAST_NUMERIC_FIELD, - }, -}); - -function DailyCrashesMeasurement5() { - Metrics.Measurement.call(this); -} - -DailyCrashesMeasurement5.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "crashes", - version: 5, - - fields: { - "main-crash": DAILY_LAST_NUMERIC_FIELD, - "main-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD, - "main-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD, - "main-hang": DAILY_LAST_NUMERIC_FIELD, - "main-hang-submission-succeeded": DAILY_LAST_NUMERIC_FIELD, - "main-hang-submission-failed": DAILY_LAST_NUMERIC_FIELD, - "content-crash": DAILY_LAST_NUMERIC_FIELD, - "content-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD, - "content-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD, - "content-hang": DAILY_LAST_NUMERIC_FIELD, - "content-hang-submission-succeeded": DAILY_LAST_NUMERIC_FIELD, - "content-hang-submission-failed": DAILY_LAST_NUMERIC_FIELD, - "plugin-crash": DAILY_LAST_NUMERIC_FIELD, - "plugin-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD, - "plugin-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD, - "plugin-hang": DAILY_LAST_NUMERIC_FIELD, - "plugin-hang-submission-succeeded": DAILY_LAST_NUMERIC_FIELD, - "plugin-hang-submission-failed": DAILY_LAST_NUMERIC_FIELD, - "gmplugin-crash": DAILY_LAST_NUMERIC_FIELD, - "gmplugin-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD, - "gmplugin-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD, - }, -}); - -function DailyCrashesMeasurement6() { - Metrics.Measurement.call(this); -} - -DailyCrashesMeasurement6.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "crashes", - version: 6, - - fields: { - "main-crash": DAILY_LAST_NUMERIC_FIELD, - "main-crash-oom": DAILY_LAST_NUMERIC_FIELD, - "main-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD, - "main-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD, - "main-hang": DAILY_LAST_NUMERIC_FIELD, - "main-hang-submission-succeeded": DAILY_LAST_NUMERIC_FIELD, - "main-hang-submission-failed": DAILY_LAST_NUMERIC_FIELD, - "content-crash": DAILY_LAST_NUMERIC_FIELD, - "content-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD, - "content-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD, - "content-hang": DAILY_LAST_NUMERIC_FIELD, - "content-hang-submission-succeeded": DAILY_LAST_NUMERIC_FIELD, - "content-hang-submission-failed": DAILY_LAST_NUMERIC_FIELD, - "plugin-crash": DAILY_LAST_NUMERIC_FIELD, - "plugin-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD, - "plugin-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD, - "plugin-hang": DAILY_LAST_NUMERIC_FIELD, - "plugin-hang-submission-succeeded": DAILY_LAST_NUMERIC_FIELD, - "plugin-hang-submission-failed": DAILY_LAST_NUMERIC_FIELD, - "gmplugin-crash": DAILY_LAST_NUMERIC_FIELD, - "gmplugin-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD, - "gmplugin-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD, - }, -}); - -this.CrashesProvider = function () { - Metrics.Provider.call(this); - - // So we can unit test. - this._manager = Services.crashmanager; -}; - -CrashesProvider.prototype = Object.freeze({ - __proto__: Metrics.Provider.prototype, - - name: "org.mozilla.crashes", - - measurementTypes: [ - DailyCrashesMeasurement1, - DailyCrashesMeasurement2, - DailyCrashesMeasurement3, - DailyCrashesMeasurement4, - DailyCrashesMeasurement5, - DailyCrashesMeasurement6, - ], - - pullOnly: true, - - collectDailyData: function () { - return this.storage.enqueueTransaction(this._populateCrashCounts.bind(this)); - }, - - _populateCrashCounts: function () { - this._log.info("Grabbing crash counts from crash manager."); - let crashCounts = yield this._manager.getCrashCountsByDay(); - - // TODO: CrashManager no longer stores submissions as crashes, but we still - // want to send the submission data to FHR. As a temporary workaround, we - // populate |crashCounts| with the submission data to match past behaviour. - // See bug 1056160. - let crashes = yield this._manager.getCrashes(); - for (let crash of crashes) { - for (let [submissionID, submission] of crash.submissions) { - if (!submission.responseDate) { - continue; - } - - let day = Metrics.dateToDays(submission.responseDate); - if (!crashCounts.has(day)) { - crashCounts.set(day, new Map()); - } - - let succeeded = - submission.result == this._manager.SUBMISSION_RESULT_OK; - let type = crash.type + "-submission-" + (succeeded ? "succeeded" : - "failed"); - - let count = (crashCounts.get(day).get(type) || 0) + 1; - crashCounts.get(day).set(type, count); - } - } - - let m = this.getMeasurement("crashes", 6); - let fields = DailyCrashesMeasurement6.prototype.fields; - - for (let [day, types] of crashCounts) { - let date = Metrics.daysToDate(day); - for (let [type, count] of types) { - if (!(type in fields)) { - this._log.warn("Unknown crash type encountered: " + type); - continue; - } - - yield m.setDailyLastNumeric(type, count, date); - } - } - }, -}); - -#endif - -/** - * Records data from update hotfixes. - * - * This measurement has dynamic fields. Field names are of the form - * . where is the hotfix version that produced - * the data. e.g. "v20140527". The sub-version of the hotfix is omitted - * because hotfixes can go through multiple minor versions during development - * and we don't want to introduce more fields than necessary. Furthermore, - * the subsequent dots make parsing field names slightly harder. By stripping, - * we can just split on the first dot. - */ -function UpdateHotfixMeasurement1() { - Metrics.Measurement.call(this); -} - -UpdateHotfixMeasurement1.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "update", - version: 1, - - hotfixFieldTypes: { - "upgradedFrom": Metrics.Storage.FIELD_LAST_TEXT, - "uninstallReason": Metrics.Storage.FIELD_LAST_TEXT, - "downloadAttempts": Metrics.Storage.FIELD_LAST_NUMERIC, - "downloadFailures": Metrics.Storage.FIELD_LAST_NUMERIC, - "installAttempts": Metrics.Storage.FIELD_LAST_NUMERIC, - "installFailures": Metrics.Storage.FIELD_LAST_NUMERIC, - "notificationsShown": Metrics.Storage.FIELD_LAST_NUMERIC, - }, - - fields: { }, - - // Our fields have dynamic names from the hotfix version that supplied them. - // We need to override the default behavior to deal with unknown fields. - shouldIncludeField: function (name) { - return name.includes("."); - }, - - fieldType: function (name) { - for (let known in this.hotfixFieldTypes) { - if (name.endsWith(known)) { - return this.hotfixFieldTypes[known]; - } - } - - return Metrics.Measurement.prototype.fieldType.call(this, name); - }, -}); - -this.HotfixProvider = function () { - Metrics.Provider.call(this); -}; - -HotfixProvider.prototype = Object.freeze({ - __proto__: Metrics.Provider.prototype, - - name: "org.mozilla.hotfix", - measurementTypes: [ - UpdateHotfixMeasurement1, - ], - - pullOnly: true, - - collectDailyData: function () { - return this.storage.enqueueTransaction(this._populateHotfixData.bind(this)); - }, - - _populateHotfixData: function* () { - let m = this.getMeasurement("update", 1); - - // The update hotfix retains its JSON state file after uninstall. - // The initial update hotfix had a hard-coded filename. We treat it - // specially. Subsequent update hotfixes named their files in a - // recognizeable pattern so we don't need to update this probe code to - // know about them. - let files = [ - ["v20140527", OS.Path.join(OS.Constants.Path.profileDir, - "hotfix.v20140527.01.json")], - ]; - - let it = new OS.File.DirectoryIterator(OS.Constants.Path.profileDir); - try { - yield it.forEach((e, index, it) => { - let m = e.name.match(/^updateHotfix\.([a-zA-Z0-9]+)\.json$/); - if (m) { - files.push([m[1], e.path]); - } - }); - } finally { - it.close(); - } - - let decoder = new TextDecoder(); - for (let e of files) { - let [version, path] = e; - let p; - try { - let data = yield OS.File.read(path); - p = JSON.parse(decoder.decode(data)); - } catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) { - continue; - } catch (ex) { - this._log.warn("Error loading update hotfix payload: " + ex.message); - } - - // Wrap just in case. - try { - for (let k in m.hotfixFieldTypes) { - if (!(k in p)) { - continue; - } - - let value = p[k]; - if (value === null && k == "uninstallReason") { - value = "STILL_INSTALLED"; - } - - let field = version + "." + k; - let fieldType; - let storageOp; - switch (typeof(value)) { - case "string": - fieldType = this.storage.FIELD_LAST_TEXT; - storageOp = "setLastTextFromFieldID"; - break; - case "number": - fieldType = this.storage.FIELD_LAST_NUMERIC; - storageOp = "setLastNumericFromFieldID"; - break; - default: - this._log.warn("Unknown value in hotfix state: " + k + "=" + value); - continue; - } - - if (this.storage.hasFieldFromMeasurement(m.id, field, fieldType)) { - let fieldID = this.storage.fieldIDFromMeasurement(m.id, field); - yield this.storage[storageOp](fieldID, value); - } else { - let fieldID = yield this.storage.registerField(m.id, field, - fieldType); - yield this.storage[storageOp](fieldID, value); - } - } - - } catch (ex) { - this._log.warn("Error processing update hotfix data: " + ex); - } - } - }, -}); - -/** - * Holds basic statistics about the Places database. - */ -function PlacesMeasurement() { - Metrics.Measurement.call(this); -} - -PlacesMeasurement.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "places", - version: 1, - - fields: { - pages: DAILY_LAST_NUMERIC_FIELD, - bookmarks: DAILY_LAST_NUMERIC_FIELD, - }, -}); - - -/** - * Collects information about Places. - */ -this.PlacesProvider = function () { - Metrics.Provider.call(this); -}; - -PlacesProvider.prototype = Object.freeze({ - __proto__: Metrics.Provider.prototype, - - name: "org.mozilla.places", - - measurementTypes: [PlacesMeasurement], - - collectDailyData: function () { - return this.storage.enqueueTransaction(this._collectData.bind(this)); - }, - - _collectData: function () { - let now = new Date(); - let data = yield this._getDailyValues(); - - let m = this.getMeasurement("places", 1); - - yield m.setDailyLastNumeric("pages", data.PLACES_PAGES_COUNT); - yield m.setDailyLastNumeric("bookmarks", data.PLACES_BOOKMARKS_COUNT); - }, - - _getDailyValues: function () { - let deferred = Promise.defer(); - - PlacesDBUtils.telemetry(null, function onResult(data) { - deferred.resolve(data); - }); - - return deferred.promise; - }, -}); - -function SearchCountMeasurement1() { - Metrics.Measurement.call(this); -} - -SearchCountMeasurement1.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "counts", - version: 1, - - // We only record searches for search engines that have partner agreements - // with Mozilla. - fields: { - "amazon.com.abouthome": DAILY_COUNTER_FIELD, - "amazon.com.contextmenu": DAILY_COUNTER_FIELD, - "amazon.com.searchbar": DAILY_COUNTER_FIELD, - "amazon.com.urlbar": DAILY_COUNTER_FIELD, - "bing.abouthome": DAILY_COUNTER_FIELD, - "bing.contextmenu": DAILY_COUNTER_FIELD, - "bing.searchbar": DAILY_COUNTER_FIELD, - "bing.urlbar": DAILY_COUNTER_FIELD, - "google.abouthome": DAILY_COUNTER_FIELD, - "google.contextmenu": DAILY_COUNTER_FIELD, - "google.searchbar": DAILY_COUNTER_FIELD, - "google.urlbar": DAILY_COUNTER_FIELD, - "yahoo.abouthome": DAILY_COUNTER_FIELD, - "yahoo.contextmenu": DAILY_COUNTER_FIELD, - "yahoo.searchbar": DAILY_COUNTER_FIELD, - "yahoo.urlbar": DAILY_COUNTER_FIELD, - "other.abouthome": DAILY_COUNTER_FIELD, - "other.contextmenu": DAILY_COUNTER_FIELD, - "other.searchbar": DAILY_COUNTER_FIELD, - "other.urlbar": DAILY_COUNTER_FIELD, - }, -}); - -/** - * Records search counts per day per engine and where search initiated. - * - * We want to record granular details for individual locale-specific search - * providers, but only if they're Mozilla partners. In order to do this, we - * track the nsISearchEngine identifier, which denotes shipped search engines, - * and intersect those with our partner list. - * - * We don't use the search engine name directly, because it is shared across - * locales; e.g., eBay-de and eBay both share the name "eBay". - */ -function SearchCountMeasurementBase() { - this._fieldSpecs = {}; - Metrics.Measurement.call(this); -} - -SearchCountMeasurementBase.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - - // Our fields are dynamic. - get fields() { - return this._fieldSpecs; - }, - - /** - * Override the default behavior: serializers should include every counter - * field from the DB, even if we don't currently have it registered. - * - * Do this so we don't have to register several hundred fields to match - * various Firefox locales. - * - * We use the "provider.type" syntax as a rudimentary check for validity. - * - * We trust that measurement versioning is sufficient to exclude old provider - * data. - */ - shouldIncludeField: function (name) { - return name.includes("."); - }, - - /** - * The measurement type mechanism doesn't introspect the DB. Override it - * so that we can assume all unknown fields are counters. - */ - fieldType: function (name) { - if (name in this.fields) { - return this.fields[name].type; - } - - // Default to a counter. - return Metrics.Storage.FIELD_DAILY_COUNTER; - }, - - SOURCES: [ - "abouthome", - "contextmenu", - "newtab", - "searchbar", - "urlbar", - ], -}); - -function SearchCountMeasurement2() { - SearchCountMeasurementBase.call(this); -} - -SearchCountMeasurement2.prototype = Object.freeze({ - __proto__: SearchCountMeasurementBase.prototype, - name: "counts", - version: 2, -}); - -function SearchCountMeasurement3() { - SearchCountMeasurementBase.call(this); -} - -SearchCountMeasurement3.prototype = Object.freeze({ - __proto__: SearchCountMeasurementBase.prototype, - name: "counts", - version: 3, - - getEngines: function () { - return Services.search.getEngines(); - }, - - getEngineID: function (engine) { - if (!engine) { - return "other"; - } - if (engine.identifier) { - return engine.identifier; - } - return "other-" + engine.name; - }, -}); - -function SearchEnginesMeasurement1() { - Metrics.Measurement.call(this); -} - -SearchEnginesMeasurement1.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "engines", - version: 2, - - fields: { - default: DAILY_LAST_TEXT_FIELD, - cohort: DAILY_LAST_TEXT_FIELD, - }, -}); - -this.SearchesProvider = function () { - Metrics.Provider.call(this); - - this._prefs = new Preferences({defaultBranch: null}); -}; - -this.SearchesProvider.prototype = Object.freeze({ - __proto__: Metrics.Provider.prototype, - - name: "org.mozilla.searches", - measurementTypes: [ - SearchCountMeasurement1, - SearchCountMeasurement2, - SearchCountMeasurement3, - SearchEnginesMeasurement1, - ], - - /** - * Initialize the search service before our measurements are touched. - */ - preInit: function (storage) { - // Initialize search service. - let deferred = Promise.defer(); - Services.search.init(function onInitComplete () { - deferred.resolve(); - }); - return deferred.promise; - }, - - collectDailyData: function () { - return this.storage.enqueueTransaction(function getDaily() { - let m = this.getMeasurement(SearchEnginesMeasurement1.prototype.name, - SearchEnginesMeasurement1.prototype.version); - - let engine; - try { - engine = Services.search.defaultEngine; - } catch (e) {} - let name; - - if (!engine) { - name = "NONE"; - } else if (engine.identifier) { - name = engine.identifier; - } else if (engine.name) { - name = "other-" + engine.name; - } else { - name = "UNDEFINED"; - } - - yield m.setDailyLastText("default", name); - - if (Services.prefs.prefHasUserValue(SEARCH_COHORT_PREF)) - yield m.setDailyLastText("cohort", Services.prefs.getCharPref(SEARCH_COHORT_PREF)); - }.bind(this)); - }, - - /** - * Record that a search occurred. - * - * @param engine - * (nsISearchEngine) The search engine used. - * @param source - * (string) Where the search was initiated from. Must be one of the - * SearchCountMeasurement2.SOURCES values. - * - * @return Promise<> - * The promise is resolved when the storage operation completes. - */ - recordSearch: function (engine, source) { - let m = this.getMeasurement("counts", 3); - - if (m.SOURCES.indexOf(source) == -1) { - throw new Error("Unknown source for search: " + source); - } - - let field = m.getEngineID(engine) + "." + source; - if (this.storage.hasFieldFromMeasurement(m.id, field, - this.storage.FIELD_DAILY_COUNTER)) { - let fieldID = this.storage.fieldIDFromMeasurement(m.id, field); - return this.enqueueStorageOperation(function recordSearchKnownField() { - return this.storage.incrementDailyCounterFromFieldID(fieldID); - }.bind(this)); - } - - // Otherwise, we first need to create the field. - return this.enqueueStorageOperation(function recordFieldAndSearch() { - // This function has to return a promise. - return Task.spawn(function () { - let fieldID = yield this.storage.registerField(m.id, field, - this.storage.FIELD_DAILY_COUNTER); - yield this.storage.incrementDailyCounterFromFieldID(fieldID); - }.bind(this)); - }.bind(this)); - }, -}); - -function HealthReportSubmissionMeasurement1() { - Metrics.Measurement.call(this); -} - -HealthReportSubmissionMeasurement1.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "submissions", - version: 1, - - fields: { - firstDocumentUploadAttempt: DAILY_COUNTER_FIELD, - continuationUploadAttempt: DAILY_COUNTER_FIELD, - uploadSuccess: DAILY_COUNTER_FIELD, - uploadTransportFailure: DAILY_COUNTER_FIELD, - uploadServerFailure: DAILY_COUNTER_FIELD, - uploadClientFailure: DAILY_COUNTER_FIELD, - }, -}); - -function HealthReportSubmissionMeasurement2() { - Metrics.Measurement.call(this); -} - -HealthReportSubmissionMeasurement2.prototype = Object.freeze({ - __proto__: Metrics.Measurement.prototype, - - name: "submissions", - version: 2, - - fields: { - firstDocumentUploadAttempt: DAILY_COUNTER_FIELD, - continuationUploadAttempt: DAILY_COUNTER_FIELD, - uploadSuccess: DAILY_COUNTER_FIELD, - uploadTransportFailure: DAILY_COUNTER_FIELD, - uploadServerFailure: DAILY_COUNTER_FIELD, - uploadClientFailure: DAILY_COUNTER_FIELD, - uploadAlreadyInProgress: DAILY_COUNTER_FIELD, - }, -}); - -this.HealthReportProvider = function () { - Metrics.Provider.call(this); -} - -HealthReportProvider.prototype = Object.freeze({ - __proto__: Metrics.Provider.prototype, - - name: "org.mozilla.healthreport", - - measurementTypes: [ - HealthReportSubmissionMeasurement1, - HealthReportSubmissionMeasurement2, - ], - - recordEvent: function (event, date=new Date()) { - let m = this.getMeasurement("submissions", 2); - return this.enqueueStorageOperation(function recordCounter() { - return m.incrementDailyCounter(event, date); - }); - }, -}); diff --git a/services/healthreport/tests/xpcshell/head.js b/services/healthreport/tests/xpcshell/head.js deleted file mode 100644 index 9bb336e0b4d7..000000000000 --- a/services/healthreport/tests/xpcshell/head.js +++ /dev/null @@ -1,21 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -// We need to initialize the profile or OS.File may not work. See bug 810543. -do_get_profile(); - -(function initMetricsTestingInfrastructure() { - let ns = {}; - Components.utils.import("resource://testing-common/services/common/logging.js", - ns); - - ns.initTestLogging(); -}).call(this); - -(function createAppInfo() { - let ns = {}; - Components.utils.import("resource://testing-common/services/healthreport/utils.jsm", ns); - ns.updateAppInfo(); -}).call(this); diff --git a/services/healthreport/tests/xpcshell/test_load_modules.js b/services/healthreport/tests/xpcshell/test_load_modules.js deleted file mode 100644 index dde9d4eb57ba..000000000000 --- a/services/healthreport/tests/xpcshell/test_load_modules.js +++ /dev/null @@ -1,20 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -const modules = [ - "healthreporter.jsm", - "profile.jsm", - "providers.jsm", -]; - -function run_test() { - for (let m of modules) { - let resource = "resource://gre/modules/services/healthreport/" + m; - Components.utils.import(resource, {}); - } - - Components.utils.import("resource://gre/modules/HealthReport.jsm", {}); -} - diff --git a/services/healthreport/tests/xpcshell/test_profile.js b/services/healthreport/tests/xpcshell/test_profile.js deleted file mode 100644 index c5d99a057f49..000000000000 --- a/services/healthreport/tests/xpcshell/test_profile.js +++ /dev/null @@ -1,258 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -var {utils: Cu} = Components; - -const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000; - -// Create profile directory before use. -// It can be no older than a day ago…. -var profile_creation_lower = Date.now() - MILLISECONDS_PER_DAY; -do_get_profile(); - -Cu.import("resource://gre/modules/Promise.jsm"); -Cu.import("resource://gre/modules/Metrics.jsm"); -Cu.import("resource://gre/modules/services/healthreport/profile.jsm"); -Cu.import("resource://gre/modules/ProfileAge.jsm"); -Cu.import("resource://gre/modules/Task.jsm"); - - -function MockProfileMetadataProvider(name="MockProfileMetadataProvider") { - this.name = name; - ProfileMetadataProvider.call(this); -} -MockProfileMetadataProvider.prototype = { - __proto__: ProfileMetadataProvider.prototype, - includeProfileReset: false, - - getProfileDays: function getProfileDays() { - let result = {profileCreation: 1234}; - if (this.includeProfileReset) { - result.profileReset = 5678; - } - return Promise.resolve(result); - }, -}; - - -function run_test() { - run_next_test(); -} - -/** - * Ensure that OS.File works in our environment. - * This test can go once there are xpcshell tests for OS.File. - */ -add_test(function use_os_file() { - Cu.import("resource://gre/modules/osfile.jsm") - - // Ensure that we get constants, too. - do_check_neq(OS.Constants.Path.profileDir, null); - - let iterator = new OS.File.DirectoryIterator("."); - iterator.forEach(function onEntry(entry) { - print("Got " + entry.path); - }).then(function onSuccess() { - iterator.close(); - print("Done."); - run_next_test(); - }, function onFail() { - iterator.close(); - do_throw("Iterating over current directory failed."); - }); -}); - -function getAccessor() { - let acc = new ProfileAge(); - print("Profile is " + acc.profilePath); - return acc; -} - -add_test(function test_time_accessor_no_file() { - let acc = getAccessor(); - - // There should be no file yet. - acc.readTimes() - .then(function onSuccess(json) { - do_throw("File existed!"); - }, - function onFailure() { - run_next_test(); - }); -}); - -add_task(function test_time_accessor_named_file() { - let acc = getAccessor(); - - // There should be no file yet. - yield acc.writeTimes({created: 12345}, "test.json"); - let json = yield acc.readTimes("test.json") - print("Read: " + JSON.stringify(json)); - do_check_eq(12345, json.created); -}); - -add_task(function test_time_accessor_creates_file() { - let lower = profile_creation_lower; - - // Ensure that provided contents are merged, and existing - // files can be overwritten. These two things occur if we - // read and then decide that we have to write. - let acc = getAccessor(); - let existing = {abc: "123", easy: "abc"}; - let expected; - - let created = yield acc.computeAndPersistCreated(existing, "test2.json") - let upper = Date.now() + 1000; - print(lower + " < " + created + " <= " + upper); - do_check_true(lower < created); - do_check_true(upper >= created); - expected = created; - - let json = yield acc.readTimes("test2.json") - print("Read: " + JSON.stringify(json)); - do_check_eq("123", json.abc); - do_check_eq("abc", json.easy); - do_check_eq(expected, json.created); -}); - -add_task(function test_time_accessor_all() { - let lower = profile_creation_lower; - let acc = getAccessor(); - let expected; - let created = yield acc.created - let upper = Date.now() + 1000; - do_check_true(lower < created); - do_check_true(upper >= created); - expected = created; - - let again = yield acc.created - do_check_eq(expected, again); -}); - -add_task(function* test_time_reset() { - let lower = profile_creation_lower; - let acc = getAccessor(); - let testTime = 100000; - yield acc.recordProfileReset(testTime); - let reset = yield acc.reset; - Assert.equal(reset, testTime); -}); - -add_test(function test_constructor() { - let provider = new ProfileMetadataProvider("named"); - run_next_test(); -}); - -add_test(function test_profile_files() { - let provider = new ProfileMetadataProvider(); - - function onSuccess(answer) { - let now = Date.now() / MILLISECONDS_PER_DAY; - print("Got " + answer.profileCreation + ", versus now = " + now); - Assert.ok(answer.profileCreation < now); - run_next_test(); - } - - function onFailure(ex) { - do_throw("Directory iteration failed: " + ex); - } - - provider.getProfileDays().then(onSuccess, onFailure); -}); - -// A generic test helper. We use this with both real -// and mock providers in these tests. -function test_collect_constant(provider, expectReset) { - return Task.spawn(function* () { - yield provider.collectConstantData(); - - let m = provider.getMeasurement("age", 2); - Assert.notEqual(m, null); - let values = yield m.getValues(); - Assert.ok(values.singular.has("profileCreation")); - let createValue = values.singular.get("profileCreation")[1]; - let resetValue; - if (expectReset) { - Assert.equal(values.singular.size, 2); - Assert.ok(values.singular.has("profileReset")); - resetValue = values.singular.get("profileReset")[1]; - } else { - Assert.equal(values.singular.size, 1); - Assert.ok(!values.singular.has("profileReset")); - } - return [createValue, resetValue]; - }); -} - -add_task(function* test_collect_constant_mock_no_reset() { - let storage = yield Metrics.Storage("collect_constant_mock"); - let provider = new MockProfileMetadataProvider(); - yield provider.init(storage); - - let v = yield test_collect_constant(provider, false); - Assert.equal(v.length, 2); - Assert.equal(v[0], 1234); - Assert.equal(v[1], undefined); - - yield storage.close(); -}); - -add_task(function* test_collect_constant_mock_with_reset() { - let storage = yield Metrics.Storage("collect_constant_mock"); - let provider = new MockProfileMetadataProvider(); - provider.includeProfileReset = true; - yield provider.init(storage); - - let v = yield test_collect_constant(provider, true); - Assert.equal(v.length, 2); - Assert.equal(v[0], 1234); - Assert.equal(v[1], 5678); - - yield storage.close(); -}); - -add_task(function* test_collect_constant_real_no_reset() { - let provider = new ProfileMetadataProvider(); - let storage = yield Metrics.Storage("collect_constant_real"); - yield provider.init(storage); - - let vals = yield test_collect_constant(provider, false); - let created = vals[0]; - let reset = vals[1]; - Assert.equal(reset, undefined); - - let ms = created * MILLISECONDS_PER_DAY; - let lower = profile_creation_lower; - let upper = Date.now() + 1000; - print("Day: " + created); - print("msec: " + ms); - print("Lower: " + lower); - print("Upper: " + upper); - Assert.ok(lower <= ms); - Assert.ok(upper >= ms); - - yield storage.close(); -}); - -add_task(function* test_collect_constant_real_with_reset() { - let now = Date.now(); - let acc = getAccessor(); - yield acc.writeTimes({created: now-MILLISECONDS_PER_DAY, // yesterday - reset: Date.now()}); // today - - let provider = new ProfileMetadataProvider(); - let storage = yield Metrics.Storage("collect_constant_real"); - yield provider.init(storage); - - let [created, reset] = yield test_collect_constant(provider, true); - // we've already tested truncate() works as expected, so here just check - // we got values. - Assert.ok(created); - Assert.ok(reset); - Assert.ok(created <= reset); - - yield storage.close(); -}); - diff --git a/services/healthreport/tests/xpcshell/test_provider_addons.js b/services/healthreport/tests/xpcshell/test_provider_addons.js deleted file mode 100644 index 27a908bd7dc6..000000000000 --- a/services/healthreport/tests/xpcshell/test_provider_addons.js +++ /dev/null @@ -1,339 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -var {utils: Cu, classes: Cc, interfaces: Ci} = Components; - - -Cu.import("resource://gre/modules/Metrics.jsm"); -Cu.import("resource://gre/modules/services/healthreport/providers.jsm"); - -// The hack, it burns. This could go away if extensions code exposed its -// test environment setup functions as a testing-only JSM. See similar -// code in Sync's head_helpers.js. -var gGlobalScope = this; -function loadAddonManager() { - let ns = {}; - Cu.import("resource://gre/modules/Services.jsm", ns); - let head = "../../../../toolkit/mozapps/extensions/test/xpcshell/head_addons.js"; - let file = do_get_file(head); - let uri = ns.Services.io.newFileURI(file); - ns.Services.scriptloader.loadSubScript(uri.spec, gGlobalScope); - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); - startupManager(); -} - -function run_test() { - loadAddonManager(); - run_next_test(); -} - -add_test(function test_constructor() { - let provider = new AddonsProvider(); - - run_next_test(); -}); - -add_task(function test_init() { - let storage = yield Metrics.Storage("init"); - let provider = new AddonsProvider(); - yield provider.init(storage); - yield provider.shutdown(); - - yield storage.close(); -}); - -function monkeypatchAddons(provider, addons) { - if (!Array.isArray(addons)) { - throw new Error("Must define array of addon objects."); - } - - Object.defineProperty(provider, "_createDataStructure", { - value: function _createDataStructure() { - return AddonsProvider.prototype._createDataStructure.call(provider, addons); - }, - }); -} - -add_task(function test_collect() { - let storage = yield Metrics.Storage("collect"); - let provider = new AddonsProvider(); - yield provider.init(storage); - - let now = new Date(); - - // FUTURE install add-on via AddonManager and don't use monkeypatching. - let testAddons = [ - { - id: "addon0", - userDisabled: false, - appDisabled: false, - version: "1", - type: "extension", - scope: 1, - foreignInstall: false, - hasBinaryComponents: false, - installDate: now, - updateDate: now, - }, - // This plugin entry should get ignored. - { - id: "addon1", - userDisabled: false, - appDisabled: false, - version: "2", - type: "plugin", - scope: 1, - foreignInstall: false, - hasBinaryComponents: false, - installDate: now, - updateDate: now, - }, - // Is counted but full details are omitted because it is a theme. - { - id: "addon2", - userDisabled: false, - appDisabled: false, - version: "3", - type: "theme", - scope: 1, - foreignInstall: false, - hasBinaryComponents: false, - installDate: now, - updateDate: now, - }, - { - id: "addon3", - userDisabled: false, - appDisabled: false, - version: "4", - type: "service", - scope: 1, - foreignInstall: false, - hasBinaryComponents: false, - installDate: now, - updateDate: now, - description: "addon3 description" - }, - { - // Should be excluded from the report completely - id: "pluginfake", - type: "plugin", - userDisabled: false, - appDisabled: false, - }, - { - // Should be in gm-plugins - id: "gmp-testgmp", - type: "plugin", - userDisabled: false, - version: "7.2", - isGMPlugin: true, - }, - ]; - - monkeypatchAddons(provider, testAddons); - - let testPlugins = { - "Test Plug-in": - { - "version": "1.0.0.0", - "description": "Plug-in for testing purposes.™ (हिन्दी 中文 العربية)", - "blocklisted": false, - "disabled": false, - "clicktoplay": false, - "mimeTypes":[ - "application/x-test" - ], - }, - "Second Test Plug-in": - { - "version": "1.0.0.0", - "description": "Second plug-in for testing purposes.", - "blocklisted": false, - "disabled": false, - "clicktoplay": false, - "mimeTypes":[ - "application/x-second-test" - ], - }, - "Java Test Plug-in": - { - "version": "1.0.0.0", - "description": "Dummy Java plug-in for testing purposes.", - "blocklisted": false, - "disabled": false, - "clicktoplay": false, - "mimeTypes":[ - "application/x-java-test" - ], - }, - "Third Test Plug-in": - { - "version": "1.0.0.0", - "description": "Third plug-in for testing purposes.", - "blocklisted": false, - "disabled": false, - "clicktoplay": false, - "mimeTypes":[ - "application/x-third-test" - ], - }, - "Flash Test Plug-in": - { - "version": "1.0.0.0", - "description": "Flash plug-in for testing purposes.", - "blocklisted": false, - "disabled": false, - "clicktoplay": false, - "mimeTypes":[ - "application/x-shockwave-flash-test" - ], - }, - "Silverlight Test Plug-in": - { - "version": "1.0.0.0", - "description": "Silverlight plug-in for testing purposes.", - "blocklisted": false, - "disabled": false, - "clicktoplay": false, - "mimeTypes":[ - "application/x-silverlight-test" - ], - }, - }; - - let pluginTags = Cc["@mozilla.org/plugin/host;1"] - .getService(Ci.nsIPluginHost) - .getPluginTags({}); - - for (let tag of pluginTags) { - if (tag.name in testPlugins) { - let p = testPlugins[tag.name]; - p.id = tag.filename+":"+tag.name+":"+p.version+":"+p.description; - } - } - - yield provider.collectConstantData(); - - // Test addons measurement. - - let addons = provider.getMeasurement("addons", 2); - let data = yield addons.getValues(); - - do_check_eq(data.days.size, 0); - do_check_eq(data.singular.size, 1); - do_check_true(data.singular.has("addons")); - - let json = data.singular.get("addons")[1]; - let value = JSON.parse(json); - do_check_eq(typeof(value), "object"); - do_check_eq(Object.keys(value).length, 2); - do_check_true("addon0" in value); - do_check_true(!("addon1" in value)); - do_check_true(!("addon2" in value)); - do_check_true("addon3" in value); - do_check_true(!("pluginfake" in value)); - do_check_true(!("gmp-testgmp" in value)); - - let serializer = addons.serializer(addons.SERIALIZE_JSON); - let serialized = serializer.singular(data.singular); - do_check_eq(typeof(serialized), "object"); - do_check_eq(Object.keys(serialized).length, 3); // Our entries, plus _v. - do_check_true("addon0" in serialized); - do_check_true("addon3" in serialized); - do_check_eq(serialized._v, 2); - - // Test plugins measurement. - - let plugins = provider.getMeasurement("plugins", 1); - data = yield plugins.getValues(); - - do_check_eq(data.days.size, 0); - do_check_eq(data.singular.size, 1); - do_check_true(data.singular.has("plugins")); - - json = data.singular.get("plugins")[1]; - value = JSON.parse(json); - do_check_eq(typeof(value), "object"); - do_check_eq(Object.keys(value).length, pluginTags.length); - - do_check_true(testPlugins["Test Plug-in"].id in value); - do_check_true(testPlugins["Second Test Plug-in"].id in value); - do_check_true(testPlugins["Java Test Plug-in"].id in value); - - for (let id in value) { - let item = value[id]; - let testData = testPlugins[item.name]; - for (let prop in testData) { - if (prop == "mimeTypes" || prop == "id") { - continue; - } - do_check_eq(testData[prop], item[prop]); - } - - for (let mime of testData.mimeTypes) { - do_check_true(item.mimeTypes.indexOf(mime) != -1); - } - } - - serializer = plugins.serializer(plugins.SERIALIZE_JSON); - serialized = serializer.singular(data.singular); - do_check_eq(typeof(serialized), "object"); - do_check_eq(Object.keys(serialized).length, pluginTags.length+1); // Our entries, plus _v. - for (let name in testPlugins) { - // Special case for bug 1165981. There is a test plugin that - // exists to make sure we don't load it on certain platforms. - // We skip the check for that plugin here, as it will work on some - // platforms but not others. - if (name == "Third Test Plug-in") { - continue; - } - do_check_true(testPlugins[name].id in serialized); - } - do_check_eq(serialized._v, 1); - - // Test GMP plugins measurement. - - let gmPlugins = provider.getMeasurement("gm-plugins", 1); - data = yield gmPlugins.getValues(); - - do_check_eq(data.days.size, 0); - do_check_eq(data.singular.size, 1); - do_check_true(data.singular.has("gm-plugins")); - - json = data.singular.get("gm-plugins")[1]; - value = JSON.parse(json); - do_print("value: " + json); - do_check_eq(typeof(value), "object"); - do_check_eq(Object.keys(value).length, 1); - - do_check_eq(value["gmp-testgmp"].version, "7.2"); - do_check_eq(value["gmp-testgmp"].userDisabled, false); - - serializer = gmPlugins.serializer(plugins.SERIALIZE_JSON); - serialized = serializer.singular(data.singular); - do_check_eq(typeof(serialized), "object"); - do_check_eq(serialized["gmp-testgmp"].version, "7.2"); - do_check_eq(serialized._v, 1); - - // Test counts measurement. - - let counts = provider.getMeasurement("counts", 2); - data = yield counts.getValues(); - do_check_eq(data.days.size, 1); - do_check_eq(data.singular.size, 0); - do_check_true(data.days.hasDay(now)); - - value = data.days.getDay(now); - do_check_eq(value.size, 4); - do_check_eq(value.get("extension"), 1); - do_check_eq(value.get("plugin"), pluginTags.length); - do_check_eq(value.get("theme"), 1); - do_check_eq(value.get("service"), 1); - - yield provider.shutdown(); - yield storage.close(); -}); - diff --git a/services/healthreport/tests/xpcshell/test_provider_appinfo.js b/services/healthreport/tests/xpcshell/test_provider_appinfo.js deleted file mode 100644 index 56b28403529b..000000000000 --- a/services/healthreport/tests/xpcshell/test_provider_appinfo.js +++ /dev/null @@ -1,270 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -var {interfaces: Ci, results: Cr, utils: Cu, classes: Cc} = Components; - -Cu.import("resource://gre/modules/Metrics.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/services/healthreport/providers.jsm"); -Cu.import("resource://testing-common/services/healthreport/utils.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - -function run_test() { - do_get_profile(); - - run_next_test(); -} - -add_test(function test_constructor() { - let provider = new AppInfoProvider(); - - run_next_test(); -}); - -add_task(function test_collect_smoketest() { - let storage = yield Metrics.Storage("collect_smoketest"); - let provider = new AppInfoProvider(); - yield provider.init(storage); - - let now = new Date(); - yield provider.collectConstantData(); - - let m = provider.getMeasurement("appinfo", 2); - let data = yield storage.getMeasurementValues(m.id); - let serializer = m.serializer(m.SERIALIZE_JSON); - let d = serializer.singular(data.singular); - - do_check_eq(d._v, 2); - do_check_eq(d.vendor, "Mozilla"); - do_check_eq(d.name, "xpcshell"); - do_check_eq(d.id, "xpcshell@tests.mozilla.org"); - do_check_eq(d.version, "1"); - do_check_eq(d.appBuildID, "20121107"); - do_check_eq(d.platformVersion, "p-ver"); - do_check_eq(d.platformBuildID, "20121106"); - do_check_eq(d.os, "XPCShell"); - do_check_eq(d.xpcomabi, "noarch-spidermonkey"); - - do_check_eq(data.days.size, 1); - do_check_true(data.days.hasDay(now)); - let day = data.days.getDay(now); - do_check_eq(day.size, 3); - do_check_true(day.has("isDefaultBrowser")); - do_check_true(day.has("isTelemetryEnabled")); - do_check_true(day.has("isBlocklistEnabled")); - - // TODO Bug 827189 Actually test this properly. On some local builds, this - // is always -1 (the service throws). On buildbot, it seems to always be 0. - do_check_neq(day.get("isDefaultBrowser"), 1); - - yield provider.shutdown(); - yield storage.close(); -}); - -add_task(function test_record_version() { - let storage = yield Metrics.Storage("record_version"); - - let provider = new AppInfoProvider(); - let now = new Date(); - yield provider.init(storage); - - // The provider records information on startup. - let m = provider.getMeasurement("versions", 2); - let data = yield m.getValues(); - - do_check_true(data.days.hasDay(now)); - let day = data.days.getDay(now); - do_check_eq(day.size, 4); - do_check_true(day.has("appVersion")); - do_check_true(day.has("platformVersion")); - do_check_true(day.has("appBuildID")); - do_check_true(day.has("platformBuildID")); - - let value = day.get("appVersion"); - do_check_true(Array.isArray(value)); - do_check_eq(value.length, 1); - let ai = getAppInfo(); - do_check_eq(value[0], ai.version); - - value = day.get("platformVersion"); - do_check_true(Array.isArray(value)); - do_check_eq(value.length, 1); - do_check_eq(value[0], ai.platformVersion); - - value = day.get("appBuildID"); - do_check_true(Array.isArray(value)); - do_check_eq(value.length, 1); - do_check_eq(value[0], ai.appBuildID); - - value = day.get("platformBuildID"); - do_check_true(Array.isArray(value)); - do_check_eq(value.length, 1); - do_check_eq(value[0], ai.platformBuildID); - - yield provider.shutdown(); - yield storage.close(); -}); - -add_task(function test_record_version_change() { - let storage = yield Metrics.Storage("record_version_change"); - - let provider = new AppInfoProvider(); - let now = new Date(); - yield provider.init(storage); - yield provider.shutdown(); - - let ai = getAppInfo(); - ai.version = "new app version"; - ai.platformVersion = "new platform version"; - ai.appBuildID = "new app id"; - ai.platformBuildID = "new platform id"; - updateAppInfo(ai); - - provider = new AppInfoProvider(); - yield provider.init(storage); - - // There should be 2 records in the versions history. - let m = provider.getMeasurement("versions", 2); - let data = yield m.getValues(); - do_check_true(data.days.hasDay(now)); - let day = data.days.getDay(now); - - let value = day.get("appVersion"); - do_check_true(Array.isArray(value)); - do_check_eq(value.length, 2); - do_check_eq(value[1], "new app version"); - - value = day.get("platformVersion"); - do_check_true(Array.isArray(value)); - do_check_eq(value.length, 2); - do_check_eq(value[1], "new platform version"); - - // There should be 2 records in the buildID history. - value = day.get("appBuildID"); - do_check_true(Array.isArray(value)); - do_check_eq(value.length, 2); - do_check_eq(value[1], "new app id"); - - value = day.get("platformBuildID"); - do_check_true(Array.isArray(value)); - do_check_eq(value.length, 2); - do_check_eq(value[1], "new platform id"); - - yield provider.shutdown(); - yield storage.close(); -}); - -add_task(function test_record_telemetry() { - let storage = yield Metrics.Storage("record_telemetry"); - let provider; - - let now = new Date(); - - Services.prefs.setBoolPref("toolkit.telemetry.enabled", true); - provider = new AppInfoProvider(); - yield provider.init(storage); - yield provider.collectConstantData(); - - let m = provider.getMeasurement("appinfo", 2); - let data = yield m.getValues(); - let d = yield m.serializer(m.SERIALIZE_JSON).daily(data.days.getDay(now)); - do_check_eq(1, d.isTelemetryEnabled); - yield provider.shutdown(); - - Services.prefs.setBoolPref("toolkit.telemetry.enabled", false); - provider = new AppInfoProvider(); - yield provider.init(storage); - yield provider.collectConstantData(); - - m = provider.getMeasurement("appinfo", 2); - data = yield m.getValues(); - d = yield m.serializer(m.SERIALIZE_JSON).daily(data.days.getDay(now)); - do_check_eq(0, d.isTelemetryEnabled); - yield provider.shutdown(); - - yield storage.close(); -}); - -add_task(function test_record_blocklist() { - let storage = yield Metrics.Storage("record_blocklist"); - - let now = new Date(); - - Services.prefs.setBoolPref("extensions.blocklist.enabled", true); - let provider = new AppInfoProvider(); - yield provider.init(storage); - yield provider.collectConstantData(); - - let m = provider.getMeasurement("appinfo", 2); - let data = yield m.getValues(); - let d = yield m.serializer(m.SERIALIZE_JSON).daily(data.days.getDay(now)); - do_check_eq(d.isBlocklistEnabled, 1); - yield provider.shutdown(); - - Services.prefs.setBoolPref("extensions.blocklist.enabled", false); - provider = new AppInfoProvider(); - yield provider.init(storage); - yield provider.collectConstantData(); - - m = provider.getMeasurement("appinfo", 2); - data = yield m.getValues(); - d = yield m.serializer(m.SERIALIZE_JSON).daily(data.days.getDay(now)); - do_check_eq(d.isBlocklistEnabled, 0); - yield provider.shutdown(); - - yield storage.close(); -}); - -add_task(function test_record_app_update () { - let storage = yield Metrics.Storage("record_update"); - - Services.prefs.setBoolPref("app.update.enabled", true); - Services.prefs.setBoolPref("app.update.auto", true); - let provider = new AppInfoProvider(); - yield provider.init(storage); - let now = new Date(); - yield provider.collectDailyData(); - - let m = provider.getMeasurement("update", 1); - let data = yield m.getValues(); - let d = yield m.serializer(m.SERIALIZE_JSON).daily(data.days.getDay(now)); - do_check_eq(d.enabled, 1); - do_check_eq(d.autoDownload, 1); - - Services.prefs.setBoolPref("app.update.enabled", false); - Services.prefs.setBoolPref("app.update.auto", false); - - yield provider.collectDailyData(); - data = yield m.getValues(); - d = yield m.serializer(m.SERIALIZE_JSON).daily(data.days.getDay(now)); - do_check_eq(d.enabled, 0); - do_check_eq(d.autoDownload, 0); - - yield provider.shutdown(); - yield storage.close(); -}); - -add_task(function test_healthreporter_integration () { - let reporter = getHealthReporter("healthreporter_integration"); - yield reporter.init(); - - try { - yield reporter._providerManager.registerProviderFromType(AppInfoProvider); - yield reporter.collectMeasurements(); - - let payload = yield reporter.getJSONPayload(true); - let days = payload['data']['days']; - - for (let [day, measurements] in Iterator(days)) { - do_check_eq(Object.keys(measurements).length, 3); - do_check_true("org.mozilla.appInfo.appinfo" in measurements); - do_check_true("org.mozilla.appInfo.update" in measurements); - do_check_true("org.mozilla.appInfo.versions" in measurements); - } - } finally { - yield reporter._shutdown(); - } -}); diff --git a/services/healthreport/tests/xpcshell/test_provider_crashes.js b/services/healthreport/tests/xpcshell/test_provider_crashes.js deleted file mode 100644 index 066b395a3ae0..000000000000 --- a/services/healthreport/tests/xpcshell/test_provider_crashes.js +++ /dev/null @@ -1,137 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -var {utils: Cu} = Components; - - -Cu.import("resource://gre/modules/Metrics.jsm"); -Cu.import("resource://gre/modules/services/healthreport/providers.jsm"); -Cu.import("resource://testing-common/AppData.jsm"); -Cu.import("resource://testing-common/services/healthreport/utils.jsm"); -Cu.import("resource://testing-common/CrashManagerTest.jsm"); - - -function run_test() { - run_next_test(); -} - -add_task(function* init() { - do_get_profile(); - yield makeFakeAppDir(); -}); - -add_task(function test_constructor() { - let provider = new CrashesProvider(); -}); - -add_task(function* test_init() { - let storage = yield Metrics.Storage("init"); - let provider = new CrashesProvider(); - yield provider.init(storage); - yield provider.shutdown(); - - yield storage.close(); -}); - -add_task(function* test_collect() { - let storage = yield Metrics.Storage("collect"); - let provider = new CrashesProvider(); - yield provider.init(storage); - - // Install custom manager so we don't interfere with other tests. - let manager = yield getManager(); - provider._manager = manager; - - let day1 = new Date(2014, 0, 1, 0, 0, 0); - let day2 = new Date(2014, 0, 3, 0, 0, 0); - - yield manager.addCrash(manager.PROCESS_TYPE_MAIN, - manager.CRASH_TYPE_CRASH, - "mc1", day1, { OOMAllocationSize: 1073741824 }); - yield manager.addCrash(manager.PROCESS_TYPE_MAIN, - manager.CRASH_TYPE_CRASH, - "mc2", day1); - yield manager.addCrash(manager.PROCESS_TYPE_CONTENT, - manager.CRASH_TYPE_HANG, - "ch", day1); - yield manager.addCrash(manager.PROCESS_TYPE_PLUGIN, - manager.CRASH_TYPE_CRASH, - "pc", day1); - - yield manager.addSubmissionAttempt("mc1", "sub1", day1); - yield manager.addSubmissionResult("mc1", "sub1", day1, - manager.SUBMISSION_RESULT_OK); - yield manager.addSubmissionAttempt("ch", "sub1", day1); - yield manager.addSubmissionResult("ch", "sub1", day1, - manager.SUBMISSION_RESULT_FAILED); - yield manager.addSubmissionAttempt("ch", "sub2", day1); - yield manager.addSubmissionResult("ch", "sub2", day1, - manager.SUBMISSION_RESULT_FAILED); - yield manager.addSubmissionAttempt("ch", "sub3", day1); - yield manager.addSubmissionResult("ch", "sub3", day1, - manager.SUBMISSION_RESULT_OK); - - yield manager.addCrash(manager.PROCESS_TYPE_MAIN, - manager.CRASH_TYPE_HANG, - "mh", day2); - yield manager.addCrash(manager.PROCESS_TYPE_CONTENT, - manager.CRASH_TYPE_CRASH, - "cc", day2); - yield manager.addCrash(manager.PROCESS_TYPE_PLUGIN, - manager.CRASH_TYPE_HANG, - "ph", day2); - yield manager.addCrash(manager.PROCESS_TYPE_GMPLUGIN, - manager.CRASH_TYPE_CRASH, - "gmpc", day2); - - yield provider.collectDailyData(); - - let m = provider.getMeasurement("crashes", 6); - let values = yield m.getValues(); - do_check_eq(values.days.size, 2); - do_check_true(values.days.hasDay(day1)); - do_check_true(values.days.hasDay(day2)); - - let value = values.days.getDay(day1); - do_check_true(value.has("main-crash")); - do_check_eq(value.get("main-crash"), 2); - do_check_true(value.has("main-crash-oom")); - do_check_eq(value.get("main-crash-oom"), 1); - do_check_true(value.has("content-hang")); - do_check_eq(value.get("content-hang"), 1); - do_check_true(value.has("plugin-crash")); - do_check_eq(value.get("plugin-crash"), 1); - - do_check_true(value.has("main-crash-submission-succeeded")); - do_check_eq(value.get("main-crash-submission-succeeded"), 1); - do_check_true(value.has("content-hang-submission-failed")); - do_check_eq(value.get("content-hang-submission-failed"), 2); - do_check_true(value.has("content-hang-submission-succeeded")); - do_check_eq(value.get("content-hang-submission-succeeded"), 1); - - value = values.days.getDay(day2); - do_check_true(value.has("main-hang")); - do_check_eq(value.get("main-hang"), 1); - do_check_true(value.has("content-crash")); - do_check_eq(value.get("content-crash"), 1); - do_check_true(value.has("plugin-hang")); - do_check_eq(value.get("plugin-hang"), 1); - do_check_true(value.has("gmplugin-crash")); - do_check_eq(value.get("gmplugin-crash"), 1); - - // Check that adding a new crash increments counter on next collect. - yield manager.addCrash(manager.PROCESS_TYPE_MAIN, - manager.CRASH_TYPE_HANG, - "mc3", day2); - - yield provider.collectDailyData(); - values = yield m.getValues(); - value = values.days.getDay(day2); - do_check_eq(value.get("main-hang"), 2); - - yield provider.shutdown(); - yield storage.close(); -}); - diff --git a/services/healthreport/tests/xpcshell/test_provider_hotfix.js b/services/healthreport/tests/xpcshell/test_provider_hotfix.js deleted file mode 100644 index 1657455962cc..000000000000 --- a/services/healthreport/tests/xpcshell/test_provider_hotfix.js +++ /dev/null @@ -1,179 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -var {utils: Cu} = Components; - -Cu.import("resource://gre/modules/osfile.jsm"); -Cu.import("resource://gre/modules/Metrics.jsm"); -Cu.import("resource://gre/modules/services/healthreport/providers.jsm"); - -const EXAMPLE_2014052701 = { - "upgradedFrom":"13.0.1", - "uninstallReason":"SUCCESSFUL_UPGRADE", - "_entityID":null, - "forensicsID":"29525548-b653-49db-bfb8-a160cdfbeb4a", - "_installInProgress":false, - "_everCompatible":true, - "reportedWindowsVersion":[6,1,1], - "actualWindowsVersion":[6,1,1], - "firstNotifyDay":0, - "lastNotifyDay":0, - "downloadAttempts":1, - "downloadFailures":0, - "installAttempts":1, - "installSuccesses":1, - "installLauncherFailures":0, - "installFailures":0, - "notificationsShown":0, - "notificationsClicked":0, - "notificationsDismissed":0, - "notificationsRemoved":0, - "launcherExitCodes":{"0":1} -}; - -function run_test() { - run_next_test(); -} - -add_task(function* init() { - do_get_profile(); -}); - -add_task(function test_constructor() { - new HotfixProvider(); -}); - -add_task(function* test_init() { - let storage = yield Metrics.Storage("init"); - let provider = new HotfixProvider(); - yield provider.init(storage); - yield provider.shutdown(); - - yield storage.close(); -}); - -add_task(function* test_collect_empty() { - let storage = yield Metrics.Storage("collect_empty"); - let provider = new HotfixProvider(); - yield provider.init(storage); - - yield provider.collectDailyData(); - - let m = provider.getMeasurement("update", 1); - let data = yield m.getValues(); - Assert.equal(data.singular.size, 0); - Assert.equal(data.days.size, 0); - - yield storage.close(); -}); - -add_task(function* test_collect_20140527() { - let storage = yield Metrics.Storage("collect_20140527"); - let provider = new HotfixProvider(); - yield provider.init(storage); - - let path = OS.Path.join(OS.Constants.Path.profileDir, - "hotfix.v20140527.01.json"); - let encoder = new TextEncoder(); - yield OS.File.writeAtomic(path, - encoder.encode(JSON.stringify(EXAMPLE_2014052701))); - - yield provider.collectDailyData(); - - let m = provider.getMeasurement("update", 1); - let data = yield m.getValues(); - let s = data.singular; - Assert.equal(s.size, 7); - Assert.equal(s.get("v20140527.upgradedFrom")[1], "13.0.1"); - Assert.equal(s.get("v20140527.uninstallReason")[1], "SUCCESSFUL_UPGRADE"); - Assert.equal(s.get("v20140527.downloadAttempts")[1], 1); - Assert.equal(s.get("v20140527.downloadFailures")[1], 0); - Assert.equal(s.get("v20140527.installAttempts")[1], 1); - Assert.equal(s.get("v20140527.installFailures")[1], 0); - Assert.equal(s.get("v20140527.notificationsShown")[1], 0); - - // Ensure the dynamic fields get serialized. - let serializer = m.serializer(m.SERIALIZE_JSON); - let d = serializer.singular(s); - - Assert.deepEqual(d, { - "_v": 1, - "v20140527.upgradedFrom": "13.0.1", - "v20140527.uninstallReason": "SUCCESSFUL_UPGRADE", - "v20140527.downloadAttempts": 1, - "v20140527.downloadFailures": 0, - "v20140527.installAttempts": 1, - "v20140527.installFailures": 0, - "v20140527.notificationsShown": 0, - }); - - // Don't interfere with next test. - yield OS.File.remove(path); - - yield storage.close(); -}); - -add_task(function* test_collect_multiple_versions() { - let storage = yield Metrics.Storage("collect_multiple_versions"); - let provider = new HotfixProvider(); - yield provider.init(storage); - - let p1 = { - upgradedFrom: "12.0", - uninstallReason: "SUCCESSFUL_UPGRADE", - downloadAttempts: 3, - downloadFailures: 1, - installAttempts: 1, - installFailures: 1, - notificationsShown: 2, - }; - - let p2 = { - downloadAttempts: 5, - downloadFailures: 3, - installAttempts: 2, - installFailures: 2, - uninstallReason: null, - notificationsShown: 1, - }; - - let path1 = OS.Path.join(OS.Constants.Path.profileDir, "updateHotfix.v20140601.json"); - let path2 = OS.Path.join(OS.Constants.Path.profileDir, "updateHotfix.v20140701.json"); - - let encoder = new TextEncoder(); - yield OS.File.writeAtomic(path1, encoder.encode(JSON.stringify(p1))); - yield OS.File.writeAtomic(path2, encoder.encode(JSON.stringify(p2))); - - yield provider.collectDailyData(); - - let m = provider.getMeasurement("update", 1); - let data = yield m.getValues(); - - let serializer = m.serializer(m.SERIALIZE_JSON); - let d = serializer.singular(data.singular); - - Assert.deepEqual(d, { - "_v": 1, - "v20140601.upgradedFrom": "12.0", - "v20140601.uninstallReason": "SUCCESSFUL_UPGRADE", - "v20140601.downloadAttempts": 3, - "v20140601.downloadFailures": 1, - "v20140601.installAttempts": 1, - "v20140601.installFailures": 1, - "v20140601.notificationsShown": 2, - "v20140701.uninstallReason": "STILL_INSTALLED", - "v20140701.downloadAttempts": 5, - "v20140701.downloadFailures": 3, - "v20140701.installAttempts": 2, - "v20140701.installFailures": 2, - "v20140701.notificationsShown": 1, - }); - - // Don't interfere with next test. - yield OS.File.remove(path1); - yield OS.File.remove(path2); - - yield storage.close(); -}); diff --git a/services/healthreport/tests/xpcshell/test_provider_places.js b/services/healthreport/tests/xpcshell/test_provider_places.js deleted file mode 100644 index 2712d8b88335..000000000000 --- a/services/healthreport/tests/xpcshell/test_provider_places.js +++ /dev/null @@ -1,46 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -var {utils: Cu} = Components; - -Cu.import("resource://gre/modules/Metrics.jsm"); -Cu.import("resource://gre/modules/services/healthreport/providers.jsm"); - - -function run_test() { - run_next_test(); -} - -add_test(function test_constructor() { - let provider = new PlacesProvider(); - - run_next_test(); -}); - -add_task(function test_collect_smoketest() { - let storage = yield Metrics.Storage("collect_smoketest"); - let provider = new PlacesProvider(); - - yield provider.init(storage); - - let now = new Date(); - yield provider.collectDailyData(); - - let m = provider.getMeasurement("places", 1); - let data = yield storage.getMeasurementValues(m.id); - do_check_eq(data.days.size, 1); - do_check_true(data.days.hasDay(now)); - - let serializer = m.serializer(m.SERIALIZE_JSON); - let day = serializer.daily(data.days.getDay(now)); - - do_check_eq(day._v, 1); - do_check_eq(Object.keys(day).length, 3); - do_check_eq(day.pages, 0); - do_check_eq(day.bookmarks, 0); - - yield storage.close(); -}); - diff --git a/services/healthreport/tests/xpcshell/test_provider_searches.js b/services/healthreport/tests/xpcshell/test_provider_searches.js deleted file mode 100644 index 11a12a84ed2d..000000000000 --- a/services/healthreport/tests/xpcshell/test_provider_searches.js +++ /dev/null @@ -1,187 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -var {utils: Cu} = Components; - -Cu.import("resource://gre/modules/Metrics.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -var bsp = Cu.import("resource://gre/modules/services/healthreport/providers.jsm"); - -const DEFAULT_ENGINES = [ - {name: "Amazon.com", identifier: "amazondotcom"}, - {name: "Bing", identifier: "bing"}, - {name: "Google", identifier: "google"}, - {name: "Yahoo", identifier: "yahoo"}, - {name: "Foobar Search", identifier: "foobar"}, -]; - -function MockSearchCountMeasurement() { - bsp.SearchCountMeasurement3.call(this); -} -MockSearchCountMeasurement.prototype = { - __proto__: bsp.SearchCountMeasurement3.prototype, -}; - -function MockSearchesProvider() { - SearchesProvider.call(this); -} -MockSearchesProvider.prototype = { - __proto__: SearchesProvider.prototype, - measurementTypes: [MockSearchCountMeasurement], -}; - -function run_test() { - // Tell the search service we are running in the US. This also has the - // desired side-effect of preventing our geoip lookup. - Services.prefs.setBoolPref("browser.search.isUS", true); - Services.prefs.setCharPref("browser.search.countryCode", "US"); - - run_next_test(); -} - -add_test(function test_constructor() { - let provider = new SearchesProvider(); - - run_next_test(); -}); - -add_task(function* test_record() { - let storage = yield Metrics.Storage("record"); - let provider = new MockSearchesProvider(); - - yield provider.init(storage); - - let now = new Date(); - - // Record searches for all but one of our defaults, and one engine that's - // not a default. - for (let engine of DEFAULT_ENGINES.concat([{name: "Not Default", identifier: "notdef"}])) { - if (engine.identifier == "yahoo") { - continue; - } - yield provider.recordSearch(engine, "abouthome"); - yield provider.recordSearch(engine, "contextmenu"); - yield provider.recordSearch(engine, "newtab"); - yield provider.recordSearch(engine, "searchbar"); - yield provider.recordSearch(engine, "urlbar"); - } - - // Invalid sources should throw. - let errored = false; - try { - yield provider.recordSearch(DEFAULT_ENGINES[0], "bad source"); - } catch (ex) { - errored = true; - } finally { - do_check_true(errored); - } - - let m = provider.getMeasurement("counts", 3); - let data = yield m.getValues(); - do_check_eq(data.days.size, 1); - do_check_true(data.days.hasDay(now)); - - let day = data.days.getDay(now); - for (let engine of DEFAULT_ENGINES) { - let identifier = engine.identifier; - let expected = identifier != "yahoo"; - - for (let source of ["abouthome", "contextmenu", "searchbar", "urlbar"]) { - let field = identifier + "." + source; - if (expected) { - do_check_true(day.has(field)); - do_check_eq(day.get(field), 1); - } else { - do_check_false(day.has(field)); - } - } - } - - // Also, check that our non-default engine contributed, with a computed - // identifier. - let identifier = "notdef"; - for (let source of ["abouthome", "contextmenu", "searchbar", "urlbar"]) { - let field = identifier + "." + source; - do_check_true(day.has(field)); - } - - yield storage.close(); -}); - -add_task(function* test_includes_other_fields() { - let storage = yield Metrics.Storage("includes_other_fields"); - let provider = new MockSearchesProvider(); - - yield provider.init(storage); - let m = provider.getMeasurement("counts", 3); - - // Register a search against a provider that isn't live in this session. - let id = yield m.storage.registerField(m.id, "test.searchbar", - Metrics.Storage.FIELD_DAILY_COUNTER); - - let testField = "test.searchbar"; - let now = new Date(); - yield m.storage.incrementDailyCounterFromFieldID(id, now); - - // Make sure we don't know about it. - do_check_false(testField in m.fields); - - // But we want to include it in payloads. - do_check_true(m.shouldIncludeField(testField)); - - // And we do so. - let data = yield provider.storage.getMeasurementValues(m.id); - let serializer = m.serializer(m.SERIALIZE_JSON); - let formatted = serializer.daily(data.days.getDay(now)); - do_check_true(testField in formatted); - do_check_eq(formatted[testField], 1); - - yield storage.close(); -}); - -add_task(function* test_default_search_engine() { - let storage = yield Metrics.Storage("default_search_engine"); - let provider = new SearchesProvider(); - yield provider.init(storage); - - let m = provider.getMeasurement("engines", 2); - - let now = new Date(); - yield provider.collectDailyData(); - let data = yield m.getValues(); - Assert.ok(data.days.hasDay(now)); - - let day = data.days.getDay(now); - Assert.equal(day.size, 1); - Assert.ok(day.has("default")); - - // test environment doesn't have a default engine. - Assert.equal(day.get("default"), "NONE"); - - Services.search.addEngineWithDetails("testdefault", - "http://localhost/icon.png", - null, - "test description", - "GET", - "http://localhost/search/%s"); - let engine1 = Services.search.getEngineByName("testdefault"); - Assert.ok(engine1); - Services.search.defaultEngine = engine1; - - yield provider.collectDailyData(); - data = yield m.getValues(); - Assert.equal(data.days.getDay(now).get("default"), "other-testdefault"); - - // If no cohort identifier is set, we shouldn't report a cohort. - Assert.equal(data.days.getDay(now).get("cohort"), undefined); - - // Set a cohort identifier and verify we record it. - Services.prefs.setCharPref("browser.search.cohort", "testcohort"); - yield provider.collectDailyData(); - data = yield m.getValues(); - Assert.equal(data.days.getDay(now).get("cohort"), "testcohort"); - - yield storage.close(); -}); diff --git a/services/healthreport/tests/xpcshell/test_provider_sessions.js b/services/healthreport/tests/xpcshell/test_provider_sessions.js deleted file mode 100644 index d464c6be5c78..000000000000 --- a/services/healthreport/tests/xpcshell/test_provider_sessions.js +++ /dev/null @@ -1,217 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -var {utils: Cu} = Components; - - -Cu.import("resource://gre/modules/Promise.jsm"); -Cu.import("resource://gre/modules/Metrics.jsm"); -Cu.import("resource://gre/modules/Task.jsm"); -Cu.import("resource://gre/modules/services-common/utils.js"); -Cu.import("resource://gre/modules/SessionRecorder.jsm"); -Cu.import("resource://gre/modules/services/healthreport/providers.jsm"); - - -function run_test() { - run_next_test(); -} - -add_test(function test_constructor() { - let provider = new SessionsProvider(); - - run_next_test(); -}); - -add_task(function test_init() { - let storage = yield Metrics.Storage("init"); - let provider = new SessionsProvider(); - yield provider.init(storage); - yield provider.shutdown(); - - yield storage.close(); -}); - -function monkeypatchStartupInfo(recorder, start=new Date(), offset=500) { - Object.defineProperty(recorder, "_getStartupInfo", { - value: function _getStartupInfo() { - return { - process: start, - main: new Date(start.getTime() + offset), - firstPaint: new Date(start.getTime() + 2 * offset), - sessionRestored: new Date(start.getTime() + 3 * offset), - }; - } - }); -} - -function sleep(wait) { - let deferred = Promise.defer(); - - let timer = CommonUtils.namedTimer(function onTimer() { - deferred.resolve(); - }, wait, deferred.promise, "_sleepTimer"); - - return deferred.promise; -} - -function getProvider(name, now=new Date(), init=true) { - return Task.spawn(function () { - let storage = yield Metrics.Storage(name); - let provider = new SessionsProvider(); - - let recorder = new SessionRecorder("testing." + name + ".sessions."); - monkeypatchStartupInfo(recorder, now); - provider.healthReporter = {sessionRecorder: recorder}; - recorder.onStartup(); - - if (init) { - yield provider.init(storage); - } - - throw new Task.Result([provider, storage, recorder]); - }); -} - -add_task(function test_current_session() { - let now = new Date(); - let [provider, storage, recorder] = yield getProvider("current_session", now); - - yield sleep(25); - recorder.onActivity(true); - - let current = provider.getMeasurement("current", 3); - let values = yield current.getValues(); - let fields = values.singular; - - for (let field of ["startDay", "activeTicks", "totalTime", "main", "firstPaint", "sessionRestored"]) { - do_check_true(fields.has(field)); - } - - do_check_eq(fields.get("startDay")[1], Metrics.dateToDays(now)); - do_check_eq(fields.get("totalTime")[1], recorder.totalTime); - do_check_eq(fields.get("activeTicks")[1], 1); - do_check_eq(fields.get("main")[1], 500); - do_check_eq(fields.get("firstPaint")[1], 1000); - do_check_eq(fields.get("sessionRestored")[1], 1500); - - yield provider.shutdown(); - yield storage.close(); -}); - -add_task(function test_collect() { - let now = new Date(); - let [provider, storage, recorder] = yield getProvider("collect"); - - recorder.onShutdown(); - yield sleep(25); - - for (let i = 0; i < 5; i++) { - let recorder2 = new SessionRecorder("testing.collect.sessions."); - recorder2.onStartup(); - yield sleep(25); - recorder2.onShutdown(); - yield sleep(25); - } - - recorder = new SessionRecorder("testing.collect.sessions."); - recorder.onStartup(); - - // Collecting the provider should prune all previous sessions. - let sessions = recorder.getPreviousSessions(); - do_check_eq(Object.keys(sessions).length, 6); - yield provider.collectConstantData(); - sessions = recorder.getPreviousSessions(); - do_check_eq(Object.keys(sessions).length, 0); - - // And those previous sessions should make it to storage. - let daily = provider.getMeasurement("previous", 3); - let values = yield daily.getValues(); - do_check_true(values.days.hasDay(now)); - do_check_eq(values.days.size, 1); - let day = values.days.getDay(now); - do_check_eq(day.size, 5); - let previousStorageCount = day.get("main").length; - - for (let field of ["cleanActiveTicks", "cleanTotalTime", "main", "firstPaint", "sessionRestored"]) { - do_check_true(day.has(field)); - do_check_true(Array.isArray(day.get(field))); - do_check_eq(day.get(field).length, 6); - } - - let lastIndex = yield provider.getState("lastSession"); - do_check_eq(lastIndex, "" + (previousStorageCount - 1)); // 0-indexed - - // Fake an aborted session. If we create a 2nd recorder against the same - // prefs branch as a running one, this simulates what would happen if the - // first recorder didn't shut down. - let recorder2 = new SessionRecorder("testing.collect.sessions."); - recorder2.onStartup(); - do_check_eq(Object.keys(recorder.getPreviousSessions()).length, 1); - yield provider.collectConstantData(); - do_check_eq(Object.keys(recorder.getPreviousSessions()).length, 0); - - values = yield daily.getValues(); - day = values.days.getDay(now); - do_check_eq(day.size, previousStorageCount + 1); - previousStorageCount = day.get("main").length; - for (let field of ["abortedActiveTicks", "abortedTotalTime"]) { - do_check_true(day.has(field)); - do_check_true(Array.isArray(day.get(field))); - do_check_eq(day.get(field).length, 1); - } - - lastIndex = yield provider.getState("lastSession"); - do_check_eq(lastIndex, "" + (previousStorageCount - 1)); - - recorder.onShutdown(); - recorder2.onShutdown(); - - // If we try to insert a already-inserted session, it will be ignored. - recorder = new SessionRecorder("testing.collect.sessions."); - recorder._currentIndex = recorder._currentIndex - 1; - recorder._prunedIndex = recorder._currentIndex; - recorder.onStartup(); - // Session is left over from recorder2. - sessions = recorder.getPreviousSessions(); - do_check_eq(Object.keys(sessions).length, 1); - do_check_true(previousStorageCount - 1 in sessions); - yield provider.collectConstantData(); - lastIndex = yield provider.getState("lastSession"); - do_check_eq(lastIndex, "" + (previousStorageCount - 1)); - values = yield daily.getValues(); - day = values.days.getDay(now); - // We should not get additional entry. - do_check_eq(day.get("main").length, previousStorageCount); - recorder.onShutdown(); - - yield provider.shutdown(); - yield storage.close(); -}); - -add_task(function test_serialization() { - let [provider, storage, recorder] = yield getProvider("serialization"); - - yield sleep(1025); - recorder.onActivity(true); - - let current = provider.getMeasurement("current", 3); - let data = yield current.getValues(); - do_check_true("singular" in data); - - let serializer = current.serializer(current.SERIALIZE_JSON); - let fields = serializer.singular(data.singular); - - do_check_eq(fields._v, 3); - do_check_eq(fields.activeTicks, 1); - do_check_eq(fields.startDay, Metrics.dateToDays(recorder.startDate)); - do_check_eq(fields.main, 500); - do_check_eq(fields.firstPaint, 1000); - do_check_eq(fields.sessionRestored, 1500); - do_check_true(fields.totalTime > 0); - - yield provider.shutdown(); - yield storage.close(); -}); - diff --git a/services/healthreport/tests/xpcshell/test_provider_sysinfo.js b/services/healthreport/tests/xpcshell/test_provider_sysinfo.js deleted file mode 100644 index 0a985556492c..000000000000 --- a/services/healthreport/tests/xpcshell/test_provider_sysinfo.js +++ /dev/null @@ -1,41 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -var {interfaces: Ci, results: Cr, utils: Cu} = Components; - -Cu.import("resource://gre/modules/Metrics.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/services/healthreport/providers.jsm"); - - -function run_test() { - run_next_test(); -} - -add_test(function test_constructor() { - let provider = new SysInfoProvider(); - - run_next_test(); -}); - -add_task(function test_collect_smoketest() { - let storage = yield Metrics.Storage("collect_smoketest"); - let provider = new SysInfoProvider(); - yield provider.init(storage); - - yield provider.collectConstantData(); - - let m = provider.getMeasurement("sysinfo", 2); - let data = yield storage.getMeasurementValues(m.id); - let serializer = m.serializer(m.SERIALIZE_JSON); - let d = serializer.singular(data.singular); - - do_check_eq(d._v, 2); - do_check_true(d.cpuCount > 0); - do_check_neq(d.name, null); - - yield storage.close(); -}); - diff --git a/services/healthreport/tests/xpcshell/xpcshell.ini b/services/healthreport/tests/xpcshell/xpcshell.ini deleted file mode 100644 index 96915b0bb39a..000000000000 --- a/services/healthreport/tests/xpcshell/xpcshell.ini +++ /dev/null @@ -1,19 +0,0 @@ -[DEFAULT] -head = head.js -tail = -skip-if = toolkit == 'android' || toolkit == 'gonk' - -[test_load_modules.js] -[test_profile.js] -[test_provider_addons.js] -skip-if = buildapp == 'mulet' -tags = addons -[test_provider_appinfo.js] -[test_provider_crashes.js] -skip-if = !crashreporter -[test_provider_hotfix.js] -[test_provider_places.js] -[test_provider_searches.js] -[test_provider_sysinfo.js] -[test_provider_sessions.js] - diff --git a/services/moz.build b/services/moz.build index f83bdcefa94a..51f7b9bf8a28 100644 --- a/services/moz.build +++ b/services/moz.build @@ -12,12 +12,6 @@ DIRS += [ if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android' or CONFIG['MOZ_B2GDROID']: DIRS += ['fxaccounts'] -if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android': - # MOZ_SERVICES_HEALTHREPORT and therefore MOZ_DATA_REPORTING are - # defined on Android, but these features are implemented using Java. - if CONFIG['MOZ_SERVICES_HEALTHREPORT']: - DIRS += ['healthreport'] - if CONFIG['MOZ_SERVICES_METRICS']: DIRS += ['metrics'] diff --git a/services/healthreport/docs/architecture.rst b/toolkit/components/telemetry/docs/fhr/architecture.rst similarity index 100% rename from services/healthreport/docs/architecture.rst rename to toolkit/components/telemetry/docs/fhr/architecture.rst diff --git a/services/healthreport/docs/dataformat.rst b/toolkit/components/telemetry/docs/fhr/dataformat.rst similarity index 100% rename from services/healthreport/docs/dataformat.rst rename to toolkit/components/telemetry/docs/fhr/dataformat.rst diff --git a/services/healthreport/docs/identifiers.rst b/toolkit/components/telemetry/docs/fhr/identifiers.rst similarity index 100% rename from services/healthreport/docs/identifiers.rst rename to toolkit/components/telemetry/docs/fhr/identifiers.rst diff --git a/services/healthreport/docs/index.rst b/toolkit/components/telemetry/docs/fhr/index.rst similarity index 81% rename from services/healthreport/docs/index.rst rename to toolkit/components/telemetry/docs/fhr/index.rst index 047863bb5a65..f1b5aaba5395 100644 --- a/services/healthreport/docs/index.rst +++ b/toolkit/components/telemetry/docs/fhr/index.rst @@ -1,11 +1,11 @@ .. _healthreport: -===================== -Firefox Health Report -===================== +================================ +Firefox Health Report (Obsolete) +================================ -``/services/healthreport`` contains the implementation of the -``Firefox Health Report`` (FHR). +**Firefox Health Report (FHR) is obsolete and no longer ships with Firefox. +This documentation will live here for a few more cycles.** Firefox Health Report is a background service that collects application metrics and periodically submits them to a central server. The core diff --git a/toolkit/components/telemetry/moz.build b/toolkit/components/telemetry/moz.build index ace0a44bbe4d..38e6576d1912 100644 --- a/toolkit/components/telemetry/moz.build +++ b/toolkit/components/telemetry/moz.build @@ -81,3 +81,4 @@ LOCAL_INCLUDES += [ ] SPHINX_TREES['telemetry'] = 'docs' +SPHINX_TREES['healthreport'] = 'docs/fhr'