mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 812608 - Part 2: Refactor FHR on top of new Metrics APIs; r=rnewman
This also includes a lot of revamped Firefox Health Report features. The payload format has changed. There is now robust service shutdown logic. There are more prefs to control behavior. It's almost a rewritten service.
This commit is contained in:
parent
0489cc1183
commit
2f46a3aa01
@ -10,25 +10,46 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://services-common/preferences.js");
|
||||
|
||||
|
||||
const INITIAL_STARTUP_DELAY_MSEC = 10 * 1000;
|
||||
const BRANCH = "healthreport.";
|
||||
const JS_PROVIDERS_CATEGORY = "healthreport-js-provider";
|
||||
|
||||
const DEFAULT_LOAD_DELAY_MSEC = 10 * 1000;
|
||||
|
||||
/**
|
||||
* The Firefox Health Report XPCOM service.
|
||||
*
|
||||
* This instantiates an instance of HealthReporter (assuming it is enabled)
|
||||
* and starts it upon application startup.
|
||||
* External consumers will be interested in the "reporter" property of this
|
||||
* service. This property is a `HealthReporter` instance that powers the
|
||||
* service. The property may be null if the Health Report service is not
|
||||
* enabled.
|
||||
*
|
||||
* One can obtain a reference to the underlying HealthReporter instance by
|
||||
* accessing .reporter. If this property is null, the reporter isn't running
|
||||
* yet or has been disabled.
|
||||
* EXAMPLE USAGE
|
||||
* =============
|
||||
*
|
||||
* let reporter = Cc["@mozilla.org/healthreport/service;1"]
|
||||
* .getService(Ci.nsISupports)
|
||||
* .wrappedJSObject
|
||||
* .reporter;
|
||||
*
|
||||
* if (reporter.haveRemoteData) {
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
* IMPLEMENTATION NOTES
|
||||
* ====================
|
||||
*
|
||||
* In order to not adversely impact application start time, the `HealthReporter`
|
||||
* instance is not initialized until a few seconds after "final-ui-startup."
|
||||
* The exact delay is configurable via preferences so it can be adjusted with
|
||||
* a hotfix extension if the default value is ever problematic.
|
||||
*
|
||||
* Shutdown of the `HealthReporter` instance is handled completely within the
|
||||
* instance (it registers observers on initialization). See the notes on that
|
||||
* type for more.
|
||||
*/
|
||||
this.HealthReportService = function HealthReportService() {
|
||||
this.wrappedJSObject = this;
|
||||
|
||||
this.prefs = new Preferences(BRANCH);
|
||||
this._prefs = new Preferences(BRANCH);
|
||||
|
||||
this._reporter = null;
|
||||
}
|
||||
|
||||
@ -40,7 +61,7 @@ HealthReportService.prototype = {
|
||||
|
||||
observe: function observe(subject, topic, data) {
|
||||
// If the background service is disabled, don't do anything.
|
||||
if (!this.prefs.get("serviceEnabled", true)) {
|
||||
if (!this._prefs.get("service.enabled", true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -54,7 +75,9 @@ HealthReportService.prototype = {
|
||||
|
||||
case "final-ui-startup":
|
||||
os.removeObserver(this, "final-ui-startup");
|
||||
os.addObserver(this, "quit-application", true);
|
||||
|
||||
let delayInterval = this._prefs.get("service.loadDelayMsec") ||
|
||||
DEFAULT_LOAD_DELAY_MSEC;
|
||||
|
||||
// Delay service loading a little more so things have an opportunity
|
||||
// to cool down first.
|
||||
@ -66,25 +89,21 @@ HealthReportService.prototype = {
|
||||
let reporter = this.reporter;
|
||||
delete this.timer;
|
||||
}.bind(this),
|
||||
}, INITIAL_STARTUP_DELAY_MSEC, this.timer.TYPE_ONE_SHOT);
|
||||
}, delayInterval, this.timer.TYPE_ONE_SHOT);
|
||||
|
||||
break;
|
||||
|
||||
case "quit-application-granted":
|
||||
if (this.reporter) {
|
||||
this.reporter.stop();
|
||||
}
|
||||
|
||||
os.removeObserver(this, "quit-application");
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The HealthReporter instance associated with this service.
|
||||
*
|
||||
* If the service is disabled, this will return null.
|
||||
*
|
||||
* The obtained instance may not be fully initialized.
|
||||
*/
|
||||
get reporter() {
|
||||
if (!this.prefs.get("serviceEnabled", true)) {
|
||||
if (!this._prefs.get("service.enabled", true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -92,18 +111,19 @@ HealthReportService.prototype = {
|
||||
return this._reporter;
|
||||
}
|
||||
|
||||
// Lazy import so application startup isn't adversely affected.
|
||||
let ns = {};
|
||||
Cu.import("resource://services-common/log4moz.js", ns);
|
||||
// Lazy import so application startup isn't adversely affected.
|
||||
Cu.import("resource://gre/modules/Task.jsm", ns);
|
||||
Cu.import("resource://gre/modules/services/healthreport/healthreporter.jsm", ns);
|
||||
Cu.import("resource://services-common/log4moz.js", ns);
|
||||
|
||||
// How many times will we rewrite this code before rolling it up into a
|
||||
// generic module? See also bug 451283.
|
||||
const LOGGERS = [
|
||||
"Metrics",
|
||||
"Services.HealthReport",
|
||||
"Services.Metrics",
|
||||
"Services.BagheeraClient",
|
||||
"Sqlite.Connection.healthreport",
|
||||
];
|
||||
|
||||
let prefs = new Preferences(BRANCH + "logging.");
|
||||
@ -118,9 +138,8 @@ HealthReportService.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
// The reporter initializes in the background.
|
||||
this._reporter = new ns.HealthReporter(BRANCH);
|
||||
this._reporter.registerProvidersFromCategoryManager(JS_PROVIDERS_CATEGORY);
|
||||
this._reporter.start();
|
||||
|
||||
return this._reporter;
|
||||
},
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
pref("healthreport.documentServerURI", "https://data.mozilla.com/");
|
||||
pref("healthreport.documentServerNamespace", "metrics");
|
||||
pref("healthreport.serviceEnabled", true);
|
||||
pref("healthreport.logging.consoleEnabled", true);
|
||||
pref("healthreport.logging.consoleLevel", "Warn");
|
||||
pref("healthreport.policy.currentDaySubmissionFailureCount", 0);
|
||||
@ -19,4 +18,7 @@ pref("healthreport.policy.lastDataSubmissionFailureTime", "0");
|
||||
pref("healthreport.policy.lastDataSubmissionRequestedTime", "0");
|
||||
pref("healthreport.policy.lastDataSubmissionSuccessfulTime", "0");
|
||||
pref("healthreport.policy.nextDataSubmissionTime", "0");
|
||||
pref("healthreport.service.enabled", true);
|
||||
pref("healthreport.service.loadDelayMsec", 10000);
|
||||
pref("healthreport.service.providerCategories", "healthreport-js-provider");
|
||||
|
||||
|
@ -8,24 +8,33 @@ this.EXPORTED_SYMBOLS = ["HealthReporter"];
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://services-common/async.js");
|
||||
Cu.import("resource://services-common/bagheeraclient.js");
|
||||
Cu.import("resource://services-common/log4moz.js");
|
||||
Cu.import("resource://services-common/observers.js");
|
||||
Cu.import("resource://services-common/preferences.js");
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
Cu.import("resource://gre/modules/commonjs/promise/core.js");
|
||||
Cu.import("resource://gre/modules/Metrics.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.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://gre/modules/services/healthreport/policy.jsm");
|
||||
Cu.import("resource://gre/modules/services/metrics/collector.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 MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
|
||||
|
||||
const DEFAULT_DATABASE_NAME = "healthreport.sqlite";
|
||||
|
||||
|
||||
/**
|
||||
* Coordinates collection and submission of metrics.
|
||||
* Coordinates collection and submission of health report metrics.
|
||||
*
|
||||
* This is the main type for Firefox Health Report. It glues all the
|
||||
* lower-level components (such as collection and submission) together.
|
||||
@ -39,23 +48,50 @@ const OLDEST_ALLOWED_YEAR = 2012;
|
||||
* this type and *the* Firefox Health Report (e.g. the policy). This could
|
||||
* be abstracted if needed.
|
||||
*
|
||||
* IMPLEMENTATION NOTES
|
||||
* ====================
|
||||
*
|
||||
* 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 (.).
|
||||
*/
|
||||
this.HealthReporter = function HealthReporter(branch) {
|
||||
function HealthReporter(branch) {
|
||||
if (!branch.endsWith(".")) {
|
||||
throw new Error("Branch argument must end with a period (.): " + branch);
|
||||
throw new Error("Branch must end with a period (.): " + branch);
|
||||
}
|
||||
|
||||
this._log = Log4Moz.repository.getLogger("Services.HealthReport.HealthReporter");
|
||||
this._log.info("Initializing health reporter instance against " + branch);
|
||||
|
||||
this._prefs = new Preferences(branch);
|
||||
|
||||
let policyBranch = new Preferences(branch + "policy.");
|
||||
this._policy = new HealthReportPolicy(policyBranch, this);
|
||||
this._collector = new MetricsCollector();
|
||||
|
||||
if (!this.serverURI) {
|
||||
throw new Error("No server URI defined. Did you forget to define the pref?");
|
||||
}
|
||||
@ -63,9 +99,41 @@ this.HealthReporter = function HealthReporter(branch) {
|
||||
if (!this.serverNamespace) {
|
||||
throw new Error("No server namespace defined. Did you forget a pref?");
|
||||
}
|
||||
|
||||
this._dbName = this._prefs.get("dbName") || DEFAULT_DATABASE_NAME;
|
||||
|
||||
let policyBranch = new Preferences(branch + "policy.");
|
||||
this._policy = new HealthReportPolicy(policyBranch, this);
|
||||
|
||||
this._storage = null;
|
||||
this._storageInProgress = false;
|
||||
this._collector = null;
|
||||
this._initialized = false;
|
||||
this._initializeHadError = false;
|
||||
this._initializedDeferred = Promise.defer();
|
||||
this._shutdownRequested = false;
|
||||
this._shutdownInitiated = false;
|
||||
this._shutdownComplete = false;
|
||||
this._shutdownCompleteCallback = null;
|
||||
|
||||
this._ensureDirectoryExists(this._stateDir)
|
||||
.then(this._onStateDirCreated.bind(this),
|
||||
this._onInitError.bind(this));
|
||||
|
||||
}
|
||||
|
||||
HealthReporter.prototype = {
|
||||
HealthReporter.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;
|
||||
},
|
||||
|
||||
/**
|
||||
* When we last successfully submitted data to the server.
|
||||
*
|
||||
@ -146,48 +214,242 @@ HealthReporter.prototype = {
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
haveRemoteData: function haveRemoteData() {
|
||||
haveRemoteData: function () {
|
||||
return !!this.lastSubmitID;
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform post-construction initialization and start background activity.
|
||||
*
|
||||
* If this isn't called, no data upload will occur.
|
||||
*
|
||||
* This returns a promise that will be fulfilled when all initialization
|
||||
* activity is completed. It is not safe for this instance to perform
|
||||
* additional actions until this promise has been resolved.
|
||||
*/
|
||||
start: function start() {
|
||||
let onExists = function onExists() {
|
||||
this._policy.startPolling();
|
||||
this._log.info("HealthReporter started.");
|
||||
//----------------------------------------------------
|
||||
// SERVICE CONTROL FUNCTIONS
|
||||
//
|
||||
// You shouldn't need to call any of these externally.
|
||||
//----------------------------------------------------
|
||||
|
||||
return Promise.resolve();
|
||||
}.bind(this);
|
||||
_onInitError: function (error) {
|
||||
this._log.error("Error during initialization: " +
|
||||
CommonUtils.exceptionStr(error));
|
||||
this._initializeHadError = true;
|
||||
this._initiateShutdown();
|
||||
this._initializedDeferred.reject(error);
|
||||
|
||||
return this._ensureDirectoryExists(this._stateDir)
|
||||
.then(onExists);
|
||||
// FUTURE consider poisoning prototype's functions so calls fail with a
|
||||
// useful error message.
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop background functionality.
|
||||
*/
|
||||
stop: function stop() {
|
||||
_onStateDirCreated: function () {
|
||||
// As soon as we have could storage, we need to register cleanup or
|
||||
// else bad things happen on shutdown.
|
||||
Services.obs.addObserver(this, "quit-application", false);
|
||||
Services.obs.addObserver(this, "profile-before-change", false);
|
||||
|
||||
this._storageInProgress = true;
|
||||
Metrics.Storage(this._dbName).then(this._onStorageCreated.bind(this),
|
||||
this._onInitError.bind(this));
|
||||
},
|
||||
|
||||
// Called when storage has been opened.
|
||||
_onStorageCreated: function (storage) {
|
||||
this._log.info("Storage initialized.");
|
||||
this._storage = storage;
|
||||
this._storageInProgress = false;
|
||||
|
||||
if (this._shutdownRequested) {
|
||||
this._initiateShutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
Task.spawn(this._initializeCollector.bind(this))
|
||||
.then(this._onCollectorInitialized.bind(this),
|
||||
this._onInitError.bind(this));
|
||||
},
|
||||
|
||||
_initializeCollector: function () {
|
||||
if (this._collector) {
|
||||
throw new Error("Collector has already been initialized.");
|
||||
}
|
||||
|
||||
this._log.info("Initializing collector.");
|
||||
this._collector = new Metrics.Collector(this._storage);
|
||||
|
||||
let catString = this._prefs.get("service.providerCategories") || "";
|
||||
if (catString.length) {
|
||||
for (let category of catString.split(",")) {
|
||||
yield this.registerProvidersFromCategoryManager(category);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_onCollectorInitialized: function () {
|
||||
if (this._shutdownRequested) {
|
||||
this._initiateShutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
this._policy.startPolling();
|
||||
this._log.info("HealthReporter started.");
|
||||
this._initialized = true;
|
||||
Services.obs.addObserver(this, "idle-daily", false);
|
||||
this._initializedDeferred.resolve(this);
|
||||
},
|
||||
|
||||
// nsIObserver to handle shutdown.
|
||||
observe: function (subject, topic, data) {
|
||||
switch (topic) {
|
||||
case "quit-application":
|
||||
Services.obs.removeObserver(this, "quit-application");
|
||||
this._initiateShutdown();
|
||||
break;
|
||||
|
||||
case "profile-before-change":
|
||||
Services.obs.removeObserver(this, "profile-before-change");
|
||||
this._waitForShutdown();
|
||||
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;
|
||||
|
||||
// Safe to call multiple times.
|
||||
this._policy.stopPolling();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Everything from here must only be performed once or else race conditions
|
||||
// could occur.
|
||||
this._shutdownInitiated = true;
|
||||
|
||||
Services.obs.removeObserver(this, "idle-daily");
|
||||
|
||||
// If we have collectors, we need to shut down providers.
|
||||
if (this._collector) {
|
||||
let onShutdown = this._onCollectorShutdown.bind(this);
|
||||
Task.spawn(this._shutdownCollector.bind(this))
|
||||
.then(onShutdown, onShutdown);
|
||||
return;
|
||||
}
|
||||
|
||||
this._onCollectorShutdown();
|
||||
},
|
||||
|
||||
_shutdownCollector: function () {
|
||||
for (let provider of this._collector.providers) {
|
||||
try {
|
||||
yield provider.shutdown();
|
||||
} catch (ex) {
|
||||
this._log.warn("Error when shutting down provider: " +
|
||||
CommonUtils.exceptionStr(ex));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_onCollectorShutdown: function () {
|
||||
this._collector = null;
|
||||
|
||||
if (this._storage) {
|
||||
let onClose = this._onStorageClose.bind(this);
|
||||
this._storage.close().then(onClose, onClose);
|
||||
return;
|
||||
}
|
||||
|
||||
this._onStorageClose();
|
||||
},
|
||||
|
||||
_onStorageClose: function (error) {
|
||||
if (error) {
|
||||
this._log.warn("Error when closing storage: " +
|
||||
CommonUtils.exceptionStr(error));
|
||||
}
|
||||
|
||||
this._storage = null;
|
||||
this._shutdownComplete = true;
|
||||
|
||||
if (this._shutdownCompleteCallback) {
|
||||
this._shutdownCompleteCallback();
|
||||
}
|
||||
},
|
||||
|
||||
_waitForShutdown: function () {
|
||||
if (this._shutdownComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._shutdownCompleteCallback = Async.makeSpinningCallback();
|
||||
this._shutdownCompleteCallback.wait();
|
||||
this._shutdownCompleteCallback = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Register a `MetricsProvider` with this instance.
|
||||
* Convenience method to shut down the instance.
|
||||
*
|
||||
* This should *not* be called outside of tests.
|
||||
*/
|
||||
_shutdown: function () {
|
||||
this._initiateShutdown();
|
||||
this._waitForShutdown();
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a promise that is resolved once the service has been initialized.
|
||||
*/
|
||||
onInit: function () {
|
||||
if (this._initializeHadError) {
|
||||
throw new Error("Service failed to initialize.");
|
||||
}
|
||||
|
||||
if (this._initialized) {
|
||||
return Promise.resolve(this);
|
||||
}
|
||||
|
||||
return this._initializedDeferred.promise;
|
||||
},
|
||||
|
||||
_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
|
||||
//--------------------
|
||||
|
||||
/**
|
||||
* Register a `Metrics.Provider` with this instance.
|
||||
*
|
||||
* This needs to be called or no data will be collected. See also
|
||||
* registerProvidersFromCategoryManager`.
|
||||
*
|
||||
* @param provider
|
||||
* (MetricsProvider) The provider to register for collection.
|
||||
* (Metrics.Provider) The provider to register for collection.
|
||||
*/
|
||||
registerProvider: function registerProvider(provider) {
|
||||
registerProvider: function (provider) {
|
||||
return this._collector.registerProvider(provider);
|
||||
},
|
||||
|
||||
@ -198,7 +460,7 @@ HealthReporter.prototype = {
|
||||
* providers.
|
||||
*
|
||||
* Category entries are essentially JS modules and the name of the symbol
|
||||
* within that module that is a `MetricsProvider` instance.
|
||||
* within that module that is a `Metrics.Provider` instance.
|
||||
*
|
||||
* The category entry name is the name of the JS type for the provider. The
|
||||
* value is the resource:// URI to import which makes this type available.
|
||||
@ -213,18 +475,18 @@ HealthReporter.prototype = {
|
||||
*
|
||||
* Then to load them:
|
||||
*
|
||||
* let reporter = new HealthReporter("healthreport.");
|
||||
* let reporter = getHealthReporter("healthreport.");
|
||||
* reporter.registerProvidersFromCategoryManager("healthreport-js-provider");
|
||||
*
|
||||
* @param category
|
||||
* (string) Name of category to query and load from.
|
||||
*/
|
||||
registerProvidersFromCategoryManager:
|
||||
function registerProvidersFromCategoryManager(category) {
|
||||
|
||||
registerProvidersFromCategoryManager: function (category) {
|
||||
this._log.info("Registering providers from category: " + category);
|
||||
let cm = Cc["@mozilla.org/categorymanager;1"]
|
||||
.getService(Ci.nsICategoryManager);
|
||||
|
||||
let promises = [];
|
||||
let enumerator = cm.enumerateCategory(category);
|
||||
while (enumerator.hasMoreElements()) {
|
||||
let entry = enumerator.getNext()
|
||||
@ -240,20 +502,26 @@ HealthReporter.prototype = {
|
||||
Cu.import(uri, ns);
|
||||
|
||||
let provider = new ns[entry]();
|
||||
this.registerProvider(provider);
|
||||
promises.push(this.registerProvider(provider));
|
||||
} catch (ex) {
|
||||
this._log.warn("Error registering provider from category manager: " +
|
||||
entry + "; " + CommonUtils.exceptionStr(ex));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return Task.spawn(function wait() {
|
||||
for (let promise of promises) {
|
||||
yield promise;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Collect all measurements for all registered providers.
|
||||
*/
|
||||
collectMeasurements: function collectMeasurements() {
|
||||
return this._collector.collectConstantMeasurements();
|
||||
collectMeasurements: function () {
|
||||
return this._collector.collectConstantData();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -264,7 +532,7 @@ HealthReporter.prototype = {
|
||||
* @param reason
|
||||
* (string) Why data submission is being disabled.
|
||||
*/
|
||||
recordPolicyRejection: function recordPolicyRejection(reason) {
|
||||
recordPolicyRejection: function (reason) {
|
||||
this._policy.recordUserRejection(reason);
|
||||
},
|
||||
|
||||
@ -276,7 +544,7 @@ HealthReporter.prototype = {
|
||||
* @param reason
|
||||
* (string) Why data submission is being enabled.
|
||||
*/
|
||||
recordPolicyAcceptance: function recordPolicyAcceptance(reason) {
|
||||
recordPolicyAcceptance: function (reason) {
|
||||
this._policy.recordUserAcceptance(reason);
|
||||
},
|
||||
|
||||
@ -306,7 +574,7 @@ HealthReporter.prototype = {
|
||||
* callers should poll haveRemoteData() to determine when remote data is
|
||||
* deleted.
|
||||
*/
|
||||
requestDeleteRemoteData: function requestDeleteRemoteData(reason) {
|
||||
requestDeleteRemoteData: function (reason) {
|
||||
if (!this.lastSubmitID) {
|
||||
return;
|
||||
}
|
||||
@ -314,26 +582,110 @@ HealthReporter.prototype = {
|
||||
return this._policy.deleteRemoteData(reason);
|
||||
},
|
||||
|
||||
getJSONPayload: function getJSONPayload() {
|
||||
getJSONPayload: function () {
|
||||
return Task.spawn(this._getJSONPayload.bind(this, this._now()));
|
||||
},
|
||||
|
||||
_getJSONPayload: function (now) {
|
||||
let pingDateString = this._formatDate(now);
|
||||
this._log.info("Producing JSON payload for " + pingDateString);
|
||||
|
||||
let o = {
|
||||
version: 1,
|
||||
thisPingDate: this._formatDate(this._now()),
|
||||
providers: {},
|
||||
thisPingDate: pingDateString,
|
||||
data: {last: {}, days: {}},
|
||||
};
|
||||
|
||||
let outputDataDays = o.data.days;
|
||||
|
||||
// We need to be careful that data in errors does not leak potentially
|
||||
// private information.
|
||||
// FUTURE ask Privacy if we can put exception stacks in here.
|
||||
let errors = [];
|
||||
|
||||
let lastPingDate = this.lastPingDate;
|
||||
if (lastPingDate.getTime() > 0) {
|
||||
o.lastPingDate = this._formatDate(lastPingDate);
|
||||
}
|
||||
|
||||
for (let [name, provider] of this._collector.collectionResults) {
|
||||
o.providers[name] = provider;
|
||||
for (let provider of this._collector.providers) {
|
||||
let providerName = provider.name;
|
||||
|
||||
let providerEntry = {
|
||||
measurements: {},
|
||||
};
|
||||
|
||||
for (let [measurementKey, measurement] of provider.measurements) {
|
||||
let name = providerName + "." + measurement.name + "." + measurement.version;
|
||||
|
||||
let serializer;
|
||||
try {
|
||||
serializer = measurement.serializer(measurement.SERIALIZE_JSON);
|
||||
} catch (ex) {
|
||||
this._log.warn("Error obtaining serializer for measurement: " + name +
|
||||
": " + CommonUtils.exceptionStr(ex));
|
||||
errors.push("Could not obtain serializer: " + name);
|
||||
continue;
|
||||
}
|
||||
|
||||
let data;
|
||||
try {
|
||||
data = yield this._storage.getMeasurementValues(measurement.id);
|
||||
} catch (ex) {
|
||||
this._log.warn("Error obtaining data for measurement: " +
|
||||
name + ": " + CommonUtils.exceptionStr(ex));
|
||||
errors.push("Could not obtain data: " + name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data.singular.size) {
|
||||
try {
|
||||
o.data.last[name] = serializer.singular(data.singular);
|
||||
} catch (ex) {
|
||||
this._log.warn("Error serializing data: " + CommonUtils.exceptionStr(ex));
|
||||
errors.push("Error serializing singular: " + name);
|
||||
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] = {};
|
||||
}
|
||||
|
||||
outputDataDays[dateFormatted][name] = serialized;
|
||||
} catch (ex) {
|
||||
this._log.warn("Error populating data for day: " +
|
||||
CommonUtils.exceptionStr(ex));
|
||||
errors.push("Could not serialize day: " + name +
|
||||
" ( " + dateFormatted + ")");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(o);
|
||||
if (errors.length) {
|
||||
o.errors = errors;
|
||||
}
|
||||
|
||||
throw new Task.Result(JSON.stringify(o));
|
||||
},
|
||||
|
||||
_onBagheeraResult: function _onBagheeraResult(request, isDelete, result) {
|
||||
_onBagheeraResult: function (request, isDelete, result) {
|
||||
this._log.debug("Received Bagheera result.");
|
||||
|
||||
let promise = Promise.resolve(null);
|
||||
@ -362,36 +714,34 @@ HealthReporter.prototype = {
|
||||
return promise;
|
||||
},
|
||||
|
||||
_onSubmitDataRequestFailure: function _onSubmitDataRequestFailure(error) {
|
||||
_onSubmitDataRequestFailure: function (error) {
|
||||
this._log.error("Error processing request to submit data: " +
|
||||
CommonUtils.exceptionStr(error));
|
||||
},
|
||||
|
||||
_formatDate: function _formatDate(date) {
|
||||
_formatDate: function (date) {
|
||||
// Why, oh, why doesn't JS have a strftime() equivalent?
|
||||
return date.toISOString().substr(0, 10);
|
||||
},
|
||||
|
||||
|
||||
_uploadData: function _uploadData(request) {
|
||||
_uploadData: function (request) {
|
||||
let id = CommonUtils.generateUUID();
|
||||
|
||||
this._log.info("Uploading data to server: " + this.serverURI + " " +
|
||||
this.serverNamespace + ":" + id);
|
||||
let client = new BagheeraClient(this.serverURI);
|
||||
|
||||
let payload = this.getJSONPayload();
|
||||
|
||||
return this._saveLastPayload(payload)
|
||||
.then(client.uploadJSON.bind(client,
|
||||
this.serverNamespace,
|
||||
id,
|
||||
payload,
|
||||
this.lastSubmitID))
|
||||
.then(this._onBagheeraResult.bind(this, request, false));
|
||||
return Task.spawn(function doUpload() {
|
||||
let payload = yield this.getJSONPayload();
|
||||
yield this._saveLastPayload(payload);
|
||||
let result = yield client.uploadJSON(this.serverNamespace, id, payload,
|
||||
this.lastSubmitID);
|
||||
yield this._onBagheeraResult(request, false, result);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
_deleteRemoteData: function _deleteRemoteData(request) {
|
||||
_deleteRemoteData: function (request) {
|
||||
if (!this.lastSubmitID) {
|
||||
this._log.info("Received request to delete remote data but no data stored.");
|
||||
request.onNoDataAvailable();
|
||||
@ -419,7 +769,7 @@ HealthReporter.prototype = {
|
||||
return OS.Path.join(profD, "healthreport");
|
||||
},
|
||||
|
||||
_ensureDirectoryExists: function _ensureDirectoryExists(path) {
|
||||
_ensureDirectoryExists: function (path) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
OS.File.makeDir(path).then(
|
||||
@ -443,7 +793,7 @@ HealthReporter.prototype = {
|
||||
return OS.Path.join(this._stateDir, "lastpayload.json");
|
||||
},
|
||||
|
||||
_saveLastPayload: function _saveLastPayload(payload) {
|
||||
_saveLastPayload: function (payload) {
|
||||
let path = this._lastPayloadPath;
|
||||
let pathTmp = path + ".tmp";
|
||||
|
||||
@ -462,7 +812,7 @@ HealthReporter.prototype = {
|
||||
*
|
||||
* @return Promise<object>
|
||||
*/
|
||||
getLastPayload: function getLoadPayload() {
|
||||
getLastPayload: function () {
|
||||
let path = this._lastPayloadPath;
|
||||
|
||||
return OS.File.read(path).then(
|
||||
@ -486,26 +836,24 @@ HealthReporter.prototype = {
|
||||
// HealthReportPolicy listeners
|
||||
//-----------------------------
|
||||
|
||||
onRequestDataUpload: function onRequestDataSubmission(request) {
|
||||
onRequestDataUpload: function (request) {
|
||||
this.collectMeasurements()
|
||||
.then(this._uploadData.bind(this, request),
|
||||
this._onSubmitDataRequestFailure.bind(this));
|
||||
},
|
||||
|
||||
onNotifyDataPolicy: function onNotifyDataPolicy(request) {
|
||||
onNotifyDataPolicy: function (request) {
|
||||
// This isn't very loosely coupled. We may want to have this call
|
||||
// registered listeners instead.
|
||||
Observers.notify("healthreport:notify-data-policy:request", request);
|
||||
},
|
||||
|
||||
onRequestRemoteDelete: function onRequestRemoteDelete(request) {
|
||||
onRequestRemoteDelete: function (request) {
|
||||
this._deleteRemoteData(request);
|
||||
},
|
||||
|
||||
//------------------------------------
|
||||
// End of HealthReportPolicy listeners
|
||||
//------------------------------------
|
||||
};
|
||||
|
||||
Object.freeze(HealthReporter.prototype);
|
||||
});
|
||||
|
||||
|
@ -11,24 +11,26 @@ this.EXPORTED_SYMBOLS = [
|
||||
|
||||
const {utils: Cu, classes: Cc, interfaces: Ci} = Components;
|
||||
|
||||
const DEFAULT_PROFILE_MEASUREMENT_NAME = "org.mozilla.profile";
|
||||
const DEFAULT_PROFILE_MEASUREMENT_NAME = "age";
|
||||
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
|
||||
const REQUIRED_UINT32_TYPE = {type: "TYPE_UINT32"};
|
||||
|
||||
Cu.import("resource://gre/modules/commonjs/promise/core.js");
|
||||
Cu.import("resource://gre/modules/Metrics.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm")
|
||||
Cu.import("resource://gre/modules/services/metrics/dataprovider.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://services-common/log4moz.js");
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
|
||||
// Profile creation time access.
|
||||
// This is separate from the provider to simplify testing and enable extraction
|
||||
// to a shared location in the future.
|
||||
function ProfileCreationTimeAccessor(profile) {
|
||||
function ProfileCreationTimeAccessor(profile, log) {
|
||||
this.profilePath = profile || OS.Constants.Path.profileDir;
|
||||
if (!this.profilePath) {
|
||||
throw new Error("No profile directory.");
|
||||
}
|
||||
this._log = log || {"debug": function (s) { dump(s + "\n"); }};
|
||||
}
|
||||
ProfileCreationTimeAccessor.prototype = {
|
||||
/**
|
||||
@ -115,31 +117,47 @@ ProfileCreationTimeAccessor.prototype = {
|
||||
* and returning its creation timestamp.
|
||||
*/
|
||||
getOldestProfileTimestamp: function () {
|
||||
let self = this;
|
||||
let oldest = Date.now() + 1000;
|
||||
let iterator = new OS.File.DirectoryIterator(this.profilePath);
|
||||
dump("Iterating over profile " + this.profilePath);
|
||||
self._log.debug("Iterating over profile " + this.profilePath);
|
||||
if (!iterator) {
|
||||
throw new Error("Unable to fetch oldest profile entry: no profile iterator.");
|
||||
}
|
||||
|
||||
function onEntry(entry) {
|
||||
if ("winLastWriteDate" in entry) {
|
||||
// Under Windows, additional information allow us to sort files immediately
|
||||
// without having to perform additional I/O.
|
||||
let timestamp = entry.winCreationDate.getTime();
|
||||
if (timestamp < oldest) {
|
||||
oldest = timestamp;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Under other OSes, we need to call OS.File.stat.
|
||||
function onStatSuccess(info) {
|
||||
let date = info.creationDate;
|
||||
let timestamp = date.getTime();
|
||||
dump("CREATION DATE: " + entry.path + " = " + date);
|
||||
if (timestamp < oldest) {
|
||||
oldest = timestamp;
|
||||
// OS.File doesn't seem to be behaving. See Bug 827148.
|
||||
// Let's do the best we can. This whole function is defensive.
|
||||
let date;
|
||||
if ("winBirthDate" in info) {
|
||||
date = info.winBirthDate;
|
||||
} else if ("macBirthDate" in info) {
|
||||
date = info.macBirthDate;
|
||||
}
|
||||
|
||||
if (!date || !date.getTime()) {
|
||||
// Hack: as of this writing, OS.File will only return file
|
||||
// creation times of any kind of Mac and Windows, where birthTime
|
||||
// is defined. That means we're unable to function on Linux.
|
||||
// Use ctime, fall back to mtime.
|
||||
// Oh, and info.macBirthDate doesn't work.
|
||||
self._log.debug("No birth date: using ctime/mtime.");
|
||||
try {
|
||||
date = info.creationDate ||
|
||||
info.lastModificationDate ||
|
||||
info.unixLastStatusChangeDate;
|
||||
} catch (ex) {
|
||||
self._log.debug("Exception fetching creation date: " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (date) {
|
||||
let timestamp = date.getTime();
|
||||
self._log.debug("Using date: " + entry.path + " = " + date);
|
||||
if (timestamp < oldest) {
|
||||
oldest = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
return OS.File.stat(entry.path)
|
||||
@ -165,15 +183,18 @@ dump("Iterating over profile " + this.profilePath);
|
||||
/**
|
||||
* Measurements pertaining to the user's profile.
|
||||
*/
|
||||
function ProfileMetadataMeasurement(name=DEFAULT_PROFILE_MEASUREMENT_NAME) {
|
||||
MetricsMeasurement.call(this, name, 1);
|
||||
function ProfileMetadataMeasurement() {
|
||||
Metrics.Measurement.call(this);
|
||||
}
|
||||
ProfileMetadataMeasurement.prototype = {
|
||||
__proto__: MetricsMeasurement.prototype,
|
||||
__proto__: Metrics.Measurement.prototype,
|
||||
|
||||
fields: {
|
||||
name: DEFAULT_PROFILE_MEASUREMENT_NAME,
|
||||
version: 1,
|
||||
|
||||
configureStorage: function () {
|
||||
// Profile creation date. Number of days since Unix epoch.
|
||||
"profileCreation": REQUIRED_UINT32_TYPE,
|
||||
return this.registerStorageField("profileCreation", this.storage.FIELD_LAST_NUMERIC);
|
||||
},
|
||||
};
|
||||
|
||||
@ -188,41 +209,35 @@ function truncate(msec) {
|
||||
}
|
||||
|
||||
/**
|
||||
* A MetricsProvider for profile metadata, such as profile creation time.
|
||||
* A Metrics.Provider for profile metadata, such as profile creation time.
|
||||
*/
|
||||
function ProfileMetadataProvider(name="ProfileMetadataProvider") {
|
||||
MetricsProvider.call(this, name);
|
||||
function ProfileMetadataProvider() {
|
||||
Metrics.Provider.call(this);
|
||||
}
|
||||
ProfileMetadataProvider.prototype = {
|
||||
__proto__: MetricsProvider.prototype,
|
||||
__proto__: Metrics.Provider.prototype,
|
||||
|
||||
name: "org.mozilla.profile",
|
||||
|
||||
measurementTypes: [ProfileMetadataMeasurement],
|
||||
|
||||
getProfileCreationDays: function () {
|
||||
let accessor = new ProfileCreationTimeAccessor();
|
||||
let accessor = new ProfileCreationTimeAccessor(null, this._log);
|
||||
|
||||
return accessor.created
|
||||
.then(truncate);
|
||||
},
|
||||
|
||||
collectConstantMeasurements: function () {
|
||||
let result = this.createResult();
|
||||
result.expectMeasurement("org.mozilla.profile");
|
||||
result.populate = this._populateConstants.bind(this);
|
||||
return result;
|
||||
},
|
||||
collectConstantData: function () {
|
||||
let m = this.getMeasurement(DEFAULT_PROFILE_MEASUREMENT_NAME, 1);
|
||||
|
||||
_populateConstants: function (result) {
|
||||
let name = DEFAULT_PROFILE_MEASUREMENT_NAME;
|
||||
result.addMeasurement(new ProfileMetadataMeasurement(name));
|
||||
function onSuccess(days) {
|
||||
result.setValue(name, "profileCreation", days);
|
||||
result.finish();
|
||||
}
|
||||
function onFailure(ex) {
|
||||
result.addError(ex);
|
||||
result.finish();
|
||||
}
|
||||
return this.getProfileCreationDays()
|
||||
.then(onSuccess, onFailure);
|
||||
return Task.spawn(function collectConstant() {
|
||||
let createdDays = yield this.getProfileCreationDays();
|
||||
|
||||
yield this.enqueueStorageOperation(function storeDays() {
|
||||
return m.setLastNumeric("profileCreation", createdDays);
|
||||
});
|
||||
}.bind(this));
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -21,17 +21,14 @@ this.EXPORTED_SYMBOLS = [
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Metrics.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://gre/modules/services/metrics/dataprovider.jsm");
|
||||
Cu.import("resource://services-common/preferences.js");
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
|
||||
|
||||
const REQUIRED_STRING_TYPE = {type: "TYPE_STRING"};
|
||||
const OPTIONAL_STRING_TYPE = {type: "TYPE_STRING", optional: true};
|
||||
const REQUIRED_UINT32_TYPE = {type: "TYPE_UINT32"};
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
|
||||
"resource://gre/modules/UpdateChannel.jsm");
|
||||
|
||||
@ -42,40 +39,54 @@ XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
|
||||
* pieces thrown in.
|
||||
*/
|
||||
function AppInfoMeasurement() {
|
||||
MetricsMeasurement.call(this, "appinfo", 1);
|
||||
Metrics.Measurement.call(this);
|
||||
}
|
||||
|
||||
AppInfoMeasurement.prototype = {
|
||||
__proto__: MetricsMeasurement.prototype,
|
||||
AppInfoMeasurement.prototype = Object.freeze({
|
||||
__proto__: Metrics.Measurement.prototype,
|
||||
|
||||
fields: {
|
||||
vendor: REQUIRED_STRING_TYPE,
|
||||
name: REQUIRED_STRING_TYPE,
|
||||
id: REQUIRED_STRING_TYPE,
|
||||
version: REQUIRED_STRING_TYPE,
|
||||
appBuildID: REQUIRED_STRING_TYPE,
|
||||
platformVersion: REQUIRED_STRING_TYPE,
|
||||
platformBuildID: REQUIRED_STRING_TYPE,
|
||||
os: REQUIRED_STRING_TYPE,
|
||||
xpcomabi: REQUIRED_STRING_TYPE,
|
||||
updateChannel: REQUIRED_STRING_TYPE,
|
||||
distributionID: REQUIRED_STRING_TYPE,
|
||||
distributionVersion: REQUIRED_STRING_TYPE,
|
||||
hotfixVersion: REQUIRED_STRING_TYPE,
|
||||
locale: REQUIRED_STRING_TYPE,
|
||||
name: "appinfo",
|
||||
version: 1,
|
||||
|
||||
LAST_TEXT_FIELDS: [
|
||||
"vendor",
|
||||
"name",
|
||||
"id",
|
||||
"version",
|
||||
"appBuildID",
|
||||
"platformVersion",
|
||||
"platformBuildID",
|
||||
"os",
|
||||
"xpcomabi",
|
||||
"updateChannel",
|
||||
"distributionID",
|
||||
"distributionVersion",
|
||||
"hotfixVersion",
|
||||
"locale",
|
||||
],
|
||||
|
||||
configureStorage: function () {
|
||||
let self = this;
|
||||
return Task.spawn(function configureStorage() {
|
||||
for (let field of self.LAST_TEXT_FIELDS) {
|
||||
yield self.registerStorageField(field, self.storage.FIELD_LAST_TEXT);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
Object.freeze(AppInfoMeasurement.prototype);
|
||||
});
|
||||
|
||||
|
||||
this.AppInfoProvider = function AppInfoProvider() {
|
||||
MetricsProvider.call(this, "app-info");
|
||||
Metrics.Provider.call(this);
|
||||
|
||||
this._prefs = new Preferences({defaultBranch: null});
|
||||
}
|
||||
AppInfoProvider.prototype = {
|
||||
__proto__: MetricsProvider.prototype,
|
||||
AppInfoProvider.prototype = Object.freeze({
|
||||
__proto__: Metrics.Provider.prototype,
|
||||
|
||||
name: "org.mozilla.appInfo",
|
||||
|
||||
measurementTypes: [AppInfoMeasurement],
|
||||
|
||||
appInfoFields: {
|
||||
// From nsIXULAppInfo.
|
||||
@ -92,16 +103,15 @@ AppInfoProvider.prototype = {
|
||||
xpcomabi: "XPCOMABI",
|
||||
},
|
||||
|
||||
collectConstantMeasurements: function collectConstantMeasurements() {
|
||||
let result = this.createResult();
|
||||
result.expectMeasurement("appinfo");
|
||||
|
||||
result.populate = this._populateConstants.bind(this);
|
||||
return result;
|
||||
collectConstantData: function () {
|
||||
return this.enqueueStorageOperation(function collect() {
|
||||
return Task.spawn(this._populateConstants.bind(this));
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
_populateConstants: function _populateConstants(result) {
|
||||
result.addMeasurement(new AppInfoMeasurement());
|
||||
_populateConstants: function () {
|
||||
let m = this.getMeasurement(AppInfoMeasurement.prototype.name,
|
||||
AppInfoMeasurement.prototype.version);
|
||||
|
||||
let ai;
|
||||
try {
|
||||
@ -119,71 +129,71 @@ AppInfoProvider.prototype = {
|
||||
|
||||
for (let [k, v] in Iterator(this.appInfoFields)) {
|
||||
try {
|
||||
result.setValue("appinfo", k, ai[v]);
|
||||
yield m.setLastText(k, ai[v]);
|
||||
} catch (ex) {
|
||||
this._log.warn("Error obtaining Services.appinfo." + v);
|
||||
result.addError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
result.setValue("appinfo", "updateChannel", UpdateChannel.get());
|
||||
yield m.setLastText("updateChannel", UpdateChannel.get());
|
||||
} catch (ex) {
|
||||
this._log.warn("Could not obtain update channel: " +
|
||||
CommonUtils.exceptionStr(ex));
|
||||
result.addError(ex);
|
||||
}
|
||||
|
||||
result.setValue("appinfo", "distributionID", this._prefs.get("distribution.id", ""));
|
||||
result.setValue("appinfo", "distributionVersion", this._prefs.get("distribution.version", ""));
|
||||
result.setValue("appinfo", "hotfixVersion", this._prefs.get("extensions.hotfix.lastVersion", ""));
|
||||
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");
|
||||
result.setValue("appinfo", "locale", locale);
|
||||
yield m.setLastText("locale", locale);
|
||||
} catch (ex) {
|
||||
this._log.warn("Could not obtain application locale: " +
|
||||
CommonUtils.exceptionStr(ex));
|
||||
result.addError(ex);
|
||||
}
|
||||
|
||||
result.finish();
|
||||
},
|
||||
};
|
||||
|
||||
Object.freeze(AppInfoProvider.prototype);
|
||||
});
|
||||
|
||||
|
||||
function SysInfoMeasurement() {
|
||||
MetricsMeasurement.call(this, "sysinfo", 1);
|
||||
Metrics.Measurement.call(this);
|
||||
}
|
||||
|
||||
SysInfoMeasurement.prototype = {
|
||||
__proto__: MetricsMeasurement.prototype,
|
||||
SysInfoMeasurement.prototype = Object.freeze({
|
||||
__proto__: Metrics.Measurement.prototype,
|
||||
|
||||
fields: {
|
||||
cpuCount: REQUIRED_UINT32_TYPE,
|
||||
memoryMB: REQUIRED_UINT32_TYPE,
|
||||
manufacturer: OPTIONAL_STRING_TYPE,
|
||||
device: OPTIONAL_STRING_TYPE,
|
||||
hardware: OPTIONAL_STRING_TYPE,
|
||||
name: OPTIONAL_STRING_TYPE,
|
||||
version: OPTIONAL_STRING_TYPE,
|
||||
architecture: OPTIONAL_STRING_TYPE,
|
||||
name: "sysinfo",
|
||||
version: 1,
|
||||
|
||||
configureStorage: function () {
|
||||
return Task.spawn(function configureStorage() {
|
||||
yield this.registerStorageField("cpuCount", this.storage.FIELD_LAST_NUMERIC);
|
||||
yield this.registerStorageField("memoryMB", this.storage.FIELD_LAST_NUMERIC);
|
||||
yield this.registerStorageField("manufacturer", this.storage.FIELD_LAST_TEXT);
|
||||
yield this.registerStorageField("device", this.storage.FIELD_LAST_TEXT);
|
||||
yield this.registerStorageField("hardware", this.storage.FIELD_LAST_TEXT);
|
||||
yield this.registerStorageField("name", this.storage.FIELD_LAST_TEXT);
|
||||
yield this.registerStorageField("version", this.storage.FIELD_LAST_TEXT);
|
||||
yield this.registerStorageField("architecture", this.storage.FIELD_LAST_TEXT);
|
||||
}.bind(this));
|
||||
},
|
||||
},
|
||||
|
||||
Object.freeze(SysInfoMeasurement.prototype);
|
||||
});
|
||||
|
||||
|
||||
this.SysInfoProvider = function SysInfoProvider() {
|
||||
MetricsProvider.call(this, "sys-info");
|
||||
Metrics.Provider.call(this);
|
||||
};
|
||||
|
||||
SysInfoProvider.prototype = {
|
||||
__proto__: MetricsProvider.prototype,
|
||||
SysInfoProvider.prototype = Object.freeze({
|
||||
__proto__: Metrics.Provider.prototype,
|
||||
|
||||
name: "org.mozilla.sysinfo",
|
||||
|
||||
measurementTypes: [SysInfoMeasurement],
|
||||
|
||||
sysInfoFields: {
|
||||
cpucount: "cpuCount",
|
||||
@ -196,19 +206,15 @@ SysInfoProvider.prototype = {
|
||||
arch: "architecture",
|
||||
},
|
||||
|
||||
INT_FIELDS: new Set("cpucount", "memsize"),
|
||||
|
||||
collectConstantMeasurements: function collectConstantMeasurements() {
|
||||
let result = this.createResult();
|
||||
result.expectMeasurement("sysinfo");
|
||||
|
||||
result.populate = this._populateConstants.bind(this);
|
||||
|
||||
return result;
|
||||
collectConstantData: function () {
|
||||
return this.enqueueStorageOperation(function collection() {
|
||||
return Task.spawn(this._populateConstants.bind(this));
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
_populateConstants: function _populateConstants(result) {
|
||||
result.addMeasurement(new SysInfoMeasurement());
|
||||
_populateConstants: function () {
|
||||
let m = this.getMeasurement(SysInfoMeasurement.prototype.name,
|
||||
SysInfoMeasurement.prototype.version);
|
||||
|
||||
let si = Cc["@mozilla.org/system-info;1"]
|
||||
.getService(Ci.nsIPropertyBag2);
|
||||
@ -221,16 +227,16 @@ SysInfoProvider.prototype = {
|
||||
}
|
||||
|
||||
let value = si.getProperty(k);
|
||||
let method = "setLastText";
|
||||
|
||||
if (this.INT_FIELDS.has(k)) {
|
||||
if (["cpucount", "memsize"].indexOf(k) != -1) {
|
||||
let converted = parseInt(value, 10);
|
||||
if (Number.isNaN(converted)) {
|
||||
result.addError(new Error("Value is not an integer: " + k + "=" +
|
||||
value));
|
||||
continue;
|
||||
}
|
||||
|
||||
value = converted;
|
||||
method = "setLastNumeric";
|
||||
}
|
||||
|
||||
// Round memory to mebibytes.
|
||||
@ -238,17 +244,12 @@ SysInfoProvider.prototype = {
|
||||
value = Math.round(value / 1048576);
|
||||
}
|
||||
|
||||
result.setValue("sysinfo", v, value);
|
||||
yield m[method](v, value);
|
||||
} catch (ex) {
|
||||
this._log.warn("Error obtaining system info field: " + k + " " +
|
||||
CommonUtils.exceptionStr(ex));
|
||||
result.addError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
result.finish();
|
||||
},
|
||||
};
|
||||
|
||||
Object.freeze(SysInfoProvider.prototype);
|
||||
});
|
||||
|
||||
|
@ -10,6 +10,8 @@ Cu.import("resource://services-common/preferences.js");
|
||||
Cu.import("resource://gre/modules/commonjs/promise/core.js");
|
||||
Cu.import("resource://gre/modules/services/healthreport/healthreporter.jsm");
|
||||
Cu.import("resource://gre/modules/services/healthreport/policy.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://testing-common/services-common/bagheeraserver.js");
|
||||
Cu.import("resource://testing-common/services/metrics/mocks.jsm");
|
||||
|
||||
@ -17,6 +19,7 @@ Cu.import("resource://testing-common/services/metrics/mocks.jsm");
|
||||
const SERVER_HOSTNAME = "localhost";
|
||||
const SERVER_PORT = 8080;
|
||||
const SERVER_URI = "http://" + SERVER_HOSTNAME + ":" + SERVER_PORT;
|
||||
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
|
||||
|
||||
|
||||
function defineNow(policy, now) {
|
||||
@ -34,28 +37,39 @@ function getReporter(name, uri=SERVER_URI) {
|
||||
|
||||
let prefs = new Preferences(branch);
|
||||
prefs.set("documentServerURI", uri);
|
||||
prefs.set("dbName", name);
|
||||
|
||||
return new HealthReporter(branch);
|
||||
let reporter = new HealthReporter(branch);
|
||||
return reporter.onInit();
|
||||
}
|
||||
|
||||
function getReporterAndServer(name, namespace="test") {
|
||||
let reporter = getReporter(name, SERVER_URI);
|
||||
reporter.serverNamespace = namespace;
|
||||
return Task.spawn(function get() {
|
||||
let reporter = yield getReporter(name, SERVER_URI);
|
||||
reporter.serverNamespace = namespace;
|
||||
|
||||
let server = new BagheeraServer(SERVER_URI);
|
||||
server.createNamespace(namespace);
|
||||
let server = new BagheeraServer(SERVER_URI);
|
||||
server.createNamespace(namespace);
|
||||
|
||||
server.start(SERVER_PORT);
|
||||
server.start(SERVER_PORT);
|
||||
|
||||
return [reporter, server];
|
||||
throw new Task.Result([reporter, server]);
|
||||
});
|
||||
}
|
||||
|
||||
function shutdownServer(server) {
|
||||
let deferred = Promise.defer();
|
||||
server.stop(deferred.resolve.bind(deferred));
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_test(function test_constructor() {
|
||||
let reporter = getReporter("constructor");
|
||||
add_task(function test_constructor() {
|
||||
let reporter = yield getReporter("constructor");
|
||||
|
||||
do_check_eq(reporter.lastPingDate.getTime(), 0);
|
||||
do_check_null(reporter.lastSubmitID);
|
||||
@ -70,16 +84,16 @@ add_test(function test_constructor() {
|
||||
new HealthReporter("foo.bar");
|
||||
} catch (ex) {
|
||||
failed = true;
|
||||
do_check_true(ex.message.startsWith("Branch argument must end"));
|
||||
do_check_true(ex.message.startsWith("Branch must end"));
|
||||
} finally {
|
||||
do_check_true(failed);
|
||||
failed = false;
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
reporter._shutdown();
|
||||
});
|
||||
|
||||
add_test(function test_register_providers_from_category_manager() {
|
||||
add_task(function test_register_providers_from_category_manager() {
|
||||
const category = "healthreporter-js-modules";
|
||||
|
||||
let cm = Cc["@mozilla.org/categorymanager;1"]
|
||||
@ -88,113 +102,140 @@ add_test(function test_register_providers_from_category_manager() {
|
||||
"resource://testing-common/services/metrics/mocks.jsm",
|
||||
false, true);
|
||||
|
||||
let reporter = getReporter("category_manager");
|
||||
do_check_eq(reporter._collector._providers.length, 0);
|
||||
reporter.registerProvidersFromCategoryManager(category);
|
||||
do_check_eq(reporter._collector._providers.length, 1);
|
||||
let reporter = yield getReporter("category_manager");
|
||||
do_check_eq(reporter._collector._providers.size, 0);
|
||||
yield reporter.registerProvidersFromCategoryManager(category);
|
||||
do_check_eq(reporter._collector._providers.size, 1);
|
||||
|
||||
run_next_test();
|
||||
reporter._shutdown();
|
||||
});
|
||||
|
||||
add_test(function test_start() {
|
||||
let reporter = getReporter("start");
|
||||
reporter.start().then(function onStarted() {
|
||||
reporter.stop();
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_json_payload_simple() {
|
||||
let reporter = getReporter("json_payload_simple");
|
||||
add_task(function test_json_payload_simple() {
|
||||
let reporter = yield getReporter("json_payload_simple");
|
||||
|
||||
let now = new Date();
|
||||
let payload = reporter.getJSONPayload();
|
||||
let payload = yield reporter.getJSONPayload();
|
||||
let original = JSON.parse(payload);
|
||||
|
||||
do_check_eq(original.version, 1);
|
||||
do_check_eq(original.thisPingDate, reporter._formatDate(now));
|
||||
do_check_eq(Object.keys(original.providers).length, 0);
|
||||
do_check_eq(Object.keys(original.data.last).length, 0);
|
||||
do_check_eq(Object.keys(original.data.days).length, 0);
|
||||
|
||||
reporter.lastPingDate = new Date(now.getTime() - 24 * 60 * 60 * 1000 - 10);
|
||||
|
||||
original = JSON.parse(reporter.getJSONPayload());
|
||||
original = JSON.parse(yield reporter.getJSONPayload());
|
||||
do_check_eq(original.lastPingDate, reporter._formatDate(reporter.lastPingDate));
|
||||
|
||||
// This could fail if we cross UTC day boundaries at the exact instance the
|
||||
// test is executed. Let's tempt fate.
|
||||
do_check_eq(original.thisPingDate, reporter._formatDate(now));
|
||||
|
||||
run_next_test();
|
||||
reporter._shutdown();
|
||||
});
|
||||
|
||||
add_test(function test_json_payload_dummy_provider() {
|
||||
let reporter = getReporter("json_payload_dummy_provider");
|
||||
add_task(function test_json_payload_dummy_provider() {
|
||||
let reporter = yield getReporter("json_payload_dummy_provider");
|
||||
|
||||
reporter.registerProvider(new DummyProvider());
|
||||
reporter.collectMeasurements().then(function onResult() {
|
||||
let o = JSON.parse(reporter.getJSONPayload());
|
||||
yield reporter.registerProvider(new DummyProvider());
|
||||
yield reporter.collectMeasurements();
|
||||
let payload = yield reporter.getJSONPayload();
|
||||
print(payload);
|
||||
let o = JSON.parse(payload);
|
||||
|
||||
do_check_eq(Object.keys(o.providers).length, 1);
|
||||
do_check_true("DummyProvider" in o.providers);
|
||||
do_check_true("measurements" in o.providers.DummyProvider);
|
||||
do_check_true("DummyMeasurement" in o.providers.DummyProvider.measurements);
|
||||
do_check_eq(Object.keys(o.data.last).length, 1);
|
||||
do_check_true("DummyProvider.DummyMeasurement.1" in o.data.last);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
reporter._shutdown();
|
||||
});
|
||||
|
||||
add_test(function test_notify_policy_observers() {
|
||||
let reporter = getReporter("notify_policy_observers");
|
||||
add_task(function test_json_payload_multiple_days() {
|
||||
let reporter = yield getReporter("json_payload_multiple_days");
|
||||
let provider = new DummyProvider();
|
||||
yield reporter.registerProvider(provider);
|
||||
|
||||
Observers.add("healthreport:notify-data-policy:request",
|
||||
function onObserver(subject, data) {
|
||||
Observers.remove("healthreport:notify-data-policy:request", onObserver);
|
||||
let now = new Date();
|
||||
let m = provider.getMeasurement("DummyMeasurement", 1);
|
||||
for (let i = 0; i < 200; i++) {
|
||||
let date = new Date(now.getTime() - i * MILLISECONDS_PER_DAY);
|
||||
yield m.incrementDailyCounter("daily-counter", date);
|
||||
yield m.addDailyDiscreteNumeric("daily-discrete-numeric", i, date);
|
||||
yield m.addDailyDiscreteNumeric("daily-discrete-numeric", i + 100, date);
|
||||
yield m.addDailyDiscreteText("daily-discrete-text", "" + i, date);
|
||||
yield m.addDailyDiscreteText("daily-discrete-text", "" + (i + 50), date);
|
||||
yield m.setDailyLastNumeric("daily-last-numeric", date.getTime(), date);
|
||||
}
|
||||
|
||||
do_check_true("foo" in subject);
|
||||
let payload = yield reporter.getJSONPayload();
|
||||
print(payload);
|
||||
let o = JSON.parse(payload);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
do_check_eq(Object.keys(o.data.days).length, 180);
|
||||
let today = reporter._formatDate(now);
|
||||
do_check_true(today in o.data.days);
|
||||
|
||||
reporter.onNotifyDataPolicy({foo: "bar"});
|
||||
reporter._shutdown();
|
||||
});
|
||||
|
||||
add_test(function test_data_submission_transport_failure() {
|
||||
let reporter = getReporter("data_submission_transport_failure");
|
||||
add_task(function test_idle_daily() {
|
||||
let reporter = yield getReporter("idle_daily");
|
||||
let provider = new DummyProvider();
|
||||
yield reporter.registerProvider(provider);
|
||||
|
||||
let now = new Date();
|
||||
let m = provider.getMeasurement("DummyMeasurement", 1);
|
||||
for (let i = 0; i < 200; i++) {
|
||||
let date = new Date(now.getTime() - i * MILLISECONDS_PER_DAY);
|
||||
yield m.incrementDailyCounter("daily-counter", date);
|
||||
}
|
||||
|
||||
let values = yield m.getValues();
|
||||
do_check_eq(values.days.size, 200);
|
||||
|
||||
Services.obs.notifyObservers(null, "idle-daily", null);
|
||||
|
||||
let values = yield m.getValues();
|
||||
do_check_eq(values.days.size, 180);
|
||||
|
||||
reporter._shutdown();
|
||||
});
|
||||
|
||||
add_task(function test_data_submission_transport_failure() {
|
||||
let reporter = yield getReporter("data_submission_transport_failure");
|
||||
reporter.serverURI = "http://localhost:8080/";
|
||||
reporter.serverNamespace = "test00";
|
||||
|
||||
let deferred = Promise.defer();
|
||||
deferred.promise.then(function onResult(request) {
|
||||
do_check_eq(request.state, request.SUBMISSION_FAILURE_SOFT);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
let request = new DataSubmissionRequest(deferred, new Date(Date.now + 30000));
|
||||
reporter.onRequestDataUpload(request);
|
||||
|
||||
yield deferred.promise;
|
||||
do_check_eq(request.state, request.SUBMISSION_FAILURE_SOFT);
|
||||
|
||||
reporter._shutdown();
|
||||
});
|
||||
|
||||
add_test(function test_data_submission_success() {
|
||||
let [reporter, server] = getReporterAndServer("data_submission_success");
|
||||
add_task(function test_data_submission_success() {
|
||||
let [reporter, server] = yield getReporterAndServer("data_submission_success");
|
||||
|
||||
do_check_eq(reporter.lastPingDate.getTime(), 0);
|
||||
do_check_false(reporter.haveRemoteData());
|
||||
|
||||
let deferred = Promise.defer();
|
||||
deferred.promise.then(function onResult(request) {
|
||||
do_check_eq(request.state, request.SUBMISSION_SUCCESS);
|
||||
do_check_neq(reporter.lastPingDate.getTime(), 0);
|
||||
do_check_true(reporter.haveRemoteData());
|
||||
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
|
||||
let request = new DataSubmissionRequest(deferred, new Date());
|
||||
reporter.onRequestDataUpload(request);
|
||||
yield deferred.promise;
|
||||
do_check_eq(request.state, request.SUBMISSION_SUCCESS);
|
||||
do_check_true(reporter.lastPingDate.getTime() > 0);
|
||||
do_check_true(reporter.haveRemoteData());
|
||||
|
||||
reporter._shutdown();
|
||||
yield shutdownServer(server);
|
||||
});
|
||||
|
||||
add_test(function test_recurring_daily_pings() {
|
||||
let [reporter, server] = getReporterAndServer("recurring_daily_pings");
|
||||
add_task(function test_recurring_daily_pings() {
|
||||
let [reporter, server] = yield getReporterAndServer("recurring_daily_pings");
|
||||
reporter.registerProvider(new DummyProvider());
|
||||
|
||||
let policy = reporter._policy;
|
||||
@ -204,55 +245,52 @@ add_test(function test_recurring_daily_pings() {
|
||||
defineNow(policy, policy.nextDataSubmissionDate);
|
||||
let promise = policy.checkStateAndTrigger();
|
||||
do_check_neq(promise, null);
|
||||
yield promise;
|
||||
|
||||
promise.then(function onUploadComplete() {
|
||||
let lastID = reporter.lastSubmitID;
|
||||
let lastID = reporter.lastSubmitID;
|
||||
do_check_neq(lastID, null);
|
||||
do_check_true(server.hasDocument(reporter.serverNamespace, lastID));
|
||||
|
||||
do_check_neq(lastID, null);
|
||||
do_check_true(server.hasDocument(reporter.serverNamespace, lastID));
|
||||
// Skip forward to next scheduled submission time.
|
||||
defineNow(policy, policy.nextDataSubmissionDate);
|
||||
promise = policy.checkStateAndTrigger();
|
||||
do_check_neq(promise, null);
|
||||
yield promise;
|
||||
do_check_neq(reporter.lastSubmitID, lastID);
|
||||
do_check_true(server.hasDocument(reporter.serverNamespace, reporter.lastSubmitID));
|
||||
do_check_false(server.hasDocument(reporter.serverNamespace, lastID));
|
||||
|
||||
// Skip forward to next scheduled submission time.
|
||||
defineNow(policy, policy.nextDataSubmissionDate);
|
||||
let promise = policy.checkStateAndTrigger();
|
||||
do_check_neq(promise, null);
|
||||
promise.then(function onSecondUploadCOmplete() {
|
||||
do_check_neq(reporter.lastSubmitID, lastID);
|
||||
do_check_true(server.hasDocument(reporter.serverNamespace, reporter.lastSubmitID));
|
||||
do_check_false(server.hasDocument(reporter.serverNamespace, lastID));
|
||||
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
reporter._shutdown();
|
||||
yield shutdownServer(server);
|
||||
});
|
||||
|
||||
add_test(function test_request_remote_data_deletion() {
|
||||
let [reporter, server] = getReporterAndServer("request_remote_data_deletion");
|
||||
add_task(function test_request_remote_data_deletion() {
|
||||
let [reporter, server] = yield getReporterAndServer("request_remote_data_deletion");
|
||||
|
||||
let policy = reporter._policy;
|
||||
defineNow(policy, policy._futureDate(-24 * 60 * 60 * 1000));
|
||||
policy.recordUserAcceptance();
|
||||
defineNow(policy, policy.nextDataSubmissionDate);
|
||||
policy.checkStateAndTrigger().then(function onUploadComplete() {
|
||||
let id = reporter.lastSubmitID;
|
||||
do_check_neq(id, null);
|
||||
do_check_true(server.hasDocument(reporter.serverNamespace, id));
|
||||
yield policy.checkStateAndTrigger();
|
||||
let id = reporter.lastSubmitID;
|
||||
do_check_neq(id, null);
|
||||
do_check_true(server.hasDocument(reporter.serverNamespace, id));
|
||||
|
||||
defineNow(policy, policy._futureDate(10 * 1000));
|
||||
defineNow(policy, policy._futureDate(10 * 1000));
|
||||
|
||||
let promise = reporter.requestDeleteRemoteData();
|
||||
do_check_neq(promise, null);
|
||||
promise.then(function onDeleteComplete() {
|
||||
do_check_null(reporter.lastSubmitID);
|
||||
do_check_false(reporter.haveRemoteData());
|
||||
do_check_false(server.hasDocument(reporter.serverNamespace, id));
|
||||
let promise = reporter.requestDeleteRemoteData();
|
||||
do_check_neq(promise, null);
|
||||
yield promise;
|
||||
do_check_null(reporter.lastSubmitID);
|
||||
do_check_false(reporter.haveRemoteData());
|
||||
do_check_false(server.hasDocument(reporter.serverNamespace, id));
|
||||
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
reporter._shutdown();
|
||||
yield shutdownServer(server);
|
||||
});
|
||||
|
||||
add_test(function test_policy_accept_reject() {
|
||||
let [reporter, server] = getReporterAndServer("policy_accept_reject");
|
||||
add_task(function test_policy_accept_reject() {
|
||||
let [reporter, server] = yield getReporterAndServer("policy_accept_reject");
|
||||
|
||||
do_check_false(reporter.dataSubmissionPolicyAccepted);
|
||||
do_check_false(reporter.willUploadData);
|
||||
@ -265,21 +303,22 @@ add_test(function test_policy_accept_reject() {
|
||||
do_check_false(reporter.dataSubmissionPolicyAccepted);
|
||||
do_check_false(reporter.willUploadData);
|
||||
|
||||
server.stop(run_next_test);
|
||||
reporter._shutdown();
|
||||
yield shutdownServer(server);
|
||||
});
|
||||
|
||||
|
||||
add_test(function test_upload_save_payload() {
|
||||
let [reporter, server] = getReporterAndServer("upload_save_payload");
|
||||
add_task(function test_upload_save_payload() {
|
||||
let [reporter, server] = yield getReporterAndServer("upload_save_payload");
|
||||
|
||||
let deferred = Promise.defer();
|
||||
let request = new DataSubmissionRequest(deferred, new Date(), false);
|
||||
|
||||
reporter._uploadData(request).then(function onUpload() {
|
||||
reporter.getLastPayload().then(function onJSON(json) {
|
||||
do_check_true("thisPingDate" in json);
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
yield reporter._uploadData(request);
|
||||
let json = yield reporter.getLastPayload();
|
||||
do_check_true("thisPingDate" in json);
|
||||
|
||||
reporter._shutdown();
|
||||
yield shutdownServer(server);
|
||||
});
|
||||
|
||||
|
@ -13,12 +13,14 @@ let profile_creation_lower = Date.now() - MILLISECONDS_PER_DAY;
|
||||
do_get_profile();
|
||||
|
||||
Cu.import("resource://gre/modules/commonjs/promise/core.js");
|
||||
Cu.import("resource://gre/modules/services/metrics/dataprovider.jsm");
|
||||
Cu.import("resource://gre/modules/Metrics.jsm");
|
||||
Cu.import("resource://gre/modules/services/healthreport/profile.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
|
||||
function MockProfileMetadataProvider(name="MockProfileMetadataProvider") {
|
||||
ProfileMetadataProvider.call(this, name);
|
||||
this.name = name;
|
||||
ProfileMetadataProvider.call(this);
|
||||
}
|
||||
MockProfileMetadataProvider.prototype = {
|
||||
__proto__: ProfileMetadataProvider.prototype,
|
||||
@ -33,15 +35,6 @@ function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
/**
|
||||
* Treat the provided function as a generator of promises,
|
||||
* suitable for use with Task.spawn. Success runs next test;
|
||||
* failure throws.
|
||||
*/
|
||||
function testTask(promiseFunction) {
|
||||
Task.spawn(promiseFunction).then(run_next_test, do_throw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that OS.File works in our environment.
|
||||
* This test can go once there are xpcshell tests for OS.File.
|
||||
@ -84,22 +77,17 @@ add_test(function test_time_accessor_no_file() {
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_time_accessor_named_file() {
|
||||
add_task(function test_time_accessor_named_file() {
|
||||
let acc = getAccessor();
|
||||
|
||||
testTask(function () {
|
||||
// There should be no file yet.
|
||||
yield acc.writeTimes({created: 12345}, "test.json");
|
||||
yield acc.readTimes("test.json")
|
||||
.then(function onSuccess(json) {
|
||||
print("Read: " + JSON.stringify(json));
|
||||
do_check_eq(12345, json.created);
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
// 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_test(function test_time_accessor_creates_file() {
|
||||
add_task(function test_time_accessor_creates_file() {
|
||||
let lower = profile_creation_lower;
|
||||
|
||||
// Ensure that provided contents are merged, and existing
|
||||
@ -109,42 +97,32 @@ add_test(function test_time_accessor_creates_file() {
|
||||
let existing = {abc: "123", easy: "abc"};
|
||||
let expected;
|
||||
|
||||
testTask(function () {
|
||||
yield acc.computeAndPersistTimes(existing, "test2.json")
|
||||
.then(function onSuccess(created) {
|
||||
let upper = Date.now() + 1000;
|
||||
print(lower + " < " + created + " <= " + upper);
|
||||
do_check_true(lower < created);
|
||||
do_check_true(upper >= created);
|
||||
expected = created;
|
||||
});
|
||||
yield acc.readTimes("test2.json")
|
||||
.then(function onSuccess(json) {
|
||||
print("Read: " + JSON.stringify(json));
|
||||
do_check_eq("123", json.abc);
|
||||
do_check_eq("abc", json.easy);
|
||||
do_check_eq(expected, json.created);
|
||||
});
|
||||
});
|
||||
let created = yield acc.computeAndPersistTimes(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_test(function test_time_accessor_all() {
|
||||
add_task(function test_time_accessor_all() {
|
||||
let lower = profile_creation_lower;
|
||||
let acc = getAccessor();
|
||||
let expected;
|
||||
testTask(function () {
|
||||
yield acc.created
|
||||
.then(function onSuccess(created) {
|
||||
let upper = Date.now() + 1000;
|
||||
do_check_true(lower < created);
|
||||
do_check_true(upper >= created);
|
||||
expected = created;
|
||||
});
|
||||
yield acc.created
|
||||
.then(function onSuccess(again) {
|
||||
do_check_eq(expected, again);
|
||||
});
|
||||
});
|
||||
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_test(function test_constructor() {
|
||||
@ -171,43 +149,48 @@ add_test(function test_profile_files() {
|
||||
|
||||
// A generic test helper. We use this with both real
|
||||
// and mock providers in these tests.
|
||||
function test_collect_constant(provider, valueTest) {
|
||||
let result = provider.collectConstantMeasurements();
|
||||
do_check_true(result instanceof MetricsCollectionResult);
|
||||
function test_collect_constant(provider) {
|
||||
return Task.spawn(function () {
|
||||
yield provider.collectConstantData();
|
||||
|
||||
result.onFinished(function onFinished() {
|
||||
do_check_eq(result.expectedMeasurements.size, 1);
|
||||
do_check_true(result.expectedMeasurements.has("org.mozilla.profile"));
|
||||
let m = result.measurements.get("org.mozilla.profile");
|
||||
do_check_true(!!m);
|
||||
valueTest(m.getValue("profileCreation"));
|
||||
let m = provider.getMeasurement("age", 1);
|
||||
do_check_neq(m, null);
|
||||
let values = yield m.getValues();
|
||||
do_check_eq(values.singular.size, 1);
|
||||
do_check_true(values.singular.has("profileCreation"));
|
||||
|
||||
run_next_test();
|
||||
throw new Task.Result(values.singular.get("profileCreation")[1]);
|
||||
});
|
||||
|
||||
result.populate(result);
|
||||
}
|
||||
|
||||
add_test(function test_collect_constant_mock() {
|
||||
add_task(function test_collect_constant_mock() {
|
||||
let storage = yield Metrics.Storage("collect_constant_mock");
|
||||
let provider = new MockProfileMetadataProvider();
|
||||
function valueTest(v) {
|
||||
do_check_eq(v, 1234);
|
||||
}
|
||||
test_collect_constant(provider, valueTest);
|
||||
yield provider.init(storage);
|
||||
|
||||
let v = yield test_collect_constant(provider);
|
||||
do_check_eq(v, 1234);
|
||||
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
add_test(function test_collect_constant_real() {
|
||||
add_task(function test_collect_constant_real() {
|
||||
let provider = new ProfileMetadataProvider();
|
||||
function valueTest(v) {
|
||||
let ms = v * MILLISECONDS_PER_DAY;
|
||||
let lower = profile_creation_lower;
|
||||
let upper = Date.now() + 1000;
|
||||
print("Day: " + v);
|
||||
print("msec: " + ms);
|
||||
print("Lower: " + lower);
|
||||
print("Upper: " + upper);
|
||||
do_check_true(lower <= ms);
|
||||
do_check_true(upper >= ms);
|
||||
}
|
||||
test_collect_constant(provider, valueTest);
|
||||
let storage = yield Metrics.Storage("collect_constant_real");
|
||||
yield provider.init(storage);
|
||||
|
||||
let v = yield test_collect_constant(provider);
|
||||
|
||||
let ms = v * MILLISECONDS_PER_DAY;
|
||||
let lower = profile_creation_lower;
|
||||
let upper = Date.now() + 1000;
|
||||
print("Day: " + v);
|
||||
print("msec: " + ms);
|
||||
print("Lower: " + lower);
|
||||
print("Upper: " + upper);
|
||||
do_check_true(lower <= ms);
|
||||
do_check_true(upper >= ms);
|
||||
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
|
@ -5,9 +5,9 @@
|
||||
|
||||
const {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");
|
||||
Cu.import("resource://gre/modules/services/metrics/dataprovider.jsm");
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
@ -19,32 +19,28 @@ add_test(function test_constructor() {
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_collect_smoketest() {
|
||||
add_task(function test_collect_smoketest() {
|
||||
let storage = yield Metrics.Storage("collect_smoketest");
|
||||
let provider = new AppInfoProvider();
|
||||
yield provider.init(storage);
|
||||
|
||||
let result = provider.collectConstantMeasurements();
|
||||
do_check_true(result instanceof MetricsCollectionResult);
|
||||
yield provider.collectConstantData();
|
||||
|
||||
result.onFinished(function onFinished() {
|
||||
do_check_eq(result.expectedMeasurements.size, 1);
|
||||
do_check_true(result.expectedMeasurements.has("appinfo"));
|
||||
do_check_eq(result.measurements.size, 1);
|
||||
do_check_true(result.measurements.has("appinfo"));
|
||||
do_check_eq(result.errors.length, 0);
|
||||
let m = provider.getMeasurement("appinfo", 1);
|
||||
let data = yield storage.getMeasurementValues(m.id);
|
||||
let serializer = m.serializer(m.SERIALIZE_JSON);
|
||||
let d = serializer.singular(data.singular);
|
||||
|
||||
let ai = result.measurements.get("appinfo");
|
||||
do_check_eq(ai.getValue("vendor"), "Mozilla");
|
||||
do_check_eq(ai.getValue("name"), "xpcshell");
|
||||
do_check_eq(ai.getValue("id"), "xpcshell@tests.mozilla.org");
|
||||
do_check_eq(ai.getValue("version"), "1");
|
||||
do_check_eq(ai.getValue("appBuildID"), "20121107");
|
||||
do_check_eq(ai.getValue("platformVersion"), "p-ver");
|
||||
do_check_eq(ai.getValue("platformBuildID"), "20121106");
|
||||
do_check_eq(ai.getValue("os"), "XPCShell");
|
||||
do_check_eq(ai.getValue("xpcomabi"), "noarch-spidermonkey");
|
||||
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");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
result.populate(result);
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
|
@ -5,9 +5,9 @@
|
||||
|
||||
const {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");
|
||||
Cu.import("resource://gre/modules/services/metrics/dataprovider.jsm");
|
||||
|
||||
|
||||
function run_test() {
|
||||
@ -20,26 +20,21 @@ add_test(function test_constructor() {
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_collect_smoketest() {
|
||||
add_task(function test_collect_smoketest() {
|
||||
let storage = yield Metrics.Storage("collect_smoketest");
|
||||
let provider = new SysInfoProvider();
|
||||
yield provider.init(storage);
|
||||
|
||||
let result = provider.collectConstantMeasurements();
|
||||
do_check_true(result instanceof MetricsCollectionResult);
|
||||
yield provider.collectConstantData();
|
||||
|
||||
result.onFinished(function onFinished() {
|
||||
do_check_eq(result.expectedMeasurements.size, 1);
|
||||
do_check_true(result.expectedMeasurements.has("sysinfo"));
|
||||
do_check_eq(result.measurements.size, 1);
|
||||
do_check_true(result.measurements.has("sysinfo"));
|
||||
do_check_eq(result.errors.length, 0);
|
||||
let m = provider.getMeasurement("sysinfo", 1);
|
||||
let data = yield storage.getMeasurementValues(m.id);
|
||||
let serializer = m.serializer(m.SERIALIZE_JSON);
|
||||
let d = serializer.singular(data.singular);
|
||||
|
||||
let si = result.measurements.get("sysinfo");
|
||||
do_check_true(si.getValue("cpuCount") > 0);
|
||||
do_check_neq(si.getValue("name"), null);
|
||||
do_check_true(d.cpuCount > 0);
|
||||
do_check_neq(d.name, null);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
result.populate(result);
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user