mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 912165 - Remove the Linux-only smaps memory reporters. r=mccr8.
--HG-- extra : rebase_source : 4847c299f87a1a85944b169a808ee2c573ebf8b5
This commit is contained in:
parent
8cb604bd59
commit
4bbef5fc23
@ -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, "");
|
||||
|
@ -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\
|
||||
|
@ -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
|
@ -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
|
@ -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',
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
Loading…
Reference in New Issue
Block a user