Bug 1077442 - Create a pseudo-PerformanceFront, r=vp

This commit is contained in:
Jordan Santell 2014-10-23 12:47:00 +02:00
parent 303bccd79d
commit edeff29eb1
19 changed files with 1013 additions and 6 deletions

View File

@ -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);

View 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;

View File

@ -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']

View File

@ -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;

View 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]

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}
};
}

View File

@ -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();
});

View File

@ -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();
});

View File

@ -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();
});

View File

@ -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");
});

View File

@ -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();
}

View 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>

View 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);
}

View File

@ -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);

View File

@ -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);