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 312377 5ad925dd2e4e9f6943b228f0173d01278a74c2a8
parent 312376 88dd53397d30e1853122f1284de94f103f6e4436
child 312378 d6246f5ede4a75deda1b0294be758cb4aad855de
push id20447
push userkwierso@gmail.com
push dateFri, 02 Sep 2016 20:36:44 +0000
treeherderfx-team@969397f22187 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley
bugs1255843
milestone51.0a1
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',