Bug 847662 - Part 3: Move provider management code into provider manager; r=rnewman

--HG--
extra : rebase_source : 7096a6a63143e7e6790ccd498f4b453708baddf5
This commit is contained in:
Gregory Szorc 2013-03-11 14:12:24 -07:00
parent 979d42aa6c
commit 4ccc7f9acf
6 changed files with 221 additions and 207 deletions

View File

@ -90,8 +90,6 @@ function AbstractHealthReporter(branch, policy, sessionRecorder) {
this._errors = [];
this._pullOnlyProviders = {};
this._pullOnlyProvidersRegistered = false;
this._lastDailyDate = null;
// Yes, this will probably run concurrently with remaining constructor work.
@ -177,12 +175,13 @@ AbstractHealthReporter.prototype = Object.freeze({
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.registerProvidersFromCategoryManager(category);
yield this._providerManager.registerProvidersFromCategoryManager(category);
}
}
},
@ -400,173 +399,9 @@ AbstractHealthReporter.prototype = Object.freeze({
return this._providerManager.getProvider(name);
},
/**
* Register a `Metrics.Provider` with this instance.
*
* This needs to be called or no data will be collected. See also
* `registerProvidersFromCategoryManager`.
*
* @param provider
* (Metrics.Provider) The provider to register for collection.
*/
registerProvider: function (provider) {
return this._providerManager.registerProvider(provider);
},
/**
* Registers a provider from its constructor function.
*
* If the provider is pull-only, it will be stashed away and
* initialized later. Null will be returned.
*
* If it is not pull-only, it will be initialized immediately and a
* promise will be returned. The promise will be resolved when the
* provider has finished initializing.
*/
registerProviderFromType: function (type) {
let proto = type.prototype;
if (proto.pullOnly) {
this._log.info("Provider is pull-only. Deferring initialization: " +
proto.name);
this._pullOnlyProviders[proto.name] = type;
return null;
}
let provider = this.initProviderFromType(type);
return this.registerProvider(provider);
},
/**
* Registers providers from a category manager category.
*
* This examines the specified category entries and registers found
* providers.
*
* Category entries are essentially JS modules and the name of the symbol
* 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.
*
* Example entry:
*
* FooProvider resource://gre/modules/foo.jsm
*
* One can register entries in the application's .manifest file. e.g.
*
* category healthreport-js-provider FooProvider resource://gre/modules/foo.jsm
*
* Then to load them:
*
* let reporter = getHealthReporter("healthreport.");
* reporter.registerProvidersFromCategoryManager("healthreport-js-provider");
*
* @param category
* (string) Name of category to query and load from.
*/
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()
.QueryInterface(Ci.nsISupportsCString)
.toString();
let uri = cm.getCategoryEntry(category, entry);
this._log.info("Attempting to load provider from category manager: " +
entry + " from " + uri);
try {
let ns = {};
Cu.import(uri, ns);
let promise = this.registerProviderFromType(ns[entry]);
if (promise) {
promises.push(promise);
}
} catch (ex) {
this._recordError("Error registering provider from category manager : " +
entry + ": ", ex);
continue;
}
}
return Task.spawn(function wait() {
for (let promise of promises) {
yield promise;
}
});
},
initProviderFromType: function (providerType) {
let provider = new providerType();
_initProvider: function (provider) {
provider.initPreferences(this._branch + "provider.");
provider.healthReporter = this;
return provider;
},
/**
* Ensure that pull-only providers are registered.
*/
ensurePullOnlyProvidersRegistered: function () {
if (this._pullOnlyProvidersRegistered) {
return Promise.resolve();
}
let onFinished = function () {
this._pullOnlyProvidersRegistered = true;
return Promise.resolve();
}.bind(this);
return Task.spawn(function registerPullProviders() {
for each (let providerType in this._pullOnlyProviders) {
try {
let provider = this.initProviderFromType(providerType);
yield this.registerProvider(provider);
} catch (ex) {
this._recordError("Error registering pull-only provider", ex);
}
}
}.bind(this)).then(onFinished, onFinished);
},
ensurePullOnlyProvidersUnregistered: function () {
if (!this._pullOnlyProvidersRegistered) {
return Promise.resolve();
}
let onFinished = function () {
this._pullOnlyProvidersRegistered = false;
return Promise.resolve();
}.bind(this);
return Task.spawn(function unregisterPullProviders() {
for (let provider of this._providerManager.providers) {
if (!provider.pullOnly) {
continue;
}
this._log.info("Shutting down pull-only provider: " +
provider.name);
try {
yield provider.shutdown();
} catch (ex) {
this._recordError("Error when shutting down provider: " +
provider.name, ex);
} finally {
this._providerManager.unregisterProvider(provider.name);
}
}
}.bind(this)).then(onFinished, onFinished);
},
/**
@ -682,7 +517,7 @@ AbstractHealthReporter.prototype = Object.freeze({
*/
collectAndObtainJSONPayload: function (asObject=false) {
return Task.spawn(function collectAndObtain() {
yield this.ensurePullOnlyProvidersRegistered();
yield this._providerManager.ensurePullOnlyProvidersRegistered();
let payload;
let error;
@ -695,7 +530,7 @@ AbstractHealthReporter.prototype = Object.freeze({
this._collectException("Error collecting and/or retrieving JSON payload",
ex);
} finally {
yield this.ensurePullOnlyProvidersUnregistered();
yield this._providerManager.ensurePullOnlyProvidersUnregistered();
if (error) {
throw error;
@ -1098,7 +933,7 @@ HealthReporter.prototype = Object.freeze({
*/
requestDataUpload: function (request) {
return Task.spawn(function doUpload() {
yield this.ensurePullOnlyProvidersRegistered();
yield this._providerManager.ensurePullOnlyProvidersRegistered();
try {
yield this.collectMeasurements();
try {
@ -1107,7 +942,7 @@ HealthReporter.prototype = Object.freeze({
this._onSubmitDataRequestFailure(ex);
}
} finally {
yield this.ensurePullOnlyProvidersUnregistered();
yield this._providerManager.ensurePullOnlyProvidersUnregistered();
}
}.bind(this));
},

View File

@ -173,25 +173,6 @@ add_task(function test_shutdown_when_provider_manager_errors() {
do_check_eq(reporter.storageCloseCount, 1);
});
add_task(function test_register_providers_from_category_manager() {
const category = "healthreporter-js-modules";
let cm = Cc["@mozilla.org/categorymanager;1"]
.getService(Ci.nsICategoryManager);
cm.addCategoryEntry(category, "DummyProvider",
"resource://testing-common/services/metrics/mocks.jsm",
false, true);
let reporter = yield getReporter("category_manager");
try {
do_check_eq(reporter._providerManager._providers.size, 0);
yield reporter.registerProvidersFromCategoryManager(category);
do_check_eq(reporter._providerManager._providers.size, 1);
} finally {
reporter._shutdown();
}
});
// Pull-only providers are only initialized at collect time.
add_task(function test_pull_only_providers() {
const category = "healthreporter-constant-only";
@ -208,16 +189,16 @@ add_task(function test_pull_only_providers() {
let reporter = yield getReporter("constant_only_providers");
try {
do_check_eq(reporter._providerManager._providers.size, 0);
yield reporter.registerProvidersFromCategoryManager(category);
yield reporter._providerManager.registerProvidersFromCategoryManager(category);
do_check_eq(reporter._providerManager._providers.size, 1);
do_check_true(reporter._storage.hasProvider("DummyProvider"));
do_check_false(reporter._storage.hasProvider("DummyConstantProvider"));
do_check_neq(reporter.getProvider("DummyProvider"), null);
do_check_null(reporter.getProvider("DummyConstantProvider"));
yield reporter.ensurePullOnlyProvidersRegistered();
yield reporter._providerManager.ensurePullOnlyProvidersRegistered();
yield reporter.collectMeasurements();
yield reporter.ensurePullOnlyProvidersUnregistered();
yield reporter._providerManager.ensurePullOnlyProvidersUnregistered();
do_check_eq(reporter._providerManager._providers.size, 1);
do_check_true(reporter._storage.hasProvider("DummyConstantProvider"));
@ -236,7 +217,7 @@ add_task(function test_collect_daily() {
try {
let now = new Date();
let provider = new DummyProvider();
yield reporter.registerProvider(provider);
yield reporter._providerManager.registerProvider(provider);
yield reporter.collectMeasurements();
do_check_eq(provider.collectConstantCount, 1);
@ -295,7 +276,7 @@ add_task(function test_json_payload_dummy_provider() {
let reporter = yield getReporter("json_payload_dummy_provider");
try {
yield reporter.registerProvider(new DummyProvider());
yield reporter._providerManager.registerProvider(new DummyProvider());
yield reporter.collectMeasurements();
let payload = yield reporter.getJSONPayload();
print(payload);
@ -314,7 +295,7 @@ add_task(function test_collect_and_obtain_json_payload() {
let reporter = yield getReporter("collect_and_obtain_json_payload");
try {
yield reporter.registerProvider(new DummyProvider());
yield reporter._providerManager.registerProvider(new DummyProvider());
let payload = yield reporter.collectAndObtainJSONPayload();
do_check_eq(typeof payload, "string");
@ -343,7 +324,7 @@ add_task(function test_constant_only_providers_in_json_payload() {
let reporter = yield getReporter("constant_only_providers_in_json_payload");
try {
yield reporter.registerProvidersFromCategoryManager(category);
yield reporter._providerManager.registerProvidersFromCategoryManager(category);
let payload = yield reporter.collectAndObtainJSONPayload();
let o = JSON.parse(payload);
@ -388,7 +369,7 @@ add_task(function test_json_payload_multiple_days() {
try {
let provider = new DummyProvider();
yield reporter.registerProvider(provider);
yield reporter._providerManager.registerProvider(provider);
let now = new Date();
let m = provider.getMeasurement("DummyMeasurement", 1);
@ -418,7 +399,7 @@ add_task(function test_idle_daily() {
let reporter = yield getReporter("idle_daily");
try {
let provider = new DummyProvider();
yield reporter.registerProvider(provider);
yield reporter._providerManager.registerProvider(provider);
let now = new Date();
let m = provider.getMeasurement("DummyMeasurement", 1);
@ -459,8 +440,8 @@ add_task(function test_data_submission_transport_failure() {
add_task(function test_data_submission_success() {
let [reporter, server] = yield getReporterAndServer("data_submission_success");
try {
yield reporter.registerProviderFromType(DummyProvider);
yield reporter.registerProviderFromType(DummyConstantProvider);
yield reporter._providerManager.registerProviderFromType(DummyProvider);
yield reporter._providerManager.registerProviderFromType(DummyConstantProvider);
do_check_eq(reporter.lastPingDate.getTime(), 0);
do_check_false(reporter.haveRemoteData());
@ -488,7 +469,7 @@ add_task(function test_data_submission_success() {
add_task(function test_recurring_daily_pings() {
let [reporter, server] = yield getReporterAndServer("recurring_daily_pings");
try {
reporter.registerProvider(new DummyProvider());
reporter._providerManager.registerProvider(new DummyProvider());
let policy = reporter._policy;

View File

@ -8,7 +8,7 @@
this.EXPORTED_SYMBOLS = ["Metrics"];
const {utils: Cu} = Components;
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;

View File

@ -40,7 +40,9 @@ DummyMeasurement.prototype = {
this.DummyProvider = function DummyProvider(name="DummyProvider") {
this.name = name;
Object.defineProperty(this, "name", {
value: name,
});
this.measurementTypes = [DummyMeasurement];
@ -59,6 +61,8 @@ this.DummyProvider = function DummyProvider(name="DummyProvider") {
DummyProvider.prototype = {
__proto__: Metrics.Provider.prototype,
name: "DummyProvider",
collectConstantData: function () {
this.collectConstantCount++;
@ -100,6 +104,8 @@ this.DummyConstantProvider = function () {
DummyConstantProvider.prototype = {
__proto__: DummyProvider.prototype,
name: "DummyConstantProvider",
pullOnly: true,
};

View File

@ -7,7 +7,7 @@
#ifndef MERGED_COMPARTMENT
this.EXPORTED_SYMBOLS = ["ProviderManager"];
const {utils: Cu} = Components;
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/services/metrics/dataprovider.jsm");
#endif
@ -32,6 +32,13 @@ this.ProviderManager = function (storage) {
this._providerInitQueue = [];
this._providerInitializing = false;
this._pullOnlyProviders = {};
this._pullOnlyProvidersRegistered = false;
// Callback to allow customization of providers after they are constructed
// but before they call out into their initialization code.
this.onProviderInit = null;
}
this.ProviderManager.prototype = Object.freeze({
@ -57,6 +64,72 @@ this.ProviderManager.prototype = Object.freeze({
return provider.provider;
},
/**
* Registers providers from a category manager category.
*
* This examines the specified category entries and registers found
* providers.
*
* Category entries are essentially JS modules and the name of the symbol
* 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.
*
* Example entry:
*
* FooProvider resource://gre/modules/foo.jsm
*
* One can register entries in the application's .manifest file. e.g.
*
* category healthreport-js-provider FooProvider resource://gre/modules/foo.jsm
*
* Then to load them:
*
* let reporter = getHealthReporter("healthreport.");
* reporter.registerProvidersFromCategoryManager("healthreport-js-provider");
*
* @param category
* (string) Name of category to query and load from.
*/
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()
.QueryInterface(Ci.nsISupportsCString)
.toString();
let uri = cm.getCategoryEntry(category, entry);
this._log.info("Attempting to load provider from category manager: " +
entry + " from " + uri);
try {
let ns = {};
Cu.import(uri, ns);
let promise = this.registerProviderFromType(ns[entry]);
if (promise) {
promises.push(promise);
}
} catch (ex) {
this._recordError("Error registering provider from category manager : " +
entry + ": ", ex);
continue;
}
}
return Task.spawn(function wait() {
for (let promise of promises) {
yield promise;
}
});
},
/**
* Registers a `MetricsProvider` with this manager.
*
@ -93,6 +166,47 @@ this.ProviderManager.prototype = Object.freeze({
return deferred.promise;
},
/**
* Registers a provider from its constructor function.
*
* If the provider is pull-only, it will be stashed away and
* initialized later. Null will be returned.
*
* If it is not pull-only, it will be initialized immediately and a
* promise will be returned. The promise will be resolved when the
* provider has finished initializing.
*/
registerProviderFromType: function (type) {
let proto = type.prototype;
if (proto.pullOnly) {
this._log.info("Provider is pull-only. Deferring initialization: " +
proto.name);
this._pullOnlyProviders[proto.name] = type;
return null;
}
let provider = this._initProviderFromType(type);
return this.registerProvider(provider);
},
/**
* Initializes a provider from its type.
*
* This is how a constructor function should be turned into a provider
* instance.
*
* A side-effect is the provider is registered with the manager.
*/
_initProviderFromType: function (type) {
let provider = new type();
if (this.onProviderInit) {
this.onProviderInit(provider);
}
return provider;
},
/**
* Remove a named provider from the manager.
*
@ -103,6 +217,64 @@ this.ProviderManager.prototype = Object.freeze({
this._providers.delete(name);
},
/**
* Ensure that pull-only providers are registered.
*/
ensurePullOnlyProvidersRegistered: function () {
if (this._pullOnlyProvidersRegistered) {
return Promise.resolve();
}
let onFinished = function () {
this._pullOnlyProvidersRegistered = true;
return Promise.resolve();
}.bind(this);
return Task.spawn(function registerPullProviders() {
for each (let providerType in this._pullOnlyProviders) {
try {
let provider = this._initProviderFromType(providerType);
yield this.registerProvider(provider);
} catch (ex) {
this._recordError("Error registering pull-only provider", ex);
}
}
}.bind(this)).then(onFinished, onFinished);
},
ensurePullOnlyProvidersUnregistered: function () {
if (!this._pullOnlyProvidersRegistered) {
return Promise.resolve();
}
let onFinished = function () {
this._pullOnlyProvidersRegistered = false;
return Promise.resolve();
}.bind(this);
return Task.spawn(function unregisterPullProviders() {
for (let provider of this.providers) {
if (!provider.pullOnly) {
continue;
}
this._log.info("Shutting down pull-only provider: " +
provider.name);
try {
yield provider.shutdown();
} catch (ex) {
this._recordError("Error when shutting down provider: " +
provider.name, ex);
} finally {
this.unregisterProvider(provider.name);
}
}
}.bind(this)).then(onFinished, onFinished);
},
_popAndInitProvider: function () {
if (!this._providerInitQueue.length || this._providerInitializing) {
return;

View File

@ -3,7 +3,7 @@
"use strict";
const {utils: Cu} = Components;
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Metrics.jsm");
Cu.import("resource://testing-common/services/metrics/mocks.jsm");
@ -49,6 +49,26 @@ add_task(function test_register_provider() {
yield storage.close();
});
add_task(function test_register_providers_from_category_manager() {
const category = "metrics-providers-js-modules";
let cm = Cc["@mozilla.org/categorymanager;1"]
.getService(Ci.nsICategoryManager);
cm.addCategoryEntry(category, "DummyProvider",
"resource://testing-common/services/metrics/mocks.jsm",
false, true);
let storage = yield Metrics.Storage("register_providers_from_category_manager");
let manager = new Metrics.ProviderManager(storage);
try {
do_check_eq(manager._providers.size, 0);
yield manager.registerProvidersFromCategoryManager(category);
do_check_eq(manager._providers.size, 1);
} finally {
yield storage.close();
}
});
add_task(function test_collect_constant_data() {
let storage = yield Metrics.Storage("collect_constant_data");
let errorCount = 0;