Bug 1011350 - Collect TaggedAnonymousMemory info in SystemMemoryReporter. r=njn

To allow flexibility in tagging, and integrate with non-Gecko uses of
this facility (e.g., Bionic's malloc() tags with "libc_malloc"), the
fixed list of memory kinds used to aggregate across processes is replaced
with arbitrary strings.
This commit is contained in:
Jed Davis 2014-06-17 17:56:00 +02:00
Родитель 10b28157d6
Коммит 80b5f82cde
1 изменённых файлов: 152 добавлений и 183 удалений

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

@ -7,9 +7,12 @@
#include "mozilla/SystemMemoryReporter.h"
#include "mozilla/Attributes.h"
#include "mozilla/PodOperations.h"
#include "mozilla/Preferences.h"
#include "mozilla/TaggedAnonymousMemory.h"
#include "mozilla/unused.h"
#include "nsDataHashtable.h"
#include "nsIMemoryReporter.h"
#include "nsPrintfCString.h"
#include "nsString.h"
@ -170,32 +173,48 @@ public:
}
private:
// Keep this in sync with SystemReporter::kindPathSuffixes!
enum ProcessSizeKind {
AnonymousOutsideBrk = 0,
AnonymousBrkHeap = 1,
SharedLibrariesRX = 2,
SharedLibrariesRW = 3,
SharedLibrariesR = 4,
SharedLibrariesOther = 5,
OtherFiles = 6,
MainThreadStack = 7,
Vdso = 8,
ProcessSizeKindLimit = 9 // must be last
};
static const char* kindPathSuffixes[ProcessSizeKindLimit];
// These are the cross-cutting measurements across all processes.
struct ProcessSizes
class ProcessSizes
{
ProcessSizes()
public:
void Add(const nsACString &aKey, size_t aSize)
{
memset(this, 0, sizeof(*this));
mTagged.Put(aKey, mTagged.Get(aKey) + aSize);
}
size_t mSizes[ProcessSizeKindLimit];
void Report(nsIHandleReportCallback *aHandleReport, nsISupports *aData)
{
EnumArgs env = { aHandleReport, aData };
mTagged.EnumerateRead(ReportSizes, &env);
}
private:
nsDataHashtable<nsCStringHashKey, size_t> mTagged;
struct EnumArgs {
nsIHandleReportCallback* mHandleReport;
nsISupports* mData;
};
static PLDHashOperator ReportSizes(nsCStringHashKey::KeyType aKey,
size_t aAmount,
void *aUserArg)
{
const EnumArgs *envp = reinterpret_cast<const EnumArgs*>(aUserArg);
nsAutoCString path("processes/");
path.Append(aKey);
nsAutoCString desc("This is the sum of all processes' '");
desc.Append(aKey);
desc.AppendLiteral("' numbers.");
envp->mHandleReport->Callback(NS_LITERAL_CSTRING("System"), path,
KIND_NONHEAP, UNITS_BYTES, aAmount,
desc, envp->mData);
return PL_DHASH_NEXT;
}
};
nsresult ReadMemInfo(int64_t* aMemTotal, int64_t* aMemFree)
@ -271,18 +290,16 @@ private:
// so just skip if we can't open the file.
continue;
}
while (true) {
nsresult rv = ParseMapping(f, processName, aHandleReport, aData,
&processSizes, aTotalPss);
if (NS_FAILED(rv)) {
break;
}
}
nsresult rv = ParseMappings(f, processName, aHandleReport, aData,
&processSizes, aTotalPss);
fclose(f);
if (NS_FAILED(rv)) {
continue;
}
// Report the open file descriptors for this process.
nsPrintfCString procFdPath("/proc/%s/fd", pidStr);
nsresult rv = CollectOpenFileReports(
rv = CollectOpenFileReports(
aHandleReport, aData, procFdPath, processName);
if (NS_FAILED(rv)) {
break;
@ -292,33 +309,32 @@ private:
closedir(d);
// Report the "processes/" tree.
for (size_t i = 0; i < ProcessSizeKindLimit; i++) {
nsAutoCString path("processes/");
path.Append(kindPathSuffixes[i]);
nsAutoCString desc("This is the sum of all processes' '");
desc.Append(kindPathSuffixes[i]);
desc.AppendLiteral("' numbers.");
REPORT(path, processSizes.mSizes[i], desc);
}
processSizes.Report(aHandleReport, aData);
return NS_OK;
}
nsresult ParseMapping(FILE* aFile,
const nsACString& aProcessName,
nsIHandleReportCallback* aHandleReport,
nsISupports* aData,
ProcessSizes* aProcessSizes,
int64_t* aTotalPss)
nsresult ParseMappings(FILE* aFile,
const nsACString& aProcessName,
nsIHandleReportCallback* aHandleReport,
nsISupports* aData,
ProcessSizes* aProcessSizes,
int64_t* aTotalPss)
{
// The first line of an entry in /proc/<pid>/smaps looks just like an entry
// in /proc/<pid>/maps:
//
// address perms offset dev inode pathname
// 02366000-025d8000 rw-p 00000000 00:00 0 [heap]
//
// Each of the following lines contains a key and a value, separated
// by ": ", where the key does not contain either of those characters.
// Assuming more than this about the structure of those lines has
// failed to be future-proof in the past, so we avoid doing so.
//
// This makes it difficult to detect the start of a new entry
// until it's been removed from the stdio buffer, so we just loop
// over all lines in the file in this routine.
const int argCount = 8;
@ -331,49 +347,55 @@ private:
char devMajor[17];
char devMinor[17];
unsigned int inode;
char path[1025];
char line[1025];
// This variable holds the path of the current entry, or is void
// if we're scanning for the start of a new entry.
nsAutoCString path;
int pathOffset;
// A path might not be present on this line; set it to the empty string.
path[0] = '\0';
path.SetIsVoid(true);
while (fgets(line, sizeof(line), aFile)) {
if (path.IsVoid()) {
int n = sscanf(line,
"%llx-%llx %4s %llx "
"%16[0-9a-fA-F]:%16[0-9a-fA-F] %u %n",
&addrStart, &addrEnd, perms, &offset, devMajor,
devMinor, &inode, &pathOffset);
// 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 n = 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 (n != argCount && n != argCount - 1) {
return NS_ERROR_FAILURE;
}
nsAutoCString name, description;
ProcessSizeKind kind;
GetReporterNameAndDescription(path, perms, name, description, &kind);
while (true) {
size_t pss = 0;
nsresult rv = ParseMapBody(aFile, aProcessName, name, description,
aHandleReport, aData, &pss);
if (NS_FAILED(rv)) {
break;
if (n >= argCount - 1) {
path.Assign(line + pathOffset);
path.StripChars("\n");
}
continue;
}
// Increment the appropriate aProcessSizes values, and the total.
aProcessSizes->mSizes[kind] += pss;
*aTotalPss += pss;
}
// Now that we have a name and other metadata, scan for the PSS.
size_t pss_kb;
int n = sscanf(line, "Pss: %zu", &pss_kb);
if (n < 1) {
continue;
}
size_t pss = pss_kb * 1024;
if (pss > 0) {
nsAutoCString name, description, tag;
GetReporterNameAndDescription(path.get(), perms, name, description, tag);
nsAutoCString path("mem/processes/");
path.Append(aProcessName);
path.Append('/');
path.Append(name);
REPORT(path, pss, description);
// Increment the appropriate aProcessSizes values, and the total.
aProcessSizes->Add(tag, pss);
*aTotalPss += pss;
}
// Now that we've seen the PSS, we're done wit hthis entry.
path.SetIsVoid(true);
}
return NS_OK;
}
@ -381,48 +403,57 @@ private:
const char* aPerms,
nsACString& aName,
nsACString& aDesc,
ProcessSizeKind* aProcessSizeKind)
nsACString& aTag)
{
aName.Truncate();
aDesc.Truncate();
aTag.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.
// If aPath points to a file, we have its absolute path; it might
// also be a bracketed pseudo-name (see below). In either case
// there is also some whitespace to trim.
nsAutoCString absPath;
absPath.Append(aPath);
absPath.StripChars(" ");
nsAutoCString basename;
GetBasename(absPath, basename);
if (basename.EqualsLiteral("[heap]")) {
if (absPath.EqualsLiteral("[heap]")) {
aName.AppendLiteral("anonymous/brk-heap");
aDesc.AppendLiteral(
"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 corresponds to '[heap]' in /proc/<pid>/smaps.");
*aProcessSizeKind = AnonymousBrkHeap;
} else if (basename.EqualsLiteral("[stack]")) {
aTag = aName;
} else if (absPath.EqualsLiteral("[stack]")) {
aName.AppendLiteral("main-thread-stack");
aDesc.AppendLiteral(
"The stack size of the process's main thread. This corresponds to "
"'[stack]' in /proc/<pid>/smaps.");
*aProcessSizeKind = MainThreadStack;
} else if (basename.EqualsLiteral("[vdso]")) {
aTag = aName;
} else if (absPath.EqualsLiteral("[vdso]")) {
aName.AppendLiteral("vdso");
aDesc.AppendLiteral(
"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.");
*aProcessSizeKind = Vdso;
aTag = aName;
} else if (StringBeginsWith(absPath, NS_LITERAL_CSTRING("[anon:")) &&
EndsWithLiteral(absPath, "]")) {
// It's tagged memory; see also "mfbt/TaggedAnonymousMemory.h".
nsAutoCString tag(Substring(absPath, 6, absPath.Length() - 7));
} else if (!IsAnonymous(basename)) {
nsAutoCString dirname;
aName.AppendLiteral("anonymous/");
aName.Append(tag);
aTag = aName;
aDesc.AppendLiteral("Memory in anonymous mappings tagged with '");
aDesc.Append(tag);
aDesc.Append('\'');
} else if (!IsAnonymous(absPath)) {
// We now know it's an actual file. Truncate this to its
// basename, and put the absolute path in the description.
nsAutoCString basename, dirname;
GetBasename(absPath, basename);
GetDirname(absPath, dirname);
// Hack: A file is a shared library if the basename contains ".so" and
@ -430,36 +461,42 @@ private:
if (EndsWithLiteral(basename, ".so") ||
(basename.Find(".so") != -1 && dirname.Find("/lib") != -1)) {
aName.AppendLiteral("shared-libraries/");
aTag = aName;
if (strncmp(aPerms, "r-x", 3) == 0) {
*aProcessSizeKind = SharedLibrariesRX;
aTag.AppendLiteral("read-executable");
} else if (strncmp(aPerms, "rw-", 3) == 0) {
*aProcessSizeKind = SharedLibrariesRW;
aTag.AppendLiteral("read-write");
} else if (strncmp(aPerms, "r--", 3) == 0) {
*aProcessSizeKind = SharedLibrariesR;
aTag.AppendLiteral("read-only");
} else {
*aProcessSizeKind = SharedLibrariesOther;
aTag.AppendLiteral("other");
}
} else {
aName.AppendLiteral("other-files/");
aName.AppendLiteral("other-files");
if (EndsWithLiteral(basename, ".xpi")) {
aName.AppendLiteral("extensions/");
aName.AppendLiteral("/extensions");
} else if (dirname.Find("/fontconfig") != -1) {
aName.AppendLiteral("fontconfig/");
aName.AppendLiteral("/fontconfig");
}
*aProcessSizeKind = OtherFiles;
aTag = aName;
aName.Append('/');
}
aName.Append(basename);
aDesc.Append(absPath);
} else {
aName.AppendLiteral("anonymous/outside-brk");
aDesc.AppendLiteral(
"Memory in anonymous mappings outside the boundaries defined by "
"brk() / sbrk().");
*aProcessSizeKind = AnonymousOutsideBrk;
if (MozTaggedMemoryIsSupported()) {
aName.AppendLiteral("anonymous/untagged");
aDesc.AppendLiteral("Memory in untagged anonymous mappings.");
aTag = aName;
} else {
aName.AppendLiteral("anonymous/outside-brk");
aDesc.AppendLiteral("Memory in anonymous mappings outside the "
"boundaries defined by brk() / sbrk().");
aTag = aName;
}
}
aName.AppendLiteral("/[");
@ -474,61 +511,6 @@ private:
aDesc.Append(']');
}
nsresult ParseMapBody(
FILE* aFile,
const nsACString& aProcessName,
const nsACString& aName,
const nsACString& aDescription,
nsIHandleReportCallback* aHandleReport,
nsISupports* aData,
size_t* aPss)
{
// Most of the lines in the body look like this:
//
// Size: 132 kB
// Rss: 20 kB
// Pss: 20 kB
//
// We're only interested in Pss. In newer kernels, the last line in the
// body has a different form:
//
// VmFlags: rd wr mr mw me dw ac
//
// The strings after "VmFlags: " vary.
char desc[1025];
int64_t sizeKB;
int n = fscanf(aFile, "%1024[a-zA-Z_]: %" SCNd64 " kB\n", desc, &sizeKB);
if (n == EOF || n == 0) {
return NS_ERROR_FAILURE;
} else if (n == 1 && strcmp(desc, "VmFlags") == 0) {
// This is the "VmFlags:" line. Chew up the rest of it.
fscanf(aFile, "%*1024[a-z ]\n");
return NS_ERROR_FAILURE;
}
// Only report "Pss" values.
if (strcmp(desc, "Pss") == 0) {
*aPss = sizeKB * 1024;
// Don't report zero values.
if (*aPss == 0) {
return NS_OK;
}
nsAutoCString path("mem/processes/");
path.Append(aProcessName);
path.Append('/');
path.Append(aName);
REPORT(path, *aPss, aDescription);
} else {
*aPss = 0;
}
return NS_OK;
}
nsresult CollectPmemReports(nsIHandleReportCallback* aHandleReport,
nsISupports* aData)
{
@ -821,19 +803,6 @@ private:
NS_IMPL_ISUPPORTS(SystemReporter, nsIMemoryReporter)
// Keep this in sync with SystemReporter::ProcessSizeKind!
const char* SystemReporter::kindPathSuffixes[] = {
"anonymous/outside-brk",
"anonymous/brk-heap",
"shared-libraries/read-executable",
"shared-libraries/read-write",
"shared-libraries/read-only",
"shared-libraries/other",
"other-files",
"main-thread-stack",
"vdso"
};
void
Init()
{