зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1159480 - Pull out actor-specific logic from Performance Front. r=vp
This commit is contained in:
Родитель
109f1ff513
Коммит
d7d4d70a86
|
@ -0,0 +1,293 @@
|
|||
/* 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";
|
||||
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
const { Promise } = require("resource://gre/modules/Promise.jsm");
|
||||
const {
|
||||
actorCompatibilityBridge, getProfiler,
|
||||
MockMemoryFront, MockTimelineFront,
|
||||
memoryActorSupported, timelineActorSupported
|
||||
} = require("devtools/performance/compatibility");
|
||||
|
||||
loader.lazyRequireGetter(this, "EventEmitter",
|
||||
"devtools/toolkit/event-emitter");
|
||||
loader.lazyRequireGetter(this, "RecordingUtils",
|
||||
"devtools/performance/recording-utils", true);
|
||||
loader.lazyRequireGetter(this, "TimelineFront",
|
||||
"devtools/server/actors/timeline", true);
|
||||
loader.lazyRequireGetter(this, "MemoryFront",
|
||||
"devtools/server/actors/memory", true);
|
||||
loader.lazyRequireGetter(this, "timers",
|
||||
"resource://gre/modules/Timer.jsm");
|
||||
|
||||
// how often do we pull allocation sites from the memory actor
|
||||
const ALLOCATION_SITE_POLL_TIMER = 200; // ms
|
||||
|
||||
const MEMORY_ACTOR_METHODS = [
|
||||
"destroy", "attach", "detach", "getState", "getAllocationsSettings",
|
||||
"getAllocations", "startRecordingAllocations", "stopRecordingAllocations"
|
||||
];
|
||||
|
||||
const TIMELINE_ACTOR_METHODS = [
|
||||
"start", "stop",
|
||||
];
|
||||
|
||||
const PROFILER_ACTOR_METHODS = [
|
||||
"isActive", "startProfiler", "getStartOptions", "stopProfiler",
|
||||
"registerEventNotifications", "unregisterEventNotifications"
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructor for a facade around an underlying ProfilerFront.
|
||||
*/
|
||||
function ProfilerFrontFacade (target) {
|
||||
this._target = target;
|
||||
this._onProfilerEvent = this._onProfilerEvent.bind(this);
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
ProfilerFrontFacade.prototype = {
|
||||
EVENTS: ["console-api-profiler", "profiler-stopped"],
|
||||
|
||||
// Connects to the targets underlying real ProfilerFront.
|
||||
connect: Task.async(function*() {
|
||||
let target = this._target;
|
||||
this._actor = yield getProfiler(target);
|
||||
|
||||
// Fetch and store information about the SPS profiler and
|
||||
// server profiler.
|
||||
this.traits = {};
|
||||
this.traits.filterable = target.getTrait("profilerDataFilterable");
|
||||
|
||||
// Directly register to event notifications when connected
|
||||
// to hook into `console.profile|profileEnd` calls.
|
||||
yield this.registerEventNotifications({ events: this.EVENTS });
|
||||
// TODO bug 1159389, listen directly to actor if supporting new front
|
||||
target.client.addListener("eventNotification", this._onProfilerEvent);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Unregisters events for the underlying profiler actor.
|
||||
*/
|
||||
destroy: Task.async(function *() {
|
||||
yield this.unregisterEventNotifications({ events: this.EVENTS });
|
||||
// TODO bug 1159389, listen directly to actor if supporting new front
|
||||
this._target.client.removeListener("eventNotification", this._onProfilerEvent);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Starts the profiler actor, if necessary.
|
||||
*/
|
||||
start: Task.async(function *(options={}) {
|
||||
// Start the profiler only if it wasn't already active. The built-in
|
||||
// nsIPerformance module will be kept recording, because it's the same instance
|
||||
// for all targets and interacts with the whole platform, so we don't want
|
||||
// to affect other clients by stopping (or restarting) it.
|
||||
let profilerStatus = yield this.isActive();
|
||||
if (profilerStatus.isActive) {
|
||||
this.emit("profiler-already-active");
|
||||
return profilerStatus.currentTime;
|
||||
}
|
||||
|
||||
// Translate options from the recording model into profiler-specific
|
||||
// options for the nsIProfiler
|
||||
let profilerOptions = {
|
||||
entries: options.bufferSize,
|
||||
interval: options.sampleFrequency ? (1000 / (options.sampleFrequency * 1000)) : void 0
|
||||
};
|
||||
|
||||
yield this.startProfiler(profilerOptions);
|
||||
|
||||
this.emit("profiler-activated");
|
||||
return 0;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Returns profile data from now since `startTime`.
|
||||
*/
|
||||
getProfile: Task.async(function *(options) {
|
||||
let profilerData = yield (actorCompatibilityBridge("getProfile").call(this, options));
|
||||
// If the backend does not support filtering by start and endtime on platform (< Fx40),
|
||||
// do it on the client (much slower).
|
||||
if (!this.traits.filterable) {
|
||||
RecordingUtils.filterSamples(profilerData.profile, options.startTime || 0);
|
||||
}
|
||||
|
||||
return profilerData;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Invoked whenever a registered event was emitted by the profiler actor.
|
||||
*
|
||||
* @param object response
|
||||
* The data received from the backend.
|
||||
*/
|
||||
_onProfilerEvent: function (_, { topic, subject, details }) {
|
||||
if (topic === "console-api-profiler") {
|
||||
if (subject.action === "profile") {
|
||||
this.emit("console-profile-start", details);
|
||||
} else if (subject.action === "profileEnd") {
|
||||
this.emit("console-profile-end", details);
|
||||
}
|
||||
} else if (topic === "profiler-stopped") {
|
||||
this.emit("profiler-stopped");
|
||||
}
|
||||
},
|
||||
|
||||
toString: () => "[object ProfilerFrontFacade]"
|
||||
};
|
||||
|
||||
// Bind all the methods that directly proxy to the actor
|
||||
PROFILER_ACTOR_METHODS.forEach(method => ProfilerFrontFacade.prototype[method] = actorCompatibilityBridge(method));
|
||||
exports.ProfilerFront = ProfilerFrontFacade;
|
||||
|
||||
/**
|
||||
* Constructor for a facade around an underlying TimelineFront.
|
||||
*/
|
||||
function TimelineFrontFacade (target) {
|
||||
this._target = target;
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
TimelineFrontFacade.prototype = {
|
||||
EVENTS: ["markers", "frames", "memory", "ticks"],
|
||||
|
||||
connect: Task.async(function*() {
|
||||
let supported = yield timelineActorSupported(this._target);
|
||||
this._actor = supported ?
|
||||
new TimelineFront(this._target.client, this._target.form) :
|
||||
new MockTimelineFront();
|
||||
|
||||
this.IS_MOCK = !supported;
|
||||
|
||||
// Binds underlying actor events and consolidates them to a `timeline-data`
|
||||
// exposed event.
|
||||
this.EVENTS.forEach(type => {
|
||||
let handler = this[`_on${type}`] = this._onTimelineData.bind(this, type);
|
||||
this._actor.on(type, handler);
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* Override actor's destroy, so we can unregister listeners before
|
||||
* destroying the underlying actor.
|
||||
*/
|
||||
destroy: Task.async(function *() {
|
||||
this.EVENTS.forEach(type => this._actor.off(type, this[`_on${type}`]));
|
||||
yield this._actor.destroy();
|
||||
}),
|
||||
|
||||
/**
|
||||
* An aggregate of all events (markers, frames, memory, ticks) and exposes
|
||||
* to PerformanceActorsConnection as a single event.
|
||||
*/
|
||||
_onTimelineData: function (type, ...data) {
|
||||
this.emit("timeline-data", type, ...data);
|
||||
},
|
||||
|
||||
toString: () => "[object TimelineFrontFacade]"
|
||||
};
|
||||
|
||||
// Bind all the methods that directly proxy to the actor
|
||||
TIMELINE_ACTOR_METHODS.forEach(method => TimelineFrontFacade.prototype[method] = actorCompatibilityBridge(method));
|
||||
exports.TimelineFront = TimelineFrontFacade;
|
||||
|
||||
/**
|
||||
* Constructor for a facade around an underlying ProfilerFront.
|
||||
*/
|
||||
function MemoryFrontFacade (target) {
|
||||
this._target = target;
|
||||
this._pullAllocationSites = this._pullAllocationSites.bind(this);
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
MemoryFrontFacade.prototype = {
|
||||
connect: Task.async(function*() {
|
||||
let supported = yield memoryActorSupported(this._target);
|
||||
this._actor = supported ?
|
||||
new MemoryFront(this._target.client, this._target.form) :
|
||||
new MockMemoryFront();
|
||||
|
||||
this.IS_MOCK = !supported;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Starts polling for allocation information.
|
||||
*/
|
||||
start: Task.async(function *(options) {
|
||||
if (!options.withAllocations) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
yield this.attach();
|
||||
|
||||
let startTime = yield this.startRecordingAllocations({
|
||||
probability: options.allocationsSampleProbability,
|
||||
maxLogLength: options.allocationsMaxLogLength
|
||||
});
|
||||
|
||||
yield this._pullAllocationSites();
|
||||
|
||||
return startTime;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Stops polling for allocation information.
|
||||
*/
|
||||
stop: Task.async(function *(options) {
|
||||
if (!options.withAllocations) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Since `_pullAllocationSites` is usually running inside a timeout, and
|
||||
// it's performing asynchronous requests to the server, a recording may
|
||||
// be stopped before that method finishes executing. Therefore, we need to
|
||||
// wait for the last request to `getAllocations` to finish before actually
|
||||
// stopping recording allocations.
|
||||
yield this._lastPullAllocationSitesFinished;
|
||||
timers.clearTimeout(this._sitesPullTimeout);
|
||||
|
||||
let endTime = yield this.stopRecordingAllocations();
|
||||
yield this.detach();
|
||||
|
||||
return endTime;
|
||||
}),
|
||||
|
||||
/**
|
||||
* At regular intervals, pull allocations from the memory actor, and
|
||||
* forward them on this Front facade as "timeline-data" events. This
|
||||
* gives the illusion that the MemoryActor supports an EventEmitter-style
|
||||
* event stream.
|
||||
*/
|
||||
_pullAllocationSites: Task.async(function *() {
|
||||
let { promise, resolve } = Promise.defer();
|
||||
this._lastPullAllocationSitesFinished = promise;
|
||||
|
||||
if ((yield this.getState()) !== "attached") {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
let memoryData = yield this.getAllocations();
|
||||
// Match the signature of the TimelineFront events, with "timeline-data"
|
||||
// being the event name, and the second argument describing the type.
|
||||
this.emit("timeline-data", "allocations", {
|
||||
sites: memoryData.allocations,
|
||||
timestamps: memoryData.allocationsTimestamps,
|
||||
frames: memoryData.frames,
|
||||
counts: memoryData.counts
|
||||
});
|
||||
|
||||
this._sitesPullTimeout = timers.setTimeout(this._pullAllocationSites, ALLOCATION_SITE_POLL_TIMER);
|
||||
|
||||
resolve();
|
||||
}),
|
||||
|
||||
toString: () => "[object MemoryFrontFacade]"
|
||||
};
|
||||
|
||||
// Bind all the methods that directly proxy to the actor
|
||||
MEMORY_ACTOR_METHODS.forEach(method => MemoryFrontFacade.prototype[method] = actorCompatibilityBridge(method));
|
||||
exports.MemoryFront = MemoryFrontFacade;
|
|
@ -4,72 +4,9 @@
|
|||
"use strict";
|
||||
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
loader.lazyRequireGetter(this, "promise");
|
||||
const { Promise } = require("resource://gre/modules/Promise.jsm");
|
||||
loader.lazyRequireGetter(this, "EventEmitter",
|
||||
"devtools/toolkit/event-emitter");
|
||||
loader.lazyRequireGetter(this, "RecordingUtils",
|
||||
"devtools/performance/recording-utils", true);
|
||||
|
||||
const REQUIRED_MEMORY_ACTOR_METHODS = [
|
||||
"attach", "detach", "startRecordingAllocations", "stopRecordingAllocations", "getAllocations"
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructor for a facade around an underlying ProfilerFront.
|
||||
*/
|
||||
function ProfilerFront (target) {
|
||||
this._target = target;
|
||||
}
|
||||
|
||||
ProfilerFront.prototype = {
|
||||
// Connects to the targets underlying real ProfilerFront.
|
||||
connect: Task.async(function*() {
|
||||
let target = this._target;
|
||||
// Chrome and content process targets already have obtained a reference
|
||||
// to the profiler tab actor. Use it immediately.
|
||||
if (target.form && target.form.profilerActor) {
|
||||
this._profiler = target.form.profilerActor;
|
||||
}
|
||||
// Check if we already have a grip to the `listTabs` response object
|
||||
// and, if we do, use it to get to the profiler actor.
|
||||
else if (target.root && target.root.profilerActor) {
|
||||
this._profiler = target.root.profilerActor;
|
||||
}
|
||||
// Otherwise, call `listTabs`.
|
||||
else {
|
||||
this._profiler = (yield listTabs(target.client)).profilerActor;
|
||||
}
|
||||
|
||||
// Fetch and store information about the SPS profiler and
|
||||
// server profiler.
|
||||
this.traits = {};
|
||||
this.traits.filterable = target.getTrait("profilerDataFilterable");
|
||||
}),
|
||||
|
||||
/**
|
||||
* Makes a request to the underlying real profiler actor. Handles
|
||||
* backwards compatibility differences based off of the features
|
||||
* and traits of the actor.
|
||||
*/
|
||||
_request: function (method, ...args) {
|
||||
let deferred = promise.defer();
|
||||
let data = args[0] || {};
|
||||
data.to = this._profiler;
|
||||
data.type = method;
|
||||
this._target.client.request(data, res => {
|
||||
// If the backend does not support filtering by start and endtime on platform (< Fx40),
|
||||
// do it on the client (much slower).
|
||||
if (method === "getProfile" && !this.traits.filterable) {
|
||||
RecordingUtils.filterSamples(res.profile, data.startTime || 0);
|
||||
}
|
||||
|
||||
deferred.resolve(res);
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
};
|
||||
|
||||
exports.ProfilerFront = ProfilerFront;
|
||||
|
||||
/**
|
||||
* A dummy front decorated with the provided methods.
|
||||
|
@ -87,7 +24,8 @@ function MockFront (blueprint) {
|
|||
|
||||
function MockMemoryFront () {
|
||||
MockFront.call(this, [
|
||||
["initialize"],
|
||||
["start", 0], // for facade
|
||||
["stop", 0], // for facade
|
||||
["destroy"],
|
||||
["attach"],
|
||||
["detach"],
|
||||
|
@ -101,7 +39,6 @@ exports.MockMemoryFront = MockMemoryFront;
|
|||
|
||||
function MockTimelineFront () {
|
||||
MockFront.call(this, [
|
||||
["initialize"],
|
||||
["destroy"],
|
||||
["start", 0],
|
||||
["stop", 0],
|
||||
|
@ -169,12 +106,66 @@ function timelineActorSupported(target) {
|
|||
exports.timelineActorSupported = Task.async(timelineActorSupported);
|
||||
|
||||
/**
|
||||
* Returns a promise resolved with a listing of all the tabs in the
|
||||
* provided thread client.
|
||||
* Returns a promise resolving to the location of the profiler actor
|
||||
* within this context.
|
||||
*
|
||||
* @param {TabTarget} target
|
||||
* @return {Promise<ProfilerActor>}
|
||||
*/
|
||||
function listTabs(client) {
|
||||
let deferred = promise.defer();
|
||||
client.listTabs(deferred.resolve);
|
||||
return deferred.promise;
|
||||
function getProfiler (target) {
|
||||
let { promise, resolve } = Promise.defer();
|
||||
// Chrome and content process targets already have obtained a reference
|
||||
// to the profiler tab actor. Use it immediately.
|
||||
if (target.form && target.form.profilerActor) {
|
||||
resolve(target.form.profilerActor);
|
||||
}
|
||||
// Check if we already have a grip to the `listTabs` response object
|
||||
// and, if we do, use it to get to the profiler actor.
|
||||
else if (target.root && target.root.profilerActor) {
|
||||
resolve(target.root.profilerActor);
|
||||
}
|
||||
// Otherwise, call `listTabs`.
|
||||
else {
|
||||
target.client.listTabs(({ profilerActor }) => resolve(profilerActor));
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
exports.getProfiler = Task.async(getProfiler);
|
||||
|
||||
/**
|
||||
* Makes a request to an actor that does not have the modern `Front`
|
||||
* interface.
|
||||
*/
|
||||
function legacyRequest (target, actor, method, args) {
|
||||
let { promise, resolve } = Promise.defer();
|
||||
let data = args[0] || {};
|
||||
data.to = actor;
|
||||
data.type = method;
|
||||
target.client.request(data, resolve);
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function to be used as a method on an "Actor" in ./actors.
|
||||
* Calls the underlying actor's method, supporting the modern `Front`
|
||||
* interface if possible, otherwise, falling back to using
|
||||
* `legacyRequest`.
|
||||
*/
|
||||
function actorCompatibilityBridge (method) {
|
||||
return function () {
|
||||
// Check to see if this is a modern ActorFront, which has its
|
||||
// own `request` method. Also, check if its a mock actor, as it mimicks
|
||||
// the ActorFront interface.
|
||||
// The profiler actor does not currently support the modern `Front`
|
||||
// interface, so we have to manually push packets to it.
|
||||
// TODO bug 1159389, fix up profiler actor to not need this, however
|
||||
// we will need it for backwards compat
|
||||
if (this.IS_MOCK || this._actor.request) {
|
||||
return this._actor[method].apply(this._actor, arguments);
|
||||
}
|
||||
else {
|
||||
return legacyRequest(this._target, this._actor, method, arguments);
|
||||
}
|
||||
};
|
||||
}
|
||||
exports.actorCompatibilityBridge = actorCompatibilityBridge;
|
||||
|
|
|
@ -9,24 +9,15 @@ const { extend } = require("sdk/util/object");
|
|||
const { RecordingModel } = require("devtools/performance/recording-model");
|
||||
|
||||
loader.lazyRequireGetter(this, "Services");
|
||||
loader.lazyRequireGetter(this, "promise");
|
||||
loader.lazyRequireGetter(this, "EventEmitter",
|
||||
"devtools/toolkit/event-emitter");
|
||||
loader.lazyRequireGetter(this, "TimelineFront",
|
||||
"devtools/server/actors/timeline", true);
|
||||
loader.lazyRequireGetter(this, "MemoryFront",
|
||||
"devtools/server/actors/memory", true);
|
||||
loader.lazyRequireGetter(this, "DevToolsUtils",
|
||||
"devtools/toolkit/DevToolsUtils");
|
||||
loader.lazyRequireGetter(this, "compatibility",
|
||||
"devtools/performance/compatibility");
|
||||
loader.lazyRequireGetter(this, "actors",
|
||||
"devtools/performance/actors");
|
||||
|
||||
loader.lazyImporter(this, "gDevTools",
|
||||
"resource:///modules/devtools/gDevTools.jsm");
|
||||
loader.lazyImporter(this, "setTimeout",
|
||||
"resource://gre/modules/Timer.jsm");
|
||||
loader.lazyImporter(this, "clearTimeout",
|
||||
"resource://gre/modules/Timer.jsm");
|
||||
loader.lazyImporter(this, "Promise",
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
|
||||
|
@ -41,9 +32,6 @@ const CONNECTION_PIPE_EVENTS = [
|
|||
"recording-started", "recording-stopped"
|
||||
];
|
||||
|
||||
// Events to listen to from the profiler actor
|
||||
const PROFILER_EVENTS = ["console-api-profiler", "profiler-stopped"];
|
||||
|
||||
/**
|
||||
* A cache of all PerformanceActorsConnection instances.
|
||||
* The keys are Target objects.
|
||||
|
@ -84,17 +72,15 @@ function PerformanceActorsConnection(target) {
|
|||
|
||||
this._target = target;
|
||||
this._client = this._target.client;
|
||||
this._request = this._request.bind(this);
|
||||
this._pendingConsoleRecordings = [];
|
||||
this._sitesPullTimeout = 0;
|
||||
this._recordings = [];
|
||||
|
||||
this._onTimelineMarkers = this._onTimelineMarkers.bind(this);
|
||||
this._onTimelineFrames = this._onTimelineFrames.bind(this);
|
||||
this._onTimelineMemory = this._onTimelineMemory.bind(this);
|
||||
this._onTimelineTicks = this._onTimelineTicks.bind(this);
|
||||
this._onProfilerEvent = this._onProfilerEvent.bind(this);
|
||||
this._pullAllocationSites = this._pullAllocationSites.bind(this);
|
||||
this._pipeToConnection = this._pipeToConnection.bind(this);
|
||||
this._onTimelineData = this._onTimelineData.bind(this);
|
||||
this._onConsoleProfileStart = this._onConsoleProfileStart.bind(this);
|
||||
this._onConsoleProfileEnd = this._onConsoleProfileEnd.bind(this);
|
||||
this._onProfilerUnexpectedlyStopped = this._onProfilerUnexpectedlyStopped.bind(this);
|
||||
|
||||
Services.obs.notifyObservers(null, "performance-actors-connection-created", null);
|
||||
}
|
||||
|
@ -128,10 +114,7 @@ PerformanceActorsConnection.prototype = {
|
|||
// Only initialize the timeline and memory fronts if the respective actors
|
||||
// are available. Older Gecko versions don't have existing implementations,
|
||||
// in which case all the methods we need can be easily mocked.
|
||||
yield this._connectProfilerActor();
|
||||
yield this._connectTimelineActor();
|
||||
yield this._connectMemoryActor();
|
||||
|
||||
yield this._connectActors();
|
||||
yield this._registerListeners();
|
||||
|
||||
this._connected = true;
|
||||
|
@ -159,132 +142,64 @@ PerformanceActorsConnection.prototype = {
|
|||
}),
|
||||
|
||||
/**
|
||||
* Initializes a connection to the profiler actor. Uses a facade around the ProfilerFront
|
||||
* for similarity to the other actors in the shared connection.
|
||||
* Initializes fronts and connects to the underlying actors using the facades
|
||||
* found in ./actors.js.
|
||||
*/
|
||||
_connectProfilerActor: Task.async(function*() {
|
||||
this._profiler = new compatibility.ProfilerFront(this._target);
|
||||
yield this._profiler.connect();
|
||||
}),
|
||||
_connectActors: Task.async(function*() {
|
||||
this._profiler = new actors.ProfilerFront(this._target);
|
||||
this._memory = new actors.MemoryFront(this._target);
|
||||
this._timeline = new actors.TimelineFront(this._target);
|
||||
|
||||
/**
|
||||
* Initializes a connection to a timeline actor.
|
||||
*/
|
||||
_connectTimelineActor: function() {
|
||||
let supported = yield compatibility.timelineActorSupported(this._target);
|
||||
if (supported) {
|
||||
this._timeline = new TimelineFront(this._target.client, this._target.form);
|
||||
} else {
|
||||
this._timeline = new compatibility.MockTimelineFront();
|
||||
}
|
||||
this._timelineSupported = supported;
|
||||
},
|
||||
yield Promise.all([
|
||||
this._profiler.connect(),
|
||||
this._memory.connect(),
|
||||
this._timeline.connect()
|
||||
]);
|
||||
|
||||
/**
|
||||
* Initializes a connection to a memory actor.
|
||||
*/
|
||||
_connectMemoryActor: Task.async(function* () {
|
||||
let supported = yield compatibility.memoryActorSupported(this._target);
|
||||
if (supported) {
|
||||
this._memory = new MemoryFront(this._target.client, this._target.form);
|
||||
} else {
|
||||
this._memory = new compatibility.MockMemoryFront();
|
||||
}
|
||||
this._memorySupported = supported;
|
||||
// Expose server support status of underlying actors
|
||||
// after connecting.
|
||||
this._memorySupported = !this._memory.IS_MOCK;
|
||||
this._timelineSupported = !this._timeline.IS_MOCK;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Registers listeners on events from the underlying
|
||||
* actors, so the connection can handle them.
|
||||
*/
|
||||
_registerListeners: Task.async(function*() {
|
||||
// Pipe events from TimelineActor to the PerformanceFront
|
||||
this._timeline.on("markers", this._onTimelineMarkers);
|
||||
this._timeline.on("frames", this._onTimelineFrames);
|
||||
this._timeline.on("memory", this._onTimelineMemory);
|
||||
this._timeline.on("ticks", this._onTimelineTicks);
|
||||
|
||||
// Register events on the profiler actor to hook into `console.profile*` calls.
|
||||
yield this._request("profiler", "registerEventNotifications", { events: PROFILER_EVENTS });
|
||||
this._client.addListener("eventNotification", this._onProfilerEvent);
|
||||
}),
|
||||
_registerListeners: function () {
|
||||
this._timeline.on("timeline-data", this._onTimelineData);
|
||||
this._memory.on("timeline-data", this._onTimelineData);
|
||||
this._profiler.on("console-profile-start", this._onConsoleProfileStart);
|
||||
this._profiler.on("console-profile-end", this._onConsoleProfileEnd);
|
||||
this._profiler.on("profiler-stopped", this._onProfilerUnexpectedlyStopped);
|
||||
this._profiler.on("profiler-already-active", this._pipeToConnection);
|
||||
this._profiler.on("profiler-activated", this._pipeToConnection);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unregisters listeners on events on the underlying actors.
|
||||
*/
|
||||
_unregisterListeners: Task.async(function*() {
|
||||
this._timeline.off("markers", this._onTimelineMarkers);
|
||||
this._timeline.off("frames", this._onTimelineFrames);
|
||||
this._timeline.off("memory", this._onTimelineMemory);
|
||||
this._timeline.off("ticks", this._onTimelineTicks);
|
||||
|
||||
yield this._request("profiler", "unregisterEventNotifications", { events: PROFILER_EVENTS });
|
||||
this._client.removeListener("eventNotification", this._onProfilerEvent);
|
||||
}),
|
||||
_unregisterListeners: function () {
|
||||
this._timeline.off("timeline-data", this._onTimelineData);
|
||||
this._memory.off("timeline-data", this._onTimelineData);
|
||||
this._profiler.off("console-profile-start", this._onConsoleProfileStart);
|
||||
this._profiler.off("console-profile-end", this._onConsoleProfileEnd);
|
||||
this._profiler.off("profiler-stopped", this._onProfilerUnexpectedlyStopped);
|
||||
this._profiler.off("profiler-already-active", this._pipeToConnection);
|
||||
this._profiler.off("profiler-activated", this._pipeToConnection);
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes the connections to non-profiler actors.
|
||||
*/
|
||||
_disconnectActors: Task.async(function* () {
|
||||
yield this._timeline.destroy();
|
||||
yield this._memory.destroy();
|
||||
yield Promise.all([
|
||||
this._profiler.destroy(),
|
||||
this._timeline.destroy(),
|
||||
this._memory.destroy()
|
||||
]);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Sends the request over the remote debugging protocol to the
|
||||
* specified actor.
|
||||
*
|
||||
* @param string actor
|
||||
* Currently supported: "profiler", "timeline", "memory".
|
||||
* @param string method
|
||||
* Method to call on the backend.
|
||||
* @param any args [optional]
|
||||
* Additional data or arguments to send with the request.
|
||||
* @return object
|
||||
* A promise resolved with the response once the request finishes.
|
||||
*/
|
||||
_request: function(actor, method, ...args) {
|
||||
// Handle requests to the profiler actor.
|
||||
if (actor == "profiler") {
|
||||
return this._profiler._request(method, ...args);
|
||||
}
|
||||
|
||||
// Handle requests to the timeline actor.
|
||||
if (actor == "timeline") {
|
||||
return this._timeline[method].apply(this._timeline, args);
|
||||
}
|
||||
|
||||
// Handle requests to the memory actor.
|
||||
if (actor == "memory") {
|
||||
return this._memory[method].apply(this._memory, args);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoked whenever a registered event was emitted by the profiler actor.
|
||||
*
|
||||
* @param object response
|
||||
* The data received from the backend.
|
||||
*/
|
||||
_onProfilerEvent: function (_, { topic, subject, details }) {
|
||||
if (topic === "console-api-profiler") {
|
||||
if (subject.action === "profile") {
|
||||
this._onConsoleProfileStart(details);
|
||||
} else if (subject.action === "profileEnd") {
|
||||
this._onConsoleProfileEnd(details);
|
||||
}
|
||||
} else if (topic === "profiler-stopped") {
|
||||
this._onProfilerUnexpectedlyStopped();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* TODO handle bug 1144438
|
||||
*/
|
||||
_onProfilerUnexpectedlyStopped: function () {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoked whenever `console.profile` is called.
|
||||
*
|
||||
|
@ -294,7 +209,7 @@ PerformanceActorsConnection.prototype = {
|
|||
* The time (in milliseconds) when the call was made, relative to when
|
||||
* the nsIProfiler module was started.
|
||||
*/
|
||||
_onConsoleProfileStart: Task.async(function *({ profileLabel, currentTime: startTime }) {
|
||||
_onConsoleProfileStart: Task.async(function *(_, { profileLabel, currentTime: startTime }) {
|
||||
let recordings = this._recordings;
|
||||
|
||||
// Abort if a profile with this label already exists.
|
||||
|
@ -325,7 +240,7 @@ PerformanceActorsConnection.prototype = {
|
|||
* The time (in milliseconds) when the call was made, relative to when
|
||||
* the nsIProfiler module was started.
|
||||
*/
|
||||
_onConsoleProfileEnd: Task.async(function *(data) {
|
||||
_onConsoleProfileEnd: Task.async(function *(_, data) {
|
||||
// If no data, abort; can occur if profiler isn't running and we get a surprise
|
||||
// call to console.profileEnd()
|
||||
if (!data) {
|
||||
|
@ -361,14 +276,12 @@ PerformanceActorsConnection.prototype = {
|
|||
this.emit("console-profile-end", model);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Handlers for TimelineActor events. All pipe to `_onTimelineData`
|
||||
* with the appropriate event name.
|
||||
*/
|
||||
_onTimelineMarkers: function (markers) { this._onTimelineData("markers", markers); },
|
||||
_onTimelineFrames: function (delta, frames) { this._onTimelineData("frames", delta, frames); },
|
||||
_onTimelineMemory: function (delta, measurement) { this._onTimelineData("memory", delta, measurement); },
|
||||
_onTimelineTicks: function (delta, timestamps) { this._onTimelineData("ticks", delta, timestamps); },
|
||||
/**
|
||||
* TODO handle bug 1144438
|
||||
*/
|
||||
_onProfilerUnexpectedlyStopped: function () {
|
||||
Cu.reportError("Profiler unexpectedly stopped.", arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called whenever there is timeline data of any of the following types:
|
||||
|
@ -380,8 +293,7 @@ PerformanceActorsConnection.prototype = {
|
|||
*
|
||||
* Populate our internal store of recordings for all currently recording sessions.
|
||||
*/
|
||||
|
||||
_onTimelineData: function (...data) {
|
||||
_onTimelineData: function (_, ...data) {
|
||||
this._recordings.forEach(e => e.addTimelineData.apply(e, data));
|
||||
this.emit("timeline-data", ...data);
|
||||
},
|
||||
|
@ -399,15 +311,13 @@ PerformanceActorsConnection.prototype = {
|
|||
let model = new RecordingModel(options);
|
||||
// All actors are started asynchronously over the remote debugging protocol.
|
||||
// Get the corresponding start times from each one of them.
|
||||
let profilerStartTime = yield this._startProfiler(options);
|
||||
let timelineStartTime = yield this._startTimeline(options);
|
||||
let memoryStartTime = yield this._startMemory(options);
|
||||
// The timeline and memory actors are target-dependent, so start those as well,
|
||||
// even though these are mocked in older Geckos (FF < 35)
|
||||
let profilerStartTime = yield this._profiler.start(options);
|
||||
let timelineStartTime = yield this._timeline.start(options);
|
||||
let memoryStartTime = yield this._memory.start(options);
|
||||
|
||||
let data = {
|
||||
profilerStartTime,
|
||||
timelineStartTime,
|
||||
memoryStartTime
|
||||
};
|
||||
let data = { profilerStartTime, timelineStartTime, memoryStartTime };
|
||||
|
||||
// Signify to the model that the recording has started,
|
||||
// populate with data and store the recording model here.
|
||||
|
@ -445,7 +355,7 @@ PerformanceActorsConnection.prototype = {
|
|||
|
||||
let config = model.getConfiguration();
|
||||
let startTime = model.getProfilerStartTime();
|
||||
let profilerData = yield this._request("profiler", "getProfile", { startTime });
|
||||
let profilerData = yield this._profiler.getProfile({ startTime });
|
||||
let memoryEndTime = Date.now();
|
||||
let timelineEndTime = Date.now();
|
||||
|
||||
|
@ -454,8 +364,8 @@ PerformanceActorsConnection.prototype = {
|
|||
// juse use Date.now() for the memory and timeline end times, as those
|
||||
// are only used in tests.
|
||||
if (!this.isRecording()) {
|
||||
memoryEndTime = yield this._stopMemory(config);
|
||||
timelineEndTime = yield this._stopTimeline(config);
|
||||
memoryEndTime = yield this._memory.stop(config);
|
||||
timelineEndTime = yield this._timeline.stop(config);
|
||||
}
|
||||
|
||||
// Set the results on the RecordingModel itself.
|
||||
|
@ -484,127 +394,12 @@ PerformanceActorsConnection.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Starts the profiler actor, if necessary.
|
||||
* An event from an underlying actor that we just want
|
||||
* to pipe to the connection itself.
|
||||
*/
|
||||
_startProfiler: Task.async(function *(options={}) {
|
||||
// Start the profiler only if it wasn't already active. The built-in
|
||||
// nsIPerformance module will be kept recording, because it's the same instance
|
||||
// for all targets and interacts with the whole platform, so we don't want
|
||||
// to affect other clients by stopping (or restarting) it.
|
||||
let profilerStatus = yield this._request("profiler", "isActive");
|
||||
if (profilerStatus.isActive) {
|
||||
this.emit("profiler-already-active");
|
||||
return profilerStatus.currentTime;
|
||||
}
|
||||
|
||||
// Translate options from the recording model into profiler-specific
|
||||
// options for the nsIProfiler
|
||||
let profilerOptions = {
|
||||
entries: options.bufferSize,
|
||||
interval: options.sampleFrequency ? (1000 / (options.sampleFrequency * 1000)) : void 0
|
||||
};
|
||||
|
||||
yield this._request("profiler", "startProfiler", profilerOptions);
|
||||
|
||||
this.emit("profiler-activated");
|
||||
return 0;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Starts the timeline actor.
|
||||
*/
|
||||
_startTimeline: Task.async(function *(options) {
|
||||
// The timeline actor is target-dependent, so just make sure it's recording.
|
||||
// It won't, however, be available in older Geckos (FF < 35).
|
||||
return (yield this._request("timeline", "start", options));
|
||||
}),
|
||||
|
||||
/**
|
||||
* Stops the timeline actor.
|
||||
*/
|
||||
_stopTimeline: Task.async(function *(options) {
|
||||
return (yield this._request("timeline", "stop"));
|
||||
}),
|
||||
|
||||
/**
|
||||
* Starts polling for allocations from the memory actor, if necessary.
|
||||
*/
|
||||
_startMemory: Task.async(function *(options) {
|
||||
if (!options.withAllocations) {
|
||||
return 0;
|
||||
}
|
||||
let memoryStartTime = yield this._startRecordingAllocations(options);
|
||||
yield this._pullAllocationSites();
|
||||
return memoryStartTime;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Stops polling for allocations from the memory actor, if necessary.
|
||||
*/
|
||||
_stopMemory: Task.async(function *(options) {
|
||||
if (!options.withAllocations) {
|
||||
return 0;
|
||||
}
|
||||
// Since `_pullAllocationSites` is usually running inside a timeout, and
|
||||
// it's performing asynchronous requests to the server, a recording may
|
||||
// be stopped before that method finishes executing. Therefore, we need to
|
||||
// wait for the last request to `getAllocations` to finish before actually
|
||||
// stopping recording allocations.
|
||||
yield this._lastPullAllocationSitesFinished;
|
||||
clearTimeout(this._sitesPullTimeout);
|
||||
|
||||
return yield this._stopRecordingAllocations();
|
||||
}),
|
||||
|
||||
/**
|
||||
* Starts recording allocations in the memory actor.
|
||||
*/
|
||||
_startRecordingAllocations: Task.async(function*(options) {
|
||||
yield this._request("memory", "attach");
|
||||
let memoryStartTime = yield this._request("memory", "startRecordingAllocations", {
|
||||
probability: options.allocationsSampleProbability,
|
||||
maxLogLength: options.allocationsMaxLogLength
|
||||
});
|
||||
return memoryStartTime;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Stops recording allocations in the memory actor.
|
||||
*/
|
||||
_stopRecordingAllocations: Task.async(function*() {
|
||||
let memoryEndTime = yield this._request("memory", "stopRecordingAllocations");
|
||||
yield this._request("memory", "detach");
|
||||
return memoryEndTime;
|
||||
}),
|
||||
|
||||
/**
|
||||
* At regular intervals, pull allocations from the memory actor, and forward
|
||||
* them to consumers.
|
||||
*/
|
||||
_pullAllocationSites: Task.async(function *() {
|
||||
let deferred = promise.defer();
|
||||
this._lastPullAllocationSitesFinished = deferred.promise;
|
||||
|
||||
let isDetached = (yield this._request("memory", "getState")) !== "attached";
|
||||
if (isDetached) {
|
||||
deferred.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
let memoryData = yield this._request("memory", "getAllocations");
|
||||
|
||||
this._onTimelineData("allocations", {
|
||||
sites: memoryData.allocations,
|
||||
timestamps: memoryData.allocationsTimestamps,
|
||||
frames: memoryData.frames,
|
||||
counts: memoryData.counts
|
||||
});
|
||||
|
||||
let delay = DEFAULT_ALLOCATION_SITES_PULL_TIMEOUT;
|
||||
this._sitesPullTimeout = setTimeout(this._pullAllocationSites, delay);
|
||||
|
||||
deferred.resolve();
|
||||
}),
|
||||
_pipeToConnection: function (eventName, ...args) {
|
||||
this.emit(eventName, ...args);
|
||||
},
|
||||
|
||||
toString: () => "[object PerformanceActorsConnection]"
|
||||
};
|
||||
|
@ -620,7 +415,6 @@ function PerformanceFront(connection) {
|
|||
EventEmitter.decorate(this);
|
||||
|
||||
this._connection = connection;
|
||||
this._request = connection._request;
|
||||
|
||||
// Set when mocks are being used
|
||||
this._memorySupported = connection._memorySupported;
|
||||
|
@ -680,6 +474,17 @@ PerformanceFront.prototype = {
|
|||
*/
|
||||
isRecording: function () {
|
||||
return this._connection.isRecording();
|
||||
},
|
||||
|
||||
/**
|
||||
* Interacts with the connection's actors. Should only be used in tests.
|
||||
*/
|
||||
_request: function (actorName, method, ...args) {
|
||||
if (!gDevTools.testing) {
|
||||
throw new Error("PerformanceFront._request may only be used in tests.");
|
||||
}
|
||||
let actor = this._connection[`_${actorName}`];
|
||||
return actor[method].apply(actor, args);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
EXTRA_JS_MODULES.devtools.performance += [
|
||||
'modules/actors.js',
|
||||
'modules/compatibility.js',
|
||||
'modules/front.js',
|
||||
'modules/graphs.js',
|
||||
|
|
|
@ -18,7 +18,7 @@ function spawnTest () {
|
|||
|
||||
ok(sharedConnection,
|
||||
"A shared profiler connection for the current toolbox was retrieved.");
|
||||
is(sharedConnection._request, panel.panelWin.gFront._request,
|
||||
is(panel.panelWin.gFront._connection, sharedConnection,
|
||||
"The same shared profiler connection is used by the panel's front.");
|
||||
|
||||
yield sharedConnection.open();
|
||||
|
|
Загрузка…
Ссылка в новой задаче