Bug 1189799 - Make sure that about:performance displays each add-on only once (back-end);r=felipe

--HG--
extra : transplant_source : %8A%95z%C5D%DEM%94%0Ak%82%CB%F7%187%071%C1%21%9C
This commit is contained in:
David Rajchenbach-Teller 2015-08-25 17:05:02 +02:00
parent 131d787115
commit e28a2a27f5
2 changed files with 272 additions and 60 deletions

View File

@ -214,22 +214,15 @@ var AddonWatcher = {
// By default, warn only after an add-on has been spotted misbehaving 3 times.
let tolerance = Preferences.get("browser.addon-watch.tolerance", 3);
for (let item of snapshot.componentsData) {
let addonId = item.addonId;
if (!item.isSystem || !addonId) {
// We are only interested in add-ons.
continue;
}
for (let [addonId, item] of snapshot.addons) {
if (this._ignoreList.has(addonId)) {
// This add-on has been explicitly put in the ignore list
// by the user. Don't waste time with it.
continue;
}
// Store the activity for the group not the entire add-on, as we
// can have one group per process for each add-on.
let previous = this._previousPerformanceIndicators[item.groupId];
this._previousPerformanceIndicators[item.groupId] = item;
let previous = this._previousPerformanceIndicators[addonId];
this._previousPerformanceIndicators[addonId] = item;
if (!previous) {
// This is the first time we see the addon, so we are probably
@ -281,7 +274,7 @@ var AddonWatcher = {
stats.alerts[filter] = (stats.alerts[filter] || 0) + 1;
if (stats.alerts[filter] % tolerance != 0) {
if (stats.alerts[filter] % tolerance != 0) {
continue;
}

View File

@ -24,7 +24,7 @@ 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);
Cu.import("resource://gre/modules/ObjectUtils.jsm", this);
XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
"resource://gre/modules/PromiseUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
@ -152,7 +152,7 @@ Probe.prototype = {
if (!Array.isArray(children)) {
throw new TypeError();
}
if (!parent || !(parent instanceof PerformanceData)) {
if (!parent || !(parent instanceof PerformanceDataLeaf)) {
throw new TypeError();
}
return this._impl.importChildCompartments(parent, children);
@ -163,6 +163,13 @@ Probe.prototype = {
*/
get name() {
return this._name;
},
compose: function(stats) {
if (!Array.isArray(stats)) {
throw new TypeError();
}
return this._impl.compose(stats);
}
};
@ -208,6 +215,7 @@ var Probes = {
return {
totalUserTime: xpcom.totalUserTime,
totalSystemTime: xpcom.totalSystemTime,
totalCPUTime: xpcom.totalUserTime + xpcom.totalSystemTime,
durations: durations,
longestDuration: lastNonZero(durations)
}
@ -232,6 +240,7 @@ var Probes = {
let result = {
totalUserTime: a.totalUserTime - b.totalUserTime,
totalSystemTime: a.totalSystemTime - b.totalSystemTime,
totalCPUTime: a.totalCPUTime - b.totalCPUTime,
durations: [],
longestDuration: -1,
};
@ -242,6 +251,25 @@ var Probes = {
return result;
},
importChildCompartments: function() { /* nothing to do */ },
compose: function(stats) {
let result = {
totalUserTime: 0,
totalSystemTime: 0,
totalCPUTime: 0,
durations: [],
longestDuration: -1
};
for (let stat of stats) {
result.totalUserTime += stat.totalUserTime;
result.totalSystemTime += stat.totalSystemTime;
result.totalCPUTime += stat.totalCPUTime;
for (let i = 0; i < stat.durations.length; ++i) {
result.durations[i] += stat.durations[i];
}
result.longestDuration = Math.max(result.longestDuration, stat.longestDuration);
}
return result;
}
}),
/**
@ -273,6 +301,13 @@ var Probes = {
};
},
importChildCompartments: function() { /* nothing to do */ },
compose: function(stats) {
let totalCPOWTime = 0;
for (let stat of stats) {
totalCPOWTime += stat.totalCPOWTime;
}
return { totalCPOWTime };
},
}),
/**
@ -303,6 +338,13 @@ var Probes = {
};
},
importChildCompartments: function() { /* nothing to do */ },
compose: function(stats) {
let ticks = 0;
for (let stat of stats) {
ticks += stat.ticks;
}
return { ticks };
},
}),
compartments: new Probe("compartments", {
@ -324,6 +366,9 @@ var Probes = {
importChildCompartments: function(parent, children) {
parent.children = children;
},
compose: function(stats) {
return null;
},
}),
};
@ -391,7 +436,7 @@ PerformanceMonitor.prototype = {
* @return {Promise}
* @resolve {Snapshot}
*/
promiseSnapshot: function(options = null) {
_checkBeforeSnapshot: function(options) {
if (!this._finalizer) {
throw new Error("dispose() has already been called, this PerformanceMonitor is not usable anymore");
}
@ -414,12 +459,22 @@ PerformanceMonitor.prototype = {
} else {
probes = this._probes;
}
return probes;
},
promiseContentSnapshot: function(options = null) {
this._checkBeforeSnapshot(options);
return (new ProcessSnapshot(performanceStatsService.getSnapshot()));
},
promiseSnapshot: function(options = null) {
let probes = this._checkBeforeSnapshot(options);
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
let childProcesses = yield Process.broadcastAndCollect("collect", {probeNames: [for (probe of probes) probe.name]});
let xpcom = performanceStatsService.getSnapshot();
return new ApplicationSnapshot({
xpcom,
childProcesses,
probes,
date: Cu.now()
});
});
},
@ -466,7 +521,7 @@ PerformanceMonitor.make = function(probeNames) {
probes.push(Probes[probeName]);
}
return new PerformanceMonitor(probes);
return (new PerformanceMonitor(probes));
};
/**
@ -547,7 +602,7 @@ this.PerformanceStats = {
* @field {object|undefined} cpow See the documentation of probe "cpow".
* `undefined` if this probe is not active.
*/
function PerformanceData({xpcom, json, probes}) {
function PerformanceDataLeaf({xpcom, json, probes}) {
if (xpcom && json) {
throw new TypeError("Cannot import both xpcom and json data");
}
@ -566,15 +621,16 @@ function PerformanceData({xpcom, json, probes}) {
}
this.isChildProcess = true;
}
this.owner = null;
}
PerformanceData.prototype = {
PerformanceDataLeaf.prototype = {
/**
* Compare two instances of `PerformanceData`
*
* @return `true` if `this` and `to` have equal values in all fields.
*/
equals: function(to) {
if (!(to instanceof PerformanceData)) {
if (!(to instanceof PerformanceDataLeaf)) {
throw new TypeError();
}
for (let probeName of Object.keys(Probes)) {
@ -594,28 +650,191 @@ PerformanceData.prototype = {
* @return {PerformanceDiff} The performance usage between `to` and `this`.
*/
subtract: function(to = null) {
return new PerformanceDiff(this, to);
return (new PerformanceDiffLeaf(this, to));
}
};
function PerformanceData(timestamp) {
this._parent = null;
this._content = new Map();
this._all = [];
this._timestamp = timestamp;
}
PerformanceData.prototype = {
addChild: function(stat) {
if (!(stat instanceof PerformanceDataLeaf)) {
throw new TypeError(); // FIXME
}
if (!stat.isChildProcess) {
throw new TypeError(); // FIXME
}
this._content.set(stat.groupId, stat);
this._all.push(stat);
stat.owner = this;
},
setParent: function(stat) {
if (!(stat instanceof PerformanceDataLeaf)) {
throw new TypeError(); // FIXME
}
if (stat.isChildProcess) {
throw new TypeError(); // FIXME
}
this._parent = stat;
this._all.push(stat);
stat.owner = this;
},
equals: function(to) {
if (this._parent && !to._parent) {
return false;
}
if (!this._parent && to._parent) {
return false;
}
if (this._content.size != to._content.size) {
return false;
}
if (this._parent && !this._parent.equals(to._parent)) {
return false;
}
for (let [k, v] of this._content) {
let v2 = to._content.get(k);
if (!v2) {
return false;
}
if (!v.equals(v2)) {
return false;
}
}
return true;
},
subtract: function(to = null) {
return (new PerformanceDiff(this, to));
},
get addonId() {
return this._all[0].addonId;
},
get title() {
return this._all[0].title;
}
};
function PerformanceDiff(current, old = null) {
this.addonId = current.addonId;
this.title = current.title;
this.windowId = current.windowId;
this.deltaT = old ? current._timestamp - old._timestamp : Infinity;
this._all = [];
// Handle the parent, if any.
if (current._parent) {
this._parent = old?current._parent.subtract(old._parent):current._parent;
this._all.push(this._parent);
this._parent.owner = this;
} else {
this._parent = null;
}
// Handle the children, if any.
this._content = new Map();
for (let [k, stat] of current._content) {
let diff = stat.subtract(old ? old._content.get(k) : null);
this._content.set(k, diff);
this._all.push(diff);
diff.owner = this;
}
// Now consolidate data
for (let k of Object.keys(Probes)) {
if (!(k in this._all[0])) {
// The stats don't contain data from this probe.
continue;
}
let data = [for (item of this._all) item[k]];
let probe = Probes[k];
this[k] = probe.compose(data);
}
}
PerformanceDiff.prototype = {
toString: function() {
return `[PerformanceDiff] ${this.key}`;
},
get windowIds() {
return [for (item of this._all) item.windowId].filter(x => !!x);
},
get groupIds() {
return [for (item of this._all) item.groupId];
},
get key() {
if (this.addonId) {
return this.addonId;
}
if (this._parent) {
return this._parent.windowId;
}
return this._all[0].groupId;
},
get names() {
return [for (item of this._all) item.name];
},
get processes() {
return [for (item of this._all) { isChildProcess: item.isChildProcess, processId: item.processId}];
}
};
/**
* The delta between two instances of `PerformanceData`.
* The delta between two instances of `PerformanceDataLeaf`.
*
* Used to monitor resource usage between two timestamps.
*/
function PerformanceDiff(current, old = null) {
function PerformanceDiffLeaf(current, old = null) {
for (let k of PROPERTIES_META) {
this[k] = current[k];
}
for (let probeName of Object.keys(Probes)) {
let other = old ? old[probeName] : null;
let other = null;
if (old && probeName in old) {
other = old[probeName];
}
if (probeName in current) {
this[probeName] = Probes[probeName].subtract(current[probeName], other);
}
}
}
/**
* A snapshot of a single process.
*/
function ProcessSnapshot({xpcom, probes}) {
this.componentsData = [];
let subgroups = new Map();
let enumeration = xpcom.getComponentsData().enumerate();
while (enumeration.hasMoreElements()) {
let xpcom = enumeration.getNext().QueryInterface(Ci.nsIPerformanceStats);
let stat = (new PerformanceDataLeaf({xpcom, probes}));
if (!xpcom.parentId) {
this.componentsData.push(stat);
} else {
let siblings = subgroups.get(xpcom.parentId);
if (!siblings) {
subgroups.set(xpcom.parentId, (siblings = []));
}
siblings.push(stat);
}
}
for (let group of this.componentsData) {
for (let probe of probes) {
probe.importChildCompartments(group, subgroups.get(group.groupId) || []);
}
}
this.processData = (new PerformanceDataLeaf({xpcom: xpcom.getProcessData(), probes}));
}
/**
* A snapshot of the performance usage of the application.
*
@ -623,45 +842,45 @@ function PerformanceDiff(current, old = null) {
* @param {Array<Object>} childProcesses The data acquired from children processes.
* @param {Array<Probe>} probes The active probes.
*/
function Snapshot({xpcom, childProcesses, probes}) {
this.componentsData = [];
function ApplicationSnapshot({xpcom, childProcesses, probes, date}) {
ProcessSnapshot.call(this, {xpcom, probes});
// Current process
if (xpcom) {
let children = new Map();
let enumeration = xpcom.getComponentsData().enumerate();
while (enumeration.hasMoreElements()) {
let xpcom = enumeration.getNext().QueryInterface(Ci.nsIPerformanceStats);
let stat = new PerformanceData({xpcom, probes});
if (!stat.parentId) {
this.componentsData.push(stat);
} else {
let siblings = children.get(stat.parentId);
if (!siblings) {
children.set(stat.parentId, (siblings = []));
}
siblings.push(stat);
}
}
for (let parent of this.componentsData) {
for (let probe of probes) {
probe.importChildCompartments(parent, children.get(parent.groupId) || []);
}
}
}
this.addons = new Map();
this.webpages = new Map();
this.date = date;
// Child 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}));
}
for (let {componentsData} of (childProcesses || [])) {
// We are only interested in `componentsData` for the time being.
for (let json of componentsData) {
let leaf = (new PerformanceDataLeaf({json, probes}));
this.componentsData.push(leaf);
}
}
this.processData = new PerformanceData({xpcom: xpcom.getProcessData(), probes});
for (let leaf of this.componentsData) {
let key, map;
if (leaf.addonId) {
key = leaf.addonId;
map = this.addons;
} else if (leaf.windowId) {
key = leaf.windowId;
map = this.webpages;
} else {
continue;
}
let combined = map.get(key);
if (!combined) {
combined = new PerformanceData(date);
map.set(key, combined);
}
if (leaf.isChildProcess) {
combined.addChild(leaf);
} else {
combined.setParent(leaf);
}
}
}
/**