Bug 674290 - Expose contents of /proc/self/maps and smaps in about:memory. r=njn

--HG--
extra : rebase_source : 3bbe2f926ba3b0c46a122d51b027a5a6283ae2b0
This commit is contained in:
Justin Lebar 2011-08-05 18:22:11 -04:00
Родитель 73e9ae8488
Коммит 81a4fe113d
10 изменённых файлов: 847 добавлений и 82 удалений

Просмотреть файл

@ -69,3 +69,15 @@
-moz-user-select: none; /* no need to include this when cutting+pasting */
}
h2.tree {
cursor: pointer;
background: #ddd;
padding-left: .1em;
}
pre.tree {
}
pre.collapsed {
display: none;
}

Просмотреть файл

@ -58,6 +58,55 @@ const UNITS_PERCENTAGE = Ci.nsIMemoryReporter.UNITS_PERCENTAGE;
const kUnknown = -1; // used for _amount if a memory reporter failed
const kTreeDescriptions = {
'explicit' :
"This tree covers explicit memory allocations by the application, " +
"both at the operating system level (via calls to functions such as " +
"VirtualAlloc, vm_allocate, and mmap), and at the heap allocation level " +
"(via functions such as malloc, calloc, realloc, memalign, operator " +
"new, and operator new[]). It excludes memory that is mapped implicitly " +
"such as code and data segments, and thread stacks. It also excludes " +
"heap memory that has been freed by the application but is still being " +
"held onto by the heap allocator. It is not guaranteed to cover every " +
"explicit allocation, but it does cover most (including the entire " +
"heap), and therefore it is the single best number to focus on when " +
"trying to reduce memory usage.",
'resident':
"This tree shows how much space in physical memory each of the " +
"process's mappings is currently using (the mapping's 'resident set size', " +
"or 'RSS'). This is a good measure of the 'cost' of the mapping, although " +
"it does not take into account the fact that shared libraries may be mapped " +
"by multiple processes but appear only once in physical memory. " +
"Note that the 'resident' value here might not equal the value for " +
"'resident' under 'Other Measurements' because the two measurements are not " +
"taken at exactly the same time.",
'vsize':
"This tree shows how much virtual addres space each of the process's " +
"mappings takes up (the mapping's 'vsize'). A mapping may have a large " +
"vsize but use only a small amount of physical memory; the resident set size " +
"of a mapping is a better measure of the mapping's 'cost'. Note that the " +
"'vsize' value here might not equal the value for 'vsize' under 'Other " +
"Measurements' because the two measurements are not taken at exactly the " +
"same time.",
'swap':
"This tree shows how much space in the swap file each of the process's " +
"mappings is currently using. Mappings which are not in the swap file " +
"(i.e., nodes which would have a value of 0 in this tree) are omitted."
};
const kTreeNames = {
'explicit': 'Explicit Allocations',
'resident': 'Resident Set Size (RSS) Breakdown',
'vsize': 'Virtual Size Breakdown',
'swap': 'Swap Usage Breakdown',
'other': 'Other Measurements'
};
const kMapTreePaths = ['map/resident', 'map/vsize', 'map/swap'];
function onLoad()
{
var os = Cc["@mozilla.org/observer-service;1"].
@ -135,6 +184,17 @@ function sendHeapMinNotifications()
sendHeapMinNotificationsInner();
}
function toggleTreeVisibility(aEvent)
{
var headerElem = aEvent.target;
// Replace "header-" with "pre-" in the header element's id to get the id of
// the corresponding pre element.
var treeElem = $(headerElem.id.replace(/^header-/, 'pre-'));
treeElem.classList.toggle('collapsed');
}
function Reporter(aPath, aKind, aUnits, aAmount, aDescription)
{
this._path = aPath;
@ -261,7 +321,8 @@ function update()
text += "<div>" +
"<span class='legend'>Hover the pointer over the name of a memory " +
"reporter to see a detailed description of what it measures.</span>"
"reporter to see a detailed description of what it measures. Click a " +
"heading to expand or collapse its tree.</span>" +
"</div>";
var div = document.createElement("div");
@ -312,23 +373,41 @@ TreeNode.compare = function(a, b) {
*
* @param aReporters
* The table of Reporters, indexed by path.
* @param aTreeName
* The name of the tree being built.
* @return The built tree.
*/
function buildTree(aReporters)
function buildTree(aReporters, aTreeName)
{
const treeName = "explicit";
// We want to process all reporters that begin with |treeName|. First we
// We want to process all reporters that begin with |aTreeName|. First we
// build the tree but only fill the properties that we can with a top-down
// traversal.
// Is there any reporter which matches aTreeName? If not, we'll create a
// dummy one.
var foundReporter = false;
for (var path in aReporters) {
if (aReporters[path].treeNameMatches(aTreeName)) {
foundReporter = true;
break;
}
}
if (!foundReporter) {
// We didn't find any reporters for this tree, so create an empty one. Its
// description will be set later.
aReporters[aTreeName] =
new Reporter(aTreeName, KIND_NONHEAP, UNITS_BYTES, 0, '');
}
var t = new TreeNode("falseRoot");
for (var path in aReporters) {
// Add any missing nodes in the tree implied by the path.
var r = aReporters[path];
if (r.treeNameMatches(treeName)) {
if (r.treeNameMatches(aTreeName)) {
assert(r._kind === KIND_HEAP || r._kind === KIND_NONHEAP,
"reporters in the tree must have KIND_HEAP or KIND_NONHEAP");
assert(r._units === UNITS_BYTES);
assert(r._units === UNITS_BYTES, "r._units === UNITS_BYTES");
var names = r._path.split('/');
var u = t;
for (var i = 0; i < names.length; i++) {
@ -349,8 +428,9 @@ function buildTree(aReporters)
}
}
}
// Using falseRoot makes the above code simpler. Now discard it, leaving
// treeName at the root.
// aTreeName at the root.
t = t._kids[0];
// Next, fill in the remaining properties bottom-up.
@ -360,7 +440,7 @@ function buildTree(aReporters)
var path = aPrepath ? aPrepath + '/' + aT._name : aT._name;
if (aT._kids.length === 0) {
// Leaf node. Must have a reporter.
assert(aT._kind !== undefined);
assert(aT._kind !== undefined, "aT._kind !== undefined");
aT._description = getDescription(aReporters, path);
var amount = getBytes(aReporters, path);
if (amount !== kUnknown) {
@ -400,11 +480,32 @@ function buildTree(aReporters)
aT._description = "The sum of all entries below '" + aT._name + "'.";
}
}
assert(aT._amount !== kUnknown);
assert(aT._amount !== kUnknown, "aT._amount !== kUnknown");
return aT._amount;
}
fillInTree(t, "");
// Reduce the depth of the tree by the number of occurrences of '/' in
// aTreeName. (Thus the tree named 'foo/bar/baz' will be rooted at 'baz'.)
var slashCount = 0;
for (var i = 0; i < aTreeName.length; i++) {
if (aTreeName[i] == '/') {
assert(t._kids.length == 1, "Not expecting multiple kids here.");
t = t._kids[0];
}
}
// Set the description on the root node.
t._description = kTreeDescriptions[t._name];
return t;
}
/**
* Do some work which only makes sense for the 'explicit' tree.
*/
function fixUpExplicitTree(aT, aReporters) {
// Determine how many bytes are reported by heap reporters. Be careful
// with non-leaf reporters; if we count a non-leaf reporter we don't want
// to count any of its child reporters.
@ -429,7 +530,7 @@ function buildTree(aReporters)
var unknownHeapUsedBytes = 0;
var hasProblem = true;
if (heapUsedBytes !== kUnknown) {
unknownHeapUsedBytes = heapUsedBytes - getKnownHeapUsedBytes(t);
unknownHeapUsedBytes = heapUsedBytes - getKnownHeapUsedBytes(aT);
hasProblem = false;
}
var heapUnclassified = new TreeNode("heap-unclassified");
@ -446,10 +547,8 @@ function buildTree(aReporters)
heapUnclassified._hasProblem = true;
}
t._kids.push(heapUnclassified);
t._amount += unknownHeapUsedBytes;
return t;
aT._kids.push(heapUnclassified);
aT._amount += unknownHeapUsedBytes;
}
/**
@ -515,16 +614,26 @@ function filterTree(aTotalBytes, aT)
*/
function genProcessText(aProcess, aReporters)
{
var tree = buildTree(aReporters);
filterTree(tree._amount, tree);
var explicitTree = buildTree(aReporters, 'explicit');
fixUpExplicitTree(explicitTree, aReporters);
filterTree(explicitTree._amount, explicitTree);
var explicitText = genTreeText(explicitTree, aProcess);
// Nb: the newlines give nice spacing if we cut+paste into a text buffer.
var text = "";
text += "<h1>" + aProcess + " Process</h1>\n\n";
text += genTreeText(tree);
text += genOtherText(aReporters);
text += "<hr></hr>";
return text;
var mapTreeText = '';
kMapTreePaths.forEach(function(t) {
var tree = buildTree(aReporters, t);
filterTree(tree._amount, tree);
mapTreeText += genTreeText(tree, aProcess);
});
// We have to call genOtherText after we process all the trees, because it
// looks at all the reporters which aren't part of a tree.
var otherText = genOtherText(aReporters, aProcess);
// The newlines give nice spacing if we cut+paste into a text buffer.
return "<h1>" + aProcess + " Process</h1>\n\n" +
explicitText + mapTreeText + otherText +
"<hr></hr>";
}
/**
@ -735,12 +844,15 @@ function genMrNameText(aKind, aDesc, aName, aHasProblem, aNMerged)
*
* @param aT
* The tree.
* @param aProcess
* The process the tree corresponds to.
* @return The generated text.
*/
function genTreeText(aT)
function genTreeText(aT, aProcess)
{
var treeBytes = aT._amount;
var rootStringLength = aT.toString().length;
var isExplicitTree = aT._name == 'explicit';
/**
* Generates the text for a particular tree, without a heading.
@ -808,8 +920,11 @@ function genTreeText(aT)
}
perc = "<span class='mrPerc'>(" + perc + "%)</span> ";
// We don't want to show '(nonheap)' on a tree like 'map/vsize', since the
// whole tree is non-heap.
var kind = isExplicitTree ? aT._kind : undefined;
var text = indent + genMrValueText(tString) + " " + perc +
genMrNameText(aT._kind, aT._description, aT._name,
genMrNameText(kind, aT._description, aT._name,
aT._hasProblem, aT._nMerged);
for (var i = 0; i < aT._kids.length; i++) {
@ -822,22 +937,10 @@ function genTreeText(aT)
}
var text = genTreeText2(aT, [], rootStringLength);
// Nb: the newlines give nice spacing if we cut+paste into a text buffer.
const desc =
"This tree covers explicit memory allocations by the application, " +
"both at the operating system level (via calls to functions such as " +
"VirtualAlloc, vm_allocate, and mmap), and at the heap allocation level " +
"(via functions such as malloc, calloc, realloc, memalign, operator " +
"new, and operator new[]). It excludes memory that is mapped implicitly " +
"such as code and data segments, and thread stacks. It also excludes " +
"heap memory that has been freed by the application but is still being " +
"held onto by the heap allocator. It is not guaranteed to cover every " +
"explicit allocation, but it does cover most (including the entire " +
"heap), and therefore it is the single best number to focus on when " +
"trying to reduce memory usage.";
return "<h2 class='hasDesc' title='" + escapeQuotes(desc) +
"'>Explicit Allocations</h2>\n" + "<pre>" + text + "</pre>\n";
// The explicit tree is not collapsed, but all other trees are, so pass
// !isExplicitTree for genSectionMarkup's aCollapsed parameter.
return genSectionMarkup(aProcess, aT._name, text, !isExplicitTree);
}
function OtherReporter(aPath, aUnits, aAmount, aDescription,
@ -880,9 +983,11 @@ OtherReporter.compare = function(a, b) {
*
* @param aReportersByProcess
* Table of Reporters for this process, indexed by _path.
* @param aProcess
* The process these reporters correspond to.
* @return The generated text.
*/
function genOtherText(aReportersByProcess)
function genOtherText(aReportersByProcess, aProcess)
{
// Generate an array of Reporter-like elements, stripping out all the
// Reporters that have already been handled. Also find the width of the
@ -918,8 +1023,22 @@ function genOtherText(aReportersByProcess)
// Nb: the newlines give nice spacing if we cut+paste into a text buffer.
const desc = "This list contains other memory measurements that cross-cut " +
"the requested memory measurements above."
return "<h2 class='hasDesc' title='" + desc + "'>Other Measurements</h2>\n" +
"<pre>" + text + "</pre>\n";
return genSectionMarkup(aProcess, 'other', text, false);
}
function genSectionMarkup(aProcess, aName, aText, aCollapsed)
{
var headerId = 'header-' + aProcess + '-' + aName;
var preId = 'pre-' + aProcess + '-' + aName;
var elemClass = (aCollapsed ? 'collapsed' : '') + ' tree';
// Ugh.
return '<h2 id="' + headerId + '" class="' + elemClass + '" ' +
'onclick="toggleTreeVisibility(event)">' +
kTreeNames[aName] +
'</h2>\n' +
'<pre id="' + preId + '" class="' + elemClass + '">' + aText + '</pre>\n';
}
function assert(aCond, aMsg)

Просмотреть файл

@ -105,6 +105,17 @@
f("perc2", OTHER, PERCENTAGE, 10000);
f("perc1", OTHER, PERCENTAGE, 4567);
}
},
{ collectReports: function(cbObj, closure) {
// The amounts are given in pages, so multiply here by 4kb.
function f(p, a) { cbObj.callback("", p, NONHEAP, BYTES, a * 4 * KB, "(desc)", closure); }
f("map/vsize/a", 24);
f("map/swap/a", 1);
f("map/swap/a", 2);
f("map/vsize/a", 19);
f("map/swap/b/c", 10);
f("map/resident/a", 42);
}
}
];
for (var i = 0; i < fakeReporters.length; i++) {
@ -192,6 +203,20 @@ Explicit Allocations\n\
├───11.00 MB (01.76%) -- heap-unclassified\n\
└────0.58 MB (00.09%) -- (2 omitted)\n\
\n\
Resident Set Size (RSS) Breakdown\n\
0.16 MB (100.0%) -- resident\n\
└──0.16 MB (100.0%) -- a\n\
\n\
Virtual Size Breakdown\n\
0.17 MB (100.0%) -- vsize\n\
└──0.17 MB (100.0%) -- a [2]\n\
\n\
Swap Usage Breakdown\n\
0.05 MB (100.0%) -- swap\n\
├──0.04 MB (76.92%) -- b\n\
│ └──0.04 MB (76.92%) -- c\n\
└──0.01 MB (23.08%) -- a [2]\n\
\n\
Other Measurements\n\
500.00 MB -- heap-allocated\n\
100.00 MB -- heap-unallocated\n\
@ -213,6 +238,15 @@ Explicit Allocations\n\
├────200.00 MB (20.00%) -- compartment(this-will-be-truncated-in-non-verbose-mo...)\n\
└────101.00 MB (10.10%) -- heap-unclassified\n\
\n\
Resident Set Size (RSS) Breakdown\n\
0.00 MB (100.0%) -- resident\n\
\n\
Virtual Size Breakdown\n\
0.00 MB (100.0%) -- vsize\n\
\n\
Swap Usage Breakdown\n\
0.00 MB (100.0%) -- swap\n\
\n\
Other Measurements\n\
666.00 MB -- danger<script>window.alert(1)</script>\n\
1,000.00 MB -- heap-allocated\n\
@ -229,6 +263,15 @@ Explicit Allocations\n\
│ └────0.00 MB (00.00%) -- (1 omitted)\n\
└────0.00 MB (00.00%) -- (2 omitted)\n\
\n\
Resident Set Size (RSS) Breakdown\n\
0.00 MB (100.0%) -- resident\n\
\n\
Virtual Size Breakdown\n\
0.00 MB (100.0%) -- vsize\n\
\n\
Swap Usage Breakdown\n\
0.00 MB (100.0%) -- swap\n\
\n\
Other Measurements\n\
0.00 MB -- heap-allocated [*]\n\
0.00 MB -- other1 [*]\n\
@ -264,6 +307,20 @@ Explicit Allocations\n\
├──────510,976 B (00.08%) -- d\n\
└──────102,400 B (00.02%) -- e\n\
\n\
Resident Set Size (RSS) Breakdown\n\
172,032 B (100.0%) -- resident\n\
└──172,032 B (100.0%) -- a\n\
\n\
Virtual Size Breakdown\n\
176,128 B (100.0%) -- vsize\n\
└──176,128 B (100.0%) -- a [2]\n\
\n\
Swap Usage Breakdown\n\
53,248 B (100.0%) -- swap\n\
├──40,960 B (76.92%) -- b\n\
│ └──40,960 B (76.92%) -- c\n\
└──12,288 B (23.08%) -- a [2]\n\
\n\
Other Measurements\n\
524,288,000 B -- heap-allocated\n\
104,857,600 B -- heap-unallocated\n\
@ -285,6 +342,15 @@ Explicit Allocations\n\
├────209,715,200 B (20.00%) -- compartment(this-will-be-truncated-in-non-verbose-mode-abcdefghijklmnopqrstuvwxyz)\n\
└────105,906,176 B (10.10%) -- heap-unclassified\n\
\n\
Resident Set Size (RSS) Breakdown\n\
0 B (100.0%) -- resident\n\
\n\
Virtual Size Breakdown\n\
0 B (100.0%) -- vsize\n\
\n\
Swap Usage Breakdown\n\
0 B (100.0%) -- swap\n\
\n\
Other Measurements\n\
698,351,616 B -- danger<script>window.alert(1)</script>\n\
1,048,576,000 B -- heap-allocated\n\
@ -302,6 +368,15 @@ Explicit Allocations\n\
├────────────0 B (00.00%) -- b [*]\n\
└────────────0 B (00.00%) -- heap-unclassified [*]\n\
\n\
Resident Set Size (RSS) Breakdown\n\
0 B (100.0%) -- resident\n\
\n\
Virtual Size Breakdown\n\
0 B (100.0%) -- vsize\n\
\n\
Swap Usage Breakdown\n\
0 B (100.0%) -- swap\n\
\n\
Other Measurements\n\
0 B -- heap-allocated [*]\n\
0 B -- other1 [*]\n\
@ -328,22 +403,44 @@ Other Measurements\n\
SimpleTest.finish();
}
function check(actual, expected) {
var a = actual.QueryInterface(Ci.nsISupportsString).data;
if (a != expected) {
dump("*******ACTUAL*******\n");
dump(a);
dump("******EXPECTED******\n");
dump(expected);
dump("********************\n");
return false;
}
return true;
}
// Cut+paste the entire page and check that the cut text matches what we
// expect. This tests the output in general and also that the cutting and
// pasting works as expected.
function test(aFrame, aExpectedText, aNext) {
document.querySelector("#" + aFrame).focus();
SimpleTest.waitForClipboard(aExpectedText,
function() {
synthesizeKey("A", {accelKey: true});
synthesizeKey("C", {accelKey: true});
},
aNext,
function() {
ok(false, "pasted text doesn't match for " + aFrame);
finish();
}
);
// Click all h2.collapsed elements so they expand.
var win = document.querySelector("#" + aFrame).contentWindow;
var nodes = win.document.querySelectorAll("pre.collapsed");
for (var i = 0; i < nodes.length; i++) {
nodes[i].classList.toggle('collapsed');
}
SimpleTest.executeSoon(function() {
document.querySelector("#" + aFrame).focus();
SimpleTest.waitForClipboard(function(actual) { return check(actual, aExpectedText) },
function() {
synthesizeKey("A", {accelKey: true});
synthesizeKey("C", {accelKey: true});
},
aNext,
function() {
ok(false, "pasted text doesn't match for " + aFrame);
finish();
}
);
});
}
addLoadEvent(function() {

Просмотреть файл

@ -72,6 +72,10 @@ CPPSRCS = \
FunctionTimer.cpp \
$(NULL)
ifeq ($(OS_ARCH),Linux)
CPPSRCS += MapsMemoryReporter.cpp
endif
ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
CPPSRCS += nsMacUtilsImpl.cpp
endif
@ -94,6 +98,7 @@ EXPORTS_NAMESPACES = mozilla
EXPORTS_mozilla = \
FunctionTimer.h \
MapsMemoryReporter.h \
$(NULL)
ifeq (windows,$(MOZ_WIDGET_TOOLKIT))

Просмотреть файл

@ -0,0 +1,456 @@
/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 ci et: */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla.org code.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
*
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Justin Lebar <justin.lebar@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "mozilla/MapsMemoryReporter.h"
#include "nsIMemoryReporter.h"
#include "nsString.h"
#include "nsCOMPtr.h"
#include "nsHashSets.h"
#include <stdio.h>
namespace mozilla {
namespace MapsMemoryReporter {
#if !defined(XP_LINUX)
#error "This doesn't have a prayer of working if we're not on Linux."
#endif
// mozillaLibraries is a list of all the shared libraries we build. This list
// is used for determining whether a library is a "Mozilla library" or a
// "third-party library". But even if this list is missing items, about:memory
// will identify a library in the same directory as libxul.so as a "Mozilla
// library".
const char* mozillaLibraries[] =
{
"libfreebl3.so",
"libmozalloc.so",
"libmozsqlite3.so",
"libnspr4.so",
"libnss3.so",
"libnssckbi.so",
"libnssdbm3.so",
"libnssutil3.so",
"libplc4.so",
"libplds4.so",
"libsmime3.so",
"libsoftokn3.so",
"libssl3.so",
"libxpcom.so",
"libxul.so"
};
namespace {
bool EndsWithLiteral(const nsCString &aHaystack, const char *aNeedle)
{
PRInt32 idx = aHaystack.RFind(aNeedle);
if (idx == -1) {
return false;
}
return idx + strlen(aNeedle) == aHaystack.Length();
}
void GetDirname(const nsCString &aPath, nsACString &aOut)
{
PRInt32 idx = aPath.RFind("/");
if (idx == -1) {
aOut.Truncate();
}
else {
aOut.Assign(Substring(aPath, 0, idx));
}
}
void GetBasename(const nsCString &aPath, nsACString &aOut)
{
PRInt32 idx = aPath.RFind("/");
if (idx == -1) {
aOut.Assign(aPath);
}
else {
aOut.Assign(Substring(aPath, idx + 1));
}
}
} // anonymous namespace
class MapsReporter : public nsIMemoryMultiReporter
{
public:
MapsReporter();
NS_DECL_ISUPPORTS
NS_IMETHOD
CollectReports(nsIMemoryMultiReporterCallback *aCallback,
nsISupports *aClosure);
private:
// Search through /proc/self/maps for libxul.so, and set mLibxulDir to the
// the directory containing libxul.
nsresult FindLibxul();
nsresult
ParseMapping(FILE *aFile,
nsIMemoryMultiReporterCallback *aCallback,
nsISupports *aClosure);
void
GetReporterNameAndDescription(const char *aPath,
const char *aPermissions,
nsACString &aName,
nsACString &aDesc);
nsresult
ParseMapBody(FILE *aFile,
const nsACString &aName,
const nsACString &aDescription,
nsIMemoryMultiReporterCallback *aCallback,
nsISupports *aClosure);
nsCString mLibxulDir;
nsCStringHashSet mMozillaLibraries;
};
NS_IMPL_THREADSAFE_ISUPPORTS1(MapsReporter, nsIMemoryMultiReporter)
MapsReporter::MapsReporter()
{
const PRUint32 len = NS_ARRAY_LENGTH(mozillaLibraries);
mMozillaLibraries.Init(len);
for (PRUint32 i = 0; i < len; i++) {
nsCAutoString str;
str.Assign(mozillaLibraries[i]);
mMozillaLibraries.Put(str);
}
}
NS_IMETHODIMP
MapsReporter::CollectReports(nsIMemoryMultiReporterCallback *aCallback,
nsISupports *aClosure)
{
FILE *f = fopen("/proc/self/smaps", "r");
if (!f)
return NS_ERROR_FAILURE;
while (true) {
nsresult rv = ParseMapping(f, aCallback, aClosure);
if (NS_FAILED(rv))
break;
}
fclose(f);
return NS_OK;
}
nsresult
MapsReporter::FindLibxul()
{
mLibxulDir.Truncate();
// Note that we're scanning /proc/self/*maps*, not smaps, here.
FILE *f = fopen("/proc/self/maps", "r");
if (!f)
return NS_ERROR_FAILURE;
while (true) {
// Skip any number of non-slash characters, then capture starting with the
// slash to the newline. This is the path part of /proc/self/maps.
char path[1025];
int numRead = fscanf(f, "%*[^/]%1024[^\n]", path);
if (numRead != 1) {
break;
}
nsCAutoString pathStr;
pathStr.Append(path);
nsCAutoString basename;
GetBasename(pathStr, basename);
if (basename.EqualsLiteral("libxul.so")) {
GetDirname(pathStr, mLibxulDir);
break;
}
}
fclose(f);
return mLibxulDir.IsEmpty() ? NS_ERROR_FAILURE : NS_OK;
}
nsresult
MapsReporter::ParseMapping(
FILE *aFile,
nsIMemoryMultiReporterCallback *aCallback,
nsISupports *aClosure)
{
// We need to use native types in order to get good warnings from fscanf, so
// let's make sure that the native types have the sizes we expect.
PR_STATIC_ASSERT(sizeof(long long) == sizeof(PRInt64));
PR_STATIC_ASSERT(sizeof(int) == sizeof(PRInt32));
if (mLibxulDir.IsEmpty()) {
NS_ENSURE_SUCCESS(FindLibxul(), NS_ERROR_FAILURE);
}
// The first line of an entry in /proc/self/smaps looks just like an entry
// in /proc/maps:
//
// address perms offset dev inode pathname
// 02366000-025d8000 rw-p 00000000 00:00 0 [heap]
const int argCount = 8;
unsigned long long addrStart, addrEnd;
char perms[5];
unsigned long long offset;
unsigned int devMajor, devMinor, inode;
char path[1025];
// A path might not be present on this line; set it to the empty string.
path[0] = '\0';
// This is a bit tricky. Whitespace in a scanf pattern matches *any*
// whitespace, including newlines. We want this pattern to match a line
// with or without a path, but we don't want to look to a new line for the
// path. Thus we have %u%1024[^\n] at the end of the pattern. This will
// capture into the path some leading whitespace, which we'll later trim off.
int numRead = fscanf(aFile, "%llx-%llx %4s %llx %u:%u %u%1024[^\n]",
&addrStart, &addrEnd, perms, &offset, &devMajor,
&devMinor, &inode, path);
// Eat up any whitespace at the end of this line, including the newline.
fscanf(aFile, " ");
// We might or might not have a path, but the rest of the arguments should be
// there.
if (numRead != argCount && numRead != argCount - 1)
return NS_ERROR_FAILURE;
nsCAutoString name, description;
GetReporterNameAndDescription(path, perms, name, description);
while (true) {
nsresult rv = ParseMapBody(aFile, name, description, aCallback, aClosure);
if (NS_FAILED(rv))
break;
}
return NS_OK;
}
void
MapsReporter::GetReporterNameAndDescription(
const char *aPath,
const char *aPerms,
nsACString &aName,
nsACString &aDesc)
{
aName.Truncate();
aDesc.Truncate();
// If aPath points to a file, we have its absolute path, plus some
// whitespace. Truncate this to its basename, and put the absolute path in
// the description.
nsCAutoString absPath;
absPath.Append(aPath);
absPath.StripChars(" ");
nsCAutoString basename;
GetBasename(absPath, basename);
if (basename.EqualsLiteral("[heap]")) {
aName.Append("anonymous/anonymous, within brk()");
aDesc.Append("Memory in anonymous mappings within the boundaries "
"defined by brk() / sbrk(). This is likely to be just "
"a portion of the application's heap; the remainder "
"lives in other anonymous mappings. This node corresponds to "
"'[heap]' in /proc/self/smaps.");
}
else if (basename.EqualsLiteral("[stack]")) {
aName.Append("main thread's stack");
aDesc.Append("The stack size of the process's main thread. This node "
"corresponds to '[stack]' in /proc/self/smaps.");
}
else if (basename.EqualsLiteral("[vdso]")) {
aName.Append("vdso");
aDesc.Append("The virtual dynamically-linked shared object, also known as "
"the 'vsyscall page'. This is a memory region mapped by the "
"operating system for the purpose of allowing processes to "
"perform some privileged actions without the overhead of a "
"syscall.");
}
else if (!basename.IsEmpty()) {
NS_ASSERTION(!mLibxulDir.IsEmpty(), "mLibxulDir should not be empty.");
nsCAutoString dirname;
GetDirname(absPath, dirname);
// Hack: A file is a shared library if the basename contains ".so" and its
// dirname contains "/lib", or if the basename ends with ".so".
if (EndsWithLiteral(basename, ".so") ||
(basename.Find(".so") != -1 && dirname.Find("/lib") != -1)) {
aName.Append("shared-libraries/");
if (dirname.Equals(mLibxulDir) || mMozillaLibraries.Contains(basename)) {
aName.Append("shared-libraries-mozilla/");
}
else {
aName.Append("shared-libraries-other/");
}
}
else {
aName.Append("other-files/");
if (EndsWithLiteral(basename, ".xpi")) {
aName.Append("extensions/");
}
else if (dirname.Find("/fontconfig") != -1) {
aName.Append("fontconfig/");
}
}
aName.Append(basename);
aDesc.Append(absPath);
}
else {
aName.Append("anonymous/anonymous, outside brk()");
aDesc.Append("Memory in anonymous mappings outside the boundaries defined "
"by brk() / sbrk().");
}
aName.Append(" [");
aName.Append(aPerms);
aName.Append("]");
// Modify the description to include an explanation of the permissions.
aDesc.Append(" (");
if (strstr(aPerms, "rw")) {
aDesc.Append("read/write, ");
}
else if (strchr(aPerms, 'r')) {
aDesc.Append("read-only, ");
}
else if (strchr(aPerms, 'w')) {
aDesc.Append("write-only, ");
}
else {
aDesc.Append("not readable, not writable, ");
}
if (strchr(aPerms, 'x')) {
aDesc.Append("executable, ");
}
else {
aDesc.Append("not executable, ");
}
if (strchr(aPerms, 's')) {
aDesc.Append("shared");
}
else if (strchr(aPerms, 'p')) {
aDesc.Append("private");
}
else {
aDesc.Append("not shared or private??");
}
aDesc.Append(")");
}
nsresult
MapsReporter::ParseMapBody(
FILE *aFile,
const nsACString &aName,
const nsACString &aDescription,
nsIMemoryMultiReporterCallback *aCallback,
nsISupports *aClosure)
{
PR_STATIC_ASSERT(sizeof(long long) == sizeof(PRInt64));
const int argCount = 2;
char desc[1025];
unsigned long long size;
if (fscanf(aFile, "%1024[a-zA-Z_]: %llu kB\n",
desc, &size) != argCount) {
return NS_ERROR_FAILURE;
}
// Don't report nodes with size 0.
if (size == 0)
return NS_OK;
const char* category;
if (strcmp(desc, "Size") == 0) {
category = "vsize";
}
else if (strcmp(desc, "Rss") == 0) {
category = "resident";
}
else if (strcmp(desc, "Swap") == 0) {
category = "swap";
}
else {
// Don't report this category.
return NS_OK;
}
nsCAutoString path;
path.Append("map/");
path.Append(category);
path.Append("/");
path.Append(aName);
aCallback->Callback(NS_LITERAL_CSTRING(""),
path,
nsIMemoryReporter::KIND_NONHEAP,
nsIMemoryReporter::UNITS_BYTES,
PRInt64(size) * 1024, // convert from kB to bytes
aDescription, aClosure);
return NS_OK;
}
void Init()
{
nsCOMPtr<nsIMemoryMultiReporter> reporter = new MapsReporter();
NS_RegisterMemoryMultiReporter(reporter);
}
} // namespace MapsMemoryReporter
} // namespace mozilla

Просмотреть файл

@ -0,0 +1,58 @@
/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 ci et: */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla.org code.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
*
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Justin Lebar <justin.lebar@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef mozilla_MapsMemoryReporter_h_
#define mozilla_MapsMemoryReporter_h_
namespace mozilla {
namespace MapsMemoryReporter {
// This only works on Linux, but to make callers' lives easier, we stub out
// empty functions on other platforms.
#if defined(XP_LINUX)
void Init();
#else
void Init() {}
#endif
} // namespace MapsMemoryReporter
} // namespace mozilla
#endif

Просмотреть файл

@ -64,7 +64,7 @@ interface nsIMemoryReporter : nsISupports
/*
* The path that this memory usage should be reported under. Paths are
* '/'-delimited, eg. "a/b/c". There are two categories of paths.
* '/'-delimited, eg. "a/b/c". There are three categories of paths.
*
* - Paths starting with "explicit" represent regions of memory that have
* been explicitly allocated with an OS-level allocation (eg.
@ -91,6 +91,14 @@ interface nsIMemoryReporter : nsISupports
* So in the example above, |a| may not count any allocations counted by
* |d|, and vice versa.
*
* - Paths starting with "map" represent regions of virtual memory that the
* process has mapped. The reporter immediately beneath "map" describes
* the type of measurement; for instance, the reporter "map/rss/[stack]"
* might report how much of the process's stack is currently in physical
* memory.
*
* Reporters in this category must have kind NONHEAP and units BYTES.
*
* - All other paths represent cross-cutting values and may overlap with any
* other reporter.
*/
@ -108,11 +116,11 @@ interface nsIMemoryReporter : nsISupports
* live on the heap. Such memory is commonly allocated by calling one of
* the OS's memory-mapping functions (e.g. mmap, VirtualAlloc, or
* vm_allocate). Reporters in this category must have units UNITS_BYTES
* and must have a path starting with "explicit".
* and must have a path starting with "explicit" or "map".
*
* - OTHER: reporters which don't fit into either of these categories. Such
* reporters must have a path that does not start with "explicit" and may
* have any units.
* reporters must have a path that does not start with "explicit" or "map"
* and may have any units.
*/
const PRInt32 KIND_NONHEAP = 0;
const PRInt32 KIND_HEAP = 1;

Просмотреть файл

@ -575,7 +575,10 @@ public:
const nsACString &aDescription,
nsISupports *aWrappedMRs)
{
if (aKind == nsIMemoryReporter::KIND_NONHEAP && aAmount != PRInt64(-1)) {
if (aKind == nsIMemoryReporter::KIND_NONHEAP &&
PromiseFlatCString(aPath).Find("explicit") == 0 &&
aAmount != PRInt64(-1)) {
MemoryReportsWrapper *wrappedMRs =
static_cast<MemoryReportsWrapper *>(aWrappedMRs);
MemoryReport mr(aPath, aAmount);
@ -608,7 +611,7 @@ nsMemoryReporterManager::GetExplicit(PRInt64 *aExplicit)
PRInt64 heapUsed = PRInt64(-1);
// Get "heap-allocated" and all the KIND_NONHEAP measurements from vanilla
// reporters.
// "explicit" reporters.
nsCOMPtr<nsISimpleEnumerator> e;
EnumerateReporters(getter_AddRefs(e));
@ -621,10 +624,14 @@ nsMemoryReporterManager::GetExplicit(PRInt64 *aExplicit)
nsresult rv = r->GetKind(&kind);
NS_ENSURE_SUCCESS(rv, rv);
if (kind == nsIMemoryReporter::KIND_NONHEAP) {
nsCString path;
rv = r->GetPath(path);
NS_ENSURE_SUCCESS(rv, rv);
nsCString path;
rv = r->GetPath(path);
NS_ENSURE_SUCCESS(rv, rv);
// We're only interested in NONHEAP explicit reporters and
// the 'heap-allocated' reporter.
if (kind == nsIMemoryReporter::KIND_NONHEAP &&
path.Find("explicit") == 0) {
PRInt64 amount;
rv = r->GetAmount(&amount);
@ -636,20 +643,14 @@ nsMemoryReporterManager::GetExplicit(PRInt64 *aExplicit)
MemoryReport mr(path, amount);
nonheap.AppendElement(mr);
}
} else {
nsCString path;
rv = r->GetPath(path);
} else if (path.Equals("heap-allocated")) {
rv = r->GetAmount(&heapUsed);
NS_ENSURE_SUCCESS(rv, rv);
if (path.Equals("heap-allocated")) {
rv = r->GetAmount(&heapUsed);
NS_ENSURE_SUCCESS(rv, rv);
// If "heap-allocated" fails, we give up, because the result
// would be horribly inaccurate.
if (heapUsed == PRInt64(-1)) {
*aExplicit = PRInt64(-1);
return NS_OK;
}
// If "heap-allocated" fails, we give up, because the result
// would be horribly inaccurate.
if (heapUsed == PRInt64(-1)) {
*aExplicit = PRInt64(-1);
return NS_OK;
}
}
}
@ -659,6 +660,8 @@ nsMemoryReporterManager::GetExplicit(PRInt64 *aExplicit)
EnumerateMultiReporters(getter_AddRefs(e2));
nsRefPtr<MemoryReportsWrapper> wrappedMRs =
new MemoryReportsWrapper(&nonheap);
// This callback adds only NONHEAP explicit reporters.
nsRefPtr<MemoryReportCallback> cb = new MemoryReportCallback();
while (NS_SUCCEEDED(e2->HasMoreElements(&more)) && more) {
@ -669,8 +672,8 @@ nsMemoryReporterManager::GetExplicit(PRInt64 *aExplicit)
// Ignore (by zeroing its amount) any reporter that is a child of another
// reporter. Eg. if we have "explicit/a" and "explicit/a/b", zero the
// latter. This is quadratic in the number of NONHEAP reporters, but there
// shouldn't be many.
// latter. This is quadratic in the number of explicit NONHEAP reporters,
// but there shouldn't be many.
for (PRUint32 i = 0; i < nonheap.Length(); i++) {
const nsCString &iPath = nonheap[i].path;
for (PRUint32 j = i + 1; j < nonheap.Length(); j++) {

Просмотреть файл

@ -55,6 +55,10 @@ EXPORT_LIBRARY = 1
GRE_MODULE = 1
MOZILLA_INTERNAL_API = 1
ifeq ($(OS_ARCH),Linux)
DEFINES += -DXP_LINUX
endif
CPPSRCS = \
$(XPCOM_GLUE_SRC_LCPPSRCS) \
$(XPCOM_GLUENS_SRC_LCPPSRCS) \

Просмотреть файл

@ -150,6 +150,7 @@ extern nsresult nsStringInputStreamConstructor(nsISupports *, REFNSIID, void **)
#include "base/message_loop.h"
#include "mozilla/ipc/BrowserProcessSubThread.h"
#include "mozilla/MapsMemoryReporter.h"
using base::AtExitManager;
using mozilla::ipc::BrowserProcessSubThread;
@ -527,6 +528,8 @@ NS_InitXPCOM2(nsIServiceManager* *result,
ScheduleMediaCacheRemover();
#endif
mozilla::MapsMemoryReporter::Init();
return NS_OK;
}