Bug 963498 - Add memory tracking to Firefox OS's Developer HUD. r=vingtetun
authorJan Keromnes <janx@linux.com>
Wed, 26 Feb 2014 16:58:37 -0500
changeset 171295 588d1ff56939e47de4a73dbcf42b332e4e9b0097
parent 171294 551e6c666d5256b799605461ba74168438487c14
child 171296 2288d8f7aa7b37c2fcada7ff5fef71f2c06918fc
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersvingtetun
bugs963498
milestone30.0a1
Bug 963498 - Add memory tracking to Firefox OS's Developer HUD. r=vingtetun
b2g/chrome/content/devtools.js
b2g/chrome/content/settings.js
--- a/b2g/chrome/content/devtools.js
+++ b/b2g/chrome/content/devtools.js
@@ -1,37 +1,44 @@
 /* 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 WIDGET_PANEL_LOG_PREFIX = 'WidgetPanel';
+const DEVELOPER_HUD_LOG_PREFIX = 'DeveloperHUD';
+
+XPCOMUtils.defineLazyGetter(this, 'devtools', function() {
+  const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+  return devtools;
+});
 
 XPCOMUtils.defineLazyGetter(this, 'DebuggerClient', function() {
   return Cu.import('resource://gre/modules/devtools/dbg-client.jsm', {}).DebuggerClient;
 });
 
 XPCOMUtils.defineLazyGetter(this, 'WebConsoleUtils', function() {
-  let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
   return devtools.require("devtools/toolkit/webconsole/utils").Utils;
 });
 
 XPCOMUtils.defineLazyGetter(this, 'EventLoopLagFront', function() {
-  const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
   return devtools.require("devtools/server/actors/eventlooplag").EventLoopLagFront;
 });
 
+XPCOMUtils.defineLazyGetter(this, 'MemoryFront', function() {
+  return devtools.require("devtools/server/actors/memory").MemoryFront;
+});
+
 
 /**
- * The Widget Panel is an on-device developer tool that displays widgets,
+ * The Developer HUD is an on-device developer tool that displays widgets,
  * showing visual debug information about apps. Each widget corresponds to a
  * metric as tracked by a metric watcher (e.g. consoleWatcher).
  */
