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 233219 661453731c9a209d21f8d922d3ee8a1750ccb9e0
parent 233218 ece1933321b7f0975753ce211f28fd4a47a51662
child 233220 1ceec2777843403a6da2316ba34d00930b9d58bf
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjsantell
bugs1077438
milestone35.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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;
+}