Bug 1007200 - Create a framerate actor, r=rcampbell,benwa
authorVictor Porof <vporof@mozilla.com>
Thu, 15 May 2014 15:28:00 -0400
changeset 183443 5fff5735d8fa5eaf16068107aa51898b26f187f7
parent 183213 2018cc988098b19c80909ffe7b1f7e7b9860a68d
child 183444 45d8933aefc0433a48d921d3fd9a958339b51c37
push idunknown
push userunknown
push dateunknown
reviewersrcampbell, benwa
bugs1007200
milestone32.0a1
Bug 1007200 - Create a framerate actor, r=rcampbell,benwa
toolkit/devtools/server/actors/framerate.js
toolkit/devtools/server/main.js
toolkit/devtools/server/tests/mochitest/chrome.ini
toolkit/devtools/server/tests/mochitest/test_framerate_01.html
toolkit/devtools/server/tests/mochitest/test_framerate_02.html
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/actors/framerate.js
@@ -0,0 +1,119 @@
+/* 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 Services = require("Services");
+const events = require("sdk/event/core");
+const protocol = require("devtools/server/protocol");
+const DevToolsUtils = require("devtools/toolkit/DevToolsUtils.js");
+
+const {on, once, off, emit} = events;
+const {method, custom, Arg, Option, RetVal} = protocol;
+
+exports.register = function(handle) {
+  handle.addTabActor(FramerateActor, "framerateActor");
+};
+
+exports.unregister = function(handle) {
+  handle.removeTabActor(FramerateActor);
+};
+
+/**
+ * A very simple utility for monitoring framerate.
+ */
+let FramerateActor = exports.FramerateActor = protocol.ActorClass({
+  typeName: "framerate",
+  initialize: function(conn, tabActor) {
+    protocol.Actor.prototype.initialize.call(this, conn);
+    this.tabActor = tabActor;
+    this._contentWin = tabActor.window;
+    this._onRefreshDriverTick = this._onRefreshDriverTick.bind(this);
+  },
+  destroy: function(conn) {
+    protocol.Actor.prototype.destroy.call(this, conn);
+    this.finalize();
+  },
+
+  /**
+   * Starts monitoring framerate, storing the frames per second.
+   */
+  startRecording: method(function() {
+    if (this._recording) {
+      return;
+    }
+    this._recording = true;
+    this._ticks = [];
+
+    this._startTime = this._contentWin.performance.now();
+    this._contentWin.requestAnimationFrame(this._onRefreshDriverTick);
+  }, {
+  }),
+
+  /**
+   * Stops monitoring framerate, returning the recorded values at an
+   * interval defined by the specified resolution, in milliseconds.
+   */
+  stopRecording: method(function(resolution = 100) {
+    if (!this._recording) {
+      return {};
+    }
+    this._recording = false;
+
+    let timeline = {};
+    let ticks = this._ticks;
+    let totalTicks = ticks.length;
+
+    // We don't need to store the ticks array for future use, release it.
+    this._ticks = null;
+
+    // If the refresh driver didn't get a chance to tick before the
+    // recording was stopped, assume framerate was 0.
+    if (totalTicks == 0) {
+      timeline[resolution] = 0;
+      return timeline;
+    }
+
+    let pivotTick = 0;
+    let lastTick = ticks[totalTicks - 1];
+
+    for (let bucketTime = resolution; bucketTime < lastTick; bucketTime += resolution) {
+      let frameCount = 0;
+      while (ticks[pivotTick++] < bucketTime) frameCount++;
+
+      let framerate = 1000 / (bucketTime / frameCount);
+      timeline[bucketTime] = framerate;
+    }
+
+    return timeline;
+  }, {
+    request: { resolution: Arg(0, "nullable:number") },
+    response: { timeline: RetVal("json") }
+  }),
+
+  /**
+   * Function invoked along with the refresh driver.
+   */
+  _onRefreshDriverTick: function() {
+    if (!this._recording) {
+      return;
+    }
+    this._contentWin.requestAnimationFrame(this._onRefreshDriverTick);
+
+    // Store the amount of time passed since the recording started.
+    let currentTime = this._contentWin.performance.now();
+    let elapsedTime = currentTime - this._startTime;
+    this._ticks.push(elapsedTime);
+  }
+});
+
+/**
+ * The corresponding Front object for the FramerateActor.
+ */
+let FramerateFront = exports.FramerateFront = protocol.FrontClass(FramerateActor, {
+  initialize: function(client, { framerateActor }) {
+    protocol.Front.prototype.initialize.call(this, client, { actor: framerateActor });
+    this.manage(this);
+  }
+});
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -396,16 +396,17 @@ var DebuggerServer = {
     this.registerModule("devtools/server/actors/webgl");
     this.registerModule("devtools/server/actors/webaudio");
     this.registerModule("devtools/server/actors/stylesheets");
     this.registerModule("devtools/server/actors/styleeditor");
     this.registerModule("devtools/server/actors/storage");
     this.registerModule("devtools/server/actors/gcli");
     this.registerModule("devtools/server/actors/tracer");
     this.registerModule("devtools/server/actors/memory");
+    this.registerModule("devtools/server/actors/framerate");
     this.registerModule("devtools/server/actors/eventlooplag");
     this.registerModule("devtools/server/actors/layout");
     if ("nsIProfiler" in Ci) {
       this.addActors("resource://gre/modules/devtools/server/actors/profiler.js");
     }
   },
 
   /**
--- a/toolkit/devtools/server/tests/mochitest/chrome.ini
+++ b/toolkit/devtools/server/tests/mochitest/chrome.ini
@@ -14,16 +14,18 @@ support-files =
 
 [test_Debugger.Source.prototype.introductionScript.html]
 [test_Debugger.Source.prototype.introductionType.html]
 [test_Debugger.Source.prototype.element.html]
 [test_Debugger.Script.prototype.global.html]
 [test_connection-manager.html]
 [test_css-logic.html]
 [test_device.html]
+[test_framerate_01.html]
+[test_framerate_02.html]
 [test_inspector-changeattrs.html]
 [test_inspector-changevalue.html]
 [test_inspector-hide.html]
 [test_inspector-insert.html]
 [test_inspector-mutations-attr.html]
 [test_inspector-mutations-childlist.html]
 [test_inspector-mutations-frameload.html]
 [test_inspector-mutations-value.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/mochitest/test_framerate_01.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1007200 - Create a framerate actor
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Framerate actor test</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+
+window.onload = function() {
+  var Cu = Components.utils;
+  var Cc = Components.classes;
+  var Ci = Components.interfaces;
+
+  Cu.import("resource://gre/modules/Services.jsm");
+
+  // Always log packets when running tests.
+  Services.prefs.setBoolPref("devtools.debugger.log", true);
+  SimpleTest.registerCleanupFunction(function() {
+    Services.prefs.clearUserPref("devtools.debugger.log");
+  });
+
+  Cu.import("resource://gre/modules/devtools/Loader.jsm");
+  Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
+  Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
+
+  SimpleTest.waitForExplicitFinish();
+
+  var {FramerateFront} = devtools.require("devtools/server/actors/framerate");
+
+  DebuggerServer.init(function () { return true; });
+  DebuggerServer.addBrowserActors();
+
+  var client = new DebuggerClient(DebuggerServer.connectPipe());
+  client.connect(function onConnect() {
+    client.listTabs(function onListTabs(aResponse) {
+      var form = aResponse.tabs[aResponse.selected];
+      var front = FramerateFront(client, form);
+
+      window.setTimeout(() => {
+        front.startRecording().then(() => {
+          window.setTimeout(() => {
+            front.stopRecording().then(timeline => {
+              onRecordingStopped(timeline);
+            });
+          }, 1000);
+        });
+      }, 1000);
+    });
+  });
+
+  function onRecordingStopped(timeline) {
+    ok(timeline, "There should be a recording available.");
+
+    var ticks = Object.keys(timeline);
+    var values = ticks.map(e => timeline[e]);
+
+    ok(ticks.length >= 1,
+      "There should be at least one measurement available.");
+    is(ticks[0], 100,
+      "The first measurement should be performed when exactly 100ms passed.");
+
+    for (var tick of ticks) {
+      info("Testing tick: " + tick);
+      is(tick % 100, 0, "All ticks should be divisible by the resolution.");
+    }
+    for (var value of values) {
+      info("Testing value: " + value);
+      is(typeof value, "number", "All values should be integers.");
+    }
+
+    client.close(() => {
+      DebuggerServer.destroy();
+      SimpleTest.finish()
+    });
+  }
+}
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/mochitest/test_framerate_02.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1007200 - Create a framerate actor
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Framerate actor test</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+
+window.onload = function() {
+  var Cu = Components.utils;
+  var Cc = Components.classes;
+  var Ci = Components.interfaces;
+
+  Cu.import("resource://gre/modules/Services.jsm");
+
+  // Always log packets when running tests.
+  Services.prefs.setBoolPref("devtools.debugger.log", true);
+  SimpleTest.registerCleanupFunction(function() {
+    Services.prefs.clearUserPref("devtools.debugger.log");
+  });
+
+  Cu.import("resource://gre/modules/devtools/Loader.jsm");
+  Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
+  Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
+
+  SimpleTest.waitForExplicitFinish();
+
+  var {FramerateFront} = devtools.require("devtools/server/actors/framerate");
+
+  DebuggerServer.init(function () { return true; });
+  DebuggerServer.addBrowserActors();
+
+  var client = new DebuggerClient(DebuggerServer.connectPipe());
+  client.connect(function onConnect() {
+    client.listTabs(function onListTabs(aResponse) {
+      var form = aResponse.tabs[aResponse.selected];
+      var front = FramerateFront(client, form);
+
+      front.stopRecording().then(timeline => {
+        ok(timeline, "There should be a recording available.");
+        is(Object.keys(timeline).length, 0, "...but it should be empty.");
+
+        client.close(() => {
+          DebuggerServer.destroy();
+          SimpleTest.finish()
+        });
+      });
+    });
+  });
+}
+</script>
+</pre>
+</body>
+</html>