Bug 1077438 - Timeline actor should manage Framerate and Memory actors, r=jsantell
authorVictor Porof <vporof@mozilla.com>
Sun, 12 Oct 2014 10:50:56 +0300
changeset 210004 661453731c9a209d21f8d922d3ee8a1750ccb9e0
parent 210003 ece1933321b7f0975753ce211f28fd4a47a51662
child 210005 1ceec2777843403a6da2316ba34d00930b9d58bf
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersjsantell
bugs1077438
milestone35.0a1
Bug 1077438 - Timeline actor should manage Framerate and Memory actors, r=jsantell
toolkit/devtools/server/actors/timeline.js
toolkit/devtools/server/tests/browser/browser.ini
toolkit/devtools/server/tests/browser/browser_timeline_actors.js
--- 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;
+}