Bug 1255843 - Add process memory reporting tool to about:performance. r=mconley
☠☠ backed out by f514d79fe067 ☠ ☠
authorRutuja Surve <rutuja.r.surve@gmail.com>
Fri, 02 Sep 2016 00:21:00 -0400
changeset 312351 5ad925dd2e4e9f6943b228f0173d01278a74c2a8
parent 312350 88dd53397d30e1853122f1284de94f103f6e4436
child 312352 d6246f5ede4a75deda1b0294be758cb4aad855de
push id81347
push usermconley@mozilla.com
push dateFri, 02 Sep 2016 05:52:57 +0000
treeherdermozilla-inbound@5ad925dd2e4e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley
bugs1255843
milestone51.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 1255843 - Add process memory reporting tool to about:performance. r=mconley MozReview-Commit-ID: EHCkl6G3bTT
toolkit/components/aboutperformance/content/aboutPerformance.js
toolkit/components/aboutperformance/content/aboutPerformance.xhtml
toolkit/content/process-content.js
toolkit/modules/moz.build
--- a/toolkit/components/aboutperformance/content/aboutPerformance.js
+++ b/toolkit/components/aboutperformance/content/aboutPerformance.js
@@ -9,16 +9,18 @@
 var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
 const { AddonWatcher } = Cu.import("resource://gre/modules/AddonWatcher.jsm", {});
 const { PerformanceStats } = Cu.import("resource://gre/modules/PerformanceStats.jsm", {});
 const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
 const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
 const { ObjectUtils } = Cu.import("resource://gre/modules/ObjectUtils.jsm", {});
+const { Memory } = Cu.import("resource://gre/modules/Memory.jsm");
+const { DownloadUtils } = Cu.import("resource://gre/modules/DownloadUtils.jsm");
 
 // about:performance observes notifications on this topic.
 // if a notification is sent, this causes the page to be updated immediately,
 // regardless of whether the page is on pause.
 const TEST_DRIVER_TOPIC = "test-about:performance-test-driver";
 
 // about:performance posts notifications on this topic whenever the page
 // is updated.
@@ -937,17 +939,133 @@ var Control = {
     eltLabelRecent.textContent = `Display only the latest ${Math.round(BUFFER_DURATION_MS/1000)}s`;
 
     onModeChange(false);
   },
   // The display mode. One of `MODE_GLOBAL` or `MODE_RECENT`.
   _displayMode: MODE_GLOBAL,
 };
 
