CLOSED TREE
This commit is contained in:
Ryan VanderMeulen 2015-07-15 21:29:43 -04:00
Родитель c81b996b3b 0c009c0080
Коммит 0cb57b406c
7 изменённых файлов: 49 добавлений и 490 удалений

Просмотреть файл

@ -86,10 +86,7 @@ let AutoUpdate = {
};
let State = {
_monitor: PerformanceStats.getMonitor([
"jank", "cpow", "ticks",
"jank-content", "cpow-content", "ticks-content",
]),
_monitor: PerformanceStats.getMonitor(["jank", "cpow", "ticks"]),
/**
* @type{PerformanceData}
@ -247,7 +244,7 @@ let updateLiveData = Task.async(function*() {
let headerElt = document.createElement("tr");
dataElt.appendChild(headerElt);
headerElt.classList.add("header");
for (let column of [...MEASURES, {key: "name", name: ""}, {key: "process", name: ""}]) {
for (let column of [...MEASURES, {key: "name", name: ""}]) {
let el = document.createElement("td");
el.classList.add(column.key);
el.textContent = column.label;
@ -283,37 +280,20 @@ let updateLiveData = Task.async(function*() {
el.textContent = value;
}
{
// Name
let el = document.createElement("td");
let id = item.id;
el.classList.add("contents");
el.classList.add("name");
row.appendChild(el);
if (item.addonId) {
let _el = el;
let _item = item;
AddonManager.getAddonByID(item.addonId, a => {
_el.textContent = a ? a.name : _item.name
});
} else {
el.textContent = item.title || item.name;
}
}
{
// Process information.
let el = document.createElement("td");
el.classList.add("contents");
el.classList.add("process");
row.appendChild(el);
if (item.isChildProcess) {
el.textContent = "(child)";
row.classList.add("child");
} else {
el.textContent = "(parent)";
row.classList.add("parent");
}
// Name
let el = document.createElement("td");
let id = item.id;
el.classList.add("contents");
el.classList.add("name");
row.appendChild(el);
if (item.addonId) {
let _el = el;
let _item = item;
AddonManager.getAddonByID(item.addonId, a => {
_el.textContent = a ? a.name : _item.name
});
} else {
el.textContent = item.title || item.name;
}
}
} catch (ex) {

Просмотреть файл

@ -6,3 +6,4 @@ support-files =
browser_compartments_script.js
[browser_aboutperformance.js]
skip-if = e10s # Feature not implemented yet – bug 1140310

Просмотреть файл

@ -1,144 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* A proxy implementing communication between the PerformanceStats.jsm modules
* of the parent and children processes.
*
* This script is loaded in all processes but is essentially a NOOP in the
* parent process.
*/
"use strict";
const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
XPCOMUtils.defineLazyModuleGetter(this, "PerformanceStats",
"resource://gre/modules/PerformanceStats.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
/**
* A global performance monitor used by this process.
*
* For the sake of simplicity, rather than attempting to map each PerformanceMonitor
* of the parent to a PerformanceMonitor in each child process, we maintain a single
* PerformanceMonitor in each child process. Probes activation/deactivation for this
* monitor is controlled by the activation/deactivation of probes marked as "-content"
* in the parent.
*
* In the parent, this is always an empty monitor.
*/
let gMonitor = PerformanceStats.getMonitor([]);
/**
* `true` if this is a content process, `false` otherwise.
*/
let isContent = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
/**
* Handle message `performance-stats-service-acquire`: ensure that the global
* monitor has a given probe. This message must be sent by the parent process
* whenever a probe is activated application-wide.
*
* Note that we may miss acquire messages if they are sent before this process is
* launched. For this reason, `performance-stats-service-collect` automatically
* re-acquires probes if it realizes that they are missing.
*
* This operation is a NOOP on the parent process.
*
* @param {{payload: Array<string>}} msg.data The message received. `payload`
* must be an array of probe names.
*/
Services.cpmm.addMessageListener("performance-stats-service-acquire", function(msg) {
if (!isContent) {
return;
}
let name = msg.data.payload;
ensureAcquired(name);
});
/**
* Handle message `performance-stats-service-release`: release a given probe
* from the global monitor. This message must be sent by the parent process
* whenever a probe is deactivated application-wide.
*
* Note that we may miss release messages if they are sent before this process is
* launched. This is ok, as probes are inactive by default: if we miss the release
* message, we have already missed the acquire message, and the effect of both
* messages together is to reset to the default state.
*
* This operation is a NOOP on the parent process.
*
* @param {{payload: Array<string>}} msg.data The message received. `payload`
* must be an array of probe names.
*/
Services.cpmm.addMessageListener("performance-stats-service-release", function(msg) {
if (!isContent) {
return;
}
// Keep only the probes that do not appear in the payload
let probes = gMonitor.getProbeNames
.filter(x => msg.data.payload.indexOf(x) == -1);
gMonitor = PerformanceStats.getMonitor(probes);
});
/**
* Ensure that this process has all the probes it needs.
*
* @param {Array<string>} probeNames The name of all probes needed by the
* process.
*/
function ensureAcquired(probeNames) {
let alreadyAcquired = gMonitor.probeNames;
// Algorithm is O(n^2) because we expect that n ≤ 3.
let shouldAcquire = [];
for (let probeName of probeNames) {
if (alreadyAcquired.indexOf(probeName) == -1) {
shouldAcquire.push(probeName)
}
}
if (shouldAcquire.length == 0) {
return;
}
gMonitor = PerformanceStats.getMonitor([...alreadyAcquired, ...shouldAcquire]);
}
/**
* Handle message `performance-stats-service-collected`: collect the data
* obtained by the monitor. This message must be sent by the parent process
* whenever we grab a performance snapshot of the application.
*
* This operation provides `null` on the parent process.
*
* @param {{data: {payload: Array<string>}}} msg The message received. `payload`
* must be an array of probe names.
*/
Services.cpmm.addMessageListener("performance-stats-service-collect", Task.async(function*(msg) {
let {id, payload: {probeNames}} = msg.data;
if (!isContent) {
// This message was sent by the parent process to itself.
// As per protocol, respond `null`.
Services.cpmm.sendAsyncMessage("performance-stats-service-collect", {
id,
data: null
});
return;
}
// We may have missed acquire messages if the process was loaded too late.
// Catch up now.
ensureAcquired(probeNames);
// Collect and return data.
let data = yield gMonitor.promiseSnapshot({probeNames});
Services.cpmm.sendAsyncMessage("performance-stats-service-collect", {
id,
data
});
}));

Просмотреть файл

@ -23,14 +23,6 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/Task.jsm", this);
XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
"resource://gre/modules/PromiseUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
"resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout",
"resource://gre/modules/Timer.jsm");
// The nsIPerformanceStatsService provides lower-level
// access to SpiderMonkey and the probes.
@ -45,17 +37,14 @@ XPCOMUtils.defineLazyServiceGetter(this, "finalizer",
Ci.nsIFinalizationWitnessService
);
// The topic used to notify that a PerformanceMonitor has been garbage-collected
// and that we can release/close the probes it holds.
const FINALIZATION_TOPIC = "performancemonitor-finalize";
const PROPERTIES_META_IMMUTABLE = ["addonId", "isSystem", "isChildProcess", "groupId"];
const PROPERTIES_META = [...PROPERTIES_META_IMMUTABLE, "windowId", "title", "name"];
const PROPERTIES_META_IMMUTABLE = ["name", "addonId", "isSystem", "groupId"];
const PROPERTIES_META = [...PROPERTIES_META_IMMUTABLE, "windowId", "title"];
// How long we wait for children processes to respond.
const MAX_WAIT_FOR_CHILD_PROCESS_MS = 5000;
let isContent = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
/**
* Access to a low-level performance probe.
*
@ -136,14 +125,14 @@ Probe.prototype = {
* @return {object} An object representing `a - b`. If `b` is
* `null`, this is `a`.
*/
subtract: function(a, b) {
substract: function(a, b) {
if (a == null) {
throw new TypeError();
}
if (b == null) {
return a;
}
return this._impl.subtract(a, b);
return this._impl.substract(a, b);
},
/**
@ -215,7 +204,7 @@ let Probes = {
}
return true;
},
subtract: function(a, b) {
substract: function(a, b) {
// invariant: `a` and `b` are both non-null
let result = {
totalUserTime: a.totalUserTime - b.totalUserTime,
@ -254,7 +243,7 @@ let Probes = {
isEqual: function(a, b) {
return a.totalCPOWTime == b.totalCPOWTime;
},
subtract: function(a, b) {
substract: function(a, b) {
return {
totalCPOWTime: a.totalCPOWTime - b.totalCPOWTime
};
@ -283,78 +272,12 @@ let Probes = {
isEqual: function(a, b) {
return a.ticks == b.ticks;
},
subtract: function(a, b) {
substract: function(a, b) {
return {
ticks: a.ticks - b.ticks
};
}
}),
"jank-content": new Probe("jank-content", {
_isActive: false,
set isActive(x) {
this._isActive = x;
if (x) {
Process.broadcast("acquire", ["jank"]);
} else {
Process.broadcast("release", ["jank"]);
}
},
get isActive() {
return this._isActive;
},
extract: function(xpcom) {
return {};
},
isEqual: function(a, b) {
return true;
},
subtract: function(a, b) {
return null;
}
}),
"cpow-content": new Probe("cpow-content", {
_isActive: false,
set isActive(x) {
this._isActive = x;
if (x) {
Process.broadcast("acquire", ["cpow"]);
} else {
Process.broadcast("release", ["cpow"]);
}
},
get isActive() {
return this._isActive;
},
extract: function(xpcom) {
return {};
},
isEqual: function(a, b) {
return true;
},
subtract: function(a, b) {
return null;
}
}),
"ticks-content": new Probe("ticks-content", {
set isActive(x) {
// Ignore: This probe is always active.
},
get isActive() {
return true;
},
extract: function(xpcom) {
return {};
},
isEqual: function(a, b) {
return true;
},
subtract: function(a, b) {
return null;
}
}),
};
@ -382,13 +305,6 @@ function PerformanceMonitor(probes) {
PerformanceMonitor._monitors.set(this._id, probes);
}
PerformanceMonitor.prototype = {
/**
* The names of probes activated in this monitor.
*/
get probeNames() {
return [for (probe of this._probes) probe.name];
},
/**
* Return asynchronously a snapshot with the data
* for each probe monitored by this PerformanceMonitor.
@ -401,7 +317,7 @@ PerformanceMonitor.prototype = {
* will return a `Snapshot` in which all values are 0. For most uses,
* the appropriate scenario is to perform a first call to `promiseSnapshot()`
* to obtain a baseline, and then watch evolution of the values by calling
* `promiseSnapshot()` and `subtract()`.
* `promiseSnapshot()` and `substract()`.
*
* On the other hand, numeric values are also monotonic across several instances
* of a PerformanceMonitor with the same probes.
@ -414,45 +330,18 @@ PerformanceMonitor.prototype = {
*
* // all values of `snapshot2` are greater or equal to values of `snapshot1`.
*
* @param {object} options If provided, an object that may contain the following
* fields:
* {Array<string>} probeNames The subset of probes to use for this snapshot.
* These probes must be a subset of the probes active in the monitor.
*
* @return {Promise}
* @resolve {Snapshot}
*/
promiseSnapshot: function(options = null) {
promiseSnapshot: function() {
if (!this._finalizer) {
throw new Error("dispose() has already been called, this PerformanceMonitor is not usable anymore");
}
let probes;
if (options && options.probeNames || undefined) {
if (!Array.isArray(options.probeNames)) {
throw new TypeError();
}
// Make sure that we only request probes that we have
for (let probeName of options.probeNames) {
let probe = this._probes.find(probe => probe.name == probeName);
if (!probe) {
throw new TypeError(`I need probe ${probeName} but I only have ${this.probeNames}`);
}
if (!probes) {
probes = [];
}
probes.push(probe);
}
} else {
probes = this._probes;
}
return Task.spawn(function*() {
let collected = yield Process.broadcastAndCollect("collect", {probeNames: [for (probe of probes) probe.name]});
return new Snapshot({
xpcom: performanceStatsService.getSnapshot(),
childProcesses: collected,
probes
});
});
// Current implementation is actually synchronous.
return Promise.resolve().then(() => new Snapshot({
xpcom: performanceStatsService.getSnapshot(),
probes: this._probes
}));
},
/**
@ -492,7 +381,7 @@ PerformanceMonitor.make = function(probeNames) {
let probes = [];
for (let probeName of probeNames) {
if (!(probeName in Probes)) {
throw new TypeError("Probe not implemented: " + probeName);
throw new TypeError("Probe not implemented: " + k);
}
probes.push(Probes[probeName]);
}
@ -578,24 +467,12 @@ this.PerformanceStats = {
* @field {object|undefined} cpow See the documentation of probe "cpow".
* `undefined` if this probe is not active.
*/
function PerformanceData({xpcom, json, probes}) {
if (xpcom && json) {
throw new TypeError("Cannot import both xpcom and json data");
}
let source = xpcom || json;
function PerformanceData({xpcom, probes}) {
for (let k of PROPERTIES_META) {
this[k] = source[k];
this[k] = xpcom[k];
}
if (xpcom) {
for (let probe of probes) {
this[probe.name] = probe.extract(xpcom);
}
this.isChildProcess = false;
} else {
for (let probe of probes) {
this[probe.name] = json[probe.name];
}
this.isChildProcess = true;
for (let probe of probes) {
this[probe.name] = probe.extract(xpcom);
}
}
PerformanceData.prototype = {
@ -642,161 +519,20 @@ function PerformanceDiff(current, old = null) {
for (let probeName of Object.keys(Probes)) {
let other = old ? old[probeName] : null;
if (probeName in current) {
this[probeName] = Probes[probeName].subtract(current[probeName], other);
this[probeName] = Probes[probeName].substract(current[probeName], other);
}
}
}
/**
* A snapshot of the performance usage of the application.
*
* @param {nsIPerformanceSnapshot} xpcom The data acquired from this process.
* @param {Array<Object>} childProcesses The data acquired from children processes.
* @param {Array<Probe>} probes The active probes.
* A snapshot of the performance usage of the process.
*/
function Snapshot({xpcom, childProcesses, probes}) {
function Snapshot({xpcom, probes}) {
this.componentsData = [];
// Parent process
if (xpcom) {
let enumeration = xpcom.getComponentsData().enumerate();
while (enumeration.hasMoreElements()) {
let xpcom = enumeration.getNext().QueryInterface(Ci.nsIPerformanceStats);
this.componentsData.push(new PerformanceData({xpcom, probes}));
}
let enumeration = xpcom.getComponentsData().enumerate();
while (enumeration.hasMoreElements()) {
let stat = enumeration.getNext().QueryInterface(Ci.nsIPerformanceStats);
this.componentsData.push(new PerformanceData({xpcom: stat, probes}));
}
// Content processes
if (childProcesses) {
for (let {componentsData} of childProcesses) {
// We are only interested in `componentsData` for the time being.
for (let json of componentsData) {
this.componentsData.push(new PerformanceData({json, probes}));
}
}
}
this.processData = new PerformanceData({xpcom: xpcom.getProcessData(), probes});
}
/**
* Communication with other processes
*/
let Process = {
// `true` once communications have been initialized
_initialized: false,
// the message manager
_loader: null,
// a counter used to match responses to requests
_idcounter: 0,
/**
* If we are in a child process, return `null`.
* Otherwise, return the global parent process message manager
* and load the script to connect to children processes.
*/
get loader() {
if (this._initialized) {
return this._loader;
}
this._initialized = true;
this._loader = Services.ppmm;
if (!this._loader) {
// We are in a child process.
return null;
}
this._loader.loadProcessScript("resource://gre/modules/PerformanceStats-content.js",
true/*including future processes*/);
return this._loader;
},
/**
* Broadcast a message to all children processes.
*
* NOOP if we are in a child process.
*/
broadcast: function(topic, payload) {
if (!this.loader) {
return;
}
this.loader.broadcastAsyncMessage("performance-stats-service-" + topic, {payload});
},
/**
* Brodcast a message to all children processes and wait for answer.
*
* NOOP if we are in a child process, or if we have no children processes,
* in which case we return `undefined`.
*
* @return {undefined} If we have no children processes, in particular
* if we are in a child process.
* @return {Promise<Array<Object>>} If we have children processes, an
* array of objects with a structure similar to PerformanceData. Note
* that the array may be empty if no child process responded.
*/
broadcastAndCollect: Task.async(function*(topic, payload) {
if (!this.loader || this.loader.childCount == 1) {
return undefined;
}
const TOPIC = "performance-stats-service-" + topic;
let id = this._idcounter++;
// The number of responses we are expecting. Note that we may
// not receive all responses if a process is too long to respond.
let expecting = this.loader.childCount;
// The responses we have collected, in arbitrary order.
let collected = [];
let deferred = PromiseUtils.defer();
// The content script may be loaded more than once (bug 1184115).
// To avoid double-responses, we keep track of who has already responded.
// Note that we could it on the other end, at the expense of implementing
// an additional .jsm just for that purpose.
let responders = new Set();
let observer = function({data, target}) {
if (data.id != id) {
// Collision between two collections,
// ignore the other one.
return;
}
if (responders.has(target)) {
return;
}
responders.add(target);
if (data.data) {
collected.push(data.data)
}
if (--expecting > 0) {
// We are still waiting for at least one response.
return;
}
deferred.resolve();
};
this.loader.addMessageListener(TOPIC, observer);
this.loader.broadcastAsyncMessage(
TOPIC,
{id, payload}
);
// Processes can die/freeze/be busy loading a page..., so don't expect
// that they will always respond.
let timeout = setTimeout(() => {
if (expecting == 0) {
return;
}
deferred.resolve();
}, MAX_WAIT_FOR_CHILD_PROCESS_MS);
deferred.promise.then(() => {
clearTimeout(timeout);
});
yield deferred.promise;
this.loader.removeMessageListener(TOPIC, observer);
return collected;
})
};

Просмотреть файл

@ -13,7 +13,6 @@ XPIDL_MODULE = 'toolkit_perfmonitoring'
EXTRA_JS_MODULES += [
'AddonWatcher.jsm',
'PerformanceStats-content.js',
'PerformanceStats.jsm',
]

Просмотреть файл

@ -260,7 +260,7 @@ nsPerformanceSnapshot::GetGroupId(JSContext* cx,
groupId.AssignLiteral("process: ");
groupId.AppendInt(mProcessId);
groupId.AppendLiteral(", thread: ");
groupId.AssignLiteral(", thread: ");
groupId.AppendInt(runtimeId);
groupId.AppendLiteral(", group: ");
groupId.AppendInt(uid);

Просмотреть файл

@ -86,7 +86,7 @@ let SilentAssert = {
}
};
let isShuttingDown = false;
function monotinicity_tester(source, testName) {
// In the background, check invariants:
// - numeric data can only ever increase;
@ -130,10 +130,6 @@ function monotinicity_tester(source, testName) {
};
let iteration = 0;
let frameCheck = Task.async(function*() {
if (isShuttingDown) {
window.clearInterval(interval);
return;
}
let name = `${testName}: ${iteration++}`;
let snapshot = yield source();
if (!snapshot) {
@ -163,10 +159,7 @@ function monotinicity_tester(source, testName) {
}
let key = item.groupId;
if (map.has(key)) {
let old = map.get(key);
Assert.ok(false, `Component ${key} has already been seen. Latest: ${item.title||item.addonId||item.name}, previous: ${old.title||old.addonId||old.name}`);
}
SilentAssert.ok(!map.has(key), "The component hasn't been seen yet.");
map.set(key, item);
}
for (let [key, item] of map) {
@ -243,19 +236,13 @@ add_task(function* test() {
info("Searching by title, we didn't find the main frame");
continue;
}
info("Found the main frame");
if (skipTotalUserTime) {
info("Not looking for total user time on this platform, we're done");
} else if (parent.jank.totalUserTime > 1000) {
info("Enough CPU time detected, we're done");
if (skipTotalUserTime || parent.jank.totalUserTime > 1000) {
break;
} else {
info(`Not enough CPU time detected: ${parent.jank.totalUserTime}`);
info(`Details: ${JSON.stringify(parent, null, "\t")}`);
info(`Not enough CPU time detected: ${parent.jank.totalUserTime}`)
}
}
isShuttingDown = true;
// Cleanup
gBrowser.removeTab(newTab);