Bug 912165 - Remove the Linux-only smaps memory reporters. r=mccr8.

--HG--
extra : rebase_source : 4847c299f87a1a85944b169a808ee2c573ebf8b5
This commit is contained in:
Nicholas Nethercote 2013-09-03 21:09:15 -07:00
parent 8cb604bd59
commit 4bbef5fc23
8 changed files with 62 additions and 773 deletions

View File

@ -273,8 +273,7 @@ function appendElementWithText(aP, aTagName, aClassName, aText)
//---------------------------------------------------------------------------
const kTreeDescriptions = {
'explicit' :
const explicitTreeDescription =
"This tree covers explicit memory allocations by the application. It includes \
\n\n\
* allocations made at the operating system level (via calls to functions such as \
@ -291,69 +290,7 @@ and thread stacks. \
\n\n\
'explicit' 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.",
'rss':
"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. \
\n\n\
Note that the 'rss' value here might not equal the value for 'resident' \
under 'Other Measurements' because the two measurements are not taken at \
exactly the same time.",
'pss':
"This tree shows how much space in physical memory can be 'blamed' on this \
process. For each mapping, its 'proportional set size' (PSS) is the \
mapping's resident size divided by the number of processes which use the \
mapping. So if a mapping is private to this process, its PSS should equal \
its RSS. But if a mapping is shared between three processes, its PSS in each \
of the processes would be 1/3 its RSS.",
'size':
"This tree shows how much virtual addres space each of the process's mappings \
takes up (a.k.a. the mapping's 'vsize'). A mapping may have a large size 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'. \
\n\n\
Note that the 'size' 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 kSectionNames = {
'explicit': 'Explicit Allocations',
'rss': 'Resident Set Size (RSS) Breakdown',
'pss': 'Proportional Set Size (PSS) Breakdown',
'size': 'Virtual Size Breakdown',
'swap': 'Swap Breakdown',
'other': 'Other Measurements'
};
const kSmapsTreeNames = ['rss', 'pss', 'size', 'swap' ];
const kSmapsTreePrefixes = ['rss/', 'pss/', 'size/', 'swap/'];
function isExplicitPath(aUnsafePath)
{
return aUnsafePath.startsWith("explicit/");
}
function isSmapsPath(aUnsafePath)
{
for (let i = 0; i < kSmapsTreePrefixes.length; i++) {
if (aUnsafePath.startsWith(kSmapsTreePrefixes[i])) {
return true;
}
}
return false;
}
focus on when trying to reduce memory usage.";
//---------------------------------------------------------------------------
@ -546,8 +483,7 @@ function updateAboutMemoryFromReporters()
try {
// Process the reports from the memory reporters.
appendAboutMemoryMain(processMemoryReporters, gMgr.hasMozMallocUsableSize,
/* forceShowSmaps = */ false);
appendAboutMemoryMain(processMemoryReporters, gMgr.hasMozMallocUsableSize);
} catch (ex) {
handleException(ex);
@ -576,8 +512,7 @@ function updateAboutMemoryFromJSONObject(aObj)
let process = function(aIgnoreReporter, aIgnoreReport, aHandleReport) {
processMemoryReportsFromFile(aObj.reports, aIgnoreReport, aHandleReport);
}
appendAboutMemoryMain(process, aObj.hasMozMallocUsableSize,
/* forceShowSmaps = */ true);
appendAboutMemoryMain(process, aObj.hasMozMallocUsableSize);
} catch (ex) {
handleException(ex);
}
@ -765,6 +700,8 @@ DReport.prototype = {
//
// In those cases, we just use the description from the first-encountered
// one, which is what about:memory also does.
// (Note: reports with those paths are no longer generated, but allowing
// the descriptions to differ seems reasonable.)
},
merge: function(aJr) {
@ -930,14 +867,10 @@ function PColl()
* file.
* @param aHasMozMallocUsableSize
* Boolean indicating if moz_malloc_usable_size works.
* @param aForceShowSmaps
* True if we should show the smaps memory reporters even if we're not
* in verbose mode.
*/
function appendAboutMemoryMain(aProcessReports, aHasMozMallocUsableSize,
aForceShowSmaps)
function appendAboutMemoryMain(aProcessReports, aHasMozMallocUsableSize)
{
let pcollsByProcess = getPCollsByProcess(aProcessReports, aForceShowSmaps);
let pcollsByProcess = getPCollsByProcess(aProcessReports);
// Sort the processes.
let processes = Object.keys(pcollsByProcess);
@ -998,12 +931,9 @@ function appendAboutMemoryMain(aProcessReports, aHasMozMallocUsableSize,
* @param aProcessReports
* Function that extracts the memory reports from the reporters or from
* file.
* @param aForceShowSmaps
* True if we should show the smaps memory reporters even if we're not
* in verbose mode.
* @return The table of PColls by process.
*/
function getPCollsByProcess(aProcessReports, aForceShowSmaps)
function getPCollsByProcess(aProcessReports)
{
let pcollsByProcess = {};
@ -1012,41 +942,29 @@ function getPCollsByProcess(aProcessReports, aForceShowSmaps)
// be in parentheses, so a ')' might appear after the '.'.)
const gSentenceRegExp = /^[A-Z].*\.\)?$/m;
// Ignore the "smaps" reporter in non-verbose mode unless we're reading from
// a file or the clipboard. (Note that reports from these reporters can
// reach here via a "content-child" reporter if they were in a child
// process.)
//
// Ignore any "redundant/"-prefixed reporters and reports, which are only
// used by telemetry.
function ignoreReporter(aName)
{
return (aName === "smaps" && !gVerbose.checked && !aForceShowSmaps) ||
aName.startsWith("redundant/");
return aName.startsWith("redundant/");
}
function ignoreReport(aUnsafePath)
{
return (isSmapsPath(aUnsafePath) && !gVerbose.checked && !aForceShowSmaps) ||
aUnsafePath.startsWith("redundant/");
return aUnsafePath.startsWith("redundant/");
}
function handleReport(aProcess, aUnsafePath, aKind, aUnits, aAmount,
aDescription, aPresence)
{
if (isExplicitPath(aUnsafePath)) {
if (aUnsafePath.startsWith("explicit/")) {
assertInput(aKind === KIND_HEAP || aKind === KIND_NONHEAP,
"bad explicit kind");
assertInput(aUnits === UNITS_BYTES, "bad explicit units");
assertInput(gSentenceRegExp.test(aDescription),
"non-sentence explicit description");
} else if (isSmapsPath(aUnsafePath)) {
assertInput(aKind === KIND_NONHEAP, "bad smaps kind");
assertInput(aUnits === UNITS_BYTES, "bad smaps units");
assertInput(aDescription !== "", "empty smaps description");
} else {
assertInput(gSentenceRegExp.test(aDescription),
"non-sentence other description");
@ -1434,7 +1352,7 @@ function appendProcessAboutMemoryElements(aP, aProcess, aTrees, aDegenerates,
let hasKnownHeapAllocated;
{
let treeName = "explicit";
let pre = appendSectionHeader(aP, kSectionNames[treeName]);
let pre = appendSectionHeader(aP, "Explicit Allocations");
let t = aTrees[treeName];
if (t) {
fillInTree(t);
@ -1442,31 +1360,13 @@ function appendProcessAboutMemoryElements(aP, aProcess, aTrees, aDegenerates,
aDegenerates &&
addHeapUnclassifiedNode(t, aDegenerates["heap-allocated"], aHeapTotal);
sortTreeAndInsertAggregateNodes(t._amount, t);
t._description = kTreeDescriptions[treeName];
t._description = explicitTreeDescription;
appendTreeElements(pre, t, aProcess, "");
delete aTrees[treeName];
}
appendTextNode(aP, "\n"); // gives nice spacing when we cut and paste
}
// The smaps trees, which are only present in aTrees in verbose mode or when
// we're reading from a file or the clipboard.
kSmapsTreeNames.forEach(function(aTreeName) {
// |t| will be undefined if we don't have any reports for the given
// unsafePath.
let t = aTrees[aTreeName];
if (t) {
let pre = appendSectionHeader(aP, kSectionNames[aTreeName]);
fillInTree(t);
sortTreeAndInsertAggregateNodes(t._amount, t);
t._description = kTreeDescriptions[aTreeName];
t._hideKids = true; // smaps trees are always initially collapsed
appendTreeElements(pre, t, aProcess, "");
delete aTrees[aTreeName];
appendTextNode(aP, "\n"); // gives nice spacing when we cut and paste
}
});
// Fill in and sort all the non-degenerate other trees.
let otherTrees = [];
for (let unsafeName in aTrees) {
@ -1494,7 +1394,7 @@ function appendProcessAboutMemoryElements(aP, aProcess, aTrees, aDegenerates,
otherDegenerates.sort(TreeNode.compareUnsafeNames);
// Now generate the elements, putting non-degenerate trees first.
let pre = appendSectionHeader(aP, kSectionNames['other']);
let pre = appendSectionHeader(aP, "Other Measurements");
for (let i = 0; i < otherTrees.length; i++) {
let t = otherTrees[i];
appendTreeElements(pre, t, aProcess, "");

View File

@ -116,21 +116,6 @@
f("compartments/user/https:\\\\very-long-url.com\\very-long\\oh-so-long\\really-quite-long.html?a=2&b=3&c=4&d=5&e=abcdefghijklmnopqrstuvwxyz&f=123456789123456789123456789", OTHER, COUNT, 1);
}
},
{ name: "smaps",
collectReports: function(aCbObj, aClosure) {
// The amounts are given in pages, so multiply here by 4kb.
function f(aP, aA) {
aCbObj.callback("", aP, NONHEAP, BYTES, aA * 4 * KB, "Desc.", aClosure);
}
f("size/a", 24);
f("swap/a", 1);
f("swap/a", 2);
f("size/a", 19);
f("swap/b/c", 10);
f("rss/a", 42);
f("pss/a", 43);
}
},
{ name: "compartments",
collectReports: function(aCbObj, aClosure) {
function f(aP) {
@ -189,12 +174,6 @@
HEAP, BYTES,200 * MB);
f("2nd", "other0", OTHER, BYTES,666 * MB);
f("2nd", "other1", OTHER, BYTES,111 * MB);
// If the "smaps" reporter is in a child process it'll be passed to
// the main process under a different name. The fact that we skip
// the "smaps" reporter in the main process won't cause these
// to be skipped; the report-level skipping will be hit instead.
f("2nd", "size/e", NONHEAP, BYTES,24*4*KB);
f("2nd", "size/f", NONHEAP, BYTES,24*4*KB);
f("2nd", "redundant/blah", NONHEAP, BYTES,24*4*KB); // ignored!
// Check that we can handle "heap-allocated" not being present.
@ -437,22 +416,6 @@ Explicit Allocations\n\
├──────510,976 B (00.08%) ── d\n\
└──────102,400 B (00.02%) ── e\n\
\n\
Resident Set Size (RSS) Breakdown\n\
\n\
172,032 B (100.0%) ++ rss\n\
\n\
Proportional Set Size (PSS) Breakdown\n\
\n\
176,128 B (100.0%) ++ pss\n\
\n\
Virtual Size Breakdown\n\
\n\
176,128 B (100.0%) ++ size\n\
\n\
Swap Breakdown\n\
\n\
53,248 B (100.0%) ++ swap\n\
\n\
Other Measurements\n\
\n\
5 (100.0%) -- compartments\n\
@ -549,10 +512,6 @@ Explicit Allocations\n\
├────209,715,200 B (20.00%) ── flip/the/backslashes\n\
└────105,906,176 B (10.10%) ── heap-unclassified\n\
\n\
Virtual Size Breakdown\n\
\n\
196,608 B (100.0%) ++ size\n\
\n\
Other Measurements\n\
\n\
1,048,576,000 B ── heap-allocated\n\

View File

@ -1,578 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#include "mozilla/Util.h"
#include "mozilla/MapsMemoryReporter.h"
#include "nsIMemoryReporter.h"
#include "nsString.h"
#include "nsCOMPtr.h"
#include "nsTHashtable.h"
#include "nsHashKeys.h"
#include "mozilla/Attributes.h"
#include "mozilla/unused.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)
{
int32_t idx = aHaystack.RFind(aNeedle);
if (idx == -1) {
return false;
}
return idx + strlen(aNeedle) == aHaystack.Length();
}
void GetDirname(const nsCString &aPath, nsACString &aOut)
{
int32_t idx = aPath.RFind("/");
if (idx == -1) {
aOut.Truncate();
}
else {
aOut.Assign(Substring(aPath, 0, idx));
}
}
void GetBasename(const nsCString &aPath, nsACString &aOut)
{
nsCString out;
int32_t idx = aPath.RFind("/");
if (idx == -1) {
out.Assign(aPath);
}
else {
out.Assign(Substring(aPath, idx + 1));
}
// On Android, some entries in /dev/ashmem end with "(deleted)" (e.g.
// "/dev/ashmem/libxul.so(deleted)"). We don't care about this modifier, so
// cut it off when getting the entry's basename.
if (EndsWithLiteral(out, "(deleted)")) {
out.Assign(Substring(out, 0, out.RFind("(deleted)")));
}
out.StripChars(" ");
aOut.Assign(out);
}
// MapsReporter::CollectReports uses this stuct to keep track of whether it's
// seen a mapping under 'rss', 'pss', 'size', and 'swap'.
struct CategoriesSeen {
CategoriesSeen() :
mSeenRss(false),
mSeenPss(false),
mSeenSize(false),
mSeenSwap(false)
{
}
bool mSeenRss;
bool mSeenPss;
bool mSeenSize;
bool mSeenSwap;
};
} // anonymous namespace
class MapsReporter MOZ_FINAL : public nsIMemoryReporter
{
public:
MapsReporter();
NS_DECL_THREADSAFE_ISUPPORTS
NS_IMETHOD GetName(nsACString &aName)
{
aName.AssignLiteral("smaps");
return NS_OK;
}
NS_IMETHOD
CollectReports(nsIMemoryReporterCallback *aCb,
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,
nsIMemoryReporterCallback *aCb,
nsISupports *aClosure,
CategoriesSeen *aCategoriesSeen);
void
GetReporterNameAndDescription(const char *aPath,
const char *aPermissions,
nsACString &aName,
nsACString &aDesc);
nsresult
ParseMapBody(FILE *aFile,
const nsACString &aName,
const nsACString &aDescription,
nsIMemoryReporterCallback *aCb,
nsISupports *aClosure,
CategoriesSeen *aCategoriesSeen);
bool mSearchedForLibxul;
nsCString mLibxulDir;
nsTHashtable<nsCStringHashKey> mMozillaLibraries;
};
NS_IMPL_ISUPPORTS1(MapsReporter, nsIMemoryReporter)
MapsReporter::MapsReporter()
: mSearchedForLibxul(false)
, mMozillaLibraries(ArrayLength(mozillaLibraries))
{
const uint32_t len = ArrayLength(mozillaLibraries);
for (uint32_t i = 0; i < len; i++) {
nsAutoCString str;
str.Assign(mozillaLibraries[i]);
mMozillaLibraries.PutEntry(str);
}
}
NS_IMETHODIMP
MapsReporter::CollectReports(nsIMemoryReporterCallback *aCb,
nsISupports *aClosure)
{
CategoriesSeen categoriesSeen;
FILE *f = fopen("/proc/self/smaps", "r");
if (!f)
return NS_ERROR_FAILURE;
while (true) {
nsresult rv = ParseMapping(f, aCb, aClosure, &categoriesSeen);
if (NS_FAILED(rv))
break;
}
fclose(f);
// For sure we should have created some node under 'rss' and
// 'size'; otherwise we're probably not reading smaps correctly. If we
// didn't create a node under 'swap', create one here so about:memory
// knows to create an empty 'swap' tree; it needs a 'total' child because
// about:memory expects at least one report whose path begins with 'swap/'.
NS_ASSERTION(categoriesSeen.mSeenSize, "Didn't create a size node?");
NS_ASSERTION(categoriesSeen.mSeenRss, "Didn't create a rss node?");
NS_ASSERTION(categoriesSeen.mSeenPss, "Didn't create a pss node?");
if (!categoriesSeen.mSeenSwap) {
nsresult rv;
rv = aCb->Callback(NS_LITERAL_CSTRING(""),
NS_LITERAL_CSTRING("swap/total"),
nsIMemoryReporter::KIND_NONHEAP,
nsIMemoryReporter::UNITS_BYTES,
0,
NS_LITERAL_CSTRING("This process uses no swap space."),
aClosure);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
MapsReporter::FindLibxul()
{
if (mSearchedForLibxul)
return NS_OK;
mSearchedForLibxul = true;
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;
}
nsAutoCString pathStr;
pathStr.Append(path);
nsAutoCString 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,
nsIMemoryReporterCallback *aCb,
nsISupports *aClosure,
CategoriesSeen *aCategoriesSeen)
{
// 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.
static_assert(sizeof(long long) == sizeof(int64_t),
"size of (long long) is expected to match (int64_t)");
static_assert(sizeof(int) == sizeof(int32_t),
"size of (int) is expected to match (int32_t)");
// Don't bail if FindLibxul fails. We can still gather meaningful stats
// here.
FindLibxul();
// 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;
// The 2.6 and 3.0 kernels allocate 12 bits for the major device number and
// 20 bits for the minor device number. Future kernels might allocate more.
// 64 bits ought to be enough for anybody.
char devMajor[17];
char devMinor[17];
unsigned int 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 "
"%16[0-9a-fA-F]:%16[0-9a-fA-F] %u%1024[^\n]",
&addrStart, &addrEnd, perms, &offset, devMajor,
devMinor, &inode, path);
// Eat up any whitespace at the end of this line, including the newline.
unused << 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;
}
nsAutoCString name, description;
GetReporterNameAndDescription(path, perms, name, description);
while (true) {
nsresult rv = ParseMapBody(aFile, name, description, aCb,
aClosure, aCategoriesSeen);
if (NS_FAILED(rv))
break;
}
return NS_OK;
}
static bool
IsAnonymous(const nsACString &aName)
{
// Recent kernels (e.g. 3.5) have multiple [stack:nnnn] entries, where |nnnn|
// is a thread ID. However, [stack:nnnn] entries count both stack memory
// *and* anonymous memory because the kernel only knows about the start of
// each thread stack, not its end. So we treat such entries as anonymous
// memory instead of stack. This is consistent with older kernels that don't
// even show [stack:nnnn] entries.
return aName.IsEmpty() ||
StringBeginsWith(aName, NS_LITERAL_CSTRING("[stack:"));
}
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.
nsAutoCString absPath;
absPath.Append(aPath);
absPath.StripChars(" ");
nsAutoCString 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 (!IsAnonymous(basename)) {
nsAutoCString 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 ((!mLibxulDir.IsEmpty() && 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,
nsIMemoryReporterCallback *aCb,
nsISupports *aClosure,
CategoriesSeen *aCategoriesSeen)
{
static_assert(sizeof(long long) == sizeof(int64_t),
"size of (long long) is expected to match (int64_t)");
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 = "size";
aCategoriesSeen->mSeenSize = true;
}
else if (strcmp(desc, "Rss") == 0) {
category = "rss";
aCategoriesSeen->mSeenRss = true;
}
else if (strcmp(desc, "Pss") == 0) {
category = "pss";
aCategoriesSeen->mSeenPss = true;
}
else if (strcmp(desc, "Swap") == 0) {
category = "swap";
aCategoriesSeen->mSeenSwap = true;
}
else {
// Don't report this category.
return NS_OK;
}
nsAutoCString path;
path.Append(category);
path.Append("/");
path.Append(aName);
nsresult rv;
rv = aCb->Callback(NS_LITERAL_CSTRING(""),
path,
nsIMemoryReporter::KIND_NONHEAP,
nsIMemoryReporter::UNITS_BYTES,
int64_t(size) * 1024, // convert from kB to bytes
aDescription, aClosure);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
class ResidentUniqueReporter MOZ_FINAL : public MemoryUniReporter
{
public:
ResidentUniqueReporter()
: MemoryUniReporter("resident-unique", KIND_OTHER, UNITS_BYTES,
"Memory mapped by the process that is present in physical memory and not "
"shared with any other processes. This is also known as the process's unique "
"set size (USS). This is the amount of RAM we'd expect to be freed if we "
"closed this process.")
{}
private:
NS_IMETHOD GetAmount(int64_t *aAmount)
{
// You might be tempted to calculate USS by subtracting the "shared" value
// from the "resident" value in /proc/<pid>/statm. But at least on Linux,
// statm's "shared" value actually counts pages backed by files, which has
// little to do with whether the pages are actually shared. smaps on the
// other hand appears to give us the correct information.
//
// We could calculate this data within the smaps reporter, but the overhead
// of the smaps reporter is considerable (we don't even run the smaps
// reporter in normal about:memory operation). Hopefully this
// implementation is fast enough not to matter.
*aAmount = 0;
FILE *f = fopen("/proc/self/smaps", "r");
NS_ENSURE_STATE(f);
int64_t total = 0;
char line[256];
while (fgets(line, sizeof(line), f)) {
long long val = 0;
if (sscanf(line, "Private_Dirty: %lld kB", &val) == 1 ||
sscanf(line, "Private_Clean: %lld kB", &val) == 1) {
total += val * 1024; // convert from kB to bytes
}
}
*aAmount = total;
fclose(f);
return NS_OK;
}
};
void Init()
{
nsCOMPtr<nsIMemoryReporter> reporter = new MapsReporter();
NS_RegisterMemoryReporter(reporter);
NS_RegisterMemoryReporter(new ResidentUniqueReporter());
}
} // namespace MapsMemoryReporter
} // namespace mozilla

View File

@ -1,25 +0,0 @@
/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 ci et: */
/* 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/. */
#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

View File

@ -75,7 +75,6 @@ EXPORTS.mozilla += [
'AvailableMemoryTracker.h',
'ClearOnShutdown.h',
'CycleCollectedJSRuntime.h',
'MapsMemoryReporter.h',
'StackWalk.h',
'StaticMutex.h',
'StaticPtr.h',
@ -108,11 +107,6 @@ CPP_SOURCES += [
'nsVersionComparatorImpl.cpp',
]
if CONFIG['OS_ARCH'] == 'Linux':
CPP_SOURCES += [
'MapsMemoryReporter.cpp',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
CPP_SOURCES += [
'nsMacUtilsImpl.cpp',

View File

@ -143,11 +143,6 @@ interface nsIMemoryReporterCallback : nsISupports
* description that is a sentence (i.e. starts with a capital letter and
* ends with a period, or similar).
*
* - The "size", "rss", "pss" and "swap" trees are optional. They
* represent regions of virtual memory that the process has mapped.
* Reporters in this category must have kind NONHEAP, units BYTES, and a
* non-empty description.
*
* - The "redundant" tree is optional, and can be used for reports that are
* redundant w.r.t. other reports. These are useful for telemetry, and are
* not shown in about:memory. Reports in this tree are entirely

View File

@ -73,6 +73,49 @@ static nsresult GetResidentFast(int64_t* aN)
return GetResident(aN);
}
#define HAVE_RESIDENT_UNIQUE_REPORTER
class ResidentUniqueReporter MOZ_FINAL : public MemoryUniReporter
{
public:
ResidentUniqueReporter()
: MemoryUniReporter("resident-unique", KIND_OTHER, UNITS_BYTES,
"Memory mapped by the process that is present in physical memory and not "
"shared with any other processes. This is also known as the process's unique "
"set size (USS). This is the amount of RAM we'd expect to be freed if we "
"closed this process.")
{}
private:
NS_IMETHOD GetAmount(int64_t *aAmount)
{
// You might be tempted to calculate USS by subtracting the "shared" value
// from the "resident" value in /proc/<pid>/statm. But at least on Linux,
// statm's "shared" value actually counts pages backed by files, which has
// little to do with whether the pages are actually shared.
// /proc/self/smaps on the other hand appears to give us the correct
// information.
*aAmount = 0;
FILE *f = fopen("/proc/self/smaps", "r");
NS_ENSURE_STATE(f);
int64_t total = 0;
char line[256];
while (fgets(line, sizeof(line), f)) {
long long val = 0;
if (sscanf(line, "Private_Dirty: %lld kB", &val) == 1 ||
sscanf(line, "Private_Clean: %lld kB", &val) == 1) {
total += val * 1024; // convert from kB to bytes
}
}
*aAmount = total;
fclose(f);
return NS_OK;
}
};
#elif defined(__DragonFly__) || defined(__FreeBSD__) \
|| defined(__NetBSD__) || defined(__OpenBSD__)
@ -718,6 +761,10 @@ nsMemoryReporterManager::Init()
RegisterReporter(new ResidentFastReporter);
#endif
#ifdef HAVE_RESIDENT_UNIQUE_REPORTER
RegisterReporter(new ResidentUniqueReporter);
#endif
#ifdef HAVE_PAGE_FAULT_REPORTERS
RegisterReporter(new PageFaultsSoftReporter);
RegisterReporter(new PageFaultsHardReporter);

View File

@ -118,7 +118,6 @@ extern nsresult nsStringInputStreamConstructor(nsISupports *, REFNSIID, void **)
#include "base/message_loop.h"
#include "mozilla/ipc/BrowserProcessSubThread.h"
#include "mozilla/MapsMemoryReporter.h"
#include "mozilla/AvailableMemoryTracker.h"
#include "mozilla/ClearOnShutdown.h"
@ -574,8 +573,6 @@ NS_InitXPCOM2(nsIServiceManager* *result,
CreateAnonTempFileRemover();
#endif
mozilla::MapsMemoryReporter::Init();
// The memory reporter manager is up and running -- register a reporter for
// ICU's memory usage.
NS_RegisterMemoryReporter(new ICUReporter());