Bug 1077438 - Timeline actor should manage Framerate and Memory actors, r=jsantell
--- a/toolkit/devtools/server/actors/timeline.js
+++ b/toolkit/devtools/server/actors/timeline.js
@@ -18,19 +18,21 @@
*
* When markers are available, an event is emitted:
* TimelineFront.on("markers", function(markers) {...})
*
*/
const {Ci, Cu} = require("chrome");
const protocol = require("devtools/server/protocol");
-const {method, Arg, RetVal} = protocol;
+const {method, Arg, RetVal, Option} = protocol;
const events = require("sdk/event/core");
const {setTimeout, clearTimeout} = require("sdk/timers");
+const {MemoryActor} = require("devtools/server/actors/memory");
+const {FramerateActor} = require("devtools/server/actors/framerate");
// How often do we pull markers from the docShells, and therefore, how often do
// we send events to the front (knowing that when there are no markers in the
// docShell, no event is sent).
const DEFAULT_TIMELINE_DATA_PULL_TIMEOUT = 200; // ms
/**
* The timeline actor pops and forwards timeline markers registered in docshells.
@@ -45,16 +47,36 @@ let TimelineActor = exports.TimelineActo
* properties:
* - start {Number} ms
* - end {Number} ms
* - name {String}
*/
"markers" : {
type: "markers",
markers: Arg(0, "array:json")
+ },
+
+ /**
+ * "memory" events emitted in tandem with "markers", if this was enabled
+ * when the recording started.
+ */
+ "memory" : {
+ type: "memory",
+ delta: Arg(0, "number"),
+ measurement: Arg(1, "json")
+ },
+
+ /**
+ * "ticks" events (from the refresh driver) emitted in tandem with "markers",
+ * if this was enabled when the recording started.
+ */
+ "ticks" : {
+ type: "ticks",
+ delta: Arg(0, "number"),
+ timestamps: Arg(1, "array:number")
}
},
initialize: function(conn, tabActor) {
protocol.Actor.prototype.initialize.call(this, conn);
this.tabActor = tabActor;
this._isRecording = false;
@@ -117,16 +139,22 @@ let TimelineActor = exports.TimelineActo
let markers = [];
for (let docShell of this.docShells) {
markers = [...markers, ...docShell.popProfileTimelineMarkers()];
}
if (markers.length > 0) {
events.emit(this, "markers", markers);
}
+ if (this._memoryActor) {
+ events.emit(this, "memory", Date.now(), this._memoryActor.measure());
+ }
+ if (this._framerateActor) {
+ events.emit(this, "ticks", Date.now(), this._framerateActor.getPendingTicks());
+ }
this._dataPullTimeout = setTimeout(() => {
this._pullTimelineData();
}, DEFAULT_TIMELINE_DATA_PULL_TIMEOUT);
},
/**
* Are we recording profile markers currently?
@@ -138,38 +166,60 @@ let TimelineActor = exports.TimelineActo
response: {
value: RetVal("boolean")
}
}),
/**
* Start recording profile markers.
*/
- start: method(function() {
+ start: method(function({ withMemory, withTicks }) {
if (this._isRecording) {
return;
}
this._isRecording = true;
for (let docShell of this.docShells) {
docShell.recordProfileTimelineMarkers = true;
}
+ if (withMemory) {
+ this._memoryActor = new MemoryActor(this.conn, this.tabActor);
+ events.emit(this, "memory", Date.now(), this._memoryActor.measure());
+ }
+ if (withTicks) {
+ this._framerateActor = new FramerateActor(this.conn, this.tabActor);
+ this._framerateActor.startRecording();
+ }
+
this._pullTimelineData();
- }, {}),
+ }, {
+ request: {
+ withMemory: Option(0, "boolean"),
+ withTicks: Option(0, "boolean")
+ }
+ }),
/**
* Stop recording profile markers.
*/
stop: method(function() {
if (!this._isRecording) {
return;
}
this._isRecording = false;
+ if (this._memoryActor) {
+ this._memoryActor = null;
+ }
+ if (this._framerateActor) {
+ this._framerateActor.stopRecording();
+ this._framerateActor = null;
+ }
+
for (let docShell of this.docShells) {
docShell.recordProfileTimelineMarkers = false;
}
clearTimeout(this._dataPullTimeout);
}, {}),
/**
--- a/toolkit/devtools/server/tests/browser/browser.ini
+++ b/toolkit/devtools/server/tests/browser/browser.ini
@@ -14,10 +14,12 @@ support-files =
timeline-iframe-parent.html
[browser_navigateEvents.js]
[browser_storage_dynamic_windows.js]
[browser_storage_listings.js]
[browser_storage_updates.js]
[browser_timeline.js]
skip-if = buildapp == 'mulet'
+[browser_timeline_actors.js]
+skip-if = buildapp == 'mulet'
[browser_timeline_iframes.js]
skip-if = buildapp == 'mulet'
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/browser/browser_timeline_actors.js
@@ -0,0 +1,69 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the timeline can also record data from the memory and framerate
+// actors, emitted as events in tadem with the markers.
+
+const {TimelineFront} = require("devtools/server/actors/timeline");
+
+let test = asyncTest(function*() {
+ let doc = yield addTab("data:text/html;charset=utf-8,mop");
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = TimelineFront(client, form);
+
+ info("Start timeline marker recording");
+ yield front.start({ withMemory: true, withTicks: true });
+
+ let updatedMemory = 0;
+ let updatedTicks = 0;
+
+ front.on("memory", (delta, measurement) => {
+ ok(delta > 0, "The delta should be a timestamp.");
+ ok(measurement, "The measurement should not be null.");
+ ok(measurement.total > 0, "There should be a 'total' value in the measurement.");
+ info("Received 'memory' event at " + delta + " with " + measurement.toSource());
+ updatedMemory++;
+ });
+
+ front.on("ticks", (delta, ticks) => {
+ ok(delta > 0, "The delta should be a timestamp.");
+ ok(ticks, "The ticks should not be null.");
+ info("Received 'ticks' event with " + ticks.toSource());
+ updatedTicks++;
+ });
+
+ ok((yield waitUntil(() => updatedMemory > 1)),
+ "Some memory measurements were emitted.");
+ ok((yield waitUntil(() => updatedTicks > 1)),
+ "Some refresh driver ticks were emitted.");
+
+ info("Stop timeline marker recording");
+ yield front.stop();
+ yield closeDebuggerClient(client);
+ gBrowser.removeCurrentTab();
+});
+
+/**
+ * Waits until a predicate returns true.
+ *
+ * @param function predicate
+ * Invoked once in a while until it returns true.
+ * @param number interval [optional]
+ * How often the predicate is invoked, in milliseconds.
+ */
+function waitUntil(predicate, interval = 10) {
+ if (predicate()) {
+ return promise.resolve(true);
+ }
+ let deferred = promise.defer();
+ setTimeout(function() {
+ waitUntil(predicate).then(() => deferred.resolve(true));
+ }, interval);
+ return deferred.promise;
+}