-let devtoolsWidgetPanel = {
+let developerHUD = {
 
   _apps: new Map(),
   _urls: new Map(),
   _client: null,
   _webappsActor: null,
   _watchers: [],
 
   /**
@@ -102,17 +109,17 @@ let devtoolsWidgetPanel = {
   /**
    * This method will ask all registered watchers to track and update metrics
    * on an app.
    */
   trackApp: function dwp_trackApp(manifestURL) {
     if (this._apps.has(manifestURL))
       return;
 
-    // FIXME(Bug 962577) Factor getAppActor and watchApps out of webappsActor.
+    // FIXME(Bug 962577) Factor getAppActor out of webappsActor.
     this._client.request({
       to: this._webappsActor,
       type: 'getAppActor',
       manifestURL: manifestURL
     }, (res) => {
       if (res.error) {
         return;
       }
@@ -174,17 +181,17 @@ let devtoolsWidgetPanel = {
           return;
         this.untrackApp(manifestURL);
         this._urls.delete(mm);
         break;
     }
   },
 
   log: function dwp_log(message) {
-    dump(WIDGET_PANEL_LOG_PREFIX + ': ' + message + '\n');
+    dump(DEVELOPER_HUD_LOG_PREFIX + ': ' + message + '\n');
   }
 
 };
 
 
 /**
  * An App object represents all there is to know about a Firefox OS app that is
  * being tracked, e.g. its manifest information, current values of watched
@@ -203,17 +210,17 @@ App.prototype = {
     let metrics = this.metrics;
 
     if (metrics && metrics.size > 0) {
       for (let name of metrics.keys()) {
         data.metrics.push({name: name, value: metrics.get(name)});
       }
     }
 
-    shell.sendCustomEvent('widget-panel-update', data);
+    shell.sendCustomEvent('developer-hud-update', data);
     // FIXME(after bug 963239 lands) return event.isDefaultPrevented();
     return false;
   }
 
 };
 
 
 /**
@@ -233,19 +240,19 @@ let consoleWatcher = {
   init: function cw_init(client) {
     this._client = client;
     this.consoleListener = this.consoleListener.bind(this);
 
     let watching = this._watching;
 
     for (let key in watching) {
       let metric = key;
-      SettingsListener.observe('devtools.hud.' + metric, false, value => {
+      SettingsListener.observe('hud.' + metric, false, watch => {
         // Watch or unwatch the metric.
-        if (watching[metric] = value) {
+        if (watching[metric] = watch) {
           return;
         }
 
         // If unwatched, remove any existing widgets for that metric.
         for (let app of this._apps.values()) {
           app.metrics.set(metric, 0);
           app.display();
         }
@@ -351,46 +358,44 @@ let consoleWatcher = {
         if (sourceURL) {
           output += ' ' + this.formatSourceURL(packet);
         }
         break;
     }
 
     if (!app.display()) {
       // If the information was not displayed, log it.
-      devtoolsWidgetPanel.log(output);
+      developerHUD.log(output);
     }
   },
 
   formatSourceURL: function cw_formatSourceURL(packet) {
     // Abbreviate source URL
     let source = WebConsoleUtils.abbreviateSourceURL(packet.sourceURL);
 
     // Add function name and line number
     let {functionName, sourceLine} = packet;
     source = 'in ' + (functionName || '<anonymousFunction>') +
       ', ' + source + ':' + sourceLine;
 
     return source;
   }
 };
-
-devtoolsWidgetPanel.registerWatcher(consoleWatcher);
+developerHUD.registerWatcher(consoleWatcher);
 
 
-let jankWatcher = {
+let eventLoopLagWatcher = {
   _client: null,
   _fronts: new Map(),
   _active: false,
 
   init: function(client) {
     this._client = client;
 
-    SettingsListener.observe('devtools.hud.jank', false,
-      this.settingsListener.bind(this));
+    SettingsListener.observe('hud.jank', false, this.settingsListener.bind(this));
   },
 
   settingsListener: function(value) {
     if (this._active == value) {
       return;
     }
     this._active = value;
 
@@ -412,27 +417,130 @@ let jankWatcher = {
 
     let front = new EventLoopLagFront(this._client, app.actor);
     this._fronts.set(app, front);
 
     front.on('event-loop-lag', time => {
       app.metrics.set('jank', time);
 
       if (!app.display()) {
-        devtoolsWidgetPanel.log('jank: ' + time + 'ms');
+        developerHUD.log('jank: ' + time + 'ms');
       }
     });
 
     if (this._active) {
       front.start();
     }
   },
 
   untrackApp: function(app) {
     let fronts = this._fronts;
     if (fronts.has(app)) {
       fronts.get(app).destroy();
       fronts.delete(app);
     }
   }
 };
+developerHUD.registerWatcher(eventLoopLagWatcher);
 
-devtoolsWidgetPanel.registerWatcher(jankWatcher);
+
+/**
+ * The Memory Watcher uses devtools actors to track memory usage.
+ */
+let memoryWatcher = {
+
+  _client: null,
+  _fronts: new Map(),
+  _timers: new Map(),
+  _watching: {
+    jsobjects: false,
+    jsstrings: false,
+    jsother: false,
+    dom: false,
+    style: false,
+    other: false
+  },
+  _active: false,
+
+  init: function mw_init(client) {
+    this._client = client;
+    let watching = this._watching;
+
+    for (let key in watching) {
+      let category = key;
+      SettingsListener.observe('hud.' + category, false, watch => {
+        watching[category] = watch;
+      });
+    }
+
+    SettingsListener.observe('hud.appmemory', false, enabled => {
+      if (this._active = enabled) {
+        for (let app of this._fronts.keys()) {
+          this.measure(app);
+        }
+      } else {
+        for (let timer of this._timers.values()) {
+          clearTimeout(this._timers.get(app));
+        }
+      }
+    });
+  },
+
+  measure: function mw_measure(app) {
+
+    // TODO Also track USS (bug #976024).
+
+    let watch = this._watching;
+    let front = this._fronts.get(app);
+
+    front.measure().then((data) => {
+
+      let total = 0;
+      if (watch.jsobjects) {
+        total += parseInt(data.jsObjectsSize);
+      }
+      if (watch.jsstrings) {
+        total += parseInt(data.jsStringsSize);
+      }
+      if (watch.jsother) {
+        total += parseInt(data.jsOtherSize);
+      }
+      if (watch.dom) {
+        total += parseInt(data.domSize);
+      }
+      if (watch.style) {
+        total += parseInt(data.styleSize);
+      }
+      if (watch.other) {
+        total += parseInt(data.otherSize);
+      }
+      // TODO Also count images size (bug #976007).
+
+      app.metrics.set('memory', total);
+      app.display();
+      let duration = parseInt(data.jsMilliseconds) + parseInt(data.nonJSMilliseconds);
+      let timer = setTimeout(() => this.measure(app), 100 * duration);
+      this._timers.set(app, timer);
+    }, (err) => {
+      console.error(err);
+    });
+  },
+
+  trackApp: function mw_trackApp(app) {
+    app.metrics.set('uss', 0);
+    app.metrics.set('memory', 0);
+    this._fronts.set(app, MemoryFront(this._client, app.actor));
+    if (this._active) {
+      this.measure(app);
+    }
+  },
+
+  untrackApp: function mw_untrackApp(app) {
+    let front = this._fronts.get(app);
+    if (front) {
+      front.destroy();
+      clearTimeout(this._timers.get(app));
+      this._fronts.delete(app);
+      this._timers.delete(app);
+    }
+  }
+};
+developerHUD.registerWatcher(memoryWatcher);
--- a/b2g/chrome/content/settings.js
+++ b/b2g/chrome/content/settings.js
@@ -214,28 +214,28 @@ Components.utils.import('resource://gre/
     'deviceinfo.firmware_revision': firmware_revision,
     'deviceinfo.product_model': product_model
   }
   window.navigator.mozSettings.createLock().set(setting);
 })();
 
 // =================== DevTools ====================
 
-let devtoolsWidgetPanel;
+let developerHUD;
 SettingsListener.observe('devtools.overlay', false, (value) => {
   if (value) {
-    if (!devtoolsWidgetPanel) {
+    if (!developerHUD) {
       let scope = {};
       Services.scriptloader.loadSubScript('chrome://b2g/content/devtools.js', scope);
-      devtoolsWidgetPanel = scope.devtoolsWidgetPanel;
+      developerHUD = scope.developerHUD;
     }
-    devtoolsWidgetPanel.init();
+    developerHUD.init();
   } else {
-    if (devtoolsWidgetPanel) {
-      devtoolsWidgetPanel.uninit();
+    if (developerHUD) {
+      developerHUD.uninit();
     }
   }
 });
 
 SettingsListener.observe('devtools.eventlooplag.threshold', 100, function(value) {
   Services.prefs.setIntPref('devtools.eventlooplag.threshold', value);
 });