mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 14:30:27 +00:00
a7bc0406ee
- Bug 1155006: Fix unified build sensitivities in js/src/jit. r=shu (6e24e1af1) - Bug 1162766 - Fix more bad implicit constructors in js. r=evilpie (39961b06d) - Bug 1151606 - Stream atoms instead of raw pointers for native functions in tracked optimizations. (r=djvj) (7641ee9d6) - pointer style (540728104) - Bug 1154997 - Deal with self-hosted builtins when stringifying tracked optimization type info. (r=djvj) (92f9a54e6) - pointer style (45742d820) - Bug 1154115 - Rewrite the JSAPI profiling API to use a FrameHandle, as to avoid multiple lookups in JitcodeGlobalTable. (r=djvj) (4d202ba9e) - Bug 1119023 - Timeline in new perf tool should filter out markers, r=jsantell (6fc1a8bbe) - Bug 1132755 - Allocations tree has a bunch of columns that don't make sense, r=jsantell (1ae9ee7e2) - Bug 1142744 - Fix tests broken by bug 1132755, r=me (cc495f72d) - Bug 1133058 - OptionsView button, when clicked, should have an 'open' attribute. r=vp (65a78d896) - Bug 1132765 - Pass through performance memory options for 'probability' and 'maxLogLength' from the front to the memory actor. r=vp (f9bbbe098) - Bug 1141817 - Fix yield statement to correctly return memory actor state so that the performance tool can poll for allocations during recording. r=vp (2ddf7d528) - Bug 1141817 - Followup to fix additional intermittents like bug 1132370, r=vp (eab962f01) - Bug 1142748 - Use a single configuration for starting/stopping recordings, r=jsantell (0181b319a) - bit of Bug 879008 - New UI for the sampling Profiler (32c4d0fe8) - Bug 1123815 - Merge gum into fx-team to enable the Performance++ tool, r=me (84aabbd61) - Bug 1143933 - Expose raw JIT optimization information in performance front end. r=vp,shu (f68a6df50) - Bug 1143915 - Allow multiple calls to memory and timeline actor's start methods, to return the local start time from the actor. r=vp (028ac4187) - Bug 978948 - Add animation generator support for setTimeout in the canvas debugger. r=vp (42d623452) - Bug 985488 - Allow canvas debugger to time out and stop recording frames. Canvas debugger 'wait' style now matches other media styles. Update labels in canvas debugger to explicitly state that it's waiting for rAF cycles, rather than appearing as if something went wrong. r=vporof (b4670d843) - Bug 1144163 - Add a rulers highlighter; added unit test. r=pbrosset (5811a67d0) - Bug 1144163 - Add a rulers highlighter; added highlighter. r=pbrosset (779f88bdd) - Bug 1144163 - Add a rulers highlighter; added gcli command and button. r=pbrosset (d0d13da51) - Bug 1110550 - Enable performance overview graphs to rerender and change on devtools theme switch. r=vp (bd91ca7cf) - Bug 1149630 - Performance graphs should inherit from a common graph and be similarly styled. r=vporof (481c841f1) - Bug 1150733 - Correctly internationalize jit samples label. r=vporof, r=flod (b5612d1a6) - Bug 1137518 - FlameGraph's destroy function should be async, r=jsantell (f103e4c15) - Bug 1137503 - Avoid potential infinite loops in `findOptimalTickInterval` functions, r=jsantell (95df6c04a) - Bug 1121194 - Support vertical panning for the flamegraph in the new performance tool, r=jsantell (06241b5b2) - Bug 1121180 - Support dark theme in flamecharts for the performance tool. r=vp (c76abe237) - Bug 1059308 - Add Target.isTabActor to tell if the remote tab actor supports attach/detach requests. r=jryans (e03dcef93) - Bug 1132370 - Wrong State: Expected 'attached', but current state is 'detached', r=jsantell (e884e8db9) - No Bug - Fix documentation for _startMemory and _stopMemory in performance/modules/front.js, r=me DONTBUILD (d79090b31) - Bug 1147656 - Remove duplicate profiler defaults from the front end and just use on the server. r=vp (35c015dd0) - Bug 1046234 - Add more DevTools Telemetry measures (display size etc) r=pbrosset, r=gijs (a235681b4) - actually package telemetry.js (e8f3a58a4) - Bug 1077464 - Wire console.profile/profileEnd to the new performance tool. Move most of the recording-model logic from the front end into the PerformanceFront and PerformanceActorConnection so it can manage recordings without the front end being viewed. r=vp,jryans,pbrosset (eef8e18c3) - Bug 1144363 - Fix this._telemetry is undefined in gDevTools. r=bgrins (ba7d02902) - init telemetry, missing parts of Bug 866642 (1e70df975) - do not use sysctl.h on Linux anymore, since it is not provided by recent glibc (b2467d7ce) - clean up some telemetry issues of histogram, parts of Bug 974171 (d30c8d0ad) - move devtools to browser - part 1 (9a856f452) - Bug 1291423: Explicitly qualify the destructor call that we invoke in Maybe::reset. r=Waldo (944904a7d) - Bug 1148075 - Dynamically add XUL commands for the debugger frontend. r=vporof (60bc91f8f) - Bug 1147945 - Let the profiler's buffer size and sample rate be configurable via prefs. r=vp (acebcbdd9) - Bug 1124326 - Improve packageDir support for Cordova. r=ochameau (4b736580a) - Bug 1124326 - Support Cordova w/o build file. r=ochameau (d4b50aeae) - Bug 1134029 - Fix 'Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIURI.host]' timeouts, r=jsantell (18d16a5d0) - Bug 1147806 - Content frame filtering is confused when profiling FxOS, r=jsantell (b3c62c552) - Bug 1108843 - Generalize platform data in call tree view when platform data is hidden. r=vporof (354553ed7) - Bug 1138928 - Display only function name and file, instead of full url, in flame graphs. r=vp (4169689c1) - Bug 1152605 - Should not show host names for chrome URIs. r=vporof (c6dcf9e78) - Bug 1147604 - Inverted call trees should list (root) as leaves. r=jsantell (01768267f) - Bug 1075450 - Disable some Awesomebar actions for private windows r=mak (21d5586e7) - Bug 1120616 - Part 1: Implement filter styles in rule view r=bgrins (b66ee0282) - Bug 1120616 - Part 2: Add unit tests for filter styles in rule view r=bgrins (2892503d8) - Bug 1120616 - Part 3: Adjust the styles in the computed view's filter style search r=bgrins (41f8fae1b) - Bug 1120616 - Part 4: Add textbox context menu for rule and computed view r=bgrins (ff3f868ad) - Bug 1120616 - Part 5: Refactor style inspector tests to use synthesizeKeys r=bgrins (41db021d7) - Bug 1102219 - Part 5: Replace more `String.prototype.contains` with `String.prototype.includes` in chrome code. r=till (86ed03588) - Bug 1154018 - Check to see that nsIURI's host exists when parsing location for framenodes, and cache failures. r=vp (9494d52e7) - Bug 1160691 - Optimize FrameUtils.isContent and FrameUtils.parseLocation. (r=jsantell) (09118fd5d) - Bug 1154115 - Make the performance devtool handle the new profiler JSON format. (r=jsantell,vporof) (e3e5be7a4) - Bug 1059308 - Make frame selection button to work in browser toolbox. r=jryans,past (30fe6e61e) - Bug 1059308 - Fix tests to support chrome actor. r=jryans (01cf3926c) - Bug 1147042 - Rename attachProcess to getProcess. r=ochameau (0393ffb80) - Bug 1145824 - Profiler actor and performance tools now handle passing in a startTime to filter out SPS profiles on platform rather than client. r=vp,fitzgen (f225116ba) - Bug 1157718 - Do not use Array.prototype.includes in production code that leaves nightly in performance tool. r=fitzgen (ff06d284e) - Bug 1140728 - Rename 'Memory' to 'Allocations' in the new performance tool. r=jsantell (f584e720f) - Bug 1137500 - Always wait for the overview to be rendered in tests after a recording finishes, unless otherwise specified, r=jsantell (59825e179) - Bug 1137487 - AbstractCanvasGraph's destroy function should be async, r=jsantell (a17ae00b5) - Bug 1132758 - Performance feature visibility now based on a per recording-basis, dependent on features enabled and server support. r=vp (0d080a7c2) - Bug 1147035 - Make DeveloperToolbar.jsm use the gBrowser.contentDocumentAsCPOW shortcut. r=past. (251eff125) - Bug 1151168 - Don't flush profiled threads that are pending deletion on JS shutdown and don't delete expired markers when resetting the profile buffer. (r=djvj) (90721313a)
687 lines
23 KiB
JavaScript
687 lines
23 KiB
JavaScript
/* 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 { Cc, Ci, Cu, Cr } = require("chrome");
|
|
const { Task } = require("resource://gre/modules/Task.jsm");
|
|
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.lazyImporter(this, "gDevTools",
|
|
"resource://gre/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");
|
|
|
|
|
|
// How often do we pull allocation sites from the memory actor.
|
|
const DEFAULT_ALLOCATION_SITES_PULL_TIMEOUT = 200; // ms
|
|
|
|
// Events to pipe from PerformanceActorsConnection to the PerformanceFront
|
|
const CONNECTION_PIPE_EVENTS = [
|
|
"console-profile-start", "console-profile-ending", "console-profile-end",
|
|
"timeline-data", "profiler-already-active", "profiler-activated"
|
|
];
|
|
|
|
// 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.
|
|
*/
|
|
let SharedPerformanceActors = new WeakMap();
|
|
|
|
/**
|
|
* Instantiates a shared PerformanceActorsConnection for the specified target.
|
|
* Consumers must yield on `open` to make sure the connection is established.
|
|
*
|
|
* @param Target target
|
|
* The target owning this connection.
|
|
* @return PerformanceActorsConnection
|
|
* The shared connection for the specified target.
|
|
*/
|
|
SharedPerformanceActors.forTarget = function(target) {
|
|
if (this.has(target)) {
|
|
return this.get(target);
|
|
}
|
|
|
|
let instance = new PerformanceActorsConnection(target);
|
|
this.set(target, instance);
|
|
return instance;
|
|
};
|
|
|
|
/**
|
|
* A connection to underlying actors (profiler, memory, framerate, etc.)
|
|
* shared by all tools in a target.
|
|
*
|
|
* Use `SharedPerformanceActors.forTarget` to make sure you get the same
|
|
* instance every time, and the `PerformanceFront` to start/stop recordings.
|
|
*
|
|
* @param Target target
|
|
* The target owning this connection.
|
|
*/
|
|
function PerformanceActorsConnection(target) {
|
|
EventEmitter.decorate(this);
|
|
|
|
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);
|
|
|
|
Services.obs.notifyObservers(null, "performance-actors-connection-created", null);
|
|
}
|
|
|
|
PerformanceActorsConnection.prototype = {
|
|
|
|
// Properties set based off of server actor support
|
|
_memorySupported: true,
|
|
_timelineSupported: true,
|
|
|
|
/**
|
|
* Initializes a connection to the profiler and other miscellaneous actors.
|
|
* If in the process of opening, or already open, nothing happens.
|
|
*
|
|
* @return object
|
|
* A promise that is resolved once the connection is established.
|
|
*/
|
|
open: Task.async(function*() {
|
|
if (this._connecting) {
|
|
return this._connecting.promise;
|
|
}
|
|
|
|
// Create a promise that gets resolved upon connecting, so that
|
|
// other attempts to open the connection use the same resolution promise
|
|
this._connecting = Promise.defer();
|
|
|
|
// Local debugging needs to make the target remote.
|
|
yield this._target.makeRemote();
|
|
|
|
// Sets `this._profiler`, `this._timeline` and `this._memory`.
|
|
// 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._registerListeners();
|
|
|
|
this._connected = true;
|
|
|
|
this._connecting.resolve();
|
|
Services.obs.notifyObservers(null, "performance-actors-connection-opened", null);
|
|
}),
|
|
|
|
/**
|
|
* Destroys this connection.
|
|
*/
|
|
destroy: Task.async(function*() {
|
|
if (this._connecting && !this._connected) {
|
|
yield this._connecting.promise;
|
|
} else if (!this._connected) {
|
|
return;
|
|
}
|
|
|
|
yield this._unregisterListeners();
|
|
yield this._disconnectActors();
|
|
|
|
this._memory = this._timeline = this._profiler = this._target = this._client = null;
|
|
this._connected = false;
|
|
this._connecting = null;
|
|
}),
|
|
|
|
/**
|
|
* Initializes a connection to the profiler actor. Uses a facade around the ProfilerFront
|
|
* for similarity to the other actors in the shared connection.
|
|
*/
|
|
_connectProfilerActor: Task.async(function*() {
|
|
this._profiler = new compatibility.ProfilerFront(this._target);
|
|
yield this._profiler.connect();
|
|
}),
|
|
|
|
/**
|
|
* 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;
|
|
},
|
|
|
|
/**
|
|
* 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;
|
|
}),
|
|
|
|
/**
|
|
* 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);
|
|
}),
|
|
|
|
/**
|
|
* 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);
|
|
}),
|
|
|
|
/**
|
|
* Closes the connections to non-profiler actors.
|
|
*/
|
|
_disconnectActors: Task.async(function* () {
|
|
yield this._timeline.destroy();
|
|
yield 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.
|
|
*
|
|
* @param string profileLabel
|
|
* The provided string argument if available; undefined otherwise.
|
|
* @param number currentTime
|
|
* The time (in milliseconds) when the call was made, relative to when
|
|
* the nsIProfiler module was started.
|
|
*/
|
|
_onConsoleProfileStart: Task.async(function *({ profileLabel, currentTime: startTime }) {
|
|
let recordings = this._recordings;
|
|
|
|
// Abort if a profile with this label already exists.
|
|
if (recordings.find(e => e.getLabel() === profileLabel)) {
|
|
return;
|
|
}
|
|
|
|
// Ensure the performance front is set up and ready.
|
|
// Slight performance overhead for this, should research some more.
|
|
// This is to ensure that there is a front to receive the events for
|
|
// the console profiles.
|
|
yield gDevTools.getToolbox(this._target).loadTool("performance");
|
|
|
|
let model = yield this.startRecording(extend(getRecordingModelPrefs(), {
|
|
console: true,
|
|
label: profileLabel
|
|
}));
|
|
|
|
this.emit("console-profile-start", model);
|
|
}),
|
|
|
|
/**
|
|
* Invoked whenever `console.profileEnd` is called.
|
|
*
|
|
* @param string profileLabel
|
|
* The provided string argument if available; undefined otherwise.
|
|
* @param number currentTime
|
|
* The time (in milliseconds) when the call was made, relative to when
|
|
* the nsIProfiler module was started.
|
|
*/
|
|
_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) {
|
|
return;
|
|
}
|
|
let { profileLabel, currentTime: endTime } = data;
|
|
|
|
let pending = this._recordings.filter(r => r.isConsole() && r.isRecording());
|
|
if (pending.length === 0) {
|
|
return;
|
|
}
|
|
|
|
let model;
|
|
// Try to find the corresponding `console.profile` call if
|
|
// a label was used in profileEnd(). If no matches, abort.
|
|
if (profileLabel) {
|
|
model = pending.find(e => e.getLabel() === profileLabel);
|
|
}
|
|
// If no label supplied, pop off the most recent pending console recording
|
|
else {
|
|
model = pending[pending.length - 1];
|
|
}
|
|
|
|
// If `profileEnd()` was called with a label, and there are no matching
|
|
// sessions, abort.
|
|
if (!model) {
|
|
Cu.reportError("console.profileEnd() called with label that does not match a recording.");
|
|
return;
|
|
}
|
|
|
|
this.emit("console-profile-ending", model);
|
|
yield this.stopRecording(model);
|
|
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); },
|
|
|
|
/**
|
|
* Called whenever there is timeline data of any of the following types:
|
|
* - markers
|
|
* - frames
|
|
* - memory
|
|
* - ticks
|
|
* - allocations
|
|
*
|
|
* Populate our internal store of recordings for all currently recording sessions.
|
|
*/
|
|
|
|
_onTimelineData: function (...data) {
|
|
this._recordings.forEach(e => e.addTimelineData.apply(e, data));
|
|
this.emit("timeline-data", ...data);
|
|
},
|
|
|
|
/**
|
|
* Begins a recording session
|
|
*
|
|
* @param object options
|
|
* An options object to pass to the actors. Supported properties are
|
|
* `withTicks`, `withMemory` and `withAllocations`, `probability`, and `maxLogLength`.
|
|
* @return object
|
|
* A promise that is resolved once recording has started.
|
|
*/
|
|
startRecording: Task.async(function*(options = {}) {
|
|
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);
|
|
|
|
let data = {
|
|
profilerStartTime,
|
|
timelineStartTime,
|
|
memoryStartTime
|
|
};
|
|
|
|
// Signify to the model that the recording has started,
|
|
// populate with data and store the recording model here.
|
|
model.populate(data);
|
|
this._recordings.push(model);
|
|
|
|
return model;
|
|
}),
|
|
|
|
/**
|
|
* Manually ends the recording session for the corresponding RecordingModel.
|
|
*
|
|
* @param RecordingModel model
|
|
* The corresponding RecordingModel that belongs to the recording session wished to stop.
|
|
* @return RecordingModel
|
|
* Returns the same model, populated with the profiling data.
|
|
*/
|
|
stopRecording: Task.async(function*(model) {
|
|
// If model isn't in the PerformanceActorsConnections internal store,
|
|
// then do nothing.
|
|
if (this._recordings.indexOf(model) === -1) {
|
|
return;
|
|
}
|
|
|
|
// Currently there are two ways profiles stop recording. Either manually in the
|
|
// performance tool, or via console.profileEnd. Once a recording is done,
|
|
// we want to deliver the model to the performance tool (either as a return
|
|
// from the PerformanceFront or via `console-profile-end` event) and then
|
|
// remove it from the internal store.
|
|
//
|
|
// In the case where a console.profile is generated via the console (so the tools are
|
|
// open), we initialize the Performance tool so it can listen to those events.
|
|
this._recordings.splice(this._recordings.indexOf(model), 1);
|
|
|
|
let config = model.getConfiguration();
|
|
let startTime = model.getProfilerStartTime();
|
|
let profilerData = yield this._request("profiler", "getProfile", { startTime });
|
|
let memoryEndTime = Date.now();
|
|
let timelineEndTime = Date.now();
|
|
|
|
// Only if there are no more sessions recording do we stop
|
|
// the underlying memory and timeline actors. If we're still recording,
|
|
// 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);
|
|
}
|
|
|
|
// Set the results on the RecordingModel itself.
|
|
model._onStopRecording({
|
|
// Data available only at the end of a recording.
|
|
profile: profilerData.profile,
|
|
|
|
// End times for all the actors.
|
|
profilerEndTime: profilerData.currentTime,
|
|
timelineEndTime: timelineEndTime,
|
|
memoryEndTime: memoryEndTime
|
|
});
|
|
|
|
return model;
|
|
}),
|
|
|
|
/**
|
|
* Checks all currently stored recording models and returns a boolean
|
|
* if there is a session currently being recorded.
|
|
*
|
|
* @return Boolean
|
|
*/
|
|
isRecording: function () {
|
|
return this._recordings.some(recording => recording.isRecording());
|
|
},
|
|
|
|
/**
|
|
* Starts the profiler actor, if necessary.
|
|
*/
|
|
_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 isDetached = (yield this._request("memory", "getState")) !== "attached";
|
|
if (isDetached) {
|
|
return;
|
|
}
|
|
|
|
let memoryData = yield this._request("memory", "getAllocations");
|
|
let isStillAttached = yield this._request("memory", "getState") == "attached";
|
|
|
|
this.emit("allocations", {
|
|
sites: memoryData.allocations,
|
|
timestamps: memoryData.allocationsTimestamps,
|
|
frames: memoryData.frames,
|
|
counts: memoryData.counts
|
|
});
|
|
|
|
if (isStillAttached) {
|
|
let delay = DEFAULT_ALLOCATION_SITES_PULL_TIMEOUT;
|
|
this._sitesPullTimeout = setTimeout(this._pullAllocationSites, delay);
|
|
}
|
|
}),
|
|
|
|
toString: () => "[object PerformanceActorsConnection]"
|
|
};
|
|
|
|
/**
|
|
* A thin wrapper around a shared PerformanceActorsConnection for the parent target.
|
|
* Handles manually starting and stopping a recording.
|
|
*
|
|
* @param PerformanceActorsConnection connection
|
|
* The shared instance for the parent target.
|
|
*/
|
|
function PerformanceFront(connection) {
|
|
EventEmitter.decorate(this);
|
|
|
|
this._connection = connection;
|
|
this._request = connection._request;
|
|
|
|
// Set when mocks are being used
|
|
this._memorySupported = connection._memorySupported;
|
|
this._timelineSupported = connection._timelineSupported;
|
|
|
|
// Pipe the console profile events from the connection
|
|
// to the front so that the UI can listen.
|
|
CONNECTION_PIPE_EVENTS.forEach(eventName => this._connection.on(eventName, () => this.emit.apply(this, arguments)));
|
|
}
|
|
|
|
PerformanceFront.prototype = {
|
|
|
|
/**
|
|
* Manually begins a recording session and creates a RecordingModel.
|
|
* Calls the underlying PerformanceActorsConnection's startRecording method.
|
|
*
|
|
* @param object options
|
|
* An options object to pass to the actors. Supported properties are
|
|
* `withTicks`, `withMemory` and `withAllocations`,
|
|
* `probability` and `maxLogLength`.
|
|
* @return object
|
|
* A promise that is resolved once recording has started.
|
|
*/
|
|
startRecording: function (options) {
|
|
return this._connection.startRecording(options);
|
|
},
|
|
|
|
/**
|
|
* Manually ends the recording session for the corresponding RecordingModel.
|
|
* Calls the underlying PerformanceActorsConnection's
|
|
*
|
|
* @param RecordingModel model
|
|
* The corresponding RecordingModel that belongs to the recording session wished to stop.
|
|
* @return RecordingModel
|
|
* Returns the same model, populated with the profiling data.
|
|
*/
|
|
stopRecording: function (model) {
|
|
return this._connection.stopRecording(model);
|
|
},
|
|
|
|
/**
|
|
* Returns an object indicating what server actors are available and
|
|
* initialized. A falsy value indicates that the server does not support
|
|
* that feature, or that mock actors were explicitly requested (tests).
|
|
*/
|
|
getActorSupport: function () {
|
|
return {
|
|
memory: this._memorySupported,
|
|
timeline: this._timelineSupported
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Creates an object of configurations based off of preferences for a RecordingModel.
|
|
*/
|
|
function getRecordingModelPrefs () {
|
|
return {
|
|
withMarkers: true,
|
|
withMemory: Services.prefs.getBoolPref("devtools.performance.ui.enable-memory"),
|
|
withTicks: Services.prefs.getBoolPref("devtools.performance.ui.enable-framerate"),
|
|
withAllocations: Services.prefs.getBoolPref("devtools.performance.ui.enable-memory"),
|
|
allocationsSampleProbability: +Services.prefs.getCharPref("devtools.performance.memory.sample-probability"),
|
|
allocationsMaxLogLength: Services.prefs.getIntPref("devtools.performance.memory.max-log-length")
|
|
};
|
|
}
|
|
|
|
exports.getPerformanceActorsConnection = target => SharedPerformanceActors.forTarget(target);
|
|
exports.PerformanceFront = PerformanceFront;
|