зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1811853 - Merge gecko-profiler-interface.js into the perf actor r=jdescottes,canaltinova
Differential Revision: https://phabricator.services.mozilla.com/D167844
This commit is contained in:
Родитель
6712709070
Коммит
6fa044fc63
|
@ -6,50 +6,182 @@
|
|||
const { Actor } = require("resource://devtools/shared/protocol.js");
|
||||
const { perfSpec } = require("resource://devtools/shared/specs/perf.js");
|
||||
|
||||
const {
|
||||
actorBridgeWithSpec,
|
||||
} = require("resource://devtools/server/actors/common.js");
|
||||
const {
|
||||
ActorReadyGeckoProfilerInterface,
|
||||
} = require("resource://devtools/shared/performance-new/gecko-profiler-interface.js");
|
||||
loader.lazyRequireGetter(
|
||||
this,
|
||||
"RecordingUtils",
|
||||
"resource://devtools/shared/performance-new/recording-utils.js"
|
||||
);
|
||||
|
||||
// Some platforms are built without the Gecko Profiler.
|
||||
const IS_SUPPORTED_PLATFORM = "nsIProfiler" in Ci;
|
||||
|
||||
/**
|
||||
* Pass on the events from the bridge to the actor.
|
||||
* @param {Object} actor The perf actor
|
||||
* @param {Array<string>} names The event names
|
||||
*/
|
||||
function _bridgeEvents(actor, names) {
|
||||
for (const name of names) {
|
||||
actor.bridge.on(name, (...args) => actor.emit(name, ...args));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The PerfActor wraps the Gecko Profiler interface
|
||||
* The PerfActor wraps the Gecko Profiler interface (aka Services.profiler).
|
||||
*/
|
||||
exports.PerfActor = class PerfActor extends Actor {
|
||||
constructor(conn, targetActor) {
|
||||
constructor(conn) {
|
||||
super(conn, perfSpec);
|
||||
// The "bridge" is the actual implementation of the actor. It is separated
|
||||
// for historical reasons, and could be merged into this class.
|
||||
this.bridge = new ActorReadyGeckoProfilerInterface();
|
||||
|
||||
_bridgeEvents(this, ["profiler-started", "profiler-stopped"]);
|
||||
// Only setup the observers on a supported platform.
|
||||
if (IS_SUPPORTED_PLATFORM) {
|
||||
this._observer = {
|
||||
observe: this._observe.bind(this),
|
||||
};
|
||||
Services.obs.addObserver(this._observer, "profiler-started");
|
||||
Services.obs.addObserver(this._observer, "profiler-stopped");
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
this.bridge.destroy();
|
||||
|
||||
if (!IS_SUPPORTED_PLATFORM) {
|
||||
return;
|
||||
}
|
||||
Services.obs.removeObserver(this._observer, "profiler-started");
|
||||
Services.obs.removeObserver(this._observer, "profiler-stopped");
|
||||
}
|
||||
|
||||
// Connect the rest of the ActorReadyGeckoProfilerInterface's methods to the PerfActor.
|
||||
startProfiler = actorBridgeWithSpec("startProfiler");
|
||||
stopProfilerAndDiscardProfile = actorBridgeWithSpec(
|
||||
"stopProfilerAndDiscardProfile"
|
||||
);
|
||||
getSymbolTable = actorBridgeWithSpec("getSymbolTable");
|
||||
getProfileAndStopProfiler = actorBridgeWithSpec("getProfileAndStopProfiler");
|
||||
isActive = actorBridgeWithSpec("isActive");
|
||||
isSupportedPlatform = actorBridgeWithSpec("isSupportedPlatform");
|
||||
getSupportedFeatures = actorBridgeWithSpec("getSupportedFeatures");
|
||||
startProfiler(options) {
|
||||
if (!IS_SUPPORTED_PLATFORM) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// For a quick implementation, decide on some default values. These may need
|
||||
// to be tweaked or made configurable as needed.
|
||||
const settings = {
|
||||
entries: options.entries || 1000000,
|
||||
duration: options.duration || 0,
|
||||
interval: options.interval || 1,
|
||||
features: options.features || [
|
||||
"js",
|
||||
"stackwalk",
|
||||
"cpu",
|
||||
"responsiveness",
|
||||
],
|
||||
threads: options.threads || ["GeckoMain", "Compositor"],
|
||||
activeTabID: RecordingUtils.getActiveBrowserID(),
|
||||
};
|
||||
|
||||
try {
|
||||
// This can throw an error if the profiler is in the wrong state.
|
||||
Services.profiler.StartProfiler(
|
||||
settings.entries,
|
||||
settings.interval,
|
||||
settings.features,
|
||||
settings.threads,
|
||||
settings.activeTabID,
|
||||
settings.duration
|
||||
);
|
||||
} catch (e) {
|
||||
// In case any errors get triggered, bailout with a false.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
stopProfilerAndDiscardProfile() {
|
||||
if (!IS_SUPPORTED_PLATFORM) {
|
||||
return;
|
||||
}
|
||||
Services.profiler.StopProfiler();
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {string} debugPath
|
||||
* @type {string} breakpadId
|
||||
* @returns {Promise<[number[], number[], number[]]>}
|
||||
*/
|
||||
async getSymbolTable(debugPath, breakpadId) {
|
||||
const [addr, index, buffer] = await Services.profiler.getSymbolTable(
|
||||
debugPath,
|
||||
breakpadId
|
||||
);
|
||||
// The protocol does not support the transfer of typed arrays, so we convert
|
||||
// these typed arrays to plain JS arrays of numbers now.
|
||||
// Our return value type is declared as "array:array:number".
|
||||
return [Array.from(addr), Array.from(index), Array.from(buffer)];
|
||||
}
|
||||
|
||||
async getProfileAndStopProfiler() {
|
||||
if (!IS_SUPPORTED_PLATFORM) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Pause profiler before we collect the profile, so that we don't capture
|
||||
// more samples while the parent process or android threads wait for subprocess profiles.
|
||||
Services.profiler.Pause();
|
||||
|
||||
let profile;
|
||||
try {
|
||||
// Attempt to pull out the data.
|
||||
profile = await Services.profiler.getProfileDataAsync();
|
||||
|
||||
if (Object.keys(profile).length === 0) {
|
||||
console.error(
|
||||
"An empty object was received from getProfileDataAsync.getProfileDataAsync(), " +
|
||||
"meaning that a profile could not successfully be serialized and captured."
|
||||
);
|
||||
profile = null;
|
||||
}
|
||||
} catch (e) {
|
||||
// Explicitly set the profile to null if there as an error.
|
||||
profile = null;
|
||||
console.error(`There was an error fetching a profile`, e);
|
||||
}
|
||||
|
||||
// Stop and discard the buffers.
|
||||
Services.profiler.StopProfiler();
|
||||
|
||||
// Returns a profile when successful, and null when there is an error.
|
||||
return profile;
|
||||
}
|
||||
|
||||
isActive() {
|
||||
if (!IS_SUPPORTED_PLATFORM) {
|
||||
return false;
|
||||
}
|
||||
return Services.profiler.IsActive();
|
||||
}
|
||||
|
||||
isSupportedPlatform() {
|
||||
return IS_SUPPORTED_PLATFORM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch for events that happen within the browser. These can affect the
|
||||
* current availability and state of the Gecko Profiler.
|
||||
*/
|
||||
_observe(subject, topic, _data) {
|
||||
// Note! If emitting new events make sure and update the list of bridged
|
||||
// events in the perf actor.
|
||||
switch (topic) {
|
||||
case "profiler-started":
|
||||
const param = subject.QueryInterface(Ci.nsIProfilerStartParams);
|
||||
this.emit(
|
||||
topic,
|
||||
param.entries,
|
||||
param.interval,
|
||||
param.features,
|
||||
param.duration,
|
||||
param.activeTabID
|
||||
);
|
||||
break;
|
||||
case "profiler-stopped":
|
||||
this.emit(topic);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the supported features of the profiler for the current browser.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
getSupportedFeatures() {
|
||||
if (!IS_SUPPORTED_PLATFORM) {
|
||||
return [];
|
||||
}
|
||||
return Services.profiler.GetFeatures();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,196 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This file is for the new performance panel that targets profiler.firefox.com,
|
||||
* not the default-enabled DevTools performance panel.
|
||||
*/
|
||||
|
||||
loader.lazyRequireGetter(
|
||||
this,
|
||||
"EventEmitter",
|
||||
"resource://devtools/shared/event-emitter.js"
|
||||
);
|
||||
loader.lazyRequireGetter(
|
||||
this,
|
||||
"RecordingUtils",
|
||||
"resource://devtools/shared/performance-new/recording-utils.js"
|
||||
);
|
||||
|
||||
// Some platforms are built without the Gecko Profiler.
|
||||
const IS_SUPPORTED_PLATFORM = "nsIProfiler" in Ci;
|
||||
|
||||
/**
|
||||
* This is an implementation of the perf actor API, using nsIProfiler.
|
||||
* It is in a separate class from the actual perf actor implementation
|
||||
* for historical reasons only. It could be moved into perf.js.
|
||||
*/
|
||||
class ActorReadyGeckoProfilerInterface {
|
||||
constructor() {
|
||||
// Only setup the observers on a supported platform.
|
||||
if (IS_SUPPORTED_PLATFORM) {
|
||||
this._observer = {
|
||||
observe: this._observe.bind(this),
|
||||
};
|
||||
Services.obs.addObserver(this._observer, "profiler-started");
|
||||
Services.obs.addObserver(this._observer, "profiler-stopped");
|
||||
}
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (!IS_SUPPORTED_PLATFORM) {
|
||||
return;
|
||||
}
|
||||
Services.obs.removeObserver(this._observer, "profiler-started");
|
||||
Services.obs.removeObserver(this._observer, "profiler-stopped");
|
||||
}
|
||||
|
||||
startProfiler(options) {
|
||||
if (!IS_SUPPORTED_PLATFORM) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// For a quick implementation, decide on some default values. These may need
|
||||
// to be tweaked or made configurable as needed.
|
||||
const settings = {
|
||||
entries: options.entries || 1000000,
|
||||
duration: options.duration || 0,
|
||||
interval: options.interval || 1,
|
||||
features: options.features || [
|
||||
"js",
|
||||
"stackwalk",
|
||||
"cpu",
|
||||
"responsiveness",
|
||||
],
|
||||
threads: options.threads || ["GeckoMain", "Compositor"],
|
||||
activeTabID: RecordingUtils.getActiveBrowserID(),
|
||||
};
|
||||
|
||||
try {
|
||||
// This can throw an error if the profiler is in the wrong state.
|
||||
Services.profiler.StartProfiler(
|
||||
settings.entries,
|
||||
settings.interval,
|
||||
settings.features,
|
||||
settings.threads,
|
||||
settings.activeTabID,
|
||||
settings.duration
|
||||
);
|
||||
} catch (e) {
|
||||
// In case any errors get triggered, bailout with a false.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
stopProfilerAndDiscardProfile() {
|
||||
if (!IS_SUPPORTED_PLATFORM) {
|
||||
return;
|
||||
}
|
||||
Services.profiler.StopProfiler();
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {string} debugPath
|
||||
* @type {string} breakpadId
|
||||
* @returns {Promise<[number[], number[], number[]]>}
|
||||
*/
|
||||
async getSymbolTable(debugPath, breakpadId) {
|
||||
const [addr, index, buffer] = await Services.profiler.getSymbolTable(
|
||||
debugPath,
|
||||
breakpadId
|
||||
);
|
||||
// The protocol does not support the transfer of typed arrays, so we convert
|
||||
// these typed arrays to plain JS arrays of numbers now.
|
||||
// Our return value type is declared as "array:array:number".
|
||||
return [Array.from(addr), Array.from(index), Array.from(buffer)];
|
||||
}
|
||||
|
||||
async getProfileAndStopProfiler() {
|
||||
if (!IS_SUPPORTED_PLATFORM) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Pause profiler before we collect the profile, so that we don't capture
|
||||
// more samples while the parent process or android threads wait for subprocess profiles.
|
||||
Services.profiler.Pause();
|
||||
|
||||
let profile;
|
||||
try {
|
||||
// Attempt to pull out the data.
|
||||
profile = await Services.profiler.getProfileDataAsync();
|
||||
|
||||
if (Object.keys(profile).length === 0) {
|
||||
console.error(
|
||||
"An empty object was received from getProfileDataAsync.getProfileDataAsync(), " +
|
||||
"meaning that a profile could not successfully be serialized and captured."
|
||||
);
|
||||
profile = null;
|
||||
}
|
||||
} catch (e) {
|
||||
// Explicitly set the profile to null if there as an error.
|
||||
profile = null;
|
||||
console.error(`There was an error fetching a profile`, e);
|
||||
}
|
||||
|
||||
// Stop and discard the buffers.
|
||||
Services.profiler.StopProfiler();
|
||||
|
||||
// Returns a profile when successful, and null when there is an error.
|
||||
return profile;
|
||||
}
|
||||
|
||||
isActive() {
|
||||
if (!IS_SUPPORTED_PLATFORM) {
|
||||
return false;
|
||||
}
|
||||
return Services.profiler.IsActive();
|
||||
}
|
||||
|
||||
isSupportedPlatform() {
|
||||
return IS_SUPPORTED_PLATFORM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch for events that happen within the browser. These can affect the
|
||||
* current availability and state of the Gecko Profiler.
|
||||
*/
|
||||
_observe(subject, topic, _data) {
|
||||
// Note! If emitting new events make sure and update the list of bridged
|
||||
// events in the perf actor.
|
||||
switch (topic) {
|
||||
case "profiler-started":
|
||||
const param = subject.QueryInterface(Ci.nsIProfilerStartParams);
|
||||
this.emit(
|
||||
topic,
|
||||
param.entries,
|
||||
param.interval,
|
||||
param.features,
|
||||
param.duration,
|
||||
param.activeTabID
|
||||
);
|
||||
break;
|
||||
case "profiler-stopped":
|
||||
this.emit(topic);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the supported features of the profiler for the current browser.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
getSupportedFeatures() {
|
||||
if (!IS_SUPPORTED_PLATFORM) {
|
||||
return [];
|
||||
}
|
||||
return Services.profiler.GetFeatures();
|
||||
}
|
||||
}
|
||||
|
||||
exports.ActorReadyGeckoProfilerInterface = ActorReadyGeckoProfilerInterface;
|
|
@ -5,7 +5,6 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
"gecko-profiler-interface.js",
|
||||
"recording-utils.js",
|
||||
)
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче