mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-15 19:57:29 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
239
toolkit/components/perfmonitoring/AddonWatcher.jsm
Normal file
239
toolkit/components/perfmonitoring/AddonWatcher.jsm
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
/* 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";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["AddonWatcher"];
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
|
||||
"resource://gre/modules/Preferences.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "console",
|
||||
"resource://gre/modules/Console.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PerformanceWatcher",
|
||||
"resource://gre/modules/PerformanceWatcher.jsm");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
|
||||
"@mozilla.org/base/telemetry;1",
|
||||
Ci.nsITelemetry);
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
||||
"resource://gre/modules/Services.jsm");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "IdleService",
|
||||
"@mozilla.org/widget/idleservice;1",
|
||||
Ci.nsIIdleService);
|
||||
|
||||
/**
|
||||
* Don't notify observers of slow add-ons if at least `SUSPICIOUSLY_MANY_ADDONS`
|
||||
* show up at the same time. We assume that this indicates that the system itself
|
||||
* is busy, and that add-ons are not responsible.
|
||||
*/
|
||||
let SUSPICIOUSLY_MANY_ADDONS = 5;
|
||||
|
||||
this.AddonWatcher = {
|
||||
/**
|
||||
* Watch this topic to be informed when a slow add-on is detected and should
|
||||
* be reported to the user.
|
||||
*
|
||||
* If you need finer-grained control, use PerformanceWatcher.jsm.
|
||||
*/
|
||||
TOPIC_SLOW_ADDON_DETECTED: "addon-watcher-detected-slow-addon",
|
||||
|
||||
init: function() {
|
||||
this._initializedTimeStamp = Cu.now();
|
||||
|
||||
try {
|
||||
this._ignoreList = new Set(JSON.parse(Preferences.get("browser.addon-watch.ignore", null)));
|
||||
} catch (ex) {
|
||||
// probably some malformed JSON, ignore and carry on
|
||||
this._ignoreList = new Set();
|
||||
}
|
||||
|
||||
this._warmupPeriod = Preferences.get("browser.addon-watch.warmup-ms", 60 * 1000 /* 1 minute */);
|
||||
this._idleThreshold = Preferences.get("browser.addon-watch.deactivate-after-idle-ms", 3000);
|
||||
this.paused = false;
|
||||
},
|
||||
uninit: function() {
|
||||
this.paused = true;
|
||||
},
|
||||
_initializedTimeStamp: 0,
|
||||
|
||||
set paused(paused) {
|
||||
if (paused) {
|
||||
if (this._listener) {
|
||||
PerformanceWatcher.removePerformanceListener({addonId: "*"}, this._listener);
|
||||
}
|
||||
this._listener = null;
|
||||
} else {
|
||||
this._listener = this._onSlowAddons.bind(this);
|
||||
PerformanceWatcher.addPerformanceListener({addonId: "*"}, this._listener);
|
||||
}
|
||||
},
|
||||
get paused() {
|
||||
return !this._listener;
|
||||
},
|
||||
_listener: null,
|
||||
|
||||
/**
|
||||
* Provide the following object for each addon:
|
||||
* {number} occurrences The total number of performance alerts recorded for this addon.
|
||||
* {number} occurrencesSinceLastNotification The number of performances alerts recorded
|
||||
* since we last notified the user.
|
||||
* {number} latestNotificationTimeStamp The timestamp of the latest user notification
|
||||
* that this add-on is slow.
|
||||
*/
|
||||
_getAlerts: function(addonId) {
|
||||
let alerts = this._alerts.get(addonId);
|
||||
if (!alerts) {
|
||||
alerts = {
|
||||
occurrences: 0,
|
||||
occurrencesSinceLastNotification: 0,
|
||||
latestNotificationTimeStamp: 0,
|
||||
};
|
||||
this._alerts.set(addonId, alerts);
|
||||
}
|
||||
return alerts;
|
||||
},
|
||||
_alerts: new Map(),
|
||||
_onSlowAddons: function(addons) {
|
||||
try {
|
||||
if (IdleService.idleTime >= this._idleThreshold) {
|
||||
// The application is idle. Maybe the computer is sleeping, or maybe
|
||||
// the user isn't in front of it. Regardless, the user doesn't care
|
||||
// about things that slow down her browser while she's not using it.
|
||||
return;
|
||||
}
|
||||
|
||||
if (addons.length > SUSPICIOUSLY_MANY_ADDONS) {
|
||||
// Heuristic: if we are notified of many slow addons at once, the issue
|
||||
// is probably not with the add-ons themselves with the system. We may
|
||||
// for instance be waking up from hibernation, or the system may be
|
||||
// busy swapping.
|
||||
return;
|
||||
}
|
||||
|
||||
let now = Cu.now();
|
||||
if (now - this._initializedTimeStamp < this._warmupPeriod) {
|
||||
// Heuristic: do not report slowdowns during or just after startup.
|
||||
return;
|
||||
}
|
||||
|
||||
// Report immediately to Telemetry, regardless of whether we report to
|
||||
// the user.
|
||||
for (let {source: {addonId}, details} of addons) {
|
||||
Telemetry.getKeyedHistogramById("PERF_MONITORING_SLOW_ADDON_JANK_US").
|
||||
add(addonId, details.highestJank);
|
||||
Telemetry.getKeyedHistogramById("PERF_MONITORING_SLOW_ADDON_CPOW_US").
|
||||
add(addonId, details.highestCPOW);
|
||||
}
|
||||
|
||||
// We expect that users don't care about real-time alerts unless their
|
||||
// browser is going very, very slowly. Therefore, we use the following
|
||||
// heuristic:
|
||||
// - if jank is above freezeThreshold (e.g. 5 seconds), report immediately; otherwise
|
||||
// - if jank is below jankThreshold (e.g. 128ms), disregard; otherwise
|
||||
// - if the latest jank was more than prescriptionDelay (e.g. 5 minutes) ago, reset number of occurrences;
|
||||
// - if we have had fewer than occurrencesBetweenAlerts janks (e.g. 3) since last alert, disregard; otherwise
|
||||
// - if we have displayed an alert for this add-on less than delayBetweenAlerts ago (e.g. 6h), disregard; otherwise
|
||||
// - also, don't report more than highestNumberOfAddonsToReport (e.g. 1) at once.
|
||||
let freezeThreshold = Preferences.get("browser.addon-watch.freeze-threshold-micros", /* 5 seconds */ 5000000);
|
||||
let jankThreshold = Preferences.get("browser.addon-watch.jank-threshold-micros", /* 256 ms == 8 frames*/ 256000);
|
||||
let occurrencesBetweenAlerts = Preferences.get("browser.addon-watch.occurrences-between-alerts", 3);
|
||||
let delayBetweenAlerts = Preferences.get("browser.addon-watch.delay-between-alerts-ms", 6 * 3600 * 1000 /* 6h */);
|
||||
let delayBetweenFreezeAlerts = Preferences.get("browser.addon-watch.delay-between-freeze-alerts-ms", 2 * 60 * 1000 /* 2 min */);
|
||||
let prescriptionDelay = Preferences.get("browser.addon-watch.prescription-delay", 5 * 60 * 1000 /* 5 minutes */);
|
||||
let highestNumberOfAddonsToReport = Preferences.get("browser.addon-watch.max-simultaneous-reports", 1);
|
||||
|
||||
addons = addons.filter(x => x.details.highestJank >= jankThreshold).
|
||||
sort((a, b) => a.details.highestJank - b.details.highestJank);
|
||||
|
||||
for (let {source: {addonId}, details} of addons) {
|
||||
if (highestNumberOfAddonsToReport <= 0) {
|
||||
return;
|
||||
}
|
||||
if (this._ignoreList.has(addonId)) {
|
||||
// Add-on is ignored.
|
||||
continue;
|
||||
}
|
||||
|
||||
let alerts = this._getAlerts(addonId);
|
||||
if (now - alerts.latestOccurrence >= prescriptionDelay) {
|
||||
// While this add-on has already caused slownesss, this
|
||||
// was a long time ago, let's forgive.
|
||||
alerts.occurrencesSinceLastNotification = 0;
|
||||
}
|
||||
|
||||
alerts.occurrencesSinceLastNotification++;
|
||||
alerts.occurrences++;
|
||||
|
||||
if (details.highestJank < freezeThreshold) {
|
||||
if (alerts.occurrencesSinceLastNotification <= occurrencesBetweenAlerts) {
|
||||
// While the add-on has caused jank at least once, we are only
|
||||
// interested in repeat offenders. Store the data for future use.
|
||||
continue;
|
||||
}
|
||||
if (now - alerts.latestNotificationTimeStamp <= delayBetweenAlerts) {
|
||||
// We have already displayed an alert for this add-on recently.
|
||||
// Wait a little before displaying another one.
|
||||
continue;
|
||||
}
|
||||
} else if (now - alerts.latestNotificationTimeStamp <= delayBetweenFreezeAlerts) {
|
||||
// Even in case of freeze, we want to avoid needlessly spamming the user.
|
||||
// We have already displayed an alert for this add-on recently.
|
||||
// Wait a little before displaying another one.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ok, time to inform the user.
|
||||
alerts.latestNotificationTimeStamp = now;
|
||||
alerts.occurrencesSinceLastNotification = 0;
|
||||
Services.obs.notifyObservers(null, this.TOPIC_SLOW_ADDON_DETECTED, addonId);
|
||||
|
||||
highestNumberOfAddonsToReport--;
|
||||
}
|
||||
} catch (ex) {
|
||||
Cu.reportError("Error in AddonWatcher._onSlowAddons " + ex);
|
||||
Cu.reportError(Task.Debugging.generateReadableStack(ex.stack));
|
||||
}
|
||||
},
|
||||
|
||||
ignoreAddonForSession: function(addonid) {
|
||||
this._ignoreList.add(addonid);
|
||||
},
|
||||
ignoreAddonPermanently: function(addonid) {
|
||||
this._ignoreList.add(addonid);
|
||||
try {
|
||||
let ignoreList = JSON.parse(Preferences.get("browser.addon-watch.ignore", "[]"))
|
||||
if (!ignoreList.includes(addonid)) {
|
||||
ignoreList.push(addonid);
|
||||
Preferences.set("browser.addon-watch.ignore", JSON.stringify(ignoreList));
|
||||
}
|
||||
} catch (ex) {
|
||||
Preferences.set("browser.addon-watch.ignore", JSON.stringify([addonid]));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The list of alerts for this session.
|
||||
*
|
||||
* @type {Map<String, Object>} A map associating addonId to
|
||||
* objects with fields
|
||||
* {number} occurrences The total number of performance alerts recorded for this addon.
|
||||
* {number} occurrencesSinceLastNotification The number of performances alerts recorded
|
||||
* since we last notified the user.
|
||||
* {number} latestNotificationTimeStamp The timestamp of the latest user notification
|
||||
* that this add-on is slow.
|
||||
*/
|
||||
get alerts() {
|
||||
let result = new Map();
|
||||
for (let [k, v] of this._alerts) {
|
||||
result.set(k, Cu.cloneInto(v, this));
|
||||
}
|
||||
return result;
|
||||
},
|
||||
};
|
||||
144
toolkit/components/perfmonitoring/PerformanceStats-content.js
Normal file
144
toolkit/components/perfmonitoring/PerformanceStats-content.js
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
/* 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";
|
||||
|
||||
var { 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 in the parent.
|
||||
*
|
||||
* In the parent, this is always an empty monitor.
|
||||
*/
|
||||
var gMonitor = PerformanceStats.getMonitor([]);
|
||||
|
||||
/**
|
||||
* `true` if this is a content process, `false` otherwise.
|
||||
*/
|
||||
var 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.probeNames
|
||||
.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
|
||||
});
|
||||
}));
|
||||
1000
toolkit/components/perfmonitoring/PerformanceStats.jsm
Normal file
1000
toolkit/components/perfmonitoring/PerformanceStats.jsm
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,54 @@
|
|||
/* 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";
|
||||
|
||||
/**
|
||||
* An API for being informed of slow add-ons and tabs
|
||||
* (content process scripts).
|
||||
*/
|
||||
|
||||
const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
|
||||
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
|
||||
/**
|
||||
* `true` if this is a content process, `false` otherwise.
|
||||
*/
|
||||
let isContent = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
|
||||
|
||||
if (isContent) {
|
||||
|
||||
const { PerformanceWatcher } = Cu.import("resource://gre/modules/PerformanceWatcher.jsm", {});
|
||||
|
||||
let toMsg = function(alerts) {
|
||||
let result = [];
|
||||
for (let {source, details} of alerts) {
|
||||
// Convert xpcom values to serializable data.
|
||||
let serializableSource = {};
|
||||
for (let k of ["groupId", "name", "addonId", "windowId", "isSystem", "processId", "isContentProcess"]) {
|
||||
serializableSource[k] = source[k];
|
||||
}
|
||||
|
||||
let serializableDetails = {};
|
||||
for (let k of ["reason", "highestJank", "highestCPOW"]) {
|
||||
serializableDetails[k] = details[k];
|
||||
}
|
||||
result.push({source:serializableSource, details:serializableDetails});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
PerformanceWatcher.addPerformanceListener({addonId: "*"}, alerts => {
|
||||
Services.cpmm.sendAsyncMessage("performancewatcher-propagate-notifications",
|
||||
{addons: toMsg(alerts)}
|
||||
);
|
||||
});
|
||||
|
||||
PerformanceWatcher.addPerformanceListener({windowId: 0}, alerts => {
|
||||
Services.cpmm.sendAsyncMessage("performancewatcher-propagate-notifications",
|
||||
{windows: toMsg(alerts)}
|
||||
);
|
||||
});
|
||||
|
||||
}
|
||||
367
toolkit/components/perfmonitoring/PerformanceWatcher.jsm
Normal file
367
toolkit/components/perfmonitoring/PerformanceWatcher.jsm
Normal file
|
|
@ -0,0 +1,367 @@
|
|||
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
/* 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";
|
||||
|
||||
/**
|
||||
* An API for being informed of slow add-ons and tabs.
|
||||
*
|
||||
* Generally, this API is both more CPU-efficient and more battery-efficient
|
||||
* than PerformanceStats. As PerformanceStats, this API does not provide any
|
||||
* information during the startup or shutdown of Firefox.
|
||||
*
|
||||
* = Examples =
|
||||
*
|
||||
* Example use: reporting whenever a specific add-on slows down Firefox.
|
||||
* let listener = function(source, details) {
|
||||
* // This listener is triggered whenever the addon causes Firefox to miss
|
||||
* // frames. Argument `source` contains information about the source of the
|
||||
* // slowdown (including the process in which it happens), while `details`
|
||||
* // contains performance statistics.
|
||||
* console.log(`Oops, add-on ${source.addonId} seems to be slowing down Firefox.`, details);
|
||||
* };
|
||||
* PerformanceWatcher.addPerformanceListener({addonId: "myaddon@myself.name"}, listener);
|
||||
*
|
||||
* Example use: reporting whenever any webpage slows down Firefox.
|
||||
* let listener = function(alerts) {
|
||||
* // This listener is triggered whenever any window causes Firefox to miss
|
||||
* // frames. FieldArgument `source` contains information about the source of the
|
||||
* // slowdown (including the process in which it happens), while `details`
|
||||
* // contains performance statistics.
|
||||
* for (let {source, details} of alerts) {
|
||||
* console.log(`Oops, window ${source.windowId} seems to be slowing down Firefox.`, details);
|
||||
* };
|
||||
* // Special windowId 0 lets us to listen to all webpages.
|
||||
* PerformanceWatcher.addPerformanceListener({windowId: 0}, listener);
|
||||
*
|
||||
*
|
||||
* = How this works =
|
||||
*
|
||||
* This high-level API is based on the lower-level nsIPerformanceStatsService.
|
||||
* At the end of each event (including micro-tasks), the nsIPerformanceStatsService
|
||||
* updates its internal performance statistics and determines whether any
|
||||
* add-on/window in the current process has exceeded the jank threshold.
|
||||
*
|
||||
* The PerformanceWatcher maintains low-level performance observers in each
|
||||
* process and forwards alerts to the main process. Internal observers collate
|
||||
* low-level main process alerts and children process alerts and notify clients
|
||||
* of this API.
|
||||
*/
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["PerformanceWatcher"];
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
let { PerformanceStats, performanceStatsService } = Cu.import("resource://gre/modules/PerformanceStats.jsm", {});
|
||||
let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
|
||||
// `true` if the code is executed in content, `false` otherwise
|
||||
let isContent = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
|
||||
|
||||
if (!isContent) {
|
||||
// Initialize communication with children.
|
||||
//
|
||||
// To keep the protocol simple, the children inform the parent whenever a slow
|
||||
// add-on/tab is detected. We do not attempt to implement thresholds.
|
||||
Services.ppmm.loadProcessScript("resource://gre/modules/PerformanceWatcher-content.js",
|
||||
true/* including future processes*/);
|
||||
|
||||
Services.ppmm.addMessageListener("performancewatcher-propagate-notifications",
|
||||
(...args) => ChildManager.notifyObservers(...args)
|
||||
);
|
||||
}
|
||||
|
||||
// Configure the performance stats service to inform us in case of jank.
|
||||
performanceStatsService.jankAlertThreshold = 64000 /* us */;
|
||||
|
||||
|
||||
/**
|
||||
* Handle communications with child processes. Handle listening to
|
||||
* either a single add-on id (including the special add-on id "*",
|
||||
* which is notified for all add-ons) or a single window id (including
|
||||
* the special window id 0, which is notified for all windows).
|
||||
*
|
||||
* Acquire through `ChildManager.getAddon` and `ChildManager.getWindow`.
|
||||
*/
|
||||
function ChildManager(map, key) {
|
||||
this.key = key;
|
||||
this._map = map;
|
||||
this._listeners = new Set();
|
||||
}
|
||||
ChildManager.prototype = {
|
||||
/**
|
||||
* Add a listener, which will be notified whenever a child process
|
||||
* reports a slow performance alert for this addon/window.
|
||||
*/
|
||||
addListener: function(listener) {
|
||||
this._listeners.add(listener);
|
||||
},
|
||||
/**
|
||||
* Remove a listener.
|
||||
*/
|
||||
removeListener: function(listener) {
|
||||
let deleted = this._listeners.delete(listener);
|
||||
if (!deleted) {
|
||||
throw new Error("Unknown listener");
|
||||
}
|
||||
},
|
||||
|
||||
listeners: function() {
|
||||
return this._listeners.values();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispatch child alerts to observers.
|
||||
*
|
||||
* Triggered by messages from content processes.
|
||||
*/
|
||||
ChildManager.notifyObservers = function({data: {addons, windows}}) {
|
||||
if (addons && addons.length > 0) {
|
||||
// Dispatch the entire list to universal listeners
|
||||
this._notify(ChildManager.getAddon("*").listeners(), addons);
|
||||
|
||||
// Dispatch individual alerts to individual listeners
|
||||
for (let {source, details} of addons) {
|
||||
this._notify(ChildManager.getAddon(source.addonId).listeners(), source, details);
|
||||
}
|
||||
}
|
||||
if (windows && windows.length > 0) {
|
||||
// Dispatch the entire list to universal listeners
|
||||
this._notify(ChildManager.getWindow(0).listeners(), windows);
|
||||
|
||||
// Dispatch individual alerts to individual listeners
|
||||
for (let {source, details} of windows) {
|
||||
this._notify(ChildManager.getWindow(source.windowId).listeners(), source, details);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ChildManager._notify = function(targets, ...args) {
|
||||
for (let target of targets) {
|
||||
target(...args);
|
||||
}
|
||||
};
|
||||
|
||||
ChildManager.getAddon = function(key) {
|
||||
return this._get(this._addons, key);
|
||||
};
|
||||
ChildManager._addons = new Map();
|
||||
|
||||
ChildManager.getWindow = function(key) {
|
||||
return this._get(this._windows, key);
|
||||
};
|
||||
ChildManager._windows = new Map();
|
||||
|
||||
ChildManager._get = function(map, key) {
|
||||
let result = map.get(key);
|
||||
if (!result) {
|
||||
result = new ChildManager(map, key);
|
||||
map.set(key, result);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
let gListeners = new WeakMap();
|
||||
|
||||
/**
|
||||
* An object in charge of managing all the observables for a single
|
||||
* target (window/addon/all windows/all addons).
|
||||
*
|
||||
* In a content process, a target is represented by a single observable.
|
||||
* The situation is more sophisticated in a parent process, as a target
|
||||
* has both an in-process observable and several observables across children
|
||||
* processes.
|
||||
*
|
||||
* This class abstracts away the difference to simplify the work of
|
||||
* (un)registering observers for targets.
|
||||
*
|
||||
* @param {object} target The target being observed, as an object
|
||||
* with one of the following fields:
|
||||
* - {string} addonId Either "*" for the universal add-on observer
|
||||
* or the add-on id of an addon. Note that this class does not
|
||||
* check whether the add-on effectively exists, and that observers
|
||||
* may be registered for an add-on before the add-on is installed
|
||||
* or started.
|
||||
* - {xul:tab} tab A single tab. It must already be initialized.
|
||||
* - {number} windowId Either 0 for the universal window observer
|
||||
* or the outer window id of the window.
|
||||
*/
|
||||
function Observable(target) {
|
||||
// A mapping from `listener` (function) to `Observer`.
|
||||
this._observers = new Map();
|
||||
if ("addonId" in target) {
|
||||
this._key = `addonId: ${target.addonId}`;
|
||||
this._process = performanceStatsService.getObservableAddon(target.addonId);
|
||||
this._children = isContent ? null : ChildManager.getAddon(target.addonId);
|
||||
this._isBuffered = target.addonId == "*";
|
||||
} else if ("tab" in target || "windowId" in target) {
|
||||
let windowId;
|
||||
if ("tab" in target) {
|
||||
windowId = target.tab.linkedBrowser.outerWindowID;
|
||||
// By convention, outerWindowID may not be 0.
|
||||
} else if ("windowId" in target) {
|
||||
windowId = target.windowId;
|
||||
}
|
||||
if (windowId == undefined || windowId == null) {
|
||||
throw new TypeError(`No outerWindowID. Perhaps the target is a tab that is not initialized yet.`);
|
||||
}
|
||||
this._key = `tab-windowId: ${windowId}`;
|
||||
this._process = performanceStatsService.getObservableWindow(windowId);
|
||||
this._children = isContent ? null : ChildManager.getWindow(windowId);
|
||||
this._isBuffered = windowId == 0;
|
||||
} else {
|
||||
throw new TypeError("Unexpected target");
|
||||
}
|
||||
}
|
||||
Observable.prototype = {
|
||||
addJankObserver: function(listener) {
|
||||
if (this._observers.has(listener)) {
|
||||
throw new TypeError(`Listener already registered for target ${this._key}`);
|
||||
}
|
||||
if (this._children) {
|
||||
this._children.addListener(listener);
|
||||
}
|
||||
let observer = this._isBuffered ? new BufferedObserver(listener)
|
||||
: new Observer(listener);
|
||||
// Store the observer to be able to call `this._process.removeJankObserver`.
|
||||
this._observers.set(listener, observer);
|
||||
|
||||
this._process.addJankObserver(observer);
|
||||
},
|
||||
removeJankObserver: function(listener) {
|
||||
let observer = this._observers.get(listener);
|
||||
if (!observer) {
|
||||
throw new TypeError(`No listener for target ${this._key}`);
|
||||
}
|
||||
this._observers.delete(listener);
|
||||
|
||||
if (this._children) {
|
||||
this._children.removeListener(listener);
|
||||
}
|
||||
|
||||
this._process.removeJankObserver(observer);
|
||||
observer.dispose();
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a cached observable for a given target.
|
||||
*/
|
||||
Observable.get = function(target) {
|
||||
let key;
|
||||
if ("addonId" in target) {
|
||||
key = target.addonId;
|
||||
} else if ("tab" in target) {
|
||||
// We do not want to use a tab as a key, as this would prevent it from
|
||||
// being garbage-collected.
|
||||
key = target.tab.linkedBrowser.outerWindowID;
|
||||
} else if ("windowId" in target) {
|
||||
key = target.windowId;
|
||||
}
|
||||
if (key == null) {
|
||||
throw new TypeError(`Could not extract a key from ${JSON.stringify(target)}. Could the target be an unitialized tab?`);
|
||||
}
|
||||
let observable = this._cache.get(key);
|
||||
if (!observable) {
|
||||
observable = new Observable(target);
|
||||
this._cache.set(key, observable);
|
||||
}
|
||||
return observable;
|
||||
};
|
||||
Observable._cache = new Map();
|
||||
|
||||
/**
|
||||
* Wrap a listener callback as an unbuffered nsIPerformanceObserver.
|
||||
*
|
||||
* Each observation is propagated immediately to the listener.
|
||||
*/
|
||||
function Observer(listener) {
|
||||
// Make sure that monitoring stays alive (in all processes) at least as
|
||||
// long as the observer.
|
||||
this._monitor = PerformanceStats.getMonitor(["jank", "cpow"]);
|
||||
this._listener = listener;
|
||||
}
|
||||
Observer.prototype = {
|
||||
observe: function(...args) {
|
||||
this._listener(...args);
|
||||
},
|
||||
dispose: function() {
|
||||
this._monitor.dispose();
|
||||
this.observe = function poison() {
|
||||
throw new Error("Internal error: I should have stopped receiving notifications");
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrap a listener callback as an buffered nsIPerformanceObserver.
|
||||
*
|
||||
* Observations are buffered and dispatch in the next tick to the listener.
|
||||
*/
|
||||
function BufferedObserver(listener) {
|
||||
Observer.call(this, listener);
|
||||
this._buffer = [];
|
||||
this._isDispatching = false;
|
||||
this._pending = null;
|
||||
}
|
||||
BufferedObserver.prototype = Object.create(Observer.prototype);
|
||||
BufferedObserver.prototype.observe = function(source, details) {
|
||||
this._buffer.push({source, details});
|
||||
if (!this._isDispatching) {
|
||||
this._isDispatching = true;
|
||||
Services.tm.mainThread.dispatch(() => {
|
||||
// Grab buffer, in case something in the listener could modify it.
|
||||
let buffer = this._buffer;
|
||||
this._buffer = [];
|
||||
|
||||
// As of this point, any further observations need to use the new buffer
|
||||
// and a new dispatcher.
|
||||
this._isDispatching = false;
|
||||
|
||||
this._listener(buffer);
|
||||
}, Ci.nsIThread.DISPATCH_NORMAL);
|
||||
}
|
||||
};
|
||||
|
||||
this.PerformanceWatcher = {
|
||||
/**
|
||||
* Add a listener informed whenever we receive a slow performance alert
|
||||
* in the application.
|
||||
*
|
||||
* @param {object} target An object with one of the following fields:
|
||||
* - {string} addonId Either "*" to observe all add-ons or a full add-on ID.
|
||||
* to observe a single add-on.
|
||||
* - {number} windowId Either 0 to observe all windows or an outer window ID
|
||||
* to observe a single tab.
|
||||
* - {xul:browser} tab To observe a single tab.
|
||||
* @param {function} listener A function that will be triggered whenever
|
||||
* the target causes a slow performance notification. The notification may
|
||||
* have originated in any process of the application.
|
||||
*
|
||||
* If the listener listens to a single add-on/webpage, it is triggered with
|
||||
* the following arguments:
|
||||
* source: {groupId, name, addonId, windowId, isSystem, processId}
|
||||
* Information on the source of the notification.
|
||||
* details: {reason, highestJank, highestCPOW} Information on the
|
||||
* notification.
|
||||
*
|
||||
* If the listener listens to all add-ons/all webpages, it is triggered with
|
||||
* an array of {source, details}, as described above.
|
||||
*/
|
||||
addPerformanceListener: function(target, listener) {
|
||||
if (typeof listener != "function") {
|
||||
throw new TypeError();
|
||||
}
|
||||
let observable = Observable.get(target);
|
||||
observable.addJankObserver(listener);
|
||||
},
|
||||
removePerformanceListener: function(target, listener) {
|
||||
if (typeof listener != "function") {
|
||||
throw new TypeError();
|
||||
}
|
||||
let observable = Observable.get(target);
|
||||
observable.removeJankObserver(listener);
|
||||
},
|
||||
};
|
||||
120
toolkit/components/perfmonitoring/README.md
Normal file
120
toolkit/components/perfmonitoring/README.md
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
This directory is part of the implementation of the Performance Monitoring API
|
||||
|
||||
# What is the Performance Monitoring API?
|
||||
|
||||
The Performance Monitoring API is a set of interfaces designed to let front-end code find out if the application or a specific process is currently janky, quantify this jank and its evolution, and investigate what is causing jank (system code? a webpage? an add-on? CPOW?). In other words, this is a form of minimal profiler, designed to be lightweight enough to be enabled at all times in production code.
|
||||
|
||||
In Firefox Nightly, the Performance Monitoring API is used to:
|
||||
- inform users if their machine janks because of an add-on;
|
||||
- upload add-on performance to Telemetry for the benefit of AMO maintainers and add-on developers;
|
||||
- let users inspect the performance of their browser through about:performance.
|
||||
|
||||
# How can I use the API?
|
||||
|
||||
The API is designed mainly to be used from JavaScript client code, using PerformanceStats.jsm. If you really need to use it from C++ code, you should use the performance stats service defined in nsIPerformanceStats.idl. Note that PerformanceStats.jsm contains support for entire e10s-enabled applications, while nsIPerformanceStats.idl only supports one process at a time.
|
||||
|
||||
|
||||
# How does the Performance Monitoring API work?
|
||||
|
||||
At the time of this writing, the implementation of this API monitors only performance information related to the execution of JavaScript code, and only in the main thread. This is performed by an instrumentation of js/, orchestrated by toolkit/.
|
||||
|
||||
At low-level, the unit of code used for monitoring is the JS Compartment: one .jsm module, one XPCOM component, one sandbox, one script in an iframe, ... When executing code in a compartment, it is possible to inspect either the compartment or the script itself to find out who this compartment belongs to: a `<xul:browser>`, an add-on, etc.
|
||||
|
||||
At higher-level, the unit of code used for monitoring is the Performance Group. One Performance Group represents one or more JS Compartments, grouped together because we are interested in their performance. The current implementation uses Performance Groups to represent individual JS Compartments, entire add-ons, entire webpages including iframes and entire threads. Other applications have been discussed to represent entire eTLD+1 domains (e.g. to monitor the cost of ads), etc.
|
||||
|
||||
A choice was made to represent the CPU cost in *clock cycles* at low-level, as extracting a number of clock cycles has a very low latency (typically a few dozen cycles on recent architectures) and is much more precise than `getrusage`-style CPU clocks (which are often limited to a precision of 16ms). The drawback of this choice is that distinct CPUs/cores may, depending on the architecture, have entirely unrelated clock cycles count. We assume that the risk of false positives is reasonably low, and bound the uncertainty by renormalizing the result with the actual CPU clocks once per event.
|
||||
|
||||
## SpiderMonkey-level
|
||||
|
||||
The instrumentation of SpiderMonkey lives in `js/src/vm/Stopwatch.*`. As SpiderMonkey does not know about the Gecko event loop, or DOM events, or windows, so any such information must be provided by the embedding. To communicate with higher levels, SpiderMonkey exposes a virtual class `js::PerformanceGroup` designed to be subclassed and instantiated by the embedding based on its interests.
|
||||
|
||||
An instance of `js::PerformanceGroup` may be acquired (to mark that it is currently being monitored) and released (once monitoring is complete or cancelled) by SpiderMonkey. Furthermore, a `js::PerformanceGroup` can be marked as active (to mark that the embedding is currently interested in its performance) or inactive (otherwise) by the embedding.
|
||||
|
||||
Each `js::Performance` holds a total CPU cost measured in *clock cycles* and a total CPOW cost measured in *microseconds*. Both costs can only increase while measuring data, and can be reset to 0 by the embedding, once we have finished execution of the event loop.
|
||||
|
||||
### Upon starting to execute code in a JS Compartment `cx`
|
||||
1. If global monitoring is deactivated, bailout;
|
||||
2. If XPConnect has informed us that we are entering a nested event loop, cancel any ongoing measure on the outer event loop and proceed with the current measure;
|
||||
3. If we do not know to which performance groups `cx` is associated, request the information from the embedding;
|
||||
4. For each performance group `group` to which `cx` belongs *and* that is not acquired *and* for which monitoring is active, acquire the group;
|
||||
5. If no group was acquired, bailout;
|
||||
6. Capture a timestamp for the CPU cost of `cx`, in *clock cycles*. This value is provided directly by the CPU;
|
||||
7. Capture a timestamp for the CPOW cost of `cx`, in *CPOW microseconds*. This value is provided by the CPOW-level embedding.
|
||||
|
||||
### Upon stopping execution of the code in the JS compartment `cx`
|
||||
1. If global monitoring is deactivated, bailout;
|
||||
2. If the measure has been canceled, bailout;
|
||||
3. If no group was acquired, bailout;
|
||||
4. Capture a timestamp for the CPU cost of `cx`, use it to update the total CPU cost of each of the groups acquired;
|
||||
5. Capture a timestamp for the CPOW cost of `cx`, use it to update the total CPOW cost of each of the groups acquired;
|
||||
6. Mark acquired groups as executed recently;
|
||||
7. Release groups.
|
||||
|
||||
### When informed by the embedding that the iteration of the event loop is complete
|
||||
1. Commit all the groups executed recently to the embedding;
|
||||
2. Release all groups;
|
||||
3. Reset all CPU/CPOW costs to 0.
|
||||
|
||||
## Cross-Process Object Wrapper-level
|
||||
|
||||
The instrumentation of CPOW lives in `js/ipc/src`. It maintains a CPOW clock that increases whenever the process is blocked by a CPOW call.
|
||||
|
||||
## XPConnect-level
|
||||
|
||||
The instrumentation of XPConnect lives in `js/xpconnect/src/XPCJSContext.cpp`.
|
||||
|
||||
### When we enter a nested event loop
|
||||
|
||||
1. Inform the SpiderMonkey-level instrumentation, to let it cancel any ongoing measure.
|
||||
|
||||
### When we finish executing an iteration of the event loop, including microtasks:
|
||||
|
||||
1. Inform the SpiderMonkey-level instrumentation, to let it commit its recent data.
|
||||
|
||||
## nsIPerformanceStatsService-level
|
||||
|
||||
This code lives in `toolkit/components/perfmonitoring/ns*`. Its role is to orchestrate the information provided by SpiderMonkey at the scale of a single thread of a single process. At the time of this writing, this instrumentation is only activated on the main thread, for all Gecko processes.
|
||||
|
||||
The service defines a class `nsPerformanceGroup`, designed to be the sole concrete implementation of `js::PerformanceGroup`. `nsPerformanceGroup` extends `js::PerformanceGroup` with the global performance information gathered for the group since the start of the service. The information is:
|
||||
- total CPU time measured;
|
||||
- total CPOW time measured;
|
||||
- number of times CPU time exceeded 1ms;
|
||||
- number of times CPU time exceeded 2ms;
|
||||
- number of times CPU time exceeded 4ms;
|
||||
- ...
|
||||
- number of times CPU time exceeded 2^9ms.
|
||||
|
||||
Also, `nsPerformanceGroup` extends `js::PerformanceGroup` with high-level identification:
|
||||
- id of the window that executed the code, if any;
|
||||
- id of the add-on that provided the code, if any.
|
||||
|
||||
### When the SpiderMonkey-level instrumentation requests a list of PerformanceGroup for a compartment
|
||||
|
||||
Return a list with the following groups:
|
||||
* all compartments are associated with the "top group", which represents the entire thread;
|
||||
* find out if the compartment is code from a window, if so add a group shared by all compartments for this specific window;
|
||||
* find out if the compartment is code from an add-on, if so add a group shared by all compartments for this add-on;
|
||||
* add a group representing this specific compartment.
|
||||
|
||||
For performance reasons, groups representing a single compartment are inactive by default, while all other groups are active by default.
|
||||
|
||||
Performance groups are refcounted and destroyed with the implementation of `delete` used by toolkit/.
|
||||
|
||||
### When the SpiderMonkey-level instrumentation commits a list of PerformanceGroups
|
||||
|
||||
For each group in the list:
|
||||
1. transfer recent CPU time and recent CPOW time to total CPU time, total CPOW time, number of times CPU time exceeded *n* ms;
|
||||
2. reset group.
|
||||
|
||||
Future versions are expected to trigger low-performance alerts at this stage.
|
||||
|
||||
### Snapshotting
|
||||
|
||||
(to be documented)
|
||||
|
||||
## PerformanceStats.jsm-level
|
||||
|
||||
PerformanceStats provides a JS-friendly API on top of nsIPerformanceStatsService. The main differences are:
|
||||
- utilities for subtracting snapshots;
|
||||
- tracking clients that need specific measures;
|
||||
- synchronization between e10s processes.
|
||||
35
toolkit/components/perfmonitoring/moz.build
Normal file
35
toolkit/components/perfmonitoring/moz.build
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
|
||||
|
||||
XPIDL_MODULE = 'toolkit_perfmonitoring'
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'AddonWatcher.jsm',
|
||||
'PerformanceStats-content.js',
|
||||
'PerformanceStats.jsm',
|
||||
'PerformanceWatcher-content.js',
|
||||
'PerformanceWatcher.jsm',
|
||||
]
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsIPerformanceStats.idl',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'nsPerformanceStats.cpp'
|
||||
]
|
||||
|
||||
EXPORTS += [
|
||||
'nsPerformanceStats.h'
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/dom/base',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
333
toolkit/components/perfmonitoring/nsIPerformanceStats.idl
Normal file
333
toolkit/components/perfmonitoring/nsIPerformanceStats.idl
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-*/
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
#include "nsIArray.idl"
|
||||
#include "nsIDOMWindow.idl"
|
||||
|
||||
/**
|
||||
* Mechanisms for querying the current process about performance
|
||||
* information.
|
||||
*
|
||||
* JavaScript clients should rather use PerformanceStats.jsm.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Identification details for a performance group.
|
||||
*
|
||||
* A performance group is a set of JavaScript compartments whose
|
||||
* performance is observed as a single entity. Typical examples of
|
||||
* performance groups: an add-on, a webpage without its frames, a
|
||||
* webpage with all its frames, the entire JS runtime, ...
|
||||
*/
|
||||
[scriptable, builtinclass, uuid(994c56be-939a-4f20-8364-124f6422d86a)]
|
||||
interface nsIPerformanceGroupDetails: nsISupports {
|
||||
/**
|
||||
* An identifier unique to the component.
|
||||
*
|
||||
* This identifier is somewhat human-readable to aid with debugging,
|
||||
* but clients should not rely upon the format.
|
||||
*/
|
||||
readonly attribute AString groupId;
|
||||
|
||||
/**
|
||||
* A somewhat human-readable name for the component.
|
||||
*/
|
||||
readonly attribute AString name;
|
||||
|
||||
/**
|
||||
* If the component is an add-on, the ID of the addon,
|
||||
* otherwise an empty string.
|
||||
*/
|
||||
readonly attribute AString addonId;
|
||||
|
||||
/**
|
||||
* If the component is code executed in a window, the ID of the topmost
|
||||
* outer window (i.e. the tab), otherwise 0.
|
||||
*/
|
||||
readonly attribute uint64_t windowId;
|
||||
|
||||
/**
|
||||
* `true` if this component is executed with system privileges
|
||||
* (e.g. the platform itself or an add-on), `false` otherwise
|
||||
* (e.g. webpages).
|
||||
*/
|
||||
readonly attribute bool isSystem;
|
||||
|
||||
/**
|
||||
* The process running this group.
|
||||
*/
|
||||
readonly attribute unsigned long long processId;
|
||||
|
||||
/**
|
||||
* `true` if the code is executed in a content process, `false` otherwise.
|
||||
*/
|
||||
readonly attribute bool isContentProcess;
|
||||
};
|
||||
|
||||
/**
|
||||
* Snapshot of the performance of a component, e.g. an add-on, a web
|
||||
* page, system built-ins, a module or the entire process itself.
|
||||
*
|
||||
* All values are monotonic and are updated only when
|
||||
* `nsIPerformanceStatsService.isStopwatchActive` is `true`.
|
||||
*/
|
||||
[scriptable, builtinclass, uuid(8a635d4b-aa56-466b-9a7d-9f91ca9405ef)]
|
||||
interface nsIPerformanceStats: nsIPerformanceGroupDetails {
|
||||
/**
|
||||
* Total amount of time spent executing code in this group, in
|
||||
* microseconds.
|
||||
*/
|
||||
readonly attribute unsigned long long totalUserTime;
|
||||
readonly attribute unsigned long long totalSystemTime;
|
||||
readonly attribute unsigned long long totalCPOWTime;
|
||||
|
||||
/**
|
||||
* Total number of times code execution entered this group,
|
||||
* since process launch. This may be greater than the number
|
||||
* of times we have entered the event loop.
|
||||
*/
|
||||
readonly attribute unsigned long long ticks;
|
||||
|
||||
/**
|
||||
* Jank indicator.
|
||||
*
|
||||
* durations[i] == number of times execution of this group
|
||||
* lasted at lest 2^i ms.
|
||||
*/
|
||||
void getDurations([optional] out unsigned long aCount,
|
||||
[retval, array, size_is(aCount)]out unsigned long long aNumberOfOccurrences);
|
||||
};
|
||||
|
||||
/**
|
||||
* A snapshot of the performance data of the process.
|
||||
*/
|
||||
[scriptable, builtinclass, uuid(13cc235b-739e-4690-b0e3-d89cbe036a93)]
|
||||
interface nsIPerformanceSnapshot: nsISupports {
|
||||
/**
|
||||
* Data on all individual components.
|
||||
*/
|
||||
nsIArray getComponentsData();
|
||||
|
||||
/**
|
||||
* Information on the process itself.
|
||||
*
|
||||
* This contains the total amount of time spent executing JS code,
|
||||
* the total amount of time spent waiting for system calls while
|
||||
* executing JS code, the total amount of time performing blocking
|
||||
* inter-process calls, etc.
|
||||
*/
|
||||
nsIPerformanceStats getProcessData();
|
||||
};
|
||||
|
||||
/**
|
||||
* A performance alert.
|
||||
*/
|
||||
[scriptable, builtinclass, uuid(a85706ab-d703-4687-8865-78cd771eab93)]
|
||||
interface nsIPerformanceAlert: nsISupports {
|
||||
/**
|
||||
* A slowdown was detected.
|
||||
*
|
||||
* See REASON_JANK_* for details on whether this slowdown was user-noticeable.
|
||||
*/
|
||||
const unsigned long REASON_SLOWDOWN = 1;
|
||||
|
||||
/**
|
||||
* This alert was triggered during a jank in animation.
|
||||
*
|
||||
* In the current implementation, we consider that there is a jank
|
||||
* in animation if delivery of the vsync message to the main thread
|
||||
* has been delayed too much (see
|
||||
* nsIPerformanceStatsService.animationJankLevelThreshold).
|
||||
*
|
||||
* Note that this is a heuristic which may provide false positives,
|
||||
* so clients of this API are expected to perform post-processing to
|
||||
* filter out such false positives.
|
||||
*/
|
||||
const unsigned long REASON_JANK_IN_ANIMATION = 2;
|
||||
|
||||
/**
|
||||
* This alert was triggered during a jank in user input.
|
||||
*
|
||||
* In the current implementation, we consider that there is a jank
|
||||
* in animation if a user input was received either immediately
|
||||
* before executing the offending code (see
|
||||
* nsIPerformanceStatsService.userInputDelayThreshold) or while
|
||||
* executing the offending code.
|
||||
*
|
||||
* Note that this is a heuristic which may provide false positives,
|
||||
* so clients of this API are expected to perform post-processing to
|
||||
* filter out such false positives.
|
||||
*/
|
||||
const unsigned long REASON_JANK_IN_INPUT = 4;
|
||||
|
||||
/**
|
||||
* The reason for the alert, as a bitwise or of the various REASON_*
|
||||
* constants.
|
||||
*/
|
||||
readonly attribute unsigned long reason;
|
||||
|
||||
/**
|
||||
* Longest interval spent executing code in this group
|
||||
* since the latest alert, in microseconds.
|
||||
*
|
||||
* Note that the underlying algorithm is probabilistic and may
|
||||
* provide false positives, so clients of this API are expected to
|
||||
* perform post-processing to filter out such false positives. In
|
||||
* particular, a high system load will increase the noise level on
|
||||
* this measure.
|
||||
*/
|
||||
readonly attribute unsigned long long highestJank;
|
||||
|
||||
/**
|
||||
* Longest interval spent executing CPOW in this group
|
||||
* since the latest alert, in microseconds.
|
||||
*
|
||||
* This measure is reliable and involves no heuristics. However,
|
||||
* note that the duration of CPOWs is increased by high system
|
||||
* loads.
|
||||
*/
|
||||
readonly attribute unsigned long long highestCPOW;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* An observer for slow performance alerts.
|
||||
*/
|
||||
[scriptable, function, uuid(b746a929-3fec-420b-8ed8-c35d71995e05)]
|
||||
interface nsIPerformanceObserver: nsISupports {
|
||||
/**
|
||||
* @param target The performance group that caused the jank.
|
||||
* @param alert The performance cost that triggered the alert.
|
||||
*/
|
||||
void observe(in nsIPerformanceGroupDetails target, in nsIPerformanceAlert alert);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A part of the system that may be observed for slow performance.
|
||||
*/
|
||||
[scriptable, builtinclass, uuid(b85720d0-e328-4342-9e46-8ca1acf8c70e)]
|
||||
interface nsIPerformanceObservable: nsISupports {
|
||||
/**
|
||||
* If a single group is being observed, information on this group.
|
||||
*/
|
||||
readonly attribute nsIPerformanceGroupDetails target;
|
||||
|
||||
/**
|
||||
* Add an observer that will be informed in case of jank.
|
||||
*
|
||||
* Set `jankAlertThreshold` to determine how much jank is needed
|
||||
* to trigger alerts.
|
||||
*
|
||||
* If the same observer is added more than once, it will be
|
||||
* triggered as many times as it has been added.
|
||||
*/
|
||||
void addJankObserver(in nsIPerformanceObserver observer);
|
||||
|
||||
/**
|
||||
* Remove an observer previously added with `addJankObserver`.
|
||||
*
|
||||
* Noop if the observer hasn't been added.
|
||||
*/
|
||||
void removeJankObserver(in nsIPerformanceObserver observer);
|
||||
};
|
||||
|
||||
|
||||
[scriptable, uuid(505bc42e-be38-4a53-baba-92cb33690cde)]
|
||||
interface nsIPerformanceStatsService : nsISupports {
|
||||
/**
|
||||
* `true` if we should monitor CPOW, `false` otherwise.
|
||||
*/
|
||||
[implicit_jscontext] attribute bool isMonitoringCPOW;
|
||||
|
||||
/**
|
||||
* `true` if we should monitor jank, `false` otherwise.
|
||||
*/
|
||||
[implicit_jscontext] attribute bool isMonitoringJank;
|
||||
|
||||
/**
|
||||
* `true` if all compartments need to be monitored individually,
|
||||
* `false` if only performance groups (i.e. entire add-ons, entire
|
||||
* webpages, etc.) need to be monitored.
|
||||
*/
|
||||
[implicit_jscontext] attribute bool isMonitoringPerCompartment;
|
||||
|
||||
/**
|
||||
* Capture a snapshot of the performance data.
|
||||
*/
|
||||
[implicit_jscontext] nsIPerformanceSnapshot getSnapshot();
|
||||
|
||||
/**
|
||||
* The threshold, in microseconds, above which a performance group is
|
||||
* considered "slow" and should raise performance alerts.
|
||||
*/
|
||||
attribute unsigned long long jankAlertThreshold;
|
||||
|
||||
/**
|
||||
* If a user is seeing an animation and we spend too long executing
|
||||
* JS code while blocking refresh, this will be visible to the user.
|
||||
*
|
||||
* We assume that any jank during an animation and lasting more than
|
||||
* 2^animationJankLevelThreshold ms will be visible.
|
||||
*/
|
||||
attribute short animationJankLevelThreshold;
|
||||
|
||||
/**
|
||||
* If a user performs an input (e.g. clicking, pressing a key, but
|
||||
* *NOT* moving the mouse), and we spend too long executing JS code
|
||||
* before displaying feedback, this will be visible to the user even
|
||||
* if there is no ongoing animation.
|
||||
*
|
||||
* We assume that any jank during `userInputDelayThreshold` us after
|
||||
* the user input will be visible.
|
||||
*/
|
||||
attribute unsigned long long userInputDelayThreshold;
|
||||
|
||||
/**
|
||||
* A buffering delay, in milliseconds, used by the service to
|
||||
* regroup performance alerts, before observers are actually
|
||||
* noticed. Higher delays let the system avoid redundant
|
||||
* notifications for the same group, and are generally better for
|
||||
* performance.
|
||||
*/
|
||||
attribute unsigned long jankAlertBufferingDelay;
|
||||
|
||||
/**
|
||||
* Get a nsIPerformanceObservable representing an add-on. This
|
||||
* observable may then be used to (un)register for watching
|
||||
* performance alerts for this add-on.
|
||||
*
|
||||
* Note that this method has no way of finding out whether an add-on with this
|
||||
* id is installed on the system. Also note that this covers only the current
|
||||
* process.
|
||||
*
|
||||
* Use special add-on name "*" to get an observable that may be used
|
||||
* to (un)register for watching performance alerts of all add-ons at
|
||||
* once.
|
||||
*/
|
||||
nsIPerformanceObservable getObservableAddon(in AString addonId);
|
||||
|
||||
/**
|
||||
* Get a nsIPerformanceObservable representing a DOM window. This
|
||||
* observable may then be used to (un)register for watching
|
||||
* performance alerts for this window.
|
||||
*
|
||||
* Note that this covers only the current process.
|
||||
*
|
||||
* Use special window id 0 to get an observable that may be used to
|
||||
* (un)register for watching performance alerts of all windows at
|
||||
* once.
|
||||
*/
|
||||
nsIPerformanceObservable getObservableWindow(in unsigned long long windowId);
|
||||
};
|
||||
|
||||
|
||||
%{C++
|
||||
#define NS_TOOLKIT_PERFORMANCESTATSSERVICE_CID {0xfd7435d4, 0x9ec4, 0x4699, \
|
||||
{0xad, 0xd4, 0x1b, 0xe8, 0x3d, 0xd6, 0x8e, 0xf3} }
|
||||
#define NS_TOOLKIT_PERFORMANCESTATSSERVICE_CONTRACTID "@mozilla.org/toolkit/performance-stats-service;1"
|
||||
%}
|
||||
1620
toolkit/components/perfmonitoring/nsPerformanceStats.cpp
Normal file
1620
toolkit/components/perfmonitoring/nsPerformanceStats.cpp
Normal file
File diff suppressed because it is too large
Load diff
825
toolkit/components/perfmonitoring/nsPerformanceStats.h
Normal file
825
toolkit/components/perfmonitoring/nsPerformanceStats.h
Normal file
|
|
@ -0,0 +1,825 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef nsPerformanceStats_h
|
||||
#define nsPerformanceStats_h
|
||||
|
||||
#include "jsapi.h"
|
||||
|
||||
#include "nsHashKeys.h"
|
||||
#include "nsTHashtable.h"
|
||||
|
||||
#include "nsIObserver.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
|
||||
#include "nsIPerformanceStats.h"
|
||||
|
||||
class nsPerformanceGroup;
|
||||
class nsPerformanceGroupDetails;
|
||||
|
||||
typedef mozilla::Vector<RefPtr<nsPerformanceGroup>> GroupVector;
|
||||
|
||||
/**
|
||||
* A data structure for registering observers interested in
|
||||
* performance alerts.
|
||||
*
|
||||
* Each performance group owns a single instance of this class.
|
||||
* Additionally, the service owns instances designed to observe the
|
||||
* performance alerts in all add-ons (respectively webpages).
|
||||
*/
|
||||
class nsPerformanceObservationTarget final: public nsIPerformanceObservable {
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIPERFORMANCEOBSERVABLE
|
||||
|
||||
/**
|
||||
* `true` if this target has at least once performance observer
|
||||
* registered, `false` otherwise.
|
||||
*/
|
||||
bool HasObservers() const;
|
||||
|
||||
/**
|
||||
* Notify all the observers that jank has happened.
|
||||
*/
|
||||
void NotifyJankObservers(nsIPerformanceGroupDetails* source, nsIPerformanceAlert* gravity);
|
||||
|
||||
/**
|
||||
* Set the details on the group being observed.
|
||||
*/
|
||||
void SetTarget(nsPerformanceGroupDetails* details);
|
||||
|
||||
private:
|
||||
~nsPerformanceObservationTarget() {}
|
||||
|
||||
// The observers for this target. We hold them as a vector, despite
|
||||
// the linear removal cost, as we expect that the typical number of
|
||||
// observers will be lower than 3, and that (un)registrations will
|
||||
// be fairly infrequent.
|
||||
mozilla::Vector<nsCOMPtr<nsIPerformanceObserver>> mObservers;
|
||||
|
||||
// Details on the group being observed. May be `nullptr`.
|
||||
RefPtr<nsPerformanceGroupDetails> mDetails;
|
||||
};
|
||||
|
||||
/**
|
||||
* The base class for entries of maps from addon id/window id to
|
||||
* performance group.
|
||||
*
|
||||
* Performance observers may be registered before their group is
|
||||
* created (e.g., one may register an observer for an add-on before
|
||||
* all its modules are loaded, or even before the add-on is loaded at
|
||||
* all or for an observer for a webpage before all its iframes are
|
||||
* loaded). This class serves to hold the observation target until the
|
||||
* performance group may be created, and then to associate the
|
||||
* observation target and the performance group.
|
||||
*/
|
||||
class nsGroupHolder {
|
||||
public:
|
||||
nsGroupHolder()
|
||||
: mGroup(nullptr)
|
||||
, mPendingObservationTarget(nullptr)
|
||||
{ }
|
||||
|
||||
/**
|
||||
* Get the observation target, creating it if necessary.
|
||||
*/
|
||||
nsPerformanceObservationTarget* ObservationTarget();
|
||||
|
||||
/**
|
||||
* Get the group, if it has been created.
|
||||
*
|
||||
* May return `null` if the group hasn't been created yet.
|
||||
*/
|
||||
class nsPerformanceGroup* GetGroup();
|
||||
|
||||
/**
|
||||
* Set the group.
|
||||
*
|
||||
* Once this method has been called, calling
|
||||
* `this->ObservationTarget()` and `group->ObservationTarget()` is equivalent.
|
||||
*
|
||||
* Must only be called once.
|
||||
*/
|
||||
void SetGroup(class nsPerformanceGroup*);
|
||||
private:
|
||||
// The group. Initially `nullptr`, until we have called `SetGroup`.
|
||||
class nsPerformanceGroup* mGroup;
|
||||
|
||||
// The observation target. Instantiated by the first call to
|
||||
// `ObservationTarget()`.
|
||||
RefPtr<nsPerformanceObservationTarget> mPendingObservationTarget;
|
||||
};
|
||||
|
||||
/**
|
||||
* An implementation of the nsIPerformanceStatsService.
|
||||
*
|
||||
* Note that this implementation is not thread-safe.
|
||||
*/
|
||||
class nsPerformanceStatsService final : public nsIPerformanceStatsService,
|
||||
public nsIObserver
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIPERFORMANCESTATSSERVICE
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
||||
nsPerformanceStatsService();
|
||||
nsresult Init();
|
||||
|
||||
private:
|
||||
nsresult InitInternal();
|
||||
void Dispose();
|
||||
~nsPerformanceStatsService();
|
||||
|
||||
protected:
|
||||
friend nsPerformanceGroup;
|
||||
|
||||
/**
|
||||
* `false` until `Init()` and after `Dispose()`, `true` inbetween.
|
||||
*/
|
||||
bool mIsAvailable;
|
||||
|
||||
/**
|
||||
* `true` once we have called `Dispose()`.
|
||||
*/
|
||||
bool mDisposed;
|
||||
|
||||
/**
|
||||
* A unique identifier for the process.
|
||||
*
|
||||
* Process HANDLE under Windows, pid under Unix.
|
||||
*/
|
||||
const uint64_t mProcessId;
|
||||
|
||||
/**
|
||||
* The JS Context for the main thread.
|
||||
*/
|
||||
JSContext* const mContext;
|
||||
|
||||
/**
|
||||
* Generate unique identifiers.
|
||||
*/
|
||||
uint64_t GetNextId();
|
||||
uint64_t mUIdCounter;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Extract a snapshot of performance statistics from a performance group.
|
||||
*/
|
||||
static nsIPerformanceStats* GetStatsForGroup(const js::PerformanceGroup* group);
|
||||
static nsIPerformanceStats* GetStatsForGroup(const nsPerformanceGroup* group);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the performance groups associated to a given JS compartment.
|
||||
*
|
||||
* A compartment is typically associated to the following groups:
|
||||
* - the top group, shared by the entire process;
|
||||
* - the window group, if the code is executed in a window, shared
|
||||
* by all compartments for that window (typically, all frames);
|
||||
* - the add-on group, if the code is executed as an add-on, shared
|
||||
* by all compartments for that add-on (typically, all modules);
|
||||
* - the compartment's own group.
|
||||
*
|
||||
* Pre-condition: the VM must have entered the JS compartment.
|
||||
*
|
||||
* The caller is expected to cache the results of this method, as
|
||||
* calling it more than once may not return the same instances of
|
||||
* performance groups.
|
||||
*/
|
||||
bool GetPerformanceGroups(JSContext* cx, js::PerformanceGroupVector&);
|
||||
static bool GetPerformanceGroupsCallback(JSContext* cx, js::PerformanceGroupVector&, void* closure);
|
||||
|
||||
|
||||
|
||||
/**********************************************************
|
||||
*
|
||||
* Sets of all performance groups, indexed by several keys.
|
||||
*
|
||||
* These sets do not keep the performance groups alive. Rather, a
|
||||
* performance group is inserted in the relevant sets upon
|
||||
* construction and removed from the sets upon destruction or when
|
||||
* we Dispose() of the service.
|
||||
*
|
||||
* A `nsPerformanceGroup` is typically kept alive (as a
|
||||
* `js::PerformanceGroup`) by the JSCompartment to which it is
|
||||
* associated. It may also temporarily be kept alive by the JS
|
||||
* stack, in particular in case of nested event loops.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Set of performance groups associated to add-ons, indexed
|
||||
* by add-on id. Each item is shared by all the compartments
|
||||
* that belong to the add-on.
|
||||
*/
|
||||
struct AddonIdToGroup: public nsStringHashKey,
|
||||
public nsGroupHolder {
|
||||
explicit AddonIdToGroup(const nsAString* key)
|
||||
: nsStringHashKey(key)
|
||||
{ }
|
||||
};
|
||||
nsTHashtable<AddonIdToGroup> mAddonIdToGroup;
|
||||
|
||||
/**
|
||||
* Set of performance groups associated to windows, indexed by outer
|
||||
* window id. Each item is shared by all the compartments that
|
||||
* belong to the window.
|
||||
*/
|
||||
struct WindowIdToGroup: public nsUint64HashKey,
|
||||
public nsGroupHolder {
|
||||
explicit WindowIdToGroup(const uint64_t* key)
|
||||
: nsUint64HashKey(key)
|
||||
{}
|
||||
};
|
||||
nsTHashtable<WindowIdToGroup> mWindowIdToGroup;
|
||||
|
||||
/**
|
||||
* Set of all performance groups.
|
||||
*/
|
||||
struct Groups: public nsPtrHashKey<nsPerformanceGroup> {
|
||||
explicit Groups(const nsPerformanceGroup* key)
|
||||
: nsPtrHashKey<nsPerformanceGroup>(key)
|
||||
{}
|
||||
};
|
||||
nsTHashtable<Groups> mGroups;
|
||||
|
||||
/**
|
||||
* The performance group representing the runtime itself. All
|
||||
* compartments are associated to this group.
|
||||
*/
|
||||
RefPtr<nsPerformanceGroup> mTopGroup;
|
||||
|
||||
/**********************************************************
|
||||
*
|
||||
* Measuring and recording the CPU use of the system.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the OS-reported time spent in userland/systemland, in
|
||||
* microseconds. On most platforms, this data is per-thread,
|
||||
* but on some platforms we need to fall back to per-process.
|
||||
*
|
||||
* Data is not guaranteed to be monotonic.
|
||||
*/
|
||||
nsresult GetResources(uint64_t* userTime, uint64_t* systemTime) const;
|
||||
|
||||
/**
|
||||
* Amount of user/system CPU time used by the thread (or process,
|
||||
* for platforms that don't support per-thread measure) since start.
|
||||
* Updated by `StopwatchStart` at most once per event.
|
||||
*
|
||||
* Unit: microseconds.
|
||||
*/
|
||||
uint64_t mUserTimeStart;
|
||||
uint64_t mSystemTimeStart;
|
||||
|
||||
bool mIsHandlingUserInput;
|
||||
|
||||
/**
|
||||
* The number of user inputs since the start of the process. Used to
|
||||
* determine whether the current iteration has triggered a
|
||||
* (JS-implemented) user input.
|
||||
*/
|
||||
uint64_t mUserInputCount;
|
||||
|
||||
/**********************************************************
|
||||
*
|
||||
* Callbacks triggered by the JS VM when execution of JavaScript
|
||||
* code starts/completes.
|
||||
*
|
||||
* As measures of user CPU time/system CPU time have low resolution
|
||||
* (and are somewhat slow), we measure both only during the calls to
|
||||
* `StopwatchStart`/`StopwatchCommit` and we make the assumption
|
||||
* that each group's user/system CPU time is proportional to the
|
||||
* number of clock cycles spent executing code in the group between
|
||||
* `StopwatchStart`/`StopwatchCommit`.
|
||||
*
|
||||
* The results may be skewed by the thread being rescheduled to a
|
||||
* different CPU during the measure, but we expect that on average,
|
||||
* the skew will have limited effects, and will generally tend to
|
||||
* make already-slow executions appear slower.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Execution of JavaScript code has started. This may happen several
|
||||
* times in succession if the JavaScript code contains nested event
|
||||
* loops, in which case only the innermost call will receive
|
||||
* `StopwatchCommitCallback`.
|
||||
*
|
||||
* @param iteration The number of times we have started executing
|
||||
* JavaScript code.
|
||||
*/
|
||||
static bool StopwatchStartCallback(uint64_t iteration, void* closure);
|
||||
bool StopwatchStart(uint64_t iteration);
|
||||
|
||||
/**
|
||||
* Execution of JavaScript code has reached completion (including
|
||||
* enqueued microtasks). In cse of tested event loops, any ongoing
|
||||
* measurement on outer loops is silently cancelled without any call
|
||||
* to this method.
|
||||
*
|
||||
* @param iteration The number of times we have started executing
|
||||
* JavaScript code.
|
||||
* @param recentGroups The groups that have seen activity during this
|
||||
* event.
|
||||
*/
|
||||
static bool StopwatchCommitCallback(uint64_t iteration,
|
||||
js::PerformanceGroupVector& recentGroups,
|
||||
void* closure);
|
||||
bool StopwatchCommit(uint64_t iteration, js::PerformanceGroupVector& recentGroups);
|
||||
|
||||
/**
|
||||
* The number of times we have started executing JavaScript code.
|
||||
*/
|
||||
uint64_t mIteration;
|
||||
|
||||
/**
|
||||
* Commit performance measures of a single group.
|
||||
*
|
||||
* Data is transfered from `group->recent*` to `group->data`.
|
||||
*
|
||||
*
|
||||
* @param iteration The current iteration.
|
||||
* @param userTime The total user CPU time for this thread (or
|
||||
* process, if per-thread data is not available) between the
|
||||
* calls to `StopwatchStart` and `StopwatchCommit`.
|
||||
* @param systemTime The total system CPU time for this thread (or
|
||||
* process, if per-thread data is not available) between the
|
||||
* calls to `StopwatchStart` and `StopwatchCommit`.
|
||||
* @param cycles The total number of cycles for this thread
|
||||
* between the calls to `StopwatchStart` and `StopwatchCommit`.
|
||||
* @param isJankVisible If `true`, expect that the user will notice
|
||||
* any slowdown.
|
||||
* @param group The group containing the data to commit.
|
||||
*/
|
||||
void CommitGroup(uint64_t iteration,
|
||||
uint64_t userTime, uint64_t systemTime, uint64_t cycles,
|
||||
bool isJankVisible,
|
||||
nsPerformanceGroup* group);
|
||||
|
||||
|
||||
|
||||
|
||||
/**********************************************************
|
||||
*
|
||||
* To check whether our algorithm makes sense, we keep count of the
|
||||
* number of times the process has been rescheduled to another CPU
|
||||
* while we were monitoring the performance of a group and we upload
|
||||
* this data through Telemetry.
|
||||
*/
|
||||
nsresult UpdateTelemetry();
|
||||
|
||||
uint64_t mProcessStayed;
|
||||
uint64_t mProcessMoved;
|
||||
uint32_t mProcessUpdateCounter;
|
||||
|
||||
/**********************************************************
|
||||
*
|
||||
* Options controlling measurements.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Determine if we are measuring the performance of every individual
|
||||
* compartment (in particular, every individual module, frame,
|
||||
* sandbox). Note that this makes measurements noticeably slower.
|
||||
*/
|
||||
bool mIsMonitoringPerCompartment;
|
||||
|
||||
|
||||
/**********************************************************
|
||||
*
|
||||
* Determining whether jank is user-visible.
|
||||
*/
|
||||
|
||||
/**
|
||||
* `true` if we believe that any slowdown can cause a noticeable
|
||||
* delay in handling user-input.
|
||||
*
|
||||
* In the current implementation, we return `true` if the latest
|
||||
* user input was less than MAX_DURATION_OF_INTERACTION_MS ago. This
|
||||
* includes all inputs (mouse, keyboard, other devices), with the
|
||||
* exception of mousemove.
|
||||
*/
|
||||
bool IsHandlingUserInput();
|
||||
|
||||
|
||||
public:
|
||||
/**********************************************************
|
||||
*
|
||||
* Letting observers register themselves to watch for performance
|
||||
* alerts.
|
||||
*
|
||||
* To avoid saturating clients with alerts (or even creating loops
|
||||
* of alerts), each alert is buffered. At the end of each iteration
|
||||
* of the event loop, groups that have caused performance alerts
|
||||
* are registered in a set of pending alerts, and the collection
|
||||
* timer hasn't been started yet, it is started. Once the timer
|
||||
* firers, we gather all the pending alerts, empty the set and
|
||||
* dispatch to observers.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Clear the set of pending alerts and dispatch the pending alerts
|
||||
* to observers.
|
||||
*/
|
||||
void NotifyJankObservers(const mozilla::Vector<uint64_t>& previousJankLevels);
|
||||
|
||||
private:
|
||||
/**
|
||||
* The set of groups for which we know that an alert should be
|
||||
* raised. This set is cleared once `mPendingAlertsCollector`
|
||||
* fires.
|
||||
*
|
||||
* Invariant: no group may appear twice in this vector.
|
||||
*/
|
||||
GroupVector mPendingAlerts;
|
||||
|
||||
/**
|
||||
* A timer callback in charge of collecting the groups in
|
||||
* `mPendingAlerts` and triggering `NotifyJankObservers` to dispatch
|
||||
* performance alerts.
|
||||
*/
|
||||
RefPtr<class PendingAlertsCollector> mPendingAlertsCollector;
|
||||
|
||||
|
||||
/**
|
||||
* Observation targets that are not attached to a specific group.
|
||||
*/
|
||||
struct UniversalTargets {
|
||||
UniversalTargets();
|
||||
/**
|
||||
* A target for observers interested in watching all addons.
|
||||
*/
|
||||
RefPtr<nsPerformanceObservationTarget> mAddons;
|
||||
|
||||
/**
|
||||
* A target for observers interested in watching all windows.
|
||||
*/
|
||||
RefPtr<nsPerformanceObservationTarget> mWindows;
|
||||
};
|
||||
UniversalTargets mUniversalTargets;
|
||||
|
||||
/**
|
||||
* The threshold, in microseconds, above which a performance group is
|
||||
* considered "slow" and should raise performance alerts.
|
||||
*/
|
||||
uint64_t mJankAlertThreshold;
|
||||
|
||||
/**
|
||||
* A buffering delay, in milliseconds, used by the service to
|
||||
* regroup performance alerts, before observers are actually
|
||||
* noticed. Higher delays let the system avoid redundant
|
||||
* notifications for the same group, and are generally better for
|
||||
* performance.
|
||||
*/
|
||||
uint32_t mJankAlertBufferingDelay;
|
||||
|
||||
/**
|
||||
* The threshold above which jank, as reported by the refresh drivers,
|
||||
* is considered user-visible.
|
||||
*
|
||||
* A value of n means that any jank above 2^n ms will be considered
|
||||
* user visible.
|
||||
*/
|
||||
short mJankLevelVisibilityThreshold;
|
||||
|
||||
/**
|
||||
* The number of microseconds during which we assume that a
|
||||
* user-interaction can keep the code jank-critical. Any user
|
||||
* interaction that lasts longer than this duration is expected to
|
||||
* either have already caused jank or have caused a nested event
|
||||
* loop.
|
||||
*
|
||||
* In either case, we consider that monitoring
|
||||
* jank-during-interaction after this duration is useless.
|
||||
*/
|
||||
uint64_t mMaxExpectedDurationOfInteractionUS;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Container for performance data.
|
||||
*
|
||||
* All values are monotonic.
|
||||
*
|
||||
* All values are updated after running to completion.
|
||||
*/
|
||||
struct PerformanceData {
|
||||
/**
|
||||
* Number of times we have spent at least 2^n consecutive
|
||||
* milliseconds executing code in this group.
|
||||
* durations[0] is increased whenever we spend at least 1 ms
|
||||
* executing code in this group
|
||||
* durations[1] whenever we spend 2ms+
|
||||
* ...
|
||||
* durations[i] whenever we spend 2^ims+
|
||||
*/
|
||||
uint64_t mDurations[10];
|
||||
|
||||
/**
|
||||
* Total amount of time spent executing code in this group, in
|
||||
* microseconds.
|
||||
*/
|
||||
uint64_t mTotalUserTime;
|
||||
uint64_t mTotalSystemTime;
|
||||
uint64_t mTotalCPOWTime;
|
||||
|
||||
/**
|
||||
* Total number of times code execution entered this group, since
|
||||
* process launch. This may be greater than the number of times we
|
||||
* have entered the event loop.
|
||||
*/
|
||||
uint64_t mTicks;
|
||||
|
||||
PerformanceData();
|
||||
PerformanceData(const PerformanceData& from) = default;
|
||||
PerformanceData& operator=(const PerformanceData& from) = default;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Identification information for an item that can hold performance
|
||||
* data.
|
||||
*/
|
||||
class nsPerformanceGroupDetails final: public nsIPerformanceGroupDetails {
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIPERFORMANCEGROUPDETAILS
|
||||
|
||||
nsPerformanceGroupDetails(const nsAString& aName,
|
||||
const nsAString& aGroupId,
|
||||
const nsAString& aAddonId,
|
||||
const uint64_t aWindowId,
|
||||
const uint64_t aProcessId,
|
||||
const bool aIsSystem)
|
||||
: mName(aName)
|
||||
, mGroupId(aGroupId)
|
||||
, mAddonId(aAddonId)
|
||||
, mWindowId(aWindowId)
|
||||
, mProcessId(aProcessId)
|
||||
, mIsSystem(aIsSystem)
|
||||
{ }
|
||||
public:
|
||||
const nsAString& Name() const;
|
||||
const nsAString& GroupId() const;
|
||||
const nsAString& AddonId() const;
|
||||
uint64_t WindowId() const;
|
||||
uint64_t ProcessId() const;
|
||||
bool IsAddon() const;
|
||||
bool IsWindow() const;
|
||||
bool IsSystem() const;
|
||||
bool IsContentProcess() const;
|
||||
private:
|
||||
~nsPerformanceGroupDetails() {}
|
||||
|
||||
const nsString mName;
|
||||
const nsString mGroupId;
|
||||
const nsString mAddonId;
|
||||
const uint64_t mWindowId;
|
||||
const uint64_t mProcessId;
|
||||
const bool mIsSystem;
|
||||
};
|
||||
|
||||
/**
|
||||
* The kind of compartments represented by this group.
|
||||
*/
|
||||
enum class PerformanceGroupScope {
|
||||
/**
|
||||
* This group represents the entire runtime (i.e. the thread).
|
||||
*/
|
||||
RUNTIME,
|
||||
|
||||
/**
|
||||
* This group represents all the compartments executed in a window.
|
||||
*/
|
||||
WINDOW,
|
||||
|
||||
/**
|
||||
* This group represents all the compartments provided by an addon.
|
||||
*/
|
||||
ADDON,
|
||||
|
||||
/**
|
||||
* This group represents a single compartment.
|
||||
*/
|
||||
COMPARTMENT,
|
||||
};
|
||||
|
||||
/**
|
||||
* A concrete implementation of `js::PerformanceGroup`, also holding
|
||||
* performance data. Instances may represent individual compartments,
|
||||
* windows, addons or the entire runtime.
|
||||
*
|
||||
* This class is intended to be the sole implementation of
|
||||
* `js::PerformanceGroup`.
|
||||
*/
|
||||
class nsPerformanceGroup final: public js::PerformanceGroup {
|
||||
public:
|
||||
|
||||
// Ideally, we would define the enum class in nsPerformanceGroup,
|
||||
// but this seems to choke some versions of gcc.
|
||||
typedef PerformanceGroupScope GroupScope;
|
||||
|
||||
/**
|
||||
* Construct a performance group.
|
||||
*
|
||||
* @param cx The container context. Used to generate a unique identifier.
|
||||
* @param service The performance service. Used during destruction to
|
||||
* cleanup the hash tables.
|
||||
* @param name A name for the group, designed mostly for debugging purposes,
|
||||
* so it should be at least somewhat human-readable.
|
||||
* @param addonId The identifier of the add-on. Should be "" when the
|
||||
* group is not part of an add-on,
|
||||
* @param windowId The identifier of the window. Should be 0 when the
|
||||
* group is not part of a window.
|
||||
* @param processId A unique identifier for the process.
|
||||
* @param isSystem `true` if the code of the group is executed with
|
||||
* system credentials, `false` otherwise.
|
||||
* @param scope the scope of this group.
|
||||
*/
|
||||
static nsPerformanceGroup*
|
||||
Make(JSContext* cx,
|
||||
nsPerformanceStatsService* service,
|
||||
const nsAString& name,
|
||||
const nsAString& addonId,
|
||||
uint64_t windowId,
|
||||
uint64_t processId,
|
||||
bool isSystem,
|
||||
GroupScope scope);
|
||||
|
||||
/**
|
||||
* Utility: type-safer conversion from js::PerformanceGroup to nsPerformanceGroup.
|
||||
*/
|
||||
static inline nsPerformanceGroup* Get(js::PerformanceGroup* self) {
|
||||
return static_cast<nsPerformanceGroup*>(self);
|
||||
}
|
||||
static inline const nsPerformanceGroup* Get(const js::PerformanceGroup* self) {
|
||||
return static_cast<const nsPerformanceGroup*>(self);
|
||||
}
|
||||
|
||||
/**
|
||||
* The performance data committed to this group.
|
||||
*/
|
||||
PerformanceData data;
|
||||
|
||||
/**
|
||||
* The scope of this group. Used to determine whether the group
|
||||
* should be (de)activated.
|
||||
*/
|
||||
GroupScope Scope() const;
|
||||
|
||||
/**
|
||||
* Identification details for this group.
|
||||
*/
|
||||
nsPerformanceGroupDetails* Details() const;
|
||||
|
||||
/**
|
||||
* Cleanup any references.
|
||||
*/
|
||||
void Dispose();
|
||||
|
||||
/**
|
||||
* Set the observation target for this group.
|
||||
*
|
||||
* This method must be called exactly once, when the performance
|
||||
* group is attached to its `nsGroupHolder`.
|
||||
*/
|
||||
void SetObservationTarget(nsPerformanceObservationTarget*);
|
||||
|
||||
|
||||
/**
|
||||
* `true` if we have already noticed that a performance alert should
|
||||
* be raised for this group but we have not dispatched it yet,
|
||||
* `false` otherwise.
|
||||
*/
|
||||
bool HasPendingAlert() const;
|
||||
void SetHasPendingAlert(bool value);
|
||||
|
||||
protected:
|
||||
nsPerformanceGroup(nsPerformanceStatsService* service,
|
||||
const nsAString& name,
|
||||
const nsAString& groupId,
|
||||
const nsAString& addonId,
|
||||
uint64_t windowId,
|
||||
uint64_t processId,
|
||||
bool isSystem,
|
||||
GroupScope scope);
|
||||
|
||||
|
||||
/**
|
||||
* Virtual implementation of `delete`, to make sure that objects are
|
||||
* destoyed with an implementation of `delete` compatible with the
|
||||
* implementation of `new` used to allocate them.
|
||||
*
|
||||
* Called by SpiderMonkey.
|
||||
*/
|
||||
virtual void Delete() override {
|
||||
delete this;
|
||||
}
|
||||
~nsPerformanceGroup();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Identification details for this group.
|
||||
*/
|
||||
RefPtr<nsPerformanceGroupDetails> mDetails;
|
||||
|
||||
/**
|
||||
* The stats service. Used to perform cleanup during destruction.
|
||||
*/
|
||||
RefPtr<nsPerformanceStatsService> mService;
|
||||
|
||||
/**
|
||||
* The scope of this group. Used to determine whether the group
|
||||
* should be (de)activated.
|
||||
*/
|
||||
const GroupScope mScope;
|
||||
|
||||
// Observing performance alerts.
|
||||
|
||||
public:
|
||||
/**
|
||||
* The observation target, used to register observers.
|
||||
*/
|
||||
nsPerformanceObservationTarget* ObservationTarget() const;
|
||||
|
||||
/**
|
||||
* Record a jank duration.
|
||||
*
|
||||
* Update the highest recent jank if necessary.
|
||||
*/
|
||||
void RecordJank(uint64_t jank);
|
||||
uint64_t HighestRecentJank();
|
||||
|
||||
/**
|
||||
* Record a CPOW duration.
|
||||
*
|
||||
* Update the highest recent CPOW if necessary.
|
||||
*/
|
||||
void RecordCPOW(uint64_t cpow);
|
||||
uint64_t HighestRecentCPOW();
|
||||
|
||||
/**
|
||||
* Record that this group has recently been involved in handling
|
||||
* user input. Note that heuristics are involved here, so the
|
||||
* result is not 100% accurate.
|
||||
*/
|
||||
void RecordUserInput();
|
||||
bool HasRecentUserInput();
|
||||
|
||||
/**
|
||||
* Reset recent values (recent highest CPOW and jank, involvement in
|
||||
* user input).
|
||||
*/
|
||||
void ResetRecent();
|
||||
private:
|
||||
/**
|
||||
* The target used by observers to register for watching slow
|
||||
* performance alerts caused by this group.
|
||||
*
|
||||
* May be nullptr for groups that cannot be watched (the top group).
|
||||
*/
|
||||
RefPtr<class nsPerformanceObservationTarget> mObservationTarget;
|
||||
|
||||
/**
|
||||
* The highest jank encountered since jank observers for this group
|
||||
* were last called, in microseconds.
|
||||
*/
|
||||
uint64_t mHighestJank;
|
||||
|
||||
/**
|
||||
* The highest CPOW encountered since jank observers for this group
|
||||
* were last called, in microseconds.
|
||||
*/
|
||||
uint64_t mHighestCPOW;
|
||||
|
||||
/**
|
||||
* `true` if this group has been involved in handling user input,
|
||||
* `false` otherwise.
|
||||
*
|
||||
* Note that we use heuristics to determine whether a group is
|
||||
* involved in handling user input, so this value is not 100%
|
||||
* accurate.
|
||||
*/
|
||||
bool mHasRecentUserInput;
|
||||
|
||||
/**
|
||||
* `true` if this group has caused a performance alert and this alert
|
||||
* hasn't been dispatched yet.
|
||||
*
|
||||
* We use this as part of the buffering of performance alerts. If
|
||||
* the group generates several alerts several times during the
|
||||
* buffering delay, we only wish to add the group once to the list
|
||||
* of alerts.
|
||||
*/
|
||||
bool mHasPendingAlert;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"extends": [
|
||||
"../../../../../testing/mochitest/browser.eslintrc.js"
|
||||
]
|
||||
};
|
||||
15
toolkit/components/perfmonitoring/tests/browser/browser.ini
Normal file
15
toolkit/components/perfmonitoring/tests/browser/browser.ini
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
[DEFAULT]
|
||||
head = head.js
|
||||
tags = addons
|
||||
support-files =
|
||||
browser_Addons_sample.xpi
|
||||
browser_compartments.html
|
||||
browser_compartments_frame.html
|
||||
browser_compartments_script.js
|
||||
|
||||
[browser_AddonWatcher.js]
|
||||
[browser_compartments.js]
|
||||
skip-if = os == "linux" && !debug && e10s # Bug 1230018
|
||||
[browser_addonPerformanceAlerts.js]
|
||||
[browser_addonPerformanceAlerts_2.js]
|
||||
[browser_webpagePerformanceAlerts.js]
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Tests for AddonWatcher.jsm
|
||||
|
||||
"use strict";
|
||||
|
||||
requestLongerTimeout(2);
|
||||
|
||||
Cu.import("resource://gre/modules/Promise.jsm", this);
|
||||
Cu.import("resource://gre/modules/AddonManager.jsm", this);
|
||||
Cu.import("resource://gre/modules/Services.jsm", this);
|
||||
|
||||
const ADDON_URL = "http://example.com/browser/toolkit/components/perfmonitoring/tests/browser/browser_Addons_sample.xpi";
|
||||
const ADDON_ID = "addonwatcher-test@mozilla.com";
|
||||
|
||||
add_task(function* init() {
|
||||
info("Installing test add-on");
|
||||
let installer = yield new Promise(resolve => AddonManager.getInstallForURL(ADDON_URL, resolve, "application/x-xpinstall"));
|
||||
if (installer.error) {
|
||||
throw installer.error;
|
||||
}
|
||||
let installed = new Promise((resolve, reject) => installer.addListener({
|
||||
onInstallEnded: (_, addon) => resolve(addon),
|
||||
onInstallFailed: reject,
|
||||
onDownloadFailed: reject
|
||||
}));
|
||||
|
||||
// We also need to wait for the add-on to report that it's ready
|
||||
// to be used in the test.
|
||||
let ready = TestUtils.topicObserved("test-addonwatcher-ready");
|
||||
installer.install();
|
||||
|
||||
info("Waiting for installation to terminate");
|
||||
let addon = yield installed;
|
||||
|
||||
yield ready;
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
info("Uninstalling test add-on");
|
||||
addon.uninstall()
|
||||
});
|
||||
|
||||
Preferences.set("browser.addon-watch.warmup-ms", 0);
|
||||
Preferences.set("browser.addon-watch.freeze-threshold-micros", 0);
|
||||
Preferences.set("browser.addon-watch.jank-threshold-micros", 0);
|
||||
Preferences.set("browser.addon-watch.occurrences-between-alerts", 0);
|
||||
Preferences.set("browser.addon-watch.delay-between-alerts-ms", 0);
|
||||
Preferences.set("browser.addon-watch.delay-between-freeze-alerts-ms", 0);
|
||||
Preferences.set("browser.addon-watch.max-simultaneous-reports", 10000);
|
||||
Preferences.set("browser.addon-watch.deactivate-after-idle-ms", 100000000);
|
||||
registerCleanupFunction(() => {
|
||||
for (let k of [
|
||||
"browser.addon-watch.warmup-ms",
|
||||
"browser.addon-watch.freeze-threshold-micros",
|
||||
"browser.addon-watch.jank-threshold-micros",
|
||||
"browser.addon-watch.occurrences-between-alerts",
|
||||
"browser.addon-watch.delay-between-alerts-ms",
|
||||
"browser.addon-watch.delay-between-freeze-alerts-ms",
|
||||
"browser.addon-watch.max-simultaneous-reports",
|
||||
"browser.addon-watch.deactivate-after-idle-ms"
|
||||
]) {
|
||||
Preferences.reset(k);
|
||||
}
|
||||
});
|
||||
|
||||
let oldCanRecord = Services.telemetry.canRecordExtended;
|
||||
Services.telemetry.canRecordExtended = true;
|
||||
AddonWatcher.init();
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
AddonWatcher.paused = true;
|
||||
Services.telemetry.canRecordExtended = oldCanRecord;
|
||||
});
|
||||
});
|
||||
|
||||
// Utility function to burn some resource, trigger a reaction of the add-on watcher
|
||||
// and check both its notification and telemetry.
|
||||
let burn_rubber = Task.async(function*({histogramName, topic, expectedMinSum}) {
|
||||
let detected = false;
|
||||
let observer = (_, topic, id) => {
|
||||
Assert.equal(id, ADDON_ID, "The add-on watcher has detected the misbehaving addon");
|
||||
detected = true;
|
||||
};
|
||||
|
||||
try {
|
||||
info("Preparing add-on watcher");
|
||||
|
||||
Services.obs.addObserver(observer, AddonWatcher.TOPIC_SLOW_ADDON_DETECTED, false);
|
||||
|
||||
let histogram = Services.telemetry.getKeyedHistogramById(histogramName);
|
||||
histogram.clear();
|
||||
let snap1 = histogram.snapshot(ADDON_ID);
|
||||
Assert.equal(snap1.sum, 0, `Histogram ${histogramName} is initially empty for the add-on`);
|
||||
|
||||
let histogramUpdated = false;
|
||||
do {
|
||||
info(`Burning some CPU with ${topic}. This should cause an add-on watcher notification`);
|
||||
yield new Promise(resolve => setTimeout(resolve, 100));
|
||||
Services.obs.notifyObservers(null, topic, "");
|
||||
yield new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
let snap2 = histogram.snapshot(ADDON_ID);
|
||||
histogramUpdated = snap2.sum > 0;
|
||||
info(`For the moment, histogram ${histogramName} shows ${snap2.sum} => ${histogramUpdated}`);
|
||||
info(`For the moment, we have ${detected?"":"NOT "}detected the slow add-on`);
|
||||
} while (!histogramUpdated || !detected);
|
||||
|
||||
let snap3 = histogram.snapshot(ADDON_ID);
|
||||
Assert.ok(snap3.sum >= expectedMinSum, `Histogram ${histogramName} recorded a gravity of ${snap3.sum}, expecting at least ${expectedMinSum}.`);
|
||||
} finally {
|
||||
Services.obs.removeObserver(observer, AddonWatcher.TOPIC_SLOW_ADDON_DETECTED);
|
||||
}
|
||||
});
|
||||
|
||||
// Test that burning CPU will cause the add-on watcher to notice that
|
||||
// the add-on is misbehaving.
|
||||
add_task(function* test_burn_CPU() {
|
||||
yield burn_rubber({
|
||||
histogramName: "PERF_MONITORING_SLOW_ADDON_JANK_US",
|
||||
topic: "test-addonwatcher-burn-some-cpu",
|
||||
expectedMinSum: 7,
|
||||
});
|
||||
});
|
||||
|
||||
// Test that burning content CPU will cause the add-on watcher to notice that
|
||||
// the add-on is misbehaving.
|
||||
/*
|
||||
Blocked by bug 1227283.
|
||||
add_task(function* test_burn_content_CPU() {
|
||||
yield burn_rubber({
|
||||
histogramName: "PERF_MONITORING_SLOW_ADDON_JANK_US",
|
||||
topic: "test-addonwatcher-burn-some-content-cpu",
|
||||
expectedMinSum: 7,
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
||||
// Test that burning CPOW will cause the add-on watcher to notice that
|
||||
// the add-on is misbehaving.
|
||||
add_task(function* test_burn_CPOW() {
|
||||
if (!gMultiProcessBrowser) {
|
||||
info("This is a single-process Firefox, we can't test for CPOW");
|
||||
return;
|
||||
}
|
||||
yield burn_rubber({
|
||||
histogramName: "PERF_MONITORING_SLOW_ADDON_CPOW_US",
|
||||
topic: "test-addonwatcher-burn-some-cpow",
|
||||
expectedMinSum: 400,
|
||||
});
|
||||
});
|
||||
Binary file not shown.
105
toolkit/components/perfmonitoring/tests/browser/browser_Addons_sample/bootstrap.js
vendored
Normal file
105
toolkit/components/perfmonitoring/tests/browser/browser_Addons_sample/bootstrap.js
vendored
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Sample for browser_AddonWatcher.js
|
||||
|
||||
"use strict";
|
||||
|
||||
const {utils: Cu, classes: Cc, interfaces: Ci} = Components;
|
||||
|
||||
const TOPIC_BURNCPU = "test-addonwatcher-burn-some-cpu";
|
||||
const TOPIC_BURNCPOW = "test-addonwatcher-burn-some-cpow";
|
||||
const TOPIC_BURNCONTENTCPU = "test-addonwatcher-burn-some-content-cpu";
|
||||
const TOPIC_READY = "test-addonwatcher-ready";
|
||||
|
||||
const MESSAGE_BURNCPOW = "test-addonwatcher-cpow:init";
|
||||
const URL_FRAMESCRIPT = "chrome://addonwatcher-test/content/framescript.js";
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm", this);
|
||||
const { setTimeout } = Cu.import("resource://gre/modules/Timer.jsm", {});
|
||||
Cu.import("resource://gre/modules/Task.jsm", this);
|
||||
|
||||
let globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
|
||||
.getService(Ci.nsIMessageListenerManager);
|
||||
|
||||
/**
|
||||
* Spend some time using CPU.
|
||||
*/
|
||||
function burnCPU() {
|
||||
let ignored = [];
|
||||
let start = Date.now();
|
||||
let i = 0;
|
||||
while (Date.now() - start < 1000) {
|
||||
ignored[i++ % 2] = i;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Spend some time in CPOW.
|
||||
*/
|
||||
function burnCPOW() {
|
||||
gBurnCPOW();
|
||||
}
|
||||
let gBurnCPOW = null;
|
||||
|
||||
function burnContentCPU() {
|
||||
setTimeout(() => { try {
|
||||
gBurnContentCPU()
|
||||
} catch (ex) {
|
||||
dump(`test-addon error: ${ex}\n`);
|
||||
} }, 0);
|
||||
}
|
||||
let gBurnContentCPU = null;
|
||||
|
||||
let gTab = null;
|
||||
let gTabBrowser = null;
|
||||
|
||||
function startup() {
|
||||
Services.obs.addObserver(burnCPU, TOPIC_BURNCPU, false);
|
||||
Services.obs.addObserver(burnCPOW, TOPIC_BURNCPOW, false);
|
||||
Services.obs.addObserver(burnContentCPU, TOPIC_BURNCONTENTCPU, false);
|
||||
|
||||
let windows = Services.wm.getEnumerator("navigator:browser");
|
||||
let win = windows.getNext();
|
||||
gTabBrowser = win.gBrowser;
|
||||
gTab = gTabBrowser.addTab("about:robots");
|
||||
gBurnContentCPU = function() {
|
||||
gTab.linkedBrowser.messageManager.sendAsyncMessage("test-addonwatcher-burn-some-content-cpu", {});
|
||||
}
|
||||
|
||||
gTab.linkedBrowser.messageManager.loadFrameScript(URL_FRAMESCRIPT, false);
|
||||
globalMM.loadFrameScript(URL_FRAMESCRIPT, false);
|
||||
|
||||
if (Services.appinfo.browserTabsRemoteAutostart) {
|
||||
// This profile has e10s enabled, which means we'll want to
|
||||
// test CPOW traffic.
|
||||
globalMM.addMessageListener("test-addonwatcher-cpow:init", function waitForCPOW(msg) {
|
||||
if (Components.utils.isCrossProcessWrapper(msg.objects.burnCPOW)) {
|
||||
gBurnCPOW = msg.objects.burnCPOW;
|
||||
globalMM.removeMessageListener("test-addonwatcher-cpow:init", waitForCPOW);
|
||||
Services.obs.notifyObservers(null, TOPIC_READY, null);
|
||||
} else {
|
||||
Cu.reportError("test-addonwatcher-cpow:init didn't give us a CPOW! Expect timeouts.");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// e10s is not enabled, so a CPOW is not necessary - we can report ready
|
||||
// right away.
|
||||
Services.obs.notifyObservers(null, TOPIC_READY, null);
|
||||
}
|
||||
}
|
||||
|
||||
function shutdown() {
|
||||
Services.obs.removeObserver(burnCPU, TOPIC_BURNCPU);
|
||||
Services.obs.removeObserver(burnCPOW, TOPIC_BURNCPOW);
|
||||
Services.obs.removeObserver(burnContentCPU, TOPIC_BURNCONTENTCPU);
|
||||
gTabBrowser.removeTab(gTab);
|
||||
}
|
||||
|
||||
function install() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
function uninstall() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
echo "Rebuilding browser_Addons_sample.xpi..."
|
||||
zip -r ../browser_Addons_sample.xpi .
|
||||
echo "
|
||||
Done! Don't forget to sign it: https://wiki.mozilla.org/EngineeringProductivity/HowTo/SignExtensions"
|
||||
|
|
@ -0,0 +1 @@
|
|||
content addonwatcher-test content/
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/* 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";
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function burnCPOW(msg) {
|
||||
dump(`Addon: content burnCPU start ${Math.sin(Math.random())}\n`);
|
||||
let start = content.performance.now();
|
||||
let ignored = [];
|
||||
while (content.performance.now() - start < 5000) {
|
||||
ignored[ignored.length % 2] = ignored.length;
|
||||
}
|
||||
dump(`Addon: content burnCPU done: ${content.performance.now() - start}\n`);
|
||||
}
|
||||
|
||||
if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
|
||||
sendAsyncMessage("test-addonwatcher-cpow:init", {}, {burnCPOW});
|
||||
}
|
||||
|
||||
addMessageListener("test-addonwatcher-burn-some-content-cpu", burnCPOW);
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>addonwatcher-test@mozilla.com</em:id>
|
||||
<em:version>1.1</em:version>
|
||||
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
|
||||
<em:minVersion>0.3</em:minVersion>
|
||||
<em:maxVersion>*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>toolkit@mozilla.org</em:id>
|
||||
<em:minVersion>1</em:minVersion>
|
||||
<em:maxVersion>*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
|
||||
<em:name>Sample for browser_AddonWatcher.js</em:name>
|
||||
|
||||
</Description>
|
||||
</RDF>
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests for PerformanceWatcher watching slow addons.
|
||||
*/
|
||||
|
||||
add_task(function* init() {
|
||||
// Get rid of buffering.
|
||||
let service = Cc["@mozilla.org/toolkit/performance-stats-service;1"].getService(
|
||||
Ci.nsIPerformanceStatsService);
|
||||
let oldDelay = service.jankAlertBufferingDelay;
|
||||
|
||||
service.jankAlertBufferingDelay = 0 /* ms */;
|
||||
registerCleanupFunction(() => {
|
||||
info("Cleanup");
|
||||
service.jankAlertBufferingDelay = oldDelay;
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* test_install_addon_then_watch_it() {
|
||||
for (let topic of ["burnCPU", "promiseBurnContentCPU", "promiseBurnCPOW"]) {
|
||||
info(`Starting subtest ${topic}`);
|
||||
info("Spawning fake add-on, making sure that the compartment is initialized");
|
||||
let addon = new AddonBurner();
|
||||
yield addon.promiseInitialized;
|
||||
addon.burnCPU();
|
||||
|
||||
info(`Check that burning CPU triggers the real listener, but not the fake listener ${topic}`);
|
||||
let realListener = new AddonListener(addon.addonId, (group, details) => {
|
||||
if (group.addonId == addon.addonId) {
|
||||
return details.highestJank;
|
||||
}
|
||||
throw new Error(`I shouldn't have been called with addon ${group.addonId}`);
|
||||
});
|
||||
let fakeListener = new AddonListener(addon.addonId + "-fake-" + Math.random(), group => true); // This listener should never be triggered.
|
||||
let universalListener = new AddonListener("*", alerts => {
|
||||
info(`AddonListener: received alerts ${JSON.stringify(alerts)}`);
|
||||
let alert = alerts.find(({source}) => {
|
||||
return source.addonId == addon.addonId;
|
||||
});
|
||||
if (alert) {
|
||||
info(`AddonListener: I found an alert for ${addon.addonId}`);
|
||||
return alert.details.highestJank;
|
||||
}
|
||||
info(`AddonListener: I didn't find any alert for ${addon.addonId}`);
|
||||
return null;
|
||||
});
|
||||
|
||||
// Waiting a little – listeners are buffered.
|
||||
yield new Promise(resolve => setTimeout(resolve, 100));
|
||||
yield addon.run(topic, 10, realListener);
|
||||
// Waiting a little – listeners are buffered.
|
||||
yield new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
Assert.ok(realListener.triggered, `1. The real listener was triggered ${topic}`);
|
||||
Assert.ok(universalListener.triggered, `1. The universal listener was triggered ${topic}`);
|
||||
Assert.ok(!fakeListener.triggered, `1. The fake listener was not triggered ${topic}`);
|
||||
Assert.ok(realListener.result >= addon.jankThreshold, `1. jank is at least ${addon.jankThreshold/1000}ms (${realListener.result/1000}ms) ${topic}`);
|
||||
|
||||
info(`Attempting to remove a performance listener incorrectly, check that this does not hurt our real listener ${topic}`);
|
||||
Assert.throws(() => PerformanceWatcher.removePerformanceListener({addonId: addon.addonId}, () => {}));
|
||||
Assert.throws(() => PerformanceWatcher.removePerformanceListener({addonId: addon.addonId + "-unbound-id-" + Math.random()}, realListener.listener));
|
||||
|
||||
yield addon.run(topic, 10, realListener);
|
||||
// Waiting a little – listeners are buffered.
|
||||
yield new Promise(resolve => setTimeout(resolve, 300));
|
||||
|
||||
Assert.ok(realListener.triggered, `2. The real listener was triggered ${topic}`);
|
||||
Assert.ok(universalListener.triggered, `2. The universal listener was triggered ${topic}`);
|
||||
Assert.ok(!fakeListener.triggered, `2. The fake listener was not triggered ${topic}`);
|
||||
Assert.ok(realListener.result >= 200000, `2. jank is at least 300ms (${realListener.result/1000}ms) ${topic}`);
|
||||
|
||||
info(`Attempting to remove correctly, check if the listener is still triggered ${topic}`);
|
||||
realListener.unregister();
|
||||
yield addon.run(topic, 3, realListener);
|
||||
Assert.ok(!realListener.triggered, `3. After being unregistered, the real listener was not triggered ${topic}`);
|
||||
Assert.ok(universalListener.triggered, `3. The universal listener is still triggered ${topic}`);
|
||||
|
||||
info("Unregistering universal listener");
|
||||
// Waiting a little – listeners are buffered.
|
||||
yield new Promise(resolve => setTimeout(resolve, 100));
|
||||
universalListener.unregister();
|
||||
// Waiting a little – listeners are buffered.
|
||||
yield new Promise(resolve => setTimeout(resolve, 100));
|
||||
yield addon.run(topic, 3, realListener);
|
||||
Assert.ok(!universalListener.triggered, `4. After being unregistered, the universal listener is not triggered ${topic}`);
|
||||
|
||||
fakeListener.unregister();
|
||||
addon.dispose();
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests for PerformanceWatcher watching slow addons.
|
||||
*/
|
||||
|
||||
add_task(function* test_watch_addon_then_install_it() {
|
||||
for (let topic of ["burnCPU", "promiseBurnContentCPU", "promiseBurnCPOW"]) {
|
||||
let addonId = "addon:test_watch_addons_before_installing" + Math.random();
|
||||
let realListener = new AddonListener(addonId, (group, details) => {
|
||||
if (group.addonId == addonId) {
|
||||
return details.highestJank;
|
||||
}
|
||||
throw new Error(`I shouldn't have been called with addon ${group.addonId}`);
|
||||
});
|
||||
|
||||
info("Now install the add-on, *after* having installed the listener");
|
||||
let addon = new AddonBurner(addonId);
|
||||
|
||||
Assert.ok((yield addon.run(topic, 10, realListener)), `5. The real listener was triggered ${topic}`);
|
||||
Assert.ok(realListener.result >= addon.jankThreshold, `5. jank is at least ${addon.jankThreshold/1000}ms (${realListener.result}µs) ${topic}`);
|
||||
realListener.unregister();
|
||||
addon.dispose();
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>
|
||||
Main frame for test browser_compartments.js
|
||||
</title>
|
||||
</head>
|
||||
<body>
|
||||
Main frame.
|
||||
|
||||
<iframe src="browser_compartments_frame.html?frame=1">
|
||||
Subframe 1
|
||||
</iframe>
|
||||
|
||||
<iframe src="browser_compartments_frame.html?frame=2">
|
||||
Subframe 2.
|
||||
</iframe>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,312 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Test that we see jank that takes place in a webpage,
|
||||
* and that jank from several iframes are actually charged
|
||||
* to the top window.
|
||||
*/
|
||||
Cu.import("resource://gre/modules/PerformanceStats.jsm", this);
|
||||
Cu.import("resource://gre/modules/Services.jsm", this);
|
||||
Cu.import("resource://testing-common/ContentTask.jsm", this);
|
||||
|
||||
|
||||
const URL = "http://example.com/browser/toolkit/components/perfmonitoring/tests/browser/browser_compartments.html?test=" + Math.random();
|
||||
const PARENT_TITLE = `Main frame for test browser_compartments.js ${Math.random()}`;
|
||||
const FRAME_TITLE = `Subframe for test browser_compartments.js ${Math.random()}`;
|
||||
|
||||
const PARENT_PID = Services.appinfo.processID;
|
||||
|
||||
// This function is injected as source as a frameScript
|
||||
function frameScript() {
|
||||
try {
|
||||
"use strict";
|
||||
|
||||
const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
|
||||
Cu.import("resource://gre/modules/PerformanceStats.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
// Make sure that the stopwatch is now active.
|
||||
let monitor = PerformanceStats.getMonitor(["jank", "cpow", "ticks", "compartments"]);
|
||||
|
||||
addMessageListener("compartments-test:getStatistics", () => {
|
||||
try {
|
||||
monitor.promiseSnapshot().then(snapshot => {
|
||||
sendAsyncMessage("compartments-test:getStatistics", {snapshot, pid: Services.appinfo.processID});
|
||||
});
|
||||
} catch (ex) {
|
||||
Cu.reportError("Error in content (getStatistics): " + ex);
|
||||
Cu.reportError(ex.stack);
|
||||
}
|
||||
});
|
||||
|
||||
addMessageListener("compartments-test:setTitles", titles => {
|
||||
try {
|
||||
content.document.title = titles.data.parent;
|
||||
for (let i = 0; i < content.frames.length; ++i) {
|
||||
content.frames[i].postMessage({title: titles.data.frames}, "*");
|
||||
}
|
||||
console.log("content", "Done setting titles", content.document.title);
|
||||
sendAsyncMessage("compartments-test:setTitles");
|
||||
} catch (ex) {
|
||||
Cu.reportError("Error in content (setTitles): " + ex);
|
||||
Cu.reportError(ex.stack);
|
||||
}
|
||||
});
|
||||
} catch (ex) {
|
||||
Cu.reportError("Error in content (setup): " + ex);
|
||||
Cu.reportError(ex.stack);
|
||||
}
|
||||
}
|
||||
|
||||
// A variant of `Assert` that doesn't spam the logs
|
||||
// in case of success.
|
||||
var SilentAssert = {
|
||||
equal: function(a, b, msg) {
|
||||
if (a == b) {
|
||||
return;
|
||||
}
|
||||
Assert.equal(a, b, msg);
|
||||
},
|
||||
notEqual: function(a, b, msg) {
|
||||
if (a != b) {
|
||||
return;
|
||||
}
|
||||
Assert.notEqual(a, b, msg);
|
||||
},
|
||||
ok: function(a, msg) {
|
||||
if (a) {
|
||||
return;
|
||||
}
|
||||
Assert.ok(a, msg);
|
||||
},
|
||||
leq: function(a, b, msg) {
|
||||
this.ok(a <= b, `${msg}: ${a} <= ${b}`);
|
||||
}
|
||||
};
|
||||
|
||||
var isShuttingDown = false;
|
||||
function monotinicity_tester(source, testName) {
|
||||
// In the background, check invariants:
|
||||
// - numeric data can only ever increase;
|
||||
// - the name, addonId, isSystem of a component never changes;
|
||||
// - the name, addonId, isSystem of the process data;
|
||||
// - there is at most one component with a combination of `name` and `addonId`;
|
||||
// - types, etc.
|
||||
let previous = {
|
||||
processData: null,
|
||||
componentsMap: new Map(),
|
||||
};
|
||||
|
||||
let sanityCheck = function(prev, next) {
|
||||
if (prev == null) {
|
||||
return;
|
||||
}
|
||||
for (let k of ["groupId", "addonId", "isSystem"]) {
|
||||
SilentAssert.equal(prev[k], next[k], `Sanity check (${testName}): ${k} hasn't changed (${prev.name}).`);
|
||||
}
|
||||
for (let [probe, k] of [
|
||||
["jank", "totalUserTime"],
|
||||
["jank", "totalSystemTime"],
|
||||
["cpow", "totalCPOWTime"],
|
||||
["ticks", "ticks"]
|
||||
]) {
|
||||
SilentAssert.equal(typeof next[probe][k], "number", `Sanity check (${testName}): ${k} is a number.`);
|
||||
SilentAssert.leq(prev[probe][k], next[probe][k], `Sanity check (${testName}): ${k} is monotonic.`);
|
||||
SilentAssert.leq(0, next[probe][k], `Sanity check (${testName}): ${k} is >= 0.`)
|
||||
}
|
||||
SilentAssert.equal(prev.jank.durations.length, next.jank.durations.length);
|
||||
for (let i = 0; i < next.jank.durations.length; ++i) {
|
||||
SilentAssert.ok(typeof next.jank.durations[i] == "number" && next.jank.durations[i] >= 0,
|
||||
`Sanity check (${testName}): durations[${i}] is a non-negative number.`);
|
||||
SilentAssert.leq(prev.jank.durations[i], next.jank.durations[i],
|
||||
`Sanity check (${testName}): durations[${i}] is monotonic.`);
|
||||
}
|
||||
for (let i = 0; i < next.jank.durations.length - 1; ++i) {
|
||||
SilentAssert.leq(next.jank.durations[i + 1], next.jank.durations[i],
|
||||
`Sanity check (${testName}): durations[${i}] >= durations[${i + 1}].`)
|
||||
}
|
||||
};
|
||||
let iteration = 0;
|
||||
let frameCheck = Task.async(function*() {
|
||||
if (isShuttingDown) {
|
||||
window.clearInterval(interval);
|
||||
return;
|
||||
}
|
||||
let name = `${testName}: ${iteration++}`;
|
||||
let result = yield source();
|
||||
if (!result) {
|
||||
// This can happen at the end of the test when we attempt
|
||||
// to communicate too late with the content process.
|
||||
window.clearInterval(interval);
|
||||
return;
|
||||
}
|
||||
let {pid, snapshot} = result;
|
||||
|
||||
// Sanity check on the process data.
|
||||
sanityCheck(previous.processData, snapshot.processData);
|
||||
SilentAssert.equal(snapshot.processData.isSystem, true);
|
||||
SilentAssert.equal(snapshot.processData.name, "<process>");
|
||||
SilentAssert.equal(snapshot.processData.addonId, "");
|
||||
SilentAssert.equal(snapshot.processData.processId, pid);
|
||||
previous.procesData = snapshot.processData;
|
||||
|
||||
// Sanity check on components data.
|
||||
let map = new Map();
|
||||
for (let item of snapshot.componentsData) {
|
||||
for (let [probe, k] of [
|
||||
["jank", "totalUserTime"],
|
||||
["jank", "totalSystemTime"],
|
||||
["cpow", "totalCPOWTime"]
|
||||
]) {
|
||||
// Note that we cannot expect components data to be always smaller
|
||||
// than process data, as `getrusage` & co are not monotonic.
|
||||
SilentAssert.leq(item[probe][k], 3 * snapshot.processData[probe][k],
|
||||
`Sanity check (${name}): ${k} of component is not impossibly larger than that of process`);
|
||||
}
|
||||
|
||||
let isCorrectPid = (item.processId == pid && !item.isChildProcess)
|
||||
|| (item.processId != pid && item.isChildProcess);
|
||||
SilentAssert.ok(isCorrectPid, `Pid check (${name}): the item comes from the right process`);
|
||||
|
||||
let key = item.groupId;
|
||||
if (map.has(key)) {
|
||||
let old = map.get(key);
|
||||
Assert.ok(false, `Component ${key} has already been seen. Latest: ${item.addonId||item.name}, previous: ${old.addonId||old.name}`);
|
||||
}
|
||||
map.set(key, item);
|
||||
}
|
||||
for (let item of snapshot.componentsData) {
|
||||
if (!item.parentId) {
|
||||
continue;
|
||||
}
|
||||
let parent = map.get(item.parentId);
|
||||
SilentAssert.ok(parent, `The parent exists ${item.parentId}`);
|
||||
|
||||
for (let [probe, k] of [
|
||||
["jank", "totalUserTime"],
|
||||
["jank", "totalSystemTime"],
|
||||
["cpow", "totalCPOWTime"]
|
||||
]) {
|
||||
// Note that we cannot expect components data to be always smaller
|
||||
// than parent data, as `getrusage` & co are not monotonic.
|
||||
SilentAssert.leq(item[probe][k], 2 * parent[probe][k],
|
||||
`Sanity check (${testName}): ${k} of component is not impossibly larger than that of parent`);
|
||||
}
|
||||
}
|
||||
for (let [key, item] of map) {
|
||||
sanityCheck(previous.componentsMap.get(key), item);
|
||||
previous.componentsMap.set(key, item);
|
||||
}
|
||||
});
|
||||
let interval = window.setInterval(frameCheck, 300);
|
||||
registerCleanupFunction(() => {
|
||||
window.clearInterval(interval);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function* test() {
|
||||
let monitor = PerformanceStats.getMonitor(["jank", "cpow", "ticks"]);
|
||||
|
||||
info("Extracting initial state");
|
||||
let stats0 = yield monitor.promiseSnapshot();
|
||||
Assert.notEqual(stats0.componentsData.length, 0, "There is more than one component");
|
||||
Assert.ok(!stats0.componentsData.find(stat => stat.name.indexOf(URL) != -1),
|
||||
"The url doesn't appear yet");
|
||||
|
||||
let newTab = gBrowser.addTab();
|
||||
let browser = newTab.linkedBrowser;
|
||||
// Setup monitoring in the tab
|
||||
info("Setting up monitoring in the tab");
|
||||
yield ContentTask.spawn(newTab.linkedBrowser, null, frameScript);
|
||||
|
||||
info("Opening URL");
|
||||
newTab.linkedBrowser.loadURI(URL);
|
||||
|
||||
if (Services.sysinfo.getPropertyAsAString("name") == "Windows_NT") {
|
||||
info("Deactivating sanity checks under Windows (bug 1151240)");
|
||||
} else {
|
||||
info("Setting up sanity checks");
|
||||
monotinicity_tester(() => monitor.promiseSnapshot().then(snapshot => ({snapshot, pid: PARENT_PID})), "parent process");
|
||||
monotinicity_tester(() => promiseContentResponseOrNull(browser, "compartments-test:getStatistics", null), "content process" );
|
||||
}
|
||||
|
||||
let skipTotalUserTime = hasLowPrecision();
|
||||
|
||||
|
||||
while (true) {
|
||||
yield new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// We may have race conditions with DOM loading.
|
||||
// Don't waste too much brainpower here, let's just ask
|
||||
// repeatedly for the title to be changed, until this works.
|
||||
info("Setting titles");
|
||||
yield promiseContentResponse(browser, "compartments-test:setTitles", {
|
||||
parent: PARENT_TITLE,
|
||||
frames: FRAME_TITLE
|
||||
});
|
||||
info("Titles set");
|
||||
|
||||
let {snapshot: stats} = (yield promiseContentResponse(browser, "compartments-test:getStatistics", null));
|
||||
|
||||
// Attach titles to components.
|
||||
let titles = [];
|
||||
let map = new Map();
|
||||
let windows = Services.wm.getEnumerator("navigator:browser");
|
||||
while (windows.hasMoreElements()) {
|
||||
let window = windows.getNext();
|
||||
let tabbrowser = window.gBrowser;
|
||||
for (let browser of tabbrowser.browsers) {
|
||||
let id = browser.outerWindowID; // May be `null` if the browser isn't loaded yet
|
||||
if (id != null) {
|
||||
map.set(id, browser);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let stat of stats.componentsData) {
|
||||
if (!stat.windowId) {
|
||||
continue;
|
||||
}
|
||||
let browser = map.get(stat.windowId);
|
||||
if (!browser) {
|
||||
continue;
|
||||
}
|
||||
let title = browser.contentTitle;
|
||||
if (title) {
|
||||
stat.title = title;
|
||||
titles.push(title);
|
||||
}
|
||||
}
|
||||
|
||||
// While the webpage consists in three compartments, we should see only
|
||||
// one `PerformanceData` in `componentsData`. Its `name` is undefined
|
||||
// (could be either the main frame or one of its subframes), but its
|
||||
// `title` should be the title of the main frame.
|
||||
info(`Searching for frame title '${FRAME_TITLE}' in ${JSON.stringify(titles)} (I hope not to find it)`);
|
||||
Assert.ok(!titles.includes(FRAME_TITLE), "Searching by title, the frames don't show up in the list of components");
|
||||
|
||||
info(`Searching for window title '${PARENT_TITLE}' in ${JSON.stringify(titles)} (I hope to find it)`);
|
||||
let parent = stats.componentsData.find(x => x.title == PARENT_TITLE);
|
||||
if (!parent) {
|
||||
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");
|
||||
break;
|
||||
} else if (parent.jank.totalUserTime > 1000) {
|
||||
info("Enough CPU time detected, we're done");
|
||||
break;
|
||||
} else {
|
||||
info(`Not enough CPU time detected: ${parent.jank.totalUserTime}`);
|
||||
}
|
||||
}
|
||||
isShuttingDown = true;
|
||||
|
||||
// Cleanup
|
||||
gBrowser.removeTab(newTab, {skipPermitUnload: true});
|
||||
});
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>
|
||||
Subframe for test browser_compartments.html (do not change this title)
|
||||
</title>
|
||||
<script src="browser_compartments_script.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
Subframe loaded.
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
|
||||
var carryOn = true;
|
||||
|
||||
window.addEventListener("message", e => {
|
||||
console.log("frame content", "message", e);
|
||||
if ("title" in e.data) {
|
||||
document.title = e.data.title;
|
||||
}
|
||||
if ("stop" in e.data) {
|
||||
carryOn = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Use some CPU.
|
||||
var interval = window.setInterval(() => {
|
||||
if (!carryOn) {
|
||||
window.clearInterval(interval);
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute an arbitrary value, print it out to make sure that the JS
|
||||
// engine doesn't discard all our computation.
|
||||
var date = Date.now();
|
||||
var array = [];
|
||||
var i = 0;
|
||||
while (Date.now() - date <= 100) {
|
||||
array[i%2] = i++;
|
||||
}
|
||||
}, 300);
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests for PerformanceWatcher watching slow web pages.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Simulate a slow webpage.
|
||||
*/
|
||||
function WebpageBurner() {
|
||||
CPUBurner.call(this, "http://example.com/browser/toolkit/components/perfmonitoring/tests/browser/browser_compartments.html?test=" + Math.random(), 300000);
|
||||
}
|
||||
WebpageBurner.prototype = Object.create(CPUBurner.prototype);
|
||||
WebpageBurner.prototype.promiseBurnContentCPU = function() {
|
||||
return promiseContentResponse(this._browser, "test-performance-watcher:burn-content-cpu", {});
|
||||
};
|
||||
|
||||
function WebpageListener(windowId, accept) {
|
||||
info(`Creating WebpageListener for ${windowId}`);
|
||||
AlertListener.call(this, accept, {
|
||||
register: () => PerformanceWatcher.addPerformanceListener({windowId}, this.listener),
|
||||
unregister: () => PerformanceWatcher.removePerformanceListener({windowId}, this.listener)
|
||||
});
|
||||
}
|
||||
WebpageListener.prototype = Object.create(AlertListener.prototype);
|
||||
|
||||
add_task(function* init() {
|
||||
// Get rid of buffering.
|
||||
let service = Cc["@mozilla.org/toolkit/performance-stats-service;1"].getService(
|
||||
Ci.nsIPerformanceStatsService);
|
||||
let oldDelay = service.jankAlertBufferingDelay;
|
||||
|
||||
service.jankAlertBufferingDelay = 0 /* ms */;
|
||||
registerCleanupFunction(() => {
|
||||
info("Cleanup");
|
||||
service.jankAlertBufferingDelay = oldDelay;
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* test_open_window_then_watch_it() {
|
||||
let burner = new WebpageBurner();
|
||||
yield burner.promiseInitialized;
|
||||
yield burner.promiseBurnContentCPU();
|
||||
|
||||
info(`Check that burning CPU triggers the real listener, but not the fake listener`);
|
||||
let realListener = new WebpageListener(burner.windowId, (group, details) => {
|
||||
info(`test: realListener for ${burner.tab.linkedBrowser.outerWindowID}: ${group}, ${details}\n`);
|
||||
Assert.equal(group.windowId, burner.windowId, "We should not receive data meant for another group");
|
||||
return details;
|
||||
}); // This listener should be triggered.
|
||||
|
||||
info(`Creating fake burner`);
|
||||
let otherTab = gBrowser.addTab();
|
||||
yield BrowserTestUtils.browserLoaded(otherTab.linkedBrowser);
|
||||
info(`Check that burning CPU triggers the real listener, but not the fake listener`);
|
||||
let fakeListener = new WebpageListener(otherTab.linkedBrowser.outerWindowID, group => group.windowId == burner.windowId); // This listener should never be triggered.
|
||||
let universalListener = new WebpageListener(0, alerts =>
|
||||
alerts.find(alert => alert.source.windowId == burner.windowId)
|
||||
);
|
||||
|
||||
// Waiting a little – listeners are buffered.
|
||||
yield new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
yield burner.run("promiseBurnContentCPU", 20, realListener);
|
||||
Assert.ok(realListener.triggered, `1. The real listener was triggered`);
|
||||
Assert.ok(universalListener.triggered, `1. The universal listener was triggered`);
|
||||
Assert.ok(!fakeListener.triggered, `1. The fake listener was not triggered`);
|
||||
|
||||
if (realListener.result) {
|
||||
Assert.ok(realListener.result.highestJank >= 300, `1. jank is at least 300ms (${realListener.result.highestJank}ms)`);
|
||||
}
|
||||
|
||||
info(`Attempting to remove a performance listener incorrectly, check that this does not hurt our real listener`);
|
||||
Assert.throws(() => PerformanceWatcher.removePerformanceListener({addonId: addon.addonId}, () => {}));
|
||||
Assert.throws(() => PerformanceWatcher.removePerformanceListener({addonId: addon.addonId + "-unbound-id-" + Math.random()}, realListener.listener));
|
||||
|
||||
// Waiting a little – listeners are buffered.
|
||||
yield new Promise(resolve => setTimeout(resolve, 100));
|
||||
yield burner.run("promiseBurnContentCPU", 20, realListener);
|
||||
// Waiting a little – listeners are buffered.
|
||||
yield new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
Assert.ok(realListener.triggered, `2. The real listener was triggered`);
|
||||
Assert.ok(universalListener.triggered, `2. The universal listener was triggered`);
|
||||
Assert.ok(!fakeListener.triggered, `2. The fake listener was not triggered`);
|
||||
if (realListener.result) {
|
||||
Assert.ok(realListener.result.highestJank >= 300, `2. jank is at least 300ms (${realListener.jank}ms)`);
|
||||
}
|
||||
|
||||
info(`Attempting to remove correctly, check if the listener is still triggered`);
|
||||
// Waiting a little – listeners are buffered.
|
||||
yield new Promise(resolve => setTimeout(resolve, 100));
|
||||
realListener.unregister();
|
||||
|
||||
// Waiting a little – listeners are buffered.
|
||||
yield new Promise(resolve => setTimeout(resolve, 100));
|
||||
yield burner.run("promiseBurnContentCPU", 3, realListener);
|
||||
Assert.ok(!realListener.triggered, `3. After being unregistered, the real listener was not triggered`);
|
||||
Assert.ok(universalListener.triggered, `3. The universal listener is still triggered`);
|
||||
|
||||
universalListener.unregister();
|
||||
|
||||
// Waiting a little – listeners are buffered.
|
||||
yield new Promise(resolve => setTimeout(resolve, 100));
|
||||
yield burner.run("promiseBurnContentCPU", 3, realListener);
|
||||
Assert.ok(!universalListener.triggered, `4. After being unregistered, the universal listener is not triggered`);
|
||||
|
||||
fakeListener.unregister();
|
||||
burner.dispose();
|
||||
gBrowser.removeTab(otherTab);
|
||||
});
|
||||
287
toolkit/components/perfmonitoring/tests/browser/head.js
Normal file
287
toolkit/components/perfmonitoring/tests/browser/head.js
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
var { utils: Cu, interfaces: Ci, classes: Cc } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Promise.jsm", this);
|
||||
Cu.import("resource://gre/modules/AddonManager.jsm", this);
|
||||
Cu.import("resource://gre/modules/AddonWatcher.jsm", this);
|
||||
Cu.import("resource://gre/modules/PerformanceWatcher.jsm", this);
|
||||
Cu.import("resource://gre/modules/Services.jsm", this);
|
||||
Cu.import("resource://testing-common/ContentTaskUtils.jsm", this);
|
||||
|
||||
/**
|
||||
* Base class for simulating slow addons/webpages.
|
||||
*/
|
||||
function CPUBurner(url, jankThreshold) {
|
||||
info(`CPUBurner: Opening tab for ${url}\n`);
|
||||
this.url = url;
|
||||
this.tab = gBrowser.addTab(url);
|
||||
this.jankThreshold = jankThreshold;
|
||||
let browser = this.tab.linkedBrowser;
|
||||
this._browser = browser;
|
||||
ContentTask.spawn(this._browser, null, CPUBurner.frameScript);
|
||||
this.promiseInitialized = BrowserTestUtils.browserLoaded(browser);
|
||||
}
|
||||
CPUBurner.prototype = {
|
||||
get windowId() {
|
||||
return this._browser.outerWindowID;
|
||||
},
|
||||
/**
|
||||
* Burn CPU until it triggers a listener with the specified jank threshold.
|
||||
*/
|
||||
run: Task.async(function*(burner, max, listener) {
|
||||
listener.reset();
|
||||
for (let i = 0; i < max; ++i) {
|
||||
yield new Promise(resolve => setTimeout(resolve, 50));
|
||||
try {
|
||||
yield this[burner]();
|
||||
} catch (ex) {
|
||||
return false;
|
||||
}
|
||||
if (listener.triggered && listener.result >= this.jankThreshold) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
dispose: function() {
|
||||
info(`CPUBurner: Closing tab for ${this.url}\n`);
|
||||
gBrowser.removeTab(this.tab);
|
||||
}
|
||||
};
|
||||
// This function is injected in all frames
|
||||
CPUBurner.frameScript = function() {
|
||||
try {
|
||||
"use strict";
|
||||
|
||||
const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
|
||||
let sandboxes = new Map();
|
||||
let getSandbox = function(addonId) {
|
||||
let sandbox = sandboxes.get(addonId);
|
||||
if (!sandbox) {
|
||||
sandbox = Components.utils.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), { addonId });
|
||||
sandboxes.set(addonId, sandbox);
|
||||
}
|
||||
return sandbox;
|
||||
};
|
||||
|
||||
let burnCPU = function() {
|
||||
var start = Date.now();
|
||||
var ignored = [];
|
||||
while (Date.now() - start < 500) {
|
||||
ignored[ignored.length % 2] = ignored.length;
|
||||
}
|
||||
};
|
||||
let burnCPUInSandbox = function(addonId) {
|
||||
let sandbox = getSandbox(addonId);
|
||||
Cu.evalInSandbox(burnCPU.toSource() + "()", sandbox);
|
||||
};
|
||||
|
||||
{
|
||||
let topic = "test-performance-watcher:burn-content-cpu";
|
||||
addMessageListener(topic, function(msg) {
|
||||
try {
|
||||
if (msg.data && msg.data.addonId) {
|
||||
burnCPUInSandbox(msg.data.addonId);
|
||||
} else {
|
||||
burnCPU();
|
||||
}
|
||||
sendAsyncMessage(topic, {});
|
||||
} catch (ex) {
|
||||
dump(`This is the content attempting to burn CPU: error ${ex}\n`);
|
||||
dump(`${ex.stack}\n`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Bind the function to the global context or it might be GC'd during test
|
||||
// causing failures (bug 1230027)
|
||||
this.burnCPOWInSandbox = function(addonId) {
|
||||
try {
|
||||
burnCPUInSandbox(addonId);
|
||||
} catch (ex) {
|
||||
dump(`This is the addon attempting to burn CPOW: error ${ex}\n`);
|
||||
dump(`${ex.stack}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
sendAsyncMessage("test-performance-watcher:cpow-init", {}, {
|
||||
burnCPOWInSandbox: this.burnCPOWInSandbox
|
||||
});
|
||||
|
||||
} catch (ex) {
|
||||
Cu.reportError("This is the addon: error " + ex);
|
||||
Cu.reportError(ex.stack);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Base class for listening to slow group alerts
|
||||
*/
|
||||
function AlertListener(accept, {register, unregister}) {
|
||||
this.listener = (...args) => {
|
||||
if (this._unregistered) {
|
||||
throw new Error("Listener was unregistered");
|
||||
}
|
||||
let result = accept(...args);
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
this.result = result;
|
||||
this.triggered = true;
|
||||
return;
|
||||
};
|
||||
this.triggered = false;
|
||||
this.result = null;
|
||||
this._unregistered = false;
|
||||
this._unregister = unregister;
|
||||
registerCleanupFunction(() => {
|
||||
this.unregister();
|
||||
});
|
||||
register();
|
||||
}
|
||||
AlertListener.prototype = {
|
||||
unregister: function() {
|
||||
this.reset();
|
||||
if (this._unregistered) {
|
||||
info(`head.js: No need to unregister, we're already unregistered.\n`);
|
||||
return;
|
||||
}
|
||||
info(`head.js: Unregistering listener.\n`);
|
||||
this._unregistered = true;
|
||||
this._unregister();
|
||||
info(`head.js: Unregistration complete.\n`);
|
||||
},
|
||||
reset: function() {
|
||||
this.triggered = false;
|
||||
this.result = null;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Simulate a slow add-on.
|
||||
*/
|
||||
function AddonBurner(addonId = "fake add-on id: " + Math.random()) {
|
||||
this.jankThreshold = 200000;
|
||||
CPUBurner.call(this, `http://example.com/?uri=${addonId}`, this.jankThreshold);
|
||||
this._addonId = addonId;
|
||||
this._sandbox = Components.utils.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), { addonId: this._addonId });
|
||||
this._CPOWBurner = null;
|
||||
|
||||
this._promiseCPOWBurner = new Promise(resolve => {
|
||||
this._browser.messageManager.addMessageListener("test-performance-watcher:cpow-init", msg => {
|
||||
// Note that we cannot resolve Promises with CPOWs now that they
|
||||
// have been outlawed in bug 1233497, so we stash it in the
|
||||
// AddonBurner instance instead.
|
||||
this._CPOWBurner = msg.objects.burnCPOWInSandbox;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
AddonBurner.prototype = Object.create(CPUBurner.prototype);
|
||||
Object.defineProperty(AddonBurner.prototype, "addonId", {
|
||||
get: function() {
|
||||
return this._addonId;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Simulate slow code being executed by the add-on in the chrome.
|
||||
*/
|
||||
AddonBurner.prototype.burnCPU = function() {
|
||||
Cu.evalInSandbox(AddonBurner.burnCPU.toSource() + "()", this._sandbox);
|
||||
};
|
||||
|
||||
/**
|
||||
* Simulate slow code being executed by the add-on in a CPOW.
|
||||
*/
|
||||
AddonBurner.prototype.promiseBurnCPOW = Task.async(function*() {
|
||||
yield this._promiseCPOWBurner;
|
||||
ok(this._CPOWBurner, "Got the CPOW burner");
|
||||
let burner = this._CPOWBurner;
|
||||
info("Parent: Preparing to burn CPOW");
|
||||
try {
|
||||
yield burner(this._addonId);
|
||||
info("Parent: Done burning CPOW");
|
||||
} catch (ex) {
|
||||
info(`Parent: Error burning CPOW: ${ex}\n`);
|
||||
info(ex.stack + "\n");
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Simulate slow code being executed by the add-on in the content.
|
||||
*/
|
||||
AddonBurner.prototype.promiseBurnContentCPU = function() {
|
||||
return promiseContentResponse(this._browser, "test-performance-watcher:burn-content-cpu", {addonId: this._addonId});
|
||||
};
|
||||
AddonBurner.burnCPU = function() {
|
||||
var start = Date.now();
|
||||
var ignored = [];
|
||||
while (Date.now() - start < 500) {
|
||||
ignored[ignored.length % 2] = ignored.length;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function AddonListener(addonId, accept) {
|
||||
let target = {addonId};
|
||||
AlertListener.call(this, accept, {
|
||||
register: () => {
|
||||
info(`AddonListener: registering ${JSON.stringify(target, null, "\t")}`);
|
||||
PerformanceWatcher.addPerformanceListener({addonId}, this.listener);
|
||||
},
|
||||
unregister: () => {
|
||||
info(`AddonListener: unregistering ${JSON.stringify(target, null, "\t")}`);
|
||||
PerformanceWatcher.removePerformanceListener({addonId}, this.listener);
|
||||
}
|
||||
});
|
||||
}
|
||||
AddonListener.prototype = Object.create(AlertListener.prototype);
|
||||
|
||||
function promiseContentResponse(browser, name, message) {
|
||||
let mm = browser.messageManager;
|
||||
let promise = new Promise(resolve => {
|
||||
function removeListener() {
|
||||
mm.removeMessageListener(name, listener);
|
||||
}
|
||||
|
||||
function listener(msg) {
|
||||
removeListener();
|
||||
resolve(msg.data);
|
||||
}
|
||||
|
||||
mm.addMessageListener(name, listener);
|
||||
registerCleanupFunction(removeListener);
|
||||
});
|
||||
mm.sendAsyncMessage(name, message);
|
||||
return promise;
|
||||
}
|
||||
function promiseContentResponseOrNull(browser, name, message) {
|
||||
if (!browser.messageManager) {
|
||||
return null;
|
||||
}
|
||||
return promiseContentResponse(browser, name, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* `true` if we are running an OS in which the OS performance
|
||||
* clock has a low precision and might unpredictably
|
||||
* never be updated during the execution of the test.
|
||||
*/
|
||||
function hasLowPrecision() {
|
||||
let [sysName, sysVersion] = [Services.sysinfo.getPropertyAsAString("name"), Services.sysinfo.getPropertyAsDouble("version")];
|
||||
info(`Running ${sysName} version ${sysVersion}`);
|
||||
|
||||
if (sysName == "Windows_NT" && sysVersion < 6) {
|
||||
info("Running old Windows, need to deactivate tests due to bad precision.");
|
||||
return true;
|
||||
}
|
||||
if (sysName == "Linux" && sysVersion <= 2.6) {
|
||||
info("Running old Linux, need to deactivate tests due to bad precision.");
|
||||
return true;
|
||||
}
|
||||
info("This platform has good precision.")
|
||||
return false;
|
||||
}
|
||||
30
toolkit/components/perfmonitoring/tests/browser/install.rdf
Normal file
30
toolkit/components/perfmonitoring/tests/browser/install.rdf
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>addonwatcher-test@mozilla.com</em:id>
|
||||
<em:version>1.0</em:version>
|
||||
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
|
||||
<em:minVersion>0.3</em:minVersion>
|
||||
<em:maxVersion>*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>toolkit@mozilla.org</em:id>
|
||||
<em:minVersion>1</em:minVersion>
|
||||
<em:maxVersion>*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
|
||||
<em:name>Sample for browser_AddonWatcher.js</em:name>
|
||||
|
||||
</Description>
|
||||
</RDF>
|
||||
Loading…
Add table
Add a link
Reference in a new issue