Bug 1575682 - Bridge the perf actor so that it can be reused with the popup r=julienw

Differential Revision: https://phabricator.services.mozilla.com/D43117

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Greg Tatum 2019-09-04 20:05:18 +00:00
Родитель f76d79c608
Коммит 8d2804eb1f
4 изменённых файлов: 249 добавлений и 166 удалений

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

@ -5,183 +5,54 @@
const protocol = require("devtools/shared/protocol");
const { ActorClassWithSpec, Actor } = protocol;
const { actorBridgeWithSpec } = require("devtools/server/actors/common");
const { perfSpec } = require("devtools/shared/specs/perf");
const { Ci } = require("chrome");
const Services = require("Services");
const {
ActorReadyGeckoProfilerInterface,
} = require("devtools/server/performance-new/gecko-profiler-interface");
loader.lazyImporter(
this,
"PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm"
);
// 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
*/
exports.PerfActor = ActorClassWithSpec(perfSpec, {
initialize(conn) {
initialize: function(conn, targetActor) {
Actor.prototype.initialize.call(this, conn);
// The "bridge" is the actual implementation of the actor. It is abstracted
// out into its own class so that it can be re-used with the profiler popup.
this.bridge = new ActorReadyGeckoProfilerInterface();
// 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");
Services.obs.addObserver(
this._observer,
"chrome-document-global-created"
);
Services.obs.addObserver(this._observer, "last-pb-context-exited");
}
_bridgeEvents(this, [
"profile-locked-by-private-browsing",
"profile-unlocked-from-private-browsing",
"profiler-started",
"profiler-stopped",
]);
},
destroy() {
if (!IS_SUPPORTED_PLATFORM) {
return;
}
Services.obs.removeObserver(this._observer, "profiler-started");
Services.obs.removeObserver(this._observer, "profiler-stopped");
Services.obs.removeObserver(
this._observer,
"chrome-document-global-created"
);
Services.obs.removeObserver(this._observer, "last-pb-context-exited");
Actor.prototype.destroy.call(this);
destroy: function(conn) {
Actor.prototype.destroy.call(this, conn);
this.bridge.destroy();
},
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,
// Window length should be Infinite if nothing's been passed.
// options.duration is supported for `perfActorVersion > 0`.
duration: options.duration || 0,
interval: options.interval || 1,
features: options.features || [
"js",
"stackwalk",
"responsiveness",
"threads",
"leaf",
],
threads: options.threads || ["GeckoMain", "Compositor"],
};
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.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();
},
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;
}
let profile;
try {
// Attempt to pull out the data.
profile = await Services.profiler.getProfileDataAsync();
// Stop and discard the buffers.
Services.profiler.StopProfiler();
} catch (e) {
// If there was any kind of error, bailout with no profile.
return null;
}
// Gecko Profiler errors can return an empty object, return null for this case
// as well.
if (Object.keys(profile).length === 0) {
return null;
}
return profile;
},
isActive() {
if (!IS_SUPPORTED_PLATFORM) {
return false;
}
return Services.profiler.IsActive();
},
isSupportedPlatform() {
return IS_SUPPORTED_PLATFORM;
},
isLockedForPrivateBrowsing() {
if (!IS_SUPPORTED_PLATFORM) {
return false;
}
return !Services.profiler.CanProfile();
},
/**
* Watch for events that happen within the browser. These can affect the current
* availability and state of the Gecko Profiler.
*/
_observe(subject, topic, _data) {
switch (topic) {
case "chrome-document-global-created":
if (PrivateBrowsingUtils.isWindowPrivate(subject)) {
this.emit("profile-locked-by-private-browsing");
}
break;
case "last-pb-context-exited":
this.emit("profile-unlocked-from-private-browsing");
break;
case "profiler-started":
const param = subject.QueryInterface(Ci.nsIProfilerStartParams);
this.emit(
topic,
param.entries,
param.interval,
param.features,
param.duration
);
break;
case "profiler-stopped":
this.emit(topic);
break;
}
},
// 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"),
isLockedForPrivateBrowsing: actorBridgeWithSpec("isLockedForPrivateBrowsing"),
});

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

@ -10,6 +10,7 @@ DIRS += [
'actors',
'connectors',
'performance',
'performance-new',
'socket',
'startup',
]

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

@ -0,0 +1,199 @@
/* 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.
*/
const { Ci } = require("chrome");
const Services = require("Services");
loader.lazyImporter(
this,
"PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm"
);
loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
// Some platforms are built without the Gecko Profiler.
const IS_SUPPORTED_PLATFORM = "nsIProfiler" in Ci;
/**
* The GeckoProfiler already has an interface to control it through the
* nsIProfiler component. However, this class implements an interface that can
* be used on both the actor, and the profiler popup. This allows us to share
* the UI for the devtools front-end and the profiler popup code. The devtools
* code needs to work through the actor system, while the popup code controls
* the Gecko Profiler on the current browser.
*/
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");
Services.obs.addObserver(
this._observer,
"chrome-document-global-created"
);
Services.obs.addObserver(this._observer, "last-pb-context-exited");
}
EventEmitter.decorate(this);
}
destroy() {
if (!IS_SUPPORTED_PLATFORM) {
return;
}
Services.obs.removeObserver(this._observer, "profiler-started");
Services.obs.removeObserver(this._observer, "profiler-stopped");
Services.obs.removeObserver(
this._observer,
"chrome-document-global-created"
);
Services.obs.removeObserver(this._observer, "last-pb-context-exited");
}
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,
// Window length should be Infinite if nothing's been passed.
// options.duration is supported for `perfActorVersion > 0`.
duration: options.duration || 0,
interval: options.interval || 1,
features: options.features || [
"js",
"stackwalk",
"responsiveness",
"threads",
"leaf",
],
threads: options.threads || ["GeckoMain", "Compositor"],
};
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.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();
}
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;
}
let profile;
try {
// Attempt to pull out the data.
profile = await Services.profiler.getProfileDataAsync();
// Stop and discard the buffers.
Services.profiler.StopProfiler();
} catch (e) {
// If there was any kind of error, bailout with no profile.
return null;
}
// Gecko Profiler errors can return an empty object, return null for this
// case as well.
if (Object.keys(profile).length === 0) {
return null;
}
return profile;
}
isActive() {
if (!IS_SUPPORTED_PLATFORM) {
return false;
}
return Services.profiler.IsActive();
}
isSupportedPlatform() {
return IS_SUPPORTED_PLATFORM;
}
isLockedForPrivateBrowsing() {
if (!IS_SUPPORTED_PLATFORM) {
return false;
}
return !Services.profiler.CanProfile();
}
/**
* 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 "chrome-document-global-created":
if (PrivateBrowsingUtils.isWindowPrivate(subject)) {
this.emit("profile-locked-by-private-browsing");
}
break;
case "last-pb-context-exited":
this.emit("profile-unlocked-from-private-browsing");
break;
case "profiler-started":
const param = subject.QueryInterface(Ci.nsIProfilerStartParams);
this.emit(
topic,
param.entries,
param.interval,
param.features,
param.duration
);
break;
case "profiler-stopped":
this.emit(topic);
break;
}
}
}
exports.ActorReadyGeckoProfilerInterface = ActorReadyGeckoProfilerInterface;

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

@ -0,0 +1,12 @@
# -*- 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/.
DevToolsModules(
'gecko-profiler-interface.js',
)
with Files('**'):
BUG_COMPONENT = ('DevTools', 'Performance Tools (Profiler/Timeline)')