mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
250 lines
7.0 KiB
JavaScript
250 lines
7.0 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
"use strict";
|
|
|
|
#ifndef MERGED_COMPARTMENT
|
|
this.EXPORTED_SYMBOLS = ["Collector"];
|
|
|
|
const {utils: Cu} = Components;
|
|
|
|
Cu.import("resource://gre/modules/services/metrics/dataprovider.jsm");
|
|
#endif
|
|
|
|
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
|
|
Cu.import("resource://gre/modules/Task.jsm");
|
|
Cu.import("resource://services-common/log4moz.js");
|
|
Cu.import("resource://services-common/utils.js");
|
|
|
|
|
|
/**
|
|
* Handles and coordinates the collection of metrics data from providers.
|
|
*
|
|
* This provides an interface for managing `Metrics.Provider` instances. It
|
|
* provides APIs for bulk collection of data.
|
|
*/
|
|
this.Collector = function (storage) {
|
|
this._log = Log4Moz.repository.getLogger("Services.Metrics.Collector");
|
|
|
|
this._providers = new Map();
|
|
this._storage = storage;
|
|
|
|
this._providerInitQueue = [];
|
|
this._providerInitializing = false;
|
|
this.providerErrors = new Map();
|
|
}
|
|
|
|
Collector.prototype = Object.freeze({
|
|
get providers() {
|
|
let providers = [];
|
|
for (let [name, entry] of this._providers) {
|
|
providers.push(entry.provider);
|
|
}
|
|
|
|
return providers;
|
|
},
|
|
|
|
/**
|
|
* Obtain a provider from its name.
|
|
*/
|
|
getProvider: function (name) {
|
|
let provider = this._providers.get(name);
|
|
|
|
if (!provider) {
|
|
return null;
|
|
}
|
|
|
|
return provider.provider;
|
|
},
|
|
|
|
/**
|
|
* Registers a `MetricsProvider` with this collector.
|
|
*
|
|
* Once a `MetricsProvider` is registered, data will be collected from it
|
|
* whenever we collect data.
|
|
*
|
|
* The returned value is a promise that will be resolved once registration
|
|
* is complete.
|
|
*
|
|
* Providers are initialized as part of registration by calling
|
|
* provider.init().
|
|
*
|
|
* @param provider
|
|
* (Metrics.Provider) The provider instance to register.
|
|
*
|
|
* @return Promise<null>
|
|
*/
|
|
registerProvider: function (provider) {
|
|
if (!(provider instanceof Provider)) {
|
|
throw new Error("Argument must be a Provider instance.");
|
|
}
|
|
|
|
if (this._providers.has(provider.name)) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
let deferred = Promise.defer();
|
|
this._providerInitQueue.push([provider, deferred]);
|
|
|
|
if (this._providerInitQueue.length == 1) {
|
|
this._popAndInitProvider();
|
|
}
|
|
|
|
return deferred.promise;
|
|
},
|
|
|
|
/**
|
|
* Remove a named provider from the collector.
|
|
*
|
|
* It is the caller's responsibility to shut down the provider
|
|
* instance.
|
|
*/
|
|
unregisterProvider: function (name) {
|
|
this._providers.delete(name);
|
|
},
|
|
|
|
_popAndInitProvider: function () {
|
|
if (!this._providerInitQueue.length || this._providerInitializing) {
|
|
return;
|
|
}
|
|
|
|
let [provider, deferred] = this._providerInitQueue.shift();
|
|
this._providerInitializing = true;
|
|
|
|
this._log.info("Initializing provider with storage: " + provider.name);
|
|
|
|
Task.spawn(function initProvider() {
|
|
try {
|
|
let result = yield provider.init(this._storage);
|
|
this._log.info("Provider successfully initialized: " + provider.name);
|
|
|
|
this._providers.set(provider.name, {
|
|
provider: provider,
|
|
constantsCollected: false,
|
|
});
|
|
|
|
this.providerErrors.set(provider.name, []);
|
|
|
|
deferred.resolve(result);
|
|
} catch (ex) {
|
|
this._log.warn("Provider failed to initialize: " + provider.name +
|
|
": " + CommonUtils.exceptionStr(ex));
|
|
deferred.reject(ex);
|
|
} finally {
|
|
this._providerInitializing = false;
|
|
this._popAndInitProvider();
|
|
}
|
|
}.bind(this));
|
|
},
|
|
|
|
/**
|
|
* Collects all constant measurements from all providers.
|
|
*
|
|
* Returns a Promise that will be fulfilled once all data providers have
|
|
* provided their constant data. A side-effect of this promise fulfillment
|
|
* is that the collector is populated with the obtained collection results.
|
|
* The resolved value to the promise is this `Collector` instance.
|
|
*/
|
|
collectConstantData: function () {
|
|
let entries = [];
|
|
|
|
for (let [name, entry] of this._providers) {
|
|
if (entry.constantsCollected) {
|
|
this._log.trace("Provider has already provided constant data: " +
|
|
name);
|
|
continue;
|
|
}
|
|
|
|
entries.push(entry);
|
|
}
|
|
|
|
let onCollect = function (entry, result) {
|
|
entry.constantsCollected = true;
|
|
};
|
|
|
|
return this._callCollectOnProviders(entries, "collectConstantData",
|
|
onCollect);
|
|
},
|
|
|
|
/**
|
|
* Calls collectDailyData on all providers.
|
|
*/
|
|
collectDailyData: function () {
|
|
return this._callCollectOnProviders(this._providers.values(),
|
|
"collectDailyData");
|
|
},
|
|
|
|
_callCollectOnProviders: function (entries, fnProperty, onCollect=null) {
|
|
let promises = [];
|
|
|
|
for (let entry of entries) {
|
|
let provider = entry.provider;
|
|
let collectPromise;
|
|
try {
|
|
collectPromise = provider[fnProperty].call(provider);
|
|
} catch (ex) {
|
|
this._log.warn("Exception when calling " + provider.name + "." +
|
|
fnProperty + ": " + CommonUtils.exceptionStr(ex));
|
|
this.providerErrors.get(provider.name).push(ex);
|
|
continue;
|
|
}
|
|
|
|
if (!collectPromise) {
|
|
this._log.warn("Provider does not return a promise from " +
|
|
fnProperty + "(): " + provider.name);
|
|
continue;
|
|
}
|
|
|
|
let promise = collectPromise.then(function onCollected(result) {
|
|
if (onCollect) {
|
|
try {
|
|
onCollect(entry, result);
|
|
} catch (ex) {
|
|
this._log.warn("onCollect callback threw: " +
|
|
CommonUtils.exceptionStr(ex));
|
|
}
|
|
}
|
|
|
|
return Promise.resolve(result);
|
|
});
|
|
|
|
promises.push([provider.name, promise]);
|
|
}
|
|
|
|
return this._handleCollectionPromises(promises);
|
|
},
|
|
|
|
/**
|
|
* Handles promises returned by the collect* functions.
|
|
*
|
|
* This consumes the data resolved by the promises and returns a new promise
|
|
* that will be resolved once all promises have been resolved.
|
|
*
|
|
* The promise is resolved even if one of the underlying collection
|
|
* promises is rejected.
|
|
*/
|
|
_handleCollectionPromises: function (promises) {
|
|
return Task.spawn(function waitForPromises() {
|
|
for (let [name, promise] of promises) {
|
|
try {
|
|
yield promise;
|
|
this._log.debug("Provider collected successfully: " + name);
|
|
} catch (ex) {
|
|
this._log.warn("Provider failed to collect: " + name + ": " +
|
|
CommonUtils.exceptionStr(ex));
|
|
try {
|
|
this.providerErrors.get(name).push(ex);
|
|
} catch (ex2) {
|
|
this._log.error("Error updating provider errors. This should " +
|
|
"never happen: " + CommonUtils.exceptionStr(ex2));
|
|
}
|
|
}
|
|
}
|
|
|
|
throw new Task.Result(this);
|
|
}.bind(this));
|
|
},
|
|
});
|
|
|