mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1077442 - Create a pseudo-PerformanceFront, r=vp
This commit is contained in:
parent
303bccd79d
commit
edeff29eb1
@ -1386,6 +1386,8 @@ pref("devtools.timeline.enabled", false);
|
||||
pref("devtools.performance_dev.enabled", false);
|
||||
#endif
|
||||
|
||||
pref("devtools.performance.ui.show-timeline-memory", false);
|
||||
|
||||
// The default Profiler UI settings
|
||||
pref("devtools.profiler.ui.show-platform-data", false);
|
||||
|
||||
|
322
browser/devtools/performance/modules/front.js
Normal file
322
browser/devtools/performance/modules/front.js
Normal file
@ -0,0 +1,322 @@
|
||||
/* 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 { extend } = require("sdk/util/object");
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
|
||||
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, "DevToolsUtils",
|
||||
"devtools/toolkit/DevToolsUtils");
|
||||
|
||||
loader.lazyImporter(this, "gDevTools",
|
||||
"resource:///modules/devtools/gDevTools.jsm");
|
||||
|
||||
let showTimelineMemory = () => Services.prefs.getBoolPref("devtools.performance.ui.show-timeline-memory");
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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);
|
||||
|
||||
Services.obs.notifyObservers(null, "performance-actors-connection-created", null);
|
||||
}
|
||||
|
||||
PerformanceActorsConnection.prototype = {
|
||||
|
||||
/**
|
||||
* Initializes a connection to the profiler and other miscellaneous actors.
|
||||
* If already open, nothing happens.
|
||||
*
|
||||
* @return object
|
||||
* A promise that is resolved once the connection is established.
|
||||
*/
|
||||
open: Task.async(function*() {
|
||||
if (this._connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Local debugging needs to make the target remote.
|
||||
yield this._target.makeRemote();
|
||||
|
||||
// Sets `this._profiler`
|
||||
yield this._connectProfilerActor();
|
||||
|
||||
// Sets or shims `this._timeline`
|
||||
yield this._connectTimelineActor();
|
||||
|
||||
this._connected = true;
|
||||
|
||||
Services.obs.notifyObservers(null, "performance-actors-connection-opened", null);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Destroys this connection.
|
||||
*/
|
||||
destroy: function () {
|
||||
this._disconnectActors();
|
||||
this._connected = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes a connection to the profiler actor.
|
||||
*/
|
||||
_connectProfilerActor: Task.async(function*() {
|
||||
// Chrome debugging targets have already obtained a reference
|
||||
// to the profiler actor.
|
||||
if (this._target.chrome) {
|
||||
this._profiler = this._target.form.profilerActor;
|
||||
}
|
||||
// Or when we are debugging content processes, we already have the tab
|
||||
// specific one. Use it immediately.
|
||||
else if (this._target.form && this._target.form.profilerActor) {
|
||||
this._profiler = this._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 (this._target.root && this._target.root.profilerActor) {
|
||||
this._profiler = this._target.root.profilerActor;
|
||||
}
|
||||
// Otherwise, call `listTabs`.
|
||||
else {
|
||||
this._profiler = (yield listTabs(this._client)).profilerActor;
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Initializes a connection to a timeline actor.
|
||||
*/
|
||||
_connectTimelineActor: function() {
|
||||
// Only initialize the timeline front if the respective actor is available.
|
||||
// Older Gecko versions don't have an existing implementation, in which case
|
||||
// all the methods we need can be easily mocked.
|
||||
//
|
||||
// If the timeline actor exists, all underlying actors (memory, framerate) exist,
|
||||
// with the expected methods and behaviour. If using the Performance tool,
|
||||
// and timeline actor does not exist (FxOS devices < Gecko 35),
|
||||
// then just use the mocked actor and do not display timeline data.
|
||||
//
|
||||
// TODO use framework level feature detection from bug 1069673
|
||||
if (this._target.form && this._target.form.timelineActor) {
|
||||
this._timeline = new TimelineFront(this._target.client, this._target.form);
|
||||
} else {
|
||||
this._timeline = {
|
||||
start: () => {},
|
||||
stop: () => {},
|
||||
isRecording: () => false,
|
||||
on: () => {},
|
||||
off: () => {},
|
||||
destroy: () => {}
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes the connections to non-profiler actors.
|
||||
*/
|
||||
_disconnectActors: function () {
|
||||
this._timeline.destroy();
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends the request over the remote debugging protocol to the
|
||||
* specified actor.
|
||||
*
|
||||
* @param string actor
|
||||
* The designated actor. Currently supported: "profiler", "timeline".
|
||||
* @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") {
|
||||
let deferred = promise.defer();
|
||||
let data = args[0] || {};
|
||||
data.to = this._profiler;
|
||||
data.type = method;
|
||||
this._client.request(data, deferred.resolve);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
// Handle requests to the timeline actor.
|
||||
if (actor == "timeline") {
|
||||
return this._timeline[method].apply(this._timeline, args);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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._request = connection._request;
|
||||
|
||||
// Pipe events from TimelineActor to the PerformanceFront
|
||||
connection._timeline.on("markers", markers => this.emit("markers", markers));
|
||||
connection._timeline.on("memory", (delta, measurement) => this.emit("memory", delta, measurement));
|
||||
connection._timeline.on("ticks", (delta, timestamps) => this.emit("ticks", delta, timestamps));
|
||||
}
|
||||
|
||||
PerformanceFront.prototype = {
|
||||
/**
|
||||
* Manually begins a recording session.
|
||||
*
|
||||
* @return object
|
||||
* A promise that is resolved once recording has started.
|
||||
*/
|
||||
startRecording: Task.async(function*() {
|
||||
let { isActive, currentTime } = yield this._request("profiler", "isActive");
|
||||
|
||||
// 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.
|
||||
if (!isActive) {
|
||||
// Extend the options so that protocol.js doesn't modify
|
||||
// the source object.
|
||||
let options = extend({}, this._customPerformanceOptions);
|
||||
yield this._request("profiler", "startProfiler", options);
|
||||
this._profilingStartTime = 0;
|
||||
this.emit("profiler-activated");
|
||||
} else {
|
||||
this._profilingStartTime = currentTime;
|
||||
this.emit("profiler-already-active");
|
||||
}
|
||||
|
||||
// The timeline actor is target-dependent, so just make sure
|
||||
// it's recording.
|
||||
let withMemory = showTimelineMemory();
|
||||
yield this._request("timeline", "start", { withTicks: true, withMemory: withMemory });
|
||||
}),
|
||||
|
||||
/**
|
||||
* Manually ends the current recording session.
|
||||
*
|
||||
* @return object
|
||||
* A promise that is resolved once recording has stopped,
|
||||
* with the profiler and timeline data.
|
||||
*/
|
||||
stopRecording: Task.async(function*() {
|
||||
// We'll need to filter out all samples that fall out of current profile's
|
||||
// range. This is necessary because the profiler is continuously running.
|
||||
let profilerData = yield this._request("profiler", "getProfile");
|
||||
filterSamples(profilerData, this._profilingStartTime);
|
||||
offsetSampleTimes(profilerData, this._profilingStartTime);
|
||||
|
||||
yield this._request("timeline", "stop");
|
||||
|
||||
// Join all the acquired data and return it for outside consumers.
|
||||
return {
|
||||
recordingDuration: profilerData.currentTime - this._profilingStartTime,
|
||||
profilerData: profilerData
|
||||
};
|
||||
}),
|
||||
|
||||
/**
|
||||
* Overrides the options sent to the built-in profiler module when activating,
|
||||
* such as the maximum entries count, the sampling interval etc.
|
||||
*
|
||||
* Used in tests and for older backend implementations.
|
||||
*/
|
||||
_customPerformanceOptions: {
|
||||
entries: 1000000,
|
||||
interval: 1,
|
||||
features: ["js"]
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Filters all the samples in the provided profiler data to be more recent
|
||||
* than the specified start time.
|
||||
*
|
||||
* @param object profilerData
|
||||
* The profiler data received from the backend.
|
||||
* @param number profilingStartTime
|
||||
* The earliest acceptable sample time (in milliseconds).
|
||||
*/
|
||||
function filterSamples(profilerData, profilingStartTime) {
|
||||
let firstThread = profilerData.profile.threads[0];
|
||||
|
||||
firstThread.samples = firstThread.samples.filter(e => {
|
||||
return e.time >= profilingStartTime;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Offsets all the samples in the provided profiler data by the specified time.
|
||||
*
|
||||
* @param object profilerData
|
||||
* The profiler data received from the backend.
|
||||
* @param number timeOffset
|
||||
* The amount of time to offset by (in milliseconds).
|
||||
*/
|
||||
function offsetSampleTimes(profilerData, timeOffset) {
|
||||
let firstThreadSamples = profilerData.profile.threads[0].samples;
|
||||
|
||||
for (let sample of firstThreadSamples) {
|
||||
sample.time -= timeOffset;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A collection of small wrappers promisifying functions invoking callbacks.
|
||||
*/
|
||||
function listTabs(client) {
|
||||
let deferred = promise.defer();
|
||||
client.listTabs(deferred.resolve);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
exports.getPerformanceActorsConnection = target => SharedPerformanceActors.forTarget(target);
|
||||
exports.PerformanceFront = PerformanceFront;
|
@ -4,5 +4,8 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
EXTRA_JS_MODULES.devtools.performance += [
|
||||
'modules/front.js',
|
||||
'panel.js'
|
||||
]
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||
|
@ -6,6 +6,7 @@
|
||||
"use strict";
|
||||
|
||||
const {Cc, Ci, Cu, Cr} = require("chrome");
|
||||
const { PerformanceFront, getPerformanceActorsConnection } = require("devtools/performance/front");
|
||||
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
@ -27,16 +28,17 @@ PerformancePanel.prototype = {
|
||||
* Open is effectively an asynchronous constructor.
|
||||
*
|
||||
* @return object
|
||||
* A promise that is resolved when the Profiler completes opening.
|
||||
* A promise that is resolved when the Performance tool
|
||||
* completes opening.
|
||||
*/
|
||||
open: Task.async(function*() {
|
||||
this.panelWin.gToolbox = this._toolbox;
|
||||
this.panelWin.gTarget = this.target;
|
||||
|
||||
// Mock Front for now
|
||||
let gFront = {};
|
||||
EventEmitter.decorate(gFront);
|
||||
this.panelWin.gFront = gFront;
|
||||
this._connection = getPerformanceActorsConnection(this.target);
|
||||
yield this._connection.open();
|
||||
|
||||
this.panelWin.gFront = new PerformanceFront(this._connection);
|
||||
|
||||
yield this.panelWin.startupPerformance();
|
||||
|
||||
@ -55,6 +57,9 @@ PerformancePanel.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
// Destroy the connection to ensure packet handlers are removed from client.
|
||||
this._connection.destroy();
|
||||
|
||||
yield this.panelWin.shutdownPerformance();
|
||||
this.emit("destroyed");
|
||||
this._destroyed = true;
|
||||
|
30
browser/devtools/performance/test/browser.ini
Normal file
30
browser/devtools/performance/test/browser.ini
Normal file
@ -0,0 +1,30 @@
|
||||
[DEFAULT]
|
||||
skip-if = e10s # Handle in Bug 1077464 for profiler
|
||||
subsuite = devtools
|
||||
support-files =
|
||||
doc_simple-test.html
|
||||
head.js
|
||||
|
||||
# Commented out tests are profiler tests
|
||||
# that need to be moved over to performance tool
|
||||
|
||||
[browser_perf-aaa-run-first-leaktest.js]
|
||||
[browser_perf-front-basic-timeline-01.js]
|
||||
[browser_perf-front-basic-profiler-01.js]
|
||||
# bug 1077464
|
||||
#[browser_perf-front-profiler-01.js]
|
||||
[browser_perf-front-profiler-02.js]
|
||||
[browser_perf-front-profiler-03.js]
|
||||
[browser_perf-front-profiler-04.js]
|
||||
# bug 1077464
|
||||
#[browser_perf-front-profiler-05.js]
|
||||
# bug 1077464
|
||||
#[browser_perf-front-profiler-06.js]
|
||||
# needs shared connection with profiler's shared connection
|
||||
#[browser_perf-shared-connection-01.js]
|
||||
[browser_perf-shared-connection-02.js]
|
||||
[browser_perf-shared-connection-03.js]
|
||||
# bug 1077464
|
||||
#[browser_perf-shared-connection-04.js]
|
||||
[browser_perf-data-samples.js]
|
||||
[browser_perf-data-massaging-01.js]
|
@ -0,0 +1,22 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the performance tool leaks on initialization and sudden destruction.
|
||||
* You can also use this initialization format as a template for other tests.
|
||||
*/
|
||||
|
||||
function spawnTest () {
|
||||
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
|
||||
|
||||
ok(target, "Should have a target available.");
|
||||
ok(toolbox, "Should have a toolbox available.");
|
||||
ok(panel, "Should have a panel available.");
|
||||
|
||||
ok(panel.panelWin.gToolbox, "Should have a toolbox reference on the panel window.");
|
||||
ok(panel.panelWin.gTarget, "Should have a target reference on the panel window.");
|
||||
ok(panel.panelWin.gFront, "Should have a front reference on the panel window.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the retrieved profiler data samples are correctly filtered and
|
||||
* normalized before passed to consumers.
|
||||
*/
|
||||
|
||||
const WAIT_TIME = 1000; // ms
|
||||
|
||||
function spawnTest () {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let front = panel.panelWin.gFront;
|
||||
|
||||
// Perform the first recording...
|
||||
|
||||
yield front.startRecording();
|
||||
let profilingStartTime = front._profilingStartTime;
|
||||
info("Started profiling at: " + profilingStartTime);
|
||||
|
||||
busyWait(WAIT_TIME); // allow the profiler module to sample some cpu activity
|
||||
|
||||
let firstRecordingData = yield front.stopRecording();
|
||||
let firstRecordingFinishTime = firstRecordingData.profilerData.currentTime;
|
||||
|
||||
is(profilingStartTime, 0,
|
||||
"The profiling start time should be 0 for the first recording.");
|
||||
ok(firstRecordingData.recordingDuration >= WAIT_TIME,
|
||||
"The first recording duration is correct.");
|
||||
ok(firstRecordingFinishTime >= WAIT_TIME,
|
||||
"The first recording finish time is correct.");
|
||||
|
||||
// Perform the second recording...
|
||||
|
||||
yield front.startRecording();
|
||||
profilingStartTime = front._profilingStartTime;
|
||||
info("Started profiling at: " + profilingStartTime);
|
||||
|
||||
busyWait(WAIT_TIME); // allow the profiler module to sample more cpu activity
|
||||
|
||||
let secondRecordingData = yield front.stopRecording();
|
||||
let secondRecordingFinishTime = secondRecordingData.profilerData.currentTime;
|
||||
let secondRecordingProfile = secondRecordingData.profilerData.profile;
|
||||
let secondRecordingSamples = secondRecordingProfile.threads[0].samples;
|
||||
|
||||
isnot(profilingStartTime, 0,
|
||||
"The profiling start time should not be 0 on the second recording.");
|
||||
ok(secondRecordingData.recordingDuration >= WAIT_TIME,
|
||||
"The second recording duration is correct.");
|
||||
ok(secondRecordingFinishTime - firstRecordingFinishTime >= WAIT_TIME,
|
||||
"The second recording finish time is correct.");
|
||||
|
||||
ok(secondRecordingSamples[0].time < profilingStartTime,
|
||||
"The second recorded sample times were normalized.");
|
||||
ok(secondRecordingSamples[0].time > 0,
|
||||
"The second recorded sample times were normalized correctly.");
|
||||
ok(!secondRecordingSamples.find(e => e.time + profilingStartTime <= firstRecordingFinishTime),
|
||||
"There should be no samples from the first recording in the second one, " +
|
||||
"even though the total number of frames did not overflow.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the retrieved profiler data samples always have a (root) node.
|
||||
* If this ever changes, the |ThreadNode.prototype.insert| function in
|
||||
* browser/devtools/profiler/utils/tree-model.js will have to be changed.
|
||||
*/
|
||||
|
||||
const WAIT_TIME = 1000; // ms
|
||||
|
||||
function spawnTest () {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let front = panel.panelWin.gFront;
|
||||
|
||||
yield front.startRecording();
|
||||
busyWait(WAIT_TIME); // allow the profiler module to sample some cpu activity
|
||||
|
||||
let recordingData = yield front.stopRecording();
|
||||
let profile = recordingData.profilerData.profile;
|
||||
|
||||
for (let thread of profile.threads) {
|
||||
info("Checking thread: " + thread.name);
|
||||
|
||||
for (let sample of thread.samples) {
|
||||
if (sample.frames[0].location != "(root)") {
|
||||
ok(false, "The sample " + sample.toSource() + " doesn't have a root node.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test basic functionality of PerformanceFront
|
||||
*/
|
||||
|
||||
let WAIT = 1000;
|
||||
|
||||
function spawnTest () {
|
||||
let { target, front } = yield initBackend(SIMPLE_URL);
|
||||
|
||||
yield front.startRecording();
|
||||
|
||||
yield busyWait(WAIT);
|
||||
|
||||
let { recordingDuration, profilerData } = yield front.stopRecording();
|
||||
|
||||
ok(recordingDuration > 500, "recordingDuration exists");
|
||||
ok(profilerData, "profilerData exists");
|
||||
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test basic functionality of PerformanceFront, retrieving timeline data.
|
||||
*/
|
||||
|
||||
function spawnTest () {
|
||||
Services.prefs.setBoolPref("devtools.performance.ui.show-timeline-memory", true);
|
||||
|
||||
let { target, front } = yield initBackend(SIMPLE_URL);
|
||||
|
||||
let lastMemoryDelta = 0;
|
||||
let lastTickDelta = 0;
|
||||
|
||||
let counters = {
|
||||
markers: [],
|
||||
memory: [],
|
||||
ticks: []
|
||||
};
|
||||
|
||||
let deferreds = {
|
||||
markers: Promise.defer(),
|
||||
memory: Promise.defer(),
|
||||
ticks: Promise.defer()
|
||||
}
|
||||
|
||||
front.on("markers", handler);
|
||||
front.on("memory", handler);
|
||||
front.on("ticks", handler);
|
||||
|
||||
yield front.startRecording();
|
||||
|
||||
yield Promise.all(Object.keys(deferreds).map(type => deferreds[type].promise));
|
||||
|
||||
yield front.stopRecording();
|
||||
|
||||
is(counters.markers.length, 1, "one marker event fired.");
|
||||
is(counters.memory.length, 3, "three memory events fired.");
|
||||
is(counters.ticks.length, 3, "three ticks events fired.");
|
||||
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
|
||||
function handler (name, ...args) {
|
||||
if (name === "memory") {
|
||||
let [delta, measurement] = args;
|
||||
is(typeof delta, "number", "received `delta` in memory event");
|
||||
ok(delta > lastMemoryDelta, "received `delta` in memory event");
|
||||
ok(measurement.total, "received `total` in memory event");
|
||||
ok(measurement.domSize, "received `domSize` in memory event");
|
||||
ok(measurement.jsObjectsSize, "received `jsObjectsSize` in memory event");
|
||||
|
||||
counters.memory.push({ delta: delta, measurement: measurement });
|
||||
lastMemoryDelta = delta;
|
||||
} else if (name === "ticks") {
|
||||
let [delta, timestamps] = args;
|
||||
ok(delta > lastTickDelta, "received `delta` in ticks event");
|
||||
|
||||
// First tick doesn't contain any timestamps
|
||||
if (counters.ticks.length) {
|
||||
ok(timestamps.length, "received `timestamps` in ticks event");
|
||||
}
|
||||
|
||||
counters.ticks.push({ delta: delta, timestamps: timestamps});
|
||||
lastTickDelta = delta;
|
||||
} else if (name === "markers") {
|
||||
let [markers] = args;
|
||||
ok(markers[0].start, "received atleast one marker with `start`");
|
||||
ok(markers[0].end, "received atleast one marker with `end`");
|
||||
ok(markers[0].name, "received atleast one marker with `name`");
|
||||
counters.markers.push(markers);
|
||||
front.off(name, handler);
|
||||
deferreds[name].resolve();
|
||||
} else {
|
||||
throw new Error("unknown event");
|
||||
}
|
||||
|
||||
if (name !== "markers" && counters[name].length === 3) {
|
||||
front.off(name, handler);
|
||||
deferreds[name].resolve();
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler connection front does not activate the built-in
|
||||
* profiler module if not necessary, and doesn't deactivate it when
|
||||
* a recording is stopped.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
|
||||
let front = panel.panelWin.gFront;
|
||||
|
||||
ok(!nsIProfilerModule.IsActive(),
|
||||
"The built-in profiler module should not have been automatically started.");
|
||||
|
||||
let activated = front.once("profiler-activated");
|
||||
yield front.startRecording();
|
||||
yield activated;
|
||||
yield front.stopRecording();
|
||||
ok(nsIProfilerModule.IsActive(),
|
||||
"The built-in profiler module should still be active (1).");
|
||||
|
||||
let alreadyActive = front.once("profiler-already-active");
|
||||
yield front.startRecording();
|
||||
yield alreadyActive;
|
||||
yield front.stopRecording();
|
||||
ok(nsIProfilerModule.IsActive(),
|
||||
"The built-in profiler module should still be active (2).");
|
||||
|
||||
yield teardown(panel);
|
||||
|
||||
ok(!nsIProfilerModule.IsActive(),
|
||||
"The built-in profiler module should have been automatically stoped.");
|
||||
|
||||
finish();
|
||||
});
|
@ -0,0 +1,33 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the built-in profiler module doesn't deactivate when the toolbox
|
||||
* is destroyed if there are other consumers using it.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let { panel: firstPanel } = yield initPerformance(SIMPLE_URL);
|
||||
let firstFront = firstPanel.panelWin.gFront;
|
||||
|
||||
let activated = firstFront.once("profiler-activated");
|
||||
yield firstFront.startRecording();
|
||||
yield activated;
|
||||
|
||||
let { panel: secondPanel } = yield initPerformance(SIMPLE_URL);
|
||||
let secondFront = secondPanel.panelWin.gFront;
|
||||
|
||||
let alreadyActive = secondFront.once("profiler-already-active");
|
||||
yield secondFront.startRecording();
|
||||
yield alreadyActive;
|
||||
|
||||
yield teardown(firstPanel);
|
||||
ok(nsIProfilerModule.IsActive(),
|
||||
"The built-in profiler module should still be active.");
|
||||
|
||||
yield teardown(secondPanel);
|
||||
ok(!nsIProfilerModule.IsActive(),
|
||||
"The built-in profiler module should have been automatically stoped.");
|
||||
|
||||
finish();
|
||||
});
|
@ -0,0 +1,43 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the built-in profiler module is not reactivated if no other
|
||||
* consumer was using it over the remote debugger protocol, and ensures
|
||||
* that the actor will work properly even in such cases (e.g. the Gecko Profiler
|
||||
* addon was installed and automatically activated the profiler module).
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
// Ensure the profiler is already running when the test starts.
|
||||
let ENTRIES = 1000000;
|
||||
let INTERVAL = 1;
|
||||
let FEATURES = ["js"];
|
||||
nsIProfilerModule.StartProfiler(ENTRIES, INTERVAL, FEATURES, FEATURES.length);
|
||||
|
||||
let { panel: firstPanel } = yield initPerformance(SIMPLE_URL);
|
||||
let firstFront = firstPanel.panelWin.gFront;
|
||||
|
||||
let alredyActive = firstFront.once("profiler-already-active");
|
||||
yield firstFront.startRecording();
|
||||
yield alredyActive;
|
||||
ok(firstFront._profilingStartTime > 0, "The profiler was not restarted.");
|
||||
|
||||
let { panel: secondPanel } = yield initPerformance(SIMPLE_URL);
|
||||
let secondFront = secondPanel.panelWin.gFront;
|
||||
|
||||
let alreadyActive = secondFront.once("profiler-already-active");
|
||||
yield secondFront.startRecording();
|
||||
yield alreadyActive;
|
||||
ok(secondFront._profilingStartTime > 0, "The profiler was not restarted.");
|
||||
|
||||
yield teardown(firstPanel);
|
||||
ok(nsIProfilerModule.IsActive(),
|
||||
"The built-in profiler module should still be active.");
|
||||
|
||||
yield teardown(secondPanel);
|
||||
ok(!nsIProfilerModule.IsActive(),
|
||||
"The built-in profiler module should have been automatically stoped.");
|
||||
|
||||
finish();
|
||||
});
|
@ -0,0 +1,39 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the shared PerformanceActorsConnection is only opened once.
|
||||
*/
|
||||
|
||||
let gProfilerConnectionsOpened = 0;
|
||||
Services.obs.addObserver(profilerConnectionObserver, "performance-actors-connection-opened", false);
|
||||
|
||||
function spawnTest () {
|
||||
let { target, panel } = yield initPerformance(SIMPLE_URL);
|
||||
|
||||
is(gProfilerConnectionsOpened, 1,
|
||||
"Only one profiler connection was opened.");
|
||||
|
||||
let sharedConnection = getPerformanceActorsConnection(target);
|
||||
|
||||
ok(sharedConnection,
|
||||
"A shared profiler connection for the current toolbox was retrieved.");
|
||||
is(sharedConnection._request, panel.panelWin.gFront._request,
|
||||
"The same shared profiler connection is used by the panel's front.");
|
||||
|
||||
yield sharedConnection.open();
|
||||
is(gProfilerConnectionsOpened, 1,
|
||||
"No additional profiler connections were opened.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
||||
|
||||
function profilerConnectionObserver(subject, topic, data) {
|
||||
is(topic, "performance-actors-connection-opened", "The correct topic was observed.");
|
||||
gProfilerConnectionsOpened++;
|
||||
}
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.obs.removeObserver(profilerConnectionObserver, "performance-actors-connection-opened");
|
||||
});
|
@ -0,0 +1,29 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the shared PerformanceActorsConnection can properly send requests.
|
||||
*/
|
||||
|
||||
function spawnTest () {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let front = panel.panelWin.gFront;
|
||||
|
||||
ok(!nsIProfilerModule.IsActive(),
|
||||
"The built-in profiler module should not have been automatically started.");
|
||||
|
||||
let result = yield front._request("profiler", "startProfiler");
|
||||
is(result.started, true,
|
||||
"The request finished successfully and the profiler should've been started.");
|
||||
ok(nsIProfilerModule.IsActive(),
|
||||
"The built-in profiler module should now be active.");
|
||||
|
||||
result = yield front._request("profiler", "stopProfiler");
|
||||
is(result.started, false,
|
||||
"The request finished successfully and the profiler should've been stopped.");
|
||||
ok(!nsIProfilerModule.IsActive(),
|
||||
"The built-in profiler module should now be inactive.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
22
browser/devtools/performance/test/doc_simple-test.html
Normal file
22
browser/devtools/performance/test/doc_simple-test.html
Normal file
@ -0,0 +1,22 @@
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Profiler test page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
function test() {
|
||||
var a = "Hello world!";
|
||||
}
|
||||
|
||||
// Prevent this script from being garbage collected.
|
||||
window.setInterval(test, 1);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
209
browser/devtools/performance/test/head.js
Normal file
209
browser/devtools/performance/test/head.js
Normal file
@ -0,0 +1,209 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
|
||||
// Enable logging for all the tests. Both the debugger server and frontend will
|
||||
// be affected by this pref.
|
||||
let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
|
||||
Services.prefs.setBoolPref("devtools.debugger.log", false);
|
||||
|
||||
let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
let { Promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
|
||||
let { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
|
||||
let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
|
||||
let { getPerformanceActorsConnection, PerformanceFront } = devtools.require("devtools/performance/front");
|
||||
let nsIProfilerModule = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
|
||||
let TargetFactory = devtools.TargetFactory;
|
||||
let mm = null;
|
||||
|
||||
const FRAME_SCRIPT_UTILS_URL = "chrome://browser/content/devtools/frame-script-utils.js"
|
||||
const EXAMPLE_URL = "http://example.com/browser/browser/devtools/performance/test/";
|
||||
const SIMPLE_URL = EXAMPLE_URL + "doc_simple-test.html";
|
||||
|
||||
// All tests are asynchronous.
|
||||
waitForExplicitFinish();
|
||||
|
||||
let gToolEnabled = Services.prefs.getBoolPref("devtools.performance_dev.enabled");
|
||||
let gShowTimelineMemory = Services.prefs.getBoolPref("devtools.performance.ui.show-timeline-memory");
|
||||
|
||||
gDevTools.testing = true;
|
||||
|
||||
/**
|
||||
* Call manually in tests that use frame script utils after initializing
|
||||
* the tool. Must be called after initializing so we can detect
|
||||
* whether or not `content` is a CPOW or not. Call after init but before navigating
|
||||
* to different pages.
|
||||
*/
|
||||
function loadFrameScripts () {
|
||||
mm = gBrowser.selectedBrowser.messageManager;
|
||||
mm.loadFrameScript(FRAME_SCRIPT_UTILS_URL, false);
|
||||
}
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
gDevTools.testing = false;
|
||||
info("finish() was called, cleaning up...");
|
||||
|
||||
Services.prefs.setBoolPref("devtools.performance.ui.show-timeline-memory", gShowTimelineMemory);
|
||||
Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
|
||||
Services.prefs.setBoolPref("devtools.performance_dev.enabled", gToolEnabled);
|
||||
// Make sure the profiler module is stopped when the test finishes.
|
||||
nsIProfilerModule.StopProfiler();
|
||||
|
||||
Cu.forceGC();
|
||||
});
|
||||
|
||||
function addTab(aUrl, aWindow) {
|
||||
info("Adding tab: " + aUrl);
|
||||
|
||||
let deferred = Promise.defer();
|
||||
let targetWindow = aWindow || window;
|
||||
let targetBrowser = targetWindow.gBrowser;
|
||||
|
||||
targetWindow.focus();
|
||||
let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl);
|
||||
let linkedBrowser = tab.linkedBrowser;
|
||||
|
||||
linkedBrowser.addEventListener("load", function onLoad() {
|
||||
linkedBrowser.removeEventListener("load", onLoad, true);
|
||||
info("Tab added and finished loading: " + aUrl);
|
||||
deferred.resolve(tab);
|
||||
}, true);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function removeTab(aTab, aWindow) {
|
||||
info("Removing tab.");
|
||||
|
||||
let deferred = Promise.defer();
|
||||
let targetWindow = aWindow || window;
|
||||
let targetBrowser = targetWindow.gBrowser;
|
||||
let tabContainer = targetBrowser.tabContainer;
|
||||
|
||||
tabContainer.addEventListener("TabClose", function onClose(aEvent) {
|
||||
tabContainer.removeEventListener("TabClose", onClose, false);
|
||||
info("Tab removed and finished closing.");
|
||||
deferred.resolve();
|
||||
}, false);
|
||||
|
||||
targetBrowser.removeTab(aTab);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function handleError(aError) {
|
||||
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
|
||||
finish();
|
||||
}
|
||||
|
||||
function once(aTarget, aEventName, aUseCapture = false) {
|
||||
info("Waiting for event: '" + aEventName + "' on " + aTarget + ".");
|
||||
|
||||
let deferred = Promise.defer();
|
||||
|
||||
for (let [add, remove] of [
|
||||
["on", "off"], // Use event emitter before DOM events for consistency
|
||||
["addEventListener", "removeEventListener"],
|
||||
["addListener", "removeListener"]
|
||||
]) {
|
||||
if ((add in aTarget) && (remove in aTarget)) {
|
||||
aTarget[add](aEventName, function onEvent(...aArgs) {
|
||||
aTarget[remove](aEventName, onEvent, aUseCapture);
|
||||
deferred.resolve(...aArgs);
|
||||
}, aUseCapture);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function test () {
|
||||
Task.spawn(spawnTest).then(finish, handleError);
|
||||
}
|
||||
|
||||
function initBackend(aUrl) {
|
||||
info("Initializing a performance front.");
|
||||
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init(() => true);
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
|
||||
return Task.spawn(function*() {
|
||||
let tab = yield addTab(aUrl);
|
||||
let target = TargetFactory.forTab(tab);
|
||||
|
||||
yield target.makeRemote();
|
||||
|
||||
yield gDevTools.showToolbox(target, "performance");
|
||||
|
||||
let connection = getPerformanceActorsConnection(target);
|
||||
yield connection.open();
|
||||
let front = new PerformanceFront(connection);
|
||||
return { target, front };
|
||||
});
|
||||
}
|
||||
|
||||
function initPerformance(aUrl) {
|
||||
info("Initializing a performance pane.");
|
||||
|
||||
return Task.spawn(function*() {
|
||||
let tab = yield addTab(aUrl);
|
||||
let target = TargetFactory.forTab(tab);
|
||||
|
||||
yield target.makeRemote();
|
||||
|
||||
Services.prefs.setBoolPref("devtools.performance_dev.enabled", true);
|
||||
let toolbox = yield gDevTools.showToolbox(target, "performance");
|
||||
let panel = toolbox.getCurrentPanel();
|
||||
return { target, panel, toolbox };
|
||||
});
|
||||
}
|
||||
|
||||
function* teardown(panel) {
|
||||
info("Destroying the performance tool.");
|
||||
|
||||
let tab = panel.target.tab;
|
||||
yield panel._toolbox.destroy();
|
||||
yield removeTab(tab);
|
||||
}
|
||||
|
||||
function idleWait(time) {
|
||||
return DevToolsUtils.waitForTime(time);
|
||||
}
|
||||
|
||||
function consoleMethod (...args) {
|
||||
if (!mm) {
|
||||
throw new Error("`loadFrameScripts()` must be called before using frame scripts.");
|
||||
}
|
||||
mm.sendAsyncMessage("devtools:test:console", args);
|
||||
}
|
||||
|
||||
function* consoleProfile(connection, label) {
|
||||
let notified = connection.once("profile");
|
||||
consoleMethod("profile", label);
|
||||
yield notified;
|
||||
}
|
||||
|
||||
function* consoleProfileEnd(connection) {
|
||||
let notified = connection.once("profileEnd");
|
||||
consoleMethod("profileEnd");
|
||||
yield notified;
|
||||
}
|
||||
|
||||
function busyWait(time) {
|
||||
let start = Date.now();
|
||||
let stack;
|
||||
while (Date.now() - start < time) { stack = Components.stack; }
|
||||
}
|
||||
|
||||
function idleWait(time) {
|
||||
return DevToolsUtils.waitForTime(time);
|
||||
}
|
||||
|
@ -17,6 +17,11 @@ addMessageListener("devtools:test:reload", function ({ data }) {
|
||||
content.location.reload(data.forceget);
|
||||
});
|
||||
|
||||
addMessageListener("devtools:test:console", function ({ data }) {
|
||||
let method = data.shift();
|
||||
content.console[method].apply(content.console, data);
|
||||
});
|
||||
|
||||
addEventListener("load", function() {
|
||||
sendAsyncMessage("devtools:test:load");
|
||||
}, true);
|
||||
|
@ -212,7 +212,7 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
|
||||
|
||||
if (withMemory) {
|
||||
this._memoryActor = new MemoryActor(this.conn, this.tabActor);
|
||||
events.emit(this, "memory", Date.now(), this._memoryActor.measure());
|
||||
events.emit(this, "memory", this._startTime, this._memoryActor.measure());
|
||||
}
|
||||
if (withTicks) {
|
||||
this._framerateActor = new FramerateActor(this.conn, this.tabActor);
|
||||
|
Loading…
Reference in New Issue
Block a user