mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 19:04:45 +00:00
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:
parent
f82c259e8f
commit
27b6de8e73
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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": {
|
||||
|
77
toolkit/modules/Memory.jsm
Normal file
77
toolkit/modules/Memory.jsm
Normal 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 that’s how many content processes we’re 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);
|
||||
}
|
||||
};
|
@ -52,6 +52,7 @@ EXTRA_JS_MODULES += [
|
||||
'LoadContextInfo.jsm',
|
||||
'Locale.jsm',
|
||||
'Log.jsm',
|
||||
'Memory.jsm',
|
||||
'NewTabUtils.jsm',
|
||||
'NLP.jsm',
|
||||
'ObjectUtils.jsm',
|
||||
|
Loading…
Reference in New Issue
Block a user