+/**
+ * This functionality gets memory related information of sub-processes and
+ * updates the performance table regularly.
+ * If the page goes hidden, it also handles visibility change by not
+ * querying the content processes unnecessarily.
+ */
+var SubprocessMonitor = {
+  _timeout: null,
+
+  /**
+   * Init will start the process of updating the table if the page is not hidden,
+   * and set up an event listener for handling visibility changes.
+   */
+  init: function() {
+    if (!document.hidden) {
+      SubprocessMonitor.updateTable();
+    }
+    document.addEventListener("visibilitychange", SubprocessMonitor.handleVisibilityChange);
+  },
+
+  /**
+   * This function updates the table after an interval if the page is visible
+   * and clears the interval otherwise.
+   */
+  handleVisibilityChange: function() {
+    if (!document.hidden) {
+      SubprocessMonitor.queueUpdate();
+    } else {
+      clearTimeout(this._timeout);
+      this._timeout = null;
+    }
+  },
+
+  /**
+   * This function queues a timer to request the next summary using updateTable
+   * after some delay.
+   */
+  queueUpdate: function() {
+    this._timeout = setTimeout(() => this.updateTable(), UPDATE_INTERVAL_MS);
+  },
+
+  /**
+   * This is a helper function for updateTable, which updates a particular row.
+   * @param {<tr> node} row The row to be updated.
+   * @param {object} summaries The object with the updated RSS and USS values.
+   * @param {string} pid The pid represented by the row for which we update.
+   */
+  updateRow: function(row, summaries, pid) {
+    row.cells[0].textContent = pid;
+    let RSSval = DownloadUtils.convertByteUnits(summaries[pid].rss);
+    row.cells[1].textContent = RSSval.join(" ");
+    let USSval = DownloadUtils.convertByteUnits(summaries[pid].uss);
+    row.cells[2].textContent = USSval.join(" ");
+  },
+
+  /**
+   * This function adds a row to the subprocess-performance table for every new pid
+   * and populates and regularly updates it with RSS/USS measurements.
+   */
+  updateTable: function() {
+    if (!document.hidden) {
+      Memory.summary().then((summaries) => {
+        if (!(Object.keys(summaries).length)) {
+          // The summaries list was empty, which means we timed out getting
+          // the memory reports. We'll try again later.
+          SubprocessMonitor.queueUpdate();
+          return;
+        }
+        let resultTable = document.getElementById("subprocess-reports");
+        let recycle = [];
+        // We first iterate the table to check if summaries exist for rowPids,
+        // if yes, update them and delete the pid's summary or else hide the row
+        // for recycling it. Start at row 1 instead of 0 (to skip the header row).
+        for (let i = 1, row; row = resultTable.rows[i]; i++) {
+          let rowPid = row.dataset.pid;
+          let summary = summaries[rowPid];
+          if (summary) {
+            // Now we update the values in the row, which is hardcoded for now,
+            // but we might want to make this more adaptable in the future.
+            SubprocessMonitor.updateRow(row, summaries, rowPid);
+            delete summaries[rowPid];
+          } else {
+            // Take this unnecessary row, hide it and stash it for potential re-use.
+            row.hidden = true;
+            recycle.push(row);
+          }
+        }
+        // For the remaining pids in summaries, we choose from the recyclable
+        // (hidden) nodes, and if they get exhausted, append a row to the table.
+        for (let pid in summaries) {
+          let row = recycle.pop();
+          if (row) {
+            row.hidden = false;
+          } else {
+            // We create a new row here, and set it to row
+            row = document.createElement("tr");
+            // Insert cell for pid
+            row.insertCell();
+            // Insert a cell for USS.
+            row.insertCell();
+            // Insert another cell for RSS.
+            row.insertCell();
+          }
+          row.dataset.pid = pid;
+          // Update the row and put it at the bottom
+          SubprocessMonitor.updateRow(row, summaries, pid);
+          resultTable.appendChild(row);
+        }
+      });
+      SubprocessMonitor.queueUpdate();
+    }
+  },
+};
+
 var go = Task.async(function*() {
+
+  SubprocessMonitor.init();
   Control.init();
 
   // Setup a hook to allow tests to configure and control this page
   let testUpdate = function(subject, topic, value) {
     let options = JSON.parse(value);
     Control._setOptions(options);
     Control.update();
   };
--- a/toolkit/components/aboutperformance/content/aboutPerformance.xhtml
+++ b/toolkit/components/aboutperformance/content/aboutPerformance.xhtml
@@ -2,19 +2,31 @@
 
 <!-- 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/. -->
 
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
     <title>about:performance</title>
+    <link rel="icon" type="image/png" id="favicon"
+          href="chrome://branding/content/icon32.png"/>
+    <link rel="stylesheet" href="chrome://global/skin/in-content/common.css"
+          type="text/css"/>
     <script type="text/javascript;version=1.8" src="chrome://global/content/aboutPerformance.js"></script>
     <style>
       @import url("chrome://global/skin/in-content/common.css");
+
+      html {
+        --aboutSupport-table-background: #ebebeb;
+        background-color: var(--in-content-page-background);
+      }
+      body {
+        margin: 40px 48px;
+      }
       .hidden {
         display: none;
       }
       .summary .title {
         font-weight: bold;
       }
       a {
         text-decoration: none;
@@ -85,20 +97,70 @@
         border-left-color: rgb(216, 39, 0);
       }
       li.delta[impact="10"] {
         border-left-color: rgb(240, 15, 0);
       }
       li.delta[impact="11"] {
         border-left-color: rgb(255, 0, 0);
       }
+
+      #subprocess-reports {
+        background-color: var(--aboutSupport-table-background);
+        color: var(--in-content-text-color);
+        font: message-box;
+        text-align: start;
+        border: 1px solid var(--in-content-border-color);
+        border-spacing: 0px;
+        float: right;
+        margin-bottom: 20px;
+        -moz-margin-start: 20px;
+        -moz-margin-end: 0;
+        width: 100%;
+      }
+      #subprocess-reports:-moz-dir(rtl) {
+        float: left;
+      }
+      #subprocess-reports th,
+      #subprocess-reports td {
+        border: 1px solid var(--in-content-border-color);
+        padding: 4px;
+      }
+      #subprocess-reports thead th {
+        text-align: center;
+      }
+      #subprocess-reports th {
+        text-align: start;
+        background-color: var(--in-content-table-header-background);
+        color: var(--in-content-selected-text);
+      }
+      #subprocess-reports th.column {
+        white-space: nowrap;
+        width: 0px;
+      }
+      #subprocess-reports td {
+        background-color: #ebebeb;
+        text-align: start;
+        border-color: var(--in-content-table-border-dark-color);
+        border-spacing: 40px;
+      }
     </style>
   </head>
   <body onload="go()">
     <div>
