Bug 1255843 - Add process memory reporting tool to about:performance. r=mconley

MozReview-Commit-ID: EHCkl6G3bTT

--HG--
extra : rebase_source : 2e9d711aa977e6d0313f07d28e1a2a4861b501c0
extra : source : 5ad925dd2e4e9f6943b228f0173d01278a74c2a8
This commit is contained in:
Rutuja Surve 2016-09-02 00:21:00 -04:00
parent f82c259e8f
commit 27b6de8e73
5 changed files with 278 additions and 0 deletions

View File

@ -14,6 +14,8 @@ const { PerformanceStats } = Cu.import("resource://gre/modules/PerformanceStats.
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,
@ -942,7 +944,123 @@ var Control = {
_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

View File

@ -7,9 +7,21 @@
<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;
}
@ -90,9 +102,59 @@
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>

View File

@ -31,15 +31,35 @@ if (gInContentProcess) {
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": {

View File

@ -0,0 +1,77 @@
/* 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/. */
this.EXPORTED_SYMBOLS = ["Memory"];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
// How long we should wait for the Promise to resolve.
const TIMEOUT_INTERVAL = 2000;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
this.Memory = {
/**
* This function returns a Promise that resolves with an Object that
* describes basic memory usage for each content process and the parent
* process.
* @returns Promise
* @resolves JS Object
* An Object in the following format:
* {
* "parent": {
* uss: <int>,
* rss: <int>,
* },
* <pid>: {
* uss: <int>,
* rss: <int>,
* },
* ...
* }
*/
summary() {
if (!this._pendingPromise) {
this._pendingPromise = new Promise((resolve) => {
this._pendingResolve = resolve;
this._summaries = {};
Services.ppmm.broadcastAsyncMessage("Memory:GetSummary");
Services.ppmm.addMessageListener("Memory:Summary", this);
this._pendingTimeout = setTimeout(() => { this.finish(); }, TIMEOUT_INTERVAL);
});
}
return this._pendingPromise;
},
receiveMessage(msg) {
if (msg.name != "Memory:Summary" || !this._pendingResolve) {
return;
}
this._summaries[msg.data.pid] = msg.data.summary;
// Now we check if we are done for all content processes.
// Services.ppmm.childCount is a count of how many processes currently
// exist that might respond to messages sent through the ppmm, including
// the parent process. So we subtract the parent process with the "- 1",
// and thats how many content processes were waiting for.
if (Object.keys(this._summaries).length >= Services.ppmm.childCount - 1) {
this.finish();
}
},
finish() {
// Code to gather the USS and RSS values for the parent process. This
// functions the same way as in process-content.js.
let memMgr = Cc["@mozilla.org/memory-reporter-manager;1"]
.getService(Ci.nsIMemoryReporterManager);
let rss = memMgr.resident;
let uss = memMgr.residentUnique;
this._summaries["Parent"] = { uss, rss };
this._pendingResolve(this._summaries);
this._pendingResolve = null;
this._summaries = null;
this._pendingPromise = null;
clearTimeout(this._pendingTimeout);
Services.ppmm.removeMessageListener("Memory:Summary", this);
}
};

View File

@ -52,6 +52,7 @@ EXTRA_JS_MODULES += [
'LoadContextInfo.jsm',
'Locale.jsm',
'Log.jsm',
'Memory.jsm',
'NewTabUtils.jsm',
'NLP.jsm',
'ObjectUtils.jsm',