зеркало из https://github.com/mozilla/gecko-dev.git
481 строка
14 KiB
JavaScript
481 строка
14 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";
|
|
|
|
const DEBUG = false;
|
|
function debug(s) { dump("-*- ResourceStatsManager: " + s + "\n"); }
|
|
|
|
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
|
|
|
|
// Constant defines supported statistics.
|
|
const resourceTypeList = ["network", "power"];
|
|
|
|
function NetworkStatsData(aStatsData) {
|
|
if (DEBUG) {
|
|
debug("NetworkStatsData(): " + JSON.stringify(aStatsData));
|
|
}
|
|
|
|
this.receivedBytes = aStatsData.receivedBytes || 0;
|
|
this.sentBytes = aStatsData.sentBytes || 0;
|
|
this.timestamp = aStatsData.timestamp;
|
|
}
|
|
|
|
NetworkStatsData.prototype = {
|
|
classID: Components.ID("{dce5729a-ba92-4185-8854-e29e71b9e8a2}"),
|
|
contractID: "@mozilla.org/networkStatsData;1",
|
|
QueryInterface: XPCOMUtils.generateQI([])
|
|
};
|
|
|
|
function PowerStatsData(aStatsData) {
|
|
if (DEBUG) {
|
|
debug("PowerStatsData(): " + JSON.stringify(aStatsData));
|
|
}
|
|
|
|
this.consumedPower = aStatsData.consumedPower || 0;
|
|
this.timestamp = aStatsData.timestamp;
|
|
}
|
|
|
|
PowerStatsData.prototype = {
|
|
classID: Components.ID("{acb9af6c-8143-4e59-bc18-4bb1736a4004}"),
|
|
contractID: "@mozilla.org/powerStatsData;1",
|
|
QueryInterface: XPCOMUtils.generateQI([])
|
|
};
|
|
|
|
function ResourceStats(aWindow, aStats) {
|
|
if (DEBUG) {
|
|
debug("ResourceStats(): " + JSON.stringify(aStats));
|
|
}
|
|
|
|
this._window = aWindow;
|
|
this.type = aStats.type;
|
|
this.component = aStats.component || null;
|
|
this.serviceType = aStats.serviceType || null;
|
|
this.manifestURL = aStats.manifestURL || null;
|
|
this.start = aStats.start;
|
|
this.end = aStats.end;
|
|
this.statsData = new aWindow.Array();
|
|
|
|
// A function creates a StatsData object according to type.
|
|
let createStatsDataObject = null;
|
|
let self = this;
|
|
switch (this.type) {
|
|
case "power":
|
|
createStatsDataObject = function(aStats) {
|
|
let chromeObj = new PowerStatsData(aStats);
|
|
return self._window.PowerStatsData._create(self._window, chromeObj);
|
|
};
|
|
break;
|
|
case "network":
|
|
createStatsDataObject = function(aStats) {
|
|
let chromeObj = new NetworkStatsData(aStats);
|
|
return self._window.NetworkStatsData._create(self._window, chromeObj);
|
|
};
|
|
break;
|
|
}
|
|
|
|
let sampleRate = aStats.sampleRate;
|
|
let queryResult = aStats.statsData;
|
|
let stats = queryResult.pop(); // Pop out the last element.
|
|
let timestamp = this.start;
|
|
|
|
// Push query result to statsData, and fill empty elements so that:
|
|
// 1. the timestamp of the first element of statsData is equal to start;
|
|
// 2. the timestamp of the last element of statsData is equal to end;
|
|
// 3. the timestamp difference of every neighboring elements is SAMPLE_RATE.
|
|
for (; timestamp <= this.end; timestamp += sampleRate) {
|
|
if (!stats || stats.timestamp != timestamp) {
|
|
// If dataArray is empty or timestamp are not equal, push a dummy object
|
|
// (which stats are set to 0) to statsData.
|
|
this.statsData.push(createStatsDataObject({ timestamp: timestamp }));
|
|
} else {
|
|
// Push stats to statsData and pop a new element form queryResult.
|
|
this.statsData.push(createStatsDataObject(stats));
|
|
stats = queryResult.pop();
|
|
}
|
|
}
|
|
}
|
|
|
|
ResourceStats.prototype = {
|
|
getData: function() {
|
|
return this.statsData;
|
|
},
|
|
|
|
classID: Components.ID("{b7c970f2-3d58-4966-9633-2024feb5132b}"),
|
|
contractID: "@mozilla.org/resourceStats;1",
|
|
QueryInterface: XPCOMUtils.generateQI([])
|
|
};
|
|
|
|
function ResourceStatsAlarm(aWindow, aAlarm) {
|
|
if (DEBUG) {
|
|
debug("ResourceStatsAlarm(): " + JSON.stringify(aAlarm));
|
|
}
|
|
|
|
this.alarmId = aAlarm.alarmId;
|
|
this.type = aAlarm.type;
|
|
this.component = aAlarm.component || null;
|
|
this.serviceType = aAlarm.serviceType || null;
|
|
this.manifestURL = aAlarm.manifestURL || null;
|
|
this.threshold = aAlarm.threshold;
|
|
|
|
// Clone data object using structured clone algorithm.
|
|
this.data = null;
|
|
if (aAlarm.data) {
|
|
this.data = Cu.cloneInto(aAlarm.data, aWindow);
|
|
}
|
|
}
|
|
|
|
ResourceStatsAlarm.prototype = {
|
|
classID: Components.ID("{e2b66e7a-0ff1-4015-8690-a2a3f6a5b63a}"),
|
|
contractID: "@mozilla.org/resourceStatsAlarm;1",
|
|
QueryInterface: XPCOMUtils.generateQI([]),
|
|
};
|
|
|
|
function ResourceStatsManager() {
|
|
if (DEBUG) {
|
|
debug("constructor()");
|
|
}
|
|
}
|
|
|
|
ResourceStatsManager.prototype = {
|
|
__proto__: DOMRequestIpcHelper.prototype,
|
|
|
|
_getPromise: function(aCallback) {
|
|
let self = this;
|
|
return this.createPromise(function(aResolve, aReject) {
|
|
let resolverId = self.getPromiseResolverId({
|
|
resolve: aResolve,
|
|
reject: aReject
|
|
});
|
|
|
|
aCallback(resolverId);
|
|
});
|
|
},
|
|
|
|
// Check time range.
|
|
_checkTimeRange: function(aStart, aEnd) {
|
|
if (DEBUG) {
|
|
debug("_checkTimeRange(): " + aStart + " to " + aEnd);
|
|
}
|
|
|
|
let start = aStart ? aStart : 0;
|
|
let end = aEnd ? aEnd : Date.now();
|
|
|
|
if (start > end) {
|
|
throw Cr.NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
return { start: start, end: end };
|
|
},
|
|
|
|
getStats: function(aStatsOptions, aStart, aEnd) {
|
|
// Check time range.
|
|
let { start: start, end: end } = this._checkTimeRange(aStart, aEnd);
|
|
|
|
// Create Promise.
|
|
let self = this;
|
|
return this._getPromise(function(aResolverId) {
|
|
self.cpmm.sendAsyncMessage("ResourceStats:GetStats", {
|
|
resolverId: aResolverId,
|
|
type: self.type,
|
|
statsOptions: aStatsOptions,
|
|
manifestURL: self._manifestURL,
|
|
start: start,
|
|
end: end
|
|
});
|
|
});
|
|
},
|
|
|
|
clearStats: function(aStatsOptions, aStart, aEnd) {
|
|
// Check time range.
|
|
let {start: start, end: end} = this._checkTimeRange(aStart, aEnd);
|
|
|
|
// Create Promise.
|
|
let self = this;
|
|
return this._getPromise(function(aResolverId) {
|
|
self.cpmm.sendAsyncMessage("ResourceStats:ClearStats", {
|
|
resolverId: aResolverId,
|
|
type: self.type,
|
|
statsOptions: aStatsOptions,
|
|
manifestURL: self._manifestURL,
|
|
start: start,
|
|
end: end
|
|
});
|
|
});
|
|
},
|
|
|
|
clearAllStats: function() {
|
|
// Create Promise.
|
|
let self = this;
|
|
return this._getPromise(function(aResolverId) {
|
|
self.cpmm.sendAsyncMessage("ResourceStats:ClearAllStats", {
|
|
resolverId: aResolverId,
|
|
type: self.type,
|
|
manifestURL: self._manifestURL
|
|
});
|
|
});
|
|
},
|
|
|
|
addAlarm: function(aThreshold, aStatsOptions, aAlarmOptions) {
|
|
if (DEBUG) {
|
|
debug("aStatsOptions: " + JSON.stringify(aAlarmOptions));
|
|
debug("aAlarmOptions: " + JSON.stringify(aAlarmOptions));
|
|
}
|
|
|
|
// Parse alarm options.
|
|
let startTime = aAlarmOptions.startTime || 0;
|
|
|
|
// Clone data object using structured clone algorithm.
|
|
let data = null;
|
|
if (aAlarmOptions.data) {
|
|
data = Cu.cloneInto(aAlarmOptions.data, this._window);
|
|
}
|
|
|
|
// Create Promise.
|
|
let self = this;
|
|
return this._getPromise(function(aResolverId) {
|
|
self.cpmm.sendAsyncMessage("ResourceStats:AddAlarm", {
|
|
resolverId: aResolverId,
|
|
type: self.type,
|
|
threshold: aThreshold,
|
|
statsOptions: aStatsOptions,
|
|
manifestURL: self._manifestURL,
|
|
startTime: startTime,
|
|
data: data
|
|
});
|
|
});
|
|
},
|
|
|
|
getAlarms: function(aStatsOptions) {
|
|
// Create Promise.
|
|
let self = this;
|
|
return this._getPromise(function(aResolverId) {
|
|
self.cpmm.sendAsyncMessage("ResourceStats:GetAlarms", {
|
|
resolverId: aResolverId,
|
|
type: self.type,
|
|
statsOptions: aStatsOptions,
|
|
manifestURL: self._manifestURL
|
|
});
|
|
});
|
|
},
|
|
|
|
removeAlarm: function(aAlarmId) {
|
|
// Create Promise.
|
|
let self = this;
|
|
return this._getPromise(function(aResolverId) {
|
|
self.cpmm.sendAsyncMessage("ResourceStats:RemoveAlarm", {
|
|
resolverId: aResolverId,
|
|
type: self.type,
|
|
manifestURL: self._manifestURL,
|
|
alarmId: aAlarmId
|
|
});
|
|
});
|
|
},
|
|
|
|
removeAllAlarms: function() {
|
|
// Create Promise.
|
|
let self = this;
|
|
return this._getPromise(function(aResolverId) {
|
|
self.cpmm.sendAsyncMessage("ResourceStats:RemoveAllAlarms", {
|
|
resolverId: aResolverId,
|
|
type: self.type,
|
|
manifestURL: self._manifestURL
|
|
});
|
|
});
|
|
},
|
|
|
|
getAvailableComponents: function() {
|
|
// Create Promise.
|
|
let self = this;
|
|
return this._getPromise(function(aResolverId) {
|
|
self.cpmm.sendAsyncMessage("ResourceStats:GetComponents", {
|
|
resolverId: aResolverId,
|
|
type: self.type,
|
|
manifestURL: self._manifestURL
|
|
});
|
|
});
|
|
},
|
|
|
|
get resourceTypes() {
|
|
let types = new this._window.Array();
|
|
resourceTypeList.forEach(function(aType) {
|
|
types.push(aType);
|
|
});
|
|
|
|
return types;
|
|
},
|
|
|
|
get sampleRate() {
|
|
let msg = { manifestURL: this._manifestURL };
|
|
|
|
return this.cpmm.sendSyncMessage("ResourceStats:SampleRate", msg)[0];
|
|
},
|
|
|
|
get maxStorageAge() {
|
|
let msg = { manifestURL: this._manifestURL };
|
|
|
|
return this.cpmm.sendSyncMessage("ResourceStats:MaxStorageAge", msg)[0];
|
|
},
|
|
|
|
receiveMessage: function(aMessage) {
|
|
let data = aMessage.data;
|
|
let chromeObj = null;
|
|
let webidlObj = null;
|
|
let self = this;
|
|
|
|
if (DEBUG) {
|
|
debug("receiveMessage(): " + aMessage.name + " " + data.resolverId);
|
|
}
|
|
|
|
let resolver = this.takePromiseResolver(data.resolverId);
|
|
if (!resolver) {
|
|
return;
|
|
}
|
|
|
|
switch (aMessage.name) {
|
|
case "ResourceStats:GetStats:Resolved":
|
|
if (DEBUG) {
|
|
debug("data.value = " + JSON.stringify(data.value));
|
|
}
|
|
|
|
chromeObj = new ResourceStats(this._window, data.value);
|
|
webidlObj = this._window.ResourceStats._create(this._window, chromeObj);
|
|
resolver.resolve(webidlObj);
|
|
break;
|
|
|
|
case "ResourceStats:AddAlarm:Resolved":
|
|
if (DEBUG) {
|
|
debug("data.value = " + JSON.stringify(data.value));
|
|
}
|
|
|
|
resolver.resolve(data.value); // data.value is alarmId.
|
|
break;
|
|
|
|
case "ResourceStats:GetAlarms:Resolved":
|
|
if (DEBUG) {
|
|
debug("data.value = " + JSON.stringify(data.value));
|
|
}
|
|
|
|
let alarmArray = this._window.Array();
|
|
data.value.forEach(function(aAlarm) {
|
|
chromeObj = new ResourceStatsAlarm(self._window, aAlarm);
|
|
webidlObj = self._window.ResourceStatsAlarm._create(self._window,
|
|
chromeObj);
|
|
alarmArray.push(webidlObj);
|
|
});
|
|
resolver.resolve(alarmArray);
|
|
break;
|
|
|
|
case "ResourceStats:GetComponents:Resolved":
|
|
if (DEBUG) {
|
|
debug("data.value = " + JSON.stringify(data.value));
|
|
}
|
|
|
|
let components = this._window.Array();
|
|
data.value.forEach(function(aComponent) {
|
|
components.push(aComponent);
|
|
});
|
|
resolver.resolve(components);
|
|
break;
|
|
|
|
case "ResourceStats:ClearStats:Resolved":
|
|
case "ResourceStats:ClearAllStats:Resolved":
|
|
case "ResourceStats:RemoveAlarm:Resolved":
|
|
case "ResourceStats:RemoveAllAlarms:Resolved":
|
|
if (DEBUG) {
|
|
debug("data.value = " + JSON.stringify(data.value));
|
|
}
|
|
|
|
resolver.resolve(data.value);
|
|
break;
|
|
|
|
case "ResourceStats:GetStats:Rejected":
|
|
case "ResourceStats:ClearStats:Rejected":
|
|
case "ResourceStats:ClearAllStats:Rejected":
|
|
case "ResourceStats:AddAlarm:Rejected":
|
|
case "ResourceStats:GetAlarms:Rejected":
|
|
case "ResourceStats:RemoveAlarm:Rejected":
|
|
case "ResourceStats:RemoveAllAlarms:Rejected":
|
|
case "ResourceStats:GetComponents:Rejected":
|
|
if (DEBUG) {
|
|
debug("data.reason = " + JSON.stringify(data.reason));
|
|
}
|
|
|
|
resolver.reject(data.reason);
|
|
break;
|
|
|
|
default:
|
|
if (DEBUG) {
|
|
debug("Could not find a handler for " + aMessage.name);
|
|
}
|
|
resolver.reject();
|
|
}
|
|
},
|
|
|
|
__init: function(aType) {
|
|
if (resourceTypeList.indexOf(aType) < 0) {
|
|
if (DEBUG) {
|
|
debug("Do not support resource statistics for " + aType);
|
|
}
|
|
throw Cr.NS_ERROR_INVALID_ARG;
|
|
} else {
|
|
if (DEBUG) {
|
|
debug("Create " + aType + "Mgr");
|
|
}
|
|
this.type = aType;
|
|
}
|
|
},
|
|
|
|
init: function(aWindow) {
|
|
if (DEBUG) {
|
|
debug("init()");
|
|
}
|
|
|
|
// Get the manifestURL if this is an installed app
|
|
let principal = aWindow.document.nodePrincipal;
|
|
let appsService = Cc["@mozilla.org/AppsService;1"]
|
|
.getService(Ci.nsIAppsService);
|
|
this._manifestURL = appsService.getManifestURLByLocalId(principal.appId);
|
|
|
|
const messages = ["ResourceStats:GetStats:Resolved",
|
|
"ResourceStats:ClearStats:Resolved",
|
|
"ResourceStats:ClearAllStats:Resolved",
|
|
"ResourceStats:AddAlarm:Resolved",
|
|
"ResourceStats:GetAlarms:Resolved",
|
|
"ResourceStats:RemoveAlarm:Resolved",
|
|
"ResourceStats:RemoveAllAlarms:Resolved",
|
|
"ResourceStats:GetComponents:Resolved",
|
|
"ResourceStats:GetStats:Rejected",
|
|
"ResourceStats:ClearStats:Rejected",
|
|
"ResourceStats:ClearAllStats:Rejected",
|
|
"ResourceStats:AddAlarm:Rejected",
|
|
"ResourceStats:GetAlarms:Rejected",
|
|
"ResourceStats:RemoveAlarm:Rejected",
|
|
"ResourceStats:RemoveAllAlarms:Rejected",
|
|
"ResourceStats:GetComponents:Rejected"];
|
|
this.initDOMRequestHelper(aWindow, messages);
|
|
|
|
this.cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
|
|
.getService(Ci.nsISyncMessageSender);
|
|
},
|
|
|
|
classID: Components.ID("{101ed1f8-31b3-491c-95ea-04091e6e8027}"),
|
|
contractID: "@mozilla.org/resourceStatsManager;1",
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
|
|
Ci.nsISupportsWeakReference,
|
|
Ci.nsIObserver])
|
|
};
|
|
|
|
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkStatsData,
|
|
PowerStatsData,
|
|
ResourceStats,
|
|
ResourceStatsAlarm,
|
|
ResourceStatsManager]);
|
|
|