+      <h2>Memory usage of Subprocesses</h2>
+      <table id="subprocess-reports">
+        <tr>
+          <th>Process ID</th>
+          <th title="RSS measures the pages resident in the main memory for the process">Resident Set Size</th>
+          <th title="USS gives a count of unshared pages, unique to the process">Unique Set Size</th>
+        </tr>
+      </table>
+    </div>
+    <div>
       <input type="checkbox" checked="false" id="check-display-recent"></input> 
       <label for="check-display-recent" id="label-display-recent">Display only the last few seconds.</label>
       <input type="checkbox" checked="true" id="check-autorefresh"></input>
       <label for="check-autorefresh">Refresh automatically</label>
     </div>
     <div>
       <h2>Performance of Add-ons</h2>
       <div id="addons" class="measuring">
--- a/toolkit/content/process-content.js
+++ b/toolkit/content/process-content.js
@@ -26,25 +26,45 @@ if (gInContentProcess) {
     TOPICS: [
       "inner-window-destroyed",
       "xpcom-shutdown",
     ],
 
     init() {
       for (let topic of this.TOPICS) {
         Services.obs.addObserver(this, topic, false);
+        Services.cpmm.addMessageListener("Memory:GetSummary", this);
       }
     },
 
     uninit() {
       for (let topic of this.TOPICS) {
         Services.obs.removeObserver(this, topic);
+        Services.cpmm.removeMessageListener("Memory:GetSummary", this);
       }
     },
 
+    receiveMessage(msg) {
+      if (msg.name != "Memory:GetSummary") {
+        return;
+      }
+      let pid = Services.appinfo.processID;
+      let memMgr = Cc["@mozilla.org/memory-reporter-manager;1"]
+                     .getService(Ci.nsIMemoryReporterManager);
+      let rss = memMgr.resident;
+      let uss = memMgr.residentUnique;
+      Services.cpmm.sendAsyncMessage("Memory:Summary", {
+        pid,
+        summary: {
+          uss,
+          rss,
+        }
+      });
+    },
+
     observe(subject, topic, data) {
       switch (topic) {
         case "inner-window-destroyed": {
           // Forward inner-window-destroyed notifications with the
           // inner window ID, so that code in the parent that should
           // do something when content windows go away can do it
           let innerWindowID =
             subject.QueryInterface(Ci.nsISupportsPRUint64).data;
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -47,16 +47,17 @@ EXTRA_JS_MODULES += [
     'GMPUtils.jsm',
     'Http.jsm',
     'InlineSpellChecker.jsm',
     'InlineSpellCheckerContent.jsm',
     'Integration.jsm',
     'LoadContextInfo.jsm',
     'Locale.jsm',
     'Log.jsm',
+    'Memory.jsm',
     'NewTabUtils.jsm',
     'NLP.jsm',
     'ObjectUtils.jsm',
     'PageMenu.jsm',
     'PageMetadata.jsm',
     'PermissionsUtils.jsm',
     'PopupNotifications.jsm',
     'Preferences.jsm',