michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/SystemMemoryReporter.h" michael@0: michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/unused.h" michael@0: michael@0: #include "nsIMemoryReporter.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "nsString.h" michael@0: #include "nsTHashtable.h" michael@0: #include "nsHashKeys.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: // This file implements a Linux-specific, system-wide memory reporter. It michael@0: // gathers all the useful memory measurements obtainable from the OS in a michael@0: // single place, giving a high-level view of memory consumption for the entire michael@0: // machine/device. michael@0: // michael@0: // Other memory reporters measure part of a single process's memory consumption. michael@0: // This reporter is different in that it measures memory consumption of many michael@0: // processes, and they end up in a single reports tree. This is a slight abuse michael@0: // of the memory reporting infrastructure, and therefore the results are given michael@0: // their own "process" called "System", which means they show up in about:memory michael@0: // in their own section, distinct from the per-process sections. michael@0: michael@0: namespace mozilla { michael@0: namespace SystemMemoryReporter { michael@0: michael@0: #if !defined(XP_LINUX) michael@0: #error "This won't work if we're not on Linux." michael@0: #endif michael@0: michael@0: static bool michael@0: EndsWithLiteral(const nsCString& aHaystack, const char* aNeedle) michael@0: { michael@0: int32_t idx = aHaystack.RFind(aNeedle); michael@0: return idx != -1 && idx + strlen(aNeedle) == aHaystack.Length(); michael@0: } michael@0: michael@0: static void michael@0: GetDirname(const nsCString& aPath, nsACString& aOut) michael@0: { michael@0: int32_t idx = aPath.RFind("/"); michael@0: if (idx == -1) { michael@0: aOut.Truncate(); michael@0: } else { michael@0: aOut.Assign(Substring(aPath, 0, idx)); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: GetBasename(const nsCString& aPath, nsACString& aOut) michael@0: { michael@0: nsCString out; michael@0: int32_t idx = aPath.RFind("/"); michael@0: if (idx == -1) { michael@0: out.Assign(aPath); michael@0: } else { michael@0: out.Assign(Substring(aPath, idx + 1)); michael@0: } michael@0: michael@0: // On Android, some entries in /dev/ashmem end with "(deleted)" (e.g. michael@0: // "/dev/ashmem/libxul.so(deleted)"). We don't care about this modifier, so michael@0: // cut it off when getting the entry's basename. michael@0: if (EndsWithLiteral(out, "(deleted)")) { michael@0: out.Assign(Substring(out, 0, out.RFind("(deleted)"))); michael@0: } michael@0: out.StripChars(" "); michael@0: michael@0: aOut.Assign(out); michael@0: } michael@0: michael@0: static bool michael@0: IsNumeric(const char* s) michael@0: { michael@0: MOZ_ASSERT(*s); // shouldn't see empty strings michael@0: while (*s) { michael@0: if (!isdigit(*s)) { michael@0: return false; michael@0: } michael@0: s++; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: IsAnonymous(const nsACString& aName) michael@0: { michael@0: // Recent kernels (e.g. 3.5) have multiple [stack:nnnn] entries, where |nnnn| michael@0: // is a thread ID. However, [stack:nnnn] entries count both stack memory michael@0: // *and* anonymous memory because the kernel only knows about the start of michael@0: // each thread stack, not its end. So we treat such entries as anonymous michael@0: // memory instead of stack. This is consistent with older kernels that don't michael@0: // even show [stack:nnnn] entries. michael@0: return aName.IsEmpty() || michael@0: StringBeginsWith(aName, NS_LITERAL_CSTRING("[stack:")); michael@0: } michael@0: michael@0: class SystemReporter MOZ_FINAL : public nsIMemoryReporter michael@0: { michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: michael@0: #define REPORT_WITH_CLEANUP(_path, _units, _amount, _desc, _cleanup) \ michael@0: do { \ michael@0: size_t amount = _amount; /* evaluate _amount only once */ \ michael@0: if (amount > 0) { \ michael@0: nsresult rv; \ michael@0: rv = aHandleReport->Callback(NS_LITERAL_CSTRING("System"), _path, \ michael@0: KIND_NONHEAP, _units, amount, _desc, \ michael@0: aData); \ michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { \ michael@0: _cleanup; \ michael@0: return rv; \ michael@0: } \ michael@0: } \ michael@0: } while (0) michael@0: michael@0: #define REPORT(_path, _amount, _desc) \ michael@0: REPORT_WITH_CLEANUP(_path, UNITS_BYTES, _amount, _desc, (void)0) michael@0: michael@0: NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aData) michael@0: { michael@0: if (!Preferences::GetBool("memory.system_memory_reporter")) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Read relevant fields from /proc/meminfo. michael@0: int64_t memTotal = 0, memFree = 0; michael@0: nsresult rv = ReadMemInfo(&memTotal, &memFree); michael@0: michael@0: // Collect per-process reports from /proc//smaps. michael@0: int64_t totalPss = 0; michael@0: rv = CollectProcessReports(aHandleReport, aData, &totalPss); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Report the non-process numbers. michael@0: int64_t other = memTotal - memFree - totalPss; michael@0: REPORT(NS_LITERAL_CSTRING("mem/other"), other, NS_LITERAL_CSTRING( michael@0: "Memory which is neither owned by any user-space process nor free. Note that " michael@0: "this includes memory holding cached files from the disk which can be " michael@0: "reclaimed by the OS at any time.")); michael@0: michael@0: REPORT(NS_LITERAL_CSTRING("mem/free"), memFree, NS_LITERAL_CSTRING( michael@0: "Memory which is free and not being used for any purpose.")); michael@0: michael@0: // Report reserved memory not included in memTotal. michael@0: rv = CollectPmemReports(aHandleReport, aData); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Report zram usage statistics. michael@0: rv = CollectZramReports(aHandleReport, aData); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: private: michael@0: // Keep this in sync with SystemReporter::kindPathSuffixes! michael@0: enum ProcessSizeKind { michael@0: AnonymousOutsideBrk = 0, michael@0: AnonymousBrkHeap = 1, michael@0: SharedLibrariesRX = 2, michael@0: SharedLibrariesRW = 3, michael@0: SharedLibrariesR = 4, michael@0: SharedLibrariesOther = 5, michael@0: OtherFiles = 6, michael@0: MainThreadStack = 7, michael@0: Vdso = 8, michael@0: michael@0: ProcessSizeKindLimit = 9 // must be last michael@0: }; michael@0: michael@0: static const char* kindPathSuffixes[ProcessSizeKindLimit]; michael@0: michael@0: // These are the cross-cutting measurements across all processes. michael@0: struct ProcessSizes michael@0: { michael@0: ProcessSizes() { memset(this, 0, sizeof(*this)); } michael@0: michael@0: size_t mSizes[ProcessSizeKindLimit]; michael@0: }; michael@0: michael@0: nsresult ReadMemInfo(int64_t* aMemTotal, int64_t* aMemFree) michael@0: { michael@0: FILE* f = fopen("/proc/meminfo", "r"); michael@0: if (!f) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: int n1 = fscanf(f, "MemTotal: %" SCNd64 " kB\n", aMemTotal); michael@0: int n2 = fscanf(f, "MemFree: %" SCNd64 " kB\n", aMemFree); michael@0: michael@0: fclose(f); michael@0: michael@0: if (n1 != 1 || n2 != 1) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Convert from KB to B. michael@0: *aMemTotal *= 1024; michael@0: *aMemFree *= 1024; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult CollectProcessReports(nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aData, michael@0: int64_t* aTotalPss) michael@0: { michael@0: *aTotalPss = 0; michael@0: ProcessSizes processSizes; michael@0: michael@0: DIR* d = opendir("/proc"); michael@0: if (NS_WARN_IF(!d)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: struct dirent* ent; michael@0: while ((ent = readdir(d))) { michael@0: struct stat statbuf; michael@0: const char* pidStr = ent->d_name; michael@0: // Don't check the return value of stat() -- it can return -1 for these michael@0: // directories even when it has succeeded, apparently. michael@0: stat(pidStr, &statbuf); michael@0: if (S_ISDIR(statbuf.st_mode) && IsNumeric(pidStr)) { michael@0: nsCString processName("process("); michael@0: michael@0: // Get the command name from cmdline. If that fails, the pid is still michael@0: // shown. michael@0: nsPrintfCString cmdlinePath("/proc/%s/cmdline", pidStr); michael@0: FILE* f = fopen(cmdlinePath.get(), "r"); michael@0: if (f) { michael@0: static const size_t len = 256; michael@0: char buf[len]; michael@0: if (fgets(buf, len, f)) { michael@0: processName.Append(buf); michael@0: // A hack: replace forward slashes with '\\' so they aren't treated michael@0: // as path separators. Consumers of this reporter (such as michael@0: // about:memory) have to undo this change. michael@0: processName.ReplaceChar('/', '\\'); michael@0: processName.Append(", "); michael@0: } michael@0: fclose(f); michael@0: } michael@0: processName.Append("pid="); michael@0: processName.Append(pidStr); michael@0: processName.Append(")"); michael@0: michael@0: // Read the PSS values from the smaps file. michael@0: nsPrintfCString smapsPath("/proc/%s/smaps", pidStr); michael@0: f = fopen(smapsPath.get(), "r"); michael@0: if (!f) { michael@0: // Processes can terminate between the readdir() call above and now, michael@0: // so just skip if we can't open the file. michael@0: continue; michael@0: } michael@0: while (true) { michael@0: nsresult rv = ParseMapping(f, processName, aHandleReport, aData, michael@0: &processSizes, aTotalPss); michael@0: if (NS_FAILED(rv)) michael@0: break; michael@0: } michael@0: fclose(f); michael@0: } michael@0: } michael@0: closedir(d); michael@0: michael@0: // Report the "processes/" tree. michael@0: michael@0: for (size_t i = 0; i < ProcessSizeKindLimit; i++) { michael@0: nsAutoCString path("processes/"); michael@0: path.Append(kindPathSuffixes[i]); michael@0: michael@0: nsAutoCString desc("This is the sum of all processes' '"); michael@0: desc.Append(kindPathSuffixes[i]); michael@0: desc.Append("' numbers."); michael@0: michael@0: REPORT(path, processSizes.mSizes[i], desc); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult ParseMapping(FILE* aFile, michael@0: const nsACString& aProcessName, michael@0: nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aData, michael@0: ProcessSizes* aProcessSizes, michael@0: int64_t* aTotalPss) michael@0: { michael@0: // The first line of an entry in /proc//smaps looks just like an entry michael@0: // in /proc//maps: michael@0: // michael@0: // address perms offset dev inode pathname michael@0: // 02366000-025d8000 rw-p 00000000 00:00 0 [heap] michael@0: michael@0: const int argCount = 8; michael@0: michael@0: unsigned long long addrStart, addrEnd; michael@0: char perms[5]; michael@0: unsigned long long offset; michael@0: // The 2.6 and 3.0 kernels allocate 12 bits for the major device number and michael@0: // 20 bits for the minor device number. Future kernels might allocate more. michael@0: // 64 bits ought to be enough for anybody. michael@0: char devMajor[17]; michael@0: char devMinor[17]; michael@0: unsigned int inode; michael@0: char path[1025]; michael@0: michael@0: // A path might not be present on this line; set it to the empty string. michael@0: path[0] = '\0'; michael@0: michael@0: // This is a bit tricky. Whitespace in a scanf pattern matches *any* michael@0: // whitespace, including newlines. We want this pattern to match a line michael@0: // with or without a path, but we don't want to look to a new line for the michael@0: // path. Thus we have %u%1024[^\n] at the end of the pattern. This will michael@0: // capture into the path some leading whitespace, which we'll later trim michael@0: // off. michael@0: int n = fscanf(aFile, michael@0: "%llx-%llx %4s %llx " michael@0: "%16[0-9a-fA-F]:%16[0-9a-fA-F] %u%1024[^\n]", michael@0: &addrStart, &addrEnd, perms, &offset, devMajor, michael@0: devMinor, &inode, path); michael@0: michael@0: // Eat up any whitespace at the end of this line, including the newline. michael@0: unused << fscanf(aFile, " "); michael@0: michael@0: // We might or might not have a path, but the rest of the arguments should michael@0: // be there. michael@0: if (n != argCount && n != argCount - 1) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsAutoCString name, description; michael@0: ProcessSizeKind kind; michael@0: GetReporterNameAndDescription(path, perms, name, description, &kind); michael@0: michael@0: while (true) { michael@0: size_t pss = 0; michael@0: nsresult rv = ParseMapBody(aFile, aProcessName, name, description, michael@0: aHandleReport, aData, &pss); michael@0: if (NS_FAILED(rv)) michael@0: break; michael@0: michael@0: // Increment the appropriate aProcessSizes values, and the total. michael@0: aProcessSizes->mSizes[kind] += pss; michael@0: *aTotalPss += pss; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void GetReporterNameAndDescription(const char* aPath, michael@0: const char* aPerms, michael@0: nsACString& aName, michael@0: nsACString& aDesc, michael@0: ProcessSizeKind* aProcessSizeKind) michael@0: { michael@0: aName.Truncate(); michael@0: aDesc.Truncate(); michael@0: michael@0: // If aPath points to a file, we have its absolute path, plus some michael@0: // whitespace. Truncate this to its basename, and put the absolute path in michael@0: // the description. michael@0: nsAutoCString absPath; michael@0: absPath.Append(aPath); michael@0: absPath.StripChars(" "); michael@0: michael@0: nsAutoCString basename; michael@0: GetBasename(absPath, basename); michael@0: michael@0: if (basename.EqualsLiteral("[heap]")) { michael@0: aName.Append("anonymous/brk-heap"); michael@0: aDesc.Append("Memory in anonymous mappings within the boundaries " michael@0: "defined by brk() / sbrk(). This is likely to be just " michael@0: "a portion of the application's heap; the remainder " michael@0: "lives in other anonymous mappings. This corresponds to " michael@0: "'[heap]' in /proc//smaps."); michael@0: *aProcessSizeKind = AnonymousBrkHeap; michael@0: michael@0: } else if (basename.EqualsLiteral("[stack]")) { michael@0: aName.Append("main-thread-stack"); michael@0: aDesc.Append("The stack size of the process's main thread. This " michael@0: "corresponds to '[stack]' in /proc//smaps."); michael@0: *aProcessSizeKind = MainThreadStack; michael@0: michael@0: } else if (basename.EqualsLiteral("[vdso]")) { michael@0: aName.Append("vdso"); michael@0: aDesc.Append("The virtual dynamically-linked shared object, also known " michael@0: "as the 'vsyscall page'. This is a memory region mapped by " michael@0: "the operating system for the purpose of allowing processes " michael@0: "to perform some privileged actions without the overhead of " michael@0: "a syscall."); michael@0: *aProcessSizeKind = Vdso; michael@0: michael@0: } else if (!IsAnonymous(basename)) { michael@0: nsAutoCString dirname; michael@0: GetDirname(absPath, dirname); michael@0: michael@0: // Hack: A file is a shared library if the basename contains ".so" and michael@0: // its dirname contains "/lib", or if the basename ends with ".so". michael@0: if (EndsWithLiteral(basename, ".so") || michael@0: (basename.Find(".so") != -1 && dirname.Find("/lib") != -1)) { michael@0: aName.Append("shared-libraries/"); michael@0: michael@0: if (strncmp(aPerms, "r-x", 3) == 0) { michael@0: *aProcessSizeKind = SharedLibrariesRX; michael@0: } else if (strncmp(aPerms, "rw-", 3) == 0) { michael@0: *aProcessSizeKind = SharedLibrariesRW; michael@0: } else if (strncmp(aPerms, "r--", 3) == 0) { michael@0: *aProcessSizeKind = SharedLibrariesR; michael@0: } else { michael@0: *aProcessSizeKind = SharedLibrariesOther; michael@0: } michael@0: michael@0: } else { michael@0: aName.Append("other-files/"); michael@0: if (EndsWithLiteral(basename, ".xpi")) { michael@0: aName.Append("extensions/"); michael@0: } else if (dirname.Find("/fontconfig") != -1) { michael@0: aName.Append("fontconfig/"); michael@0: } michael@0: *aProcessSizeKind = OtherFiles; michael@0: } michael@0: michael@0: aName.Append(basename); michael@0: aDesc.Append(absPath); michael@0: michael@0: } else { michael@0: aName.Append("anonymous/outside-brk"); michael@0: aDesc.Append("Memory in anonymous mappings outside the boundaries " michael@0: "defined by brk() / sbrk()."); michael@0: *aProcessSizeKind = AnonymousOutsideBrk; michael@0: } michael@0: michael@0: aName.Append("/["); michael@0: aName.Append(aPerms); michael@0: aName.Append("]"); michael@0: michael@0: // Append the permissions. This is useful for non-verbose mode in michael@0: // about:memory when the filename is long and goes of the right side of the michael@0: // window. michael@0: aDesc.Append(" ["); michael@0: aDesc.Append(aPerms); michael@0: aDesc.Append("]"); michael@0: } michael@0: michael@0: nsresult ParseMapBody( michael@0: FILE* aFile, michael@0: const nsACString& aProcessName, michael@0: const nsACString& aName, michael@0: const nsACString& aDescription, michael@0: nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aData, michael@0: size_t* aPss) michael@0: { michael@0: // Most of the lines in the body look like this: michael@0: // michael@0: // Size: 132 kB michael@0: // Rss: 20 kB michael@0: // Pss: 20 kB michael@0: // michael@0: // We're only interested in Pss. In newer kernels, the last line in the michael@0: // body has a different form: michael@0: // michael@0: // VmFlags: rd wr mr mw me dw ac michael@0: // michael@0: // The strings after "VmFlags: " vary. michael@0: michael@0: char desc[1025]; michael@0: int64_t sizeKB; michael@0: int n = fscanf(aFile, "%1024[a-zA-Z_]: %" SCNd64 " kB\n", desc, &sizeKB); michael@0: if (n == EOF || n == 0) { michael@0: return NS_ERROR_FAILURE; michael@0: } else if (n == 1 && strcmp(desc, "VmFlags") == 0) { michael@0: // This is the "VmFlags:" line. Chew up the rest of it. michael@0: fscanf(aFile, "%*1024[a-z ]\n"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Only report "Pss" values. michael@0: if (strcmp(desc, "Pss") == 0) { michael@0: *aPss = sizeKB * 1024; michael@0: michael@0: // Don't report zero values. michael@0: if (*aPss == 0) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoCString path("mem/processes/"); michael@0: path.Append(aProcessName); michael@0: path.Append("/"); michael@0: path.Append(aName); michael@0: michael@0: REPORT(path, *aPss, aDescription); michael@0: } else { michael@0: *aPss = 0; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult CollectPmemReports(nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aData) michael@0: { michael@0: // The pmem subsystem allocates physically contiguous memory for michael@0: // interfacing with hardware. In order to ensure availability, michael@0: // this memory is reserved during boot, and allocations are made michael@0: // within these regions at runtime. michael@0: // michael@0: // There are typically several of these pools allocated at boot. michael@0: // The /sys/kernel/pmem_regions directory contains a subdirectory michael@0: // for each one. Within each subdirectory, the files we care michael@0: // about are "size" (the total amount of physical memory) and michael@0: // "mapped_regions" (a list of the current allocations within that michael@0: // area). michael@0: DIR* d = opendir("/sys/kernel/pmem_regions"); michael@0: if (!d) { michael@0: if (NS_WARN_IF(errno != ENOENT)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: // If ENOENT, system doesn't use pmem. michael@0: return NS_OK; michael@0: } michael@0: michael@0: struct dirent* ent; michael@0: while ((ent = readdir(d))) { michael@0: const char* name = ent->d_name; michael@0: uint64_t size; michael@0: int scanned; michael@0: michael@0: // Skip "." and ".." (and any other dotfiles). michael@0: if (name[0] == '.') { michael@0: continue; michael@0: } michael@0: michael@0: // Read the total size. The file gives the size in decimal and michael@0: // hex, in the form "13631488(0xd00000)"; we parse the former. michael@0: nsPrintfCString sizePath("/sys/kernel/pmem_regions/%s/size", name); michael@0: FILE* sizeFile = fopen(sizePath.get(), "r"); michael@0: if (NS_WARN_IF(!sizeFile)) { michael@0: continue; michael@0: } michael@0: scanned = fscanf(sizeFile, "%" SCNu64, &size); michael@0: if (NS_WARN_IF(scanned != 1)) { michael@0: continue; michael@0: } michael@0: fclose(sizeFile); michael@0: michael@0: // Read mapped regions; format described below. michael@0: uint64_t freeSize = size; michael@0: nsPrintfCString regionsPath("/sys/kernel/pmem_regions/%s/mapped_regions", michael@0: name); michael@0: FILE* regionsFile = fopen(regionsPath.get(), "r"); michael@0: if (regionsFile) { michael@0: static const size_t bufLen = 4096; michael@0: char buf[bufLen]; michael@0: while (fgets(buf, bufLen, regionsFile)) { michael@0: int pid; michael@0: michael@0: // Skip header line. michael@0: if (strncmp(buf, "pid #", 5) == 0) { michael@0: continue; michael@0: } michael@0: // Line format: "pid N:" + zero or more "(Start,Len) ". michael@0: // N is decimal; Start and Len are in hex. michael@0: scanned = sscanf(buf, "pid %d", &pid); michael@0: if (NS_WARN_IF(scanned != 1)) { michael@0: continue; michael@0: } michael@0: for (const char* nextParen = strchr(buf, '('); michael@0: nextParen != nullptr; michael@0: nextParen = strchr(nextParen + 1, '(')) { michael@0: uint64_t mapStart, mapLen; michael@0: michael@0: scanned = sscanf(nextParen + 1, "%" SCNx64 ",%" SCNx64, michael@0: &mapStart, &mapLen); michael@0: if (NS_WARN_IF(scanned != 2)) { michael@0: break; michael@0: } michael@0: michael@0: nsPrintfCString path("mem/pmem/used/%s/segment(pid=%d, " michael@0: "offset=0x%" PRIx64 ")", name, pid, mapStart); michael@0: nsPrintfCString desc("Physical memory reserved for the \"%s\" pool " michael@0: "and allocated to a buffer.", name); michael@0: REPORT_WITH_CLEANUP(path, UNITS_BYTES, mapLen, desc, michael@0: (fclose(regionsFile), closedir(d))); michael@0: freeSize -= mapLen; michael@0: } michael@0: } michael@0: fclose(regionsFile); michael@0: } michael@0: michael@0: nsPrintfCString path("mem/pmem/free/%s", name); michael@0: nsPrintfCString desc("Physical memory reserved for the \"%s\" pool and " michael@0: "unavailable to the rest of the system, but not " michael@0: "currently allocated.", name); michael@0: REPORT_WITH_CLEANUP(path, UNITS_BYTES, freeSize, desc, closedir(d)); michael@0: } michael@0: closedir(d); michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint64_t michael@0: ReadSizeFromFile(const char* aFilename) michael@0: { michael@0: FILE* sizeFile = fopen(aFilename, "r"); michael@0: if (NS_WARN_IF(!sizeFile)) { michael@0: return 0; michael@0: } michael@0: michael@0: uint64_t size = 0; michael@0: fscanf(sizeFile, "%" SCNu64, &size); michael@0: fclose(sizeFile); michael@0: michael@0: return size; michael@0: } michael@0: michael@0: nsresult michael@0: CollectZramReports(nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aData) michael@0: { michael@0: // zram usage stats files can be found under: michael@0: // /sys/block/zram michael@0: // |--> disksize - Maximum amount of uncompressed data that can be michael@0: // stored on the disk (bytes) michael@0: // |--> orig_data_size - Uncompressed size of data in the disk (bytes) michael@0: // |--> compr_data_size - Compressed size of the data in the disk (bytes) michael@0: // |--> num_reads - Number of attempted reads to the disk (count) michael@0: // |--> num_writes - Number of attempted writes to the disk (count) michael@0: // michael@0: // Each file contains a single integer value in decimal form. michael@0: michael@0: DIR* d = opendir("/sys/block"); michael@0: if (!d) { michael@0: if (NS_WARN_IF(errno != ENOENT)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: struct dirent* ent; michael@0: while ((ent = readdir(d))) { michael@0: const char* name = ent->d_name; michael@0: michael@0: // Skip non-zram entries. michael@0: if (strncmp("zram", name, 4) != 0) { michael@0: continue; michael@0: } michael@0: michael@0: // Report disk size statistics. michael@0: nsPrintfCString diskSizeFile("/sys/block/%s/disksize", name); michael@0: nsPrintfCString origSizeFile("/sys/block/%s/orig_data_size", name); michael@0: michael@0: uint64_t diskSize = ReadSizeFromFile(diskSizeFile.get()); michael@0: uint64_t origSize = ReadSizeFromFile(origSizeFile.get()); michael@0: uint64_t unusedSize = diskSize - origSize; michael@0: michael@0: nsPrintfCString diskUsedPath("zram-disksize/%s/used", name); michael@0: nsPrintfCString diskUsedDesc( michael@0: "The uncompressed size of data stored in \"%s.\" " michael@0: "This excludes zero-filled pages since " michael@0: "no memory is allocated for them.", name); michael@0: REPORT_WITH_CLEANUP(diskUsedPath, UNITS_BYTES, origSize, michael@0: diskUsedDesc, closedir(d)); michael@0: michael@0: nsPrintfCString diskUnusedPath("zram-disksize/%s/unused", name); michael@0: nsPrintfCString diskUnusedDesc( michael@0: "The amount of uncompressed data that can still be " michael@0: "be stored in \"%s\"", name); michael@0: REPORT_WITH_CLEANUP(diskUnusedPath, UNITS_BYTES, unusedSize, michael@0: diskUnusedDesc, closedir(d)); michael@0: michael@0: // Report disk accesses. michael@0: nsPrintfCString readsFile("/sys/block/%s/num_reads", name); michael@0: nsPrintfCString writesFile("/sys/block/%s/num_writes", name); michael@0: michael@0: uint64_t reads = ReadSizeFromFile(readsFile.get()); michael@0: uint64_t writes = ReadSizeFromFile(writesFile.get()); michael@0: michael@0: nsPrintfCString readsDesc( michael@0: "The number of reads (failed or successful) done on " michael@0: "\"%s\"", name); michael@0: nsPrintfCString readsPath("zram-accesses/%s/reads", name); michael@0: REPORT_WITH_CLEANUP(readsPath, UNITS_COUNT_CUMULATIVE, reads, michael@0: readsDesc, closedir(d)); michael@0: michael@0: nsPrintfCString writesDesc( michael@0: "The number of writes (failed or successful) done " michael@0: "on \"%s\"", name); michael@0: nsPrintfCString writesPath("zram-accesses/%s/writes", name); michael@0: REPORT_WITH_CLEANUP(writesPath, UNITS_COUNT_CUMULATIVE, writes, michael@0: writesDesc, closedir(d)); michael@0: michael@0: // Report compressed data size. michael@0: nsPrintfCString comprSizeFile("/sys/block/%s/compr_data_size", name); michael@0: uint64_t comprSize = ReadSizeFromFile(comprSizeFile.get()); michael@0: michael@0: nsPrintfCString comprSizeDesc( michael@0: "The compressed size of data stored in \"%s\"", michael@0: name); michael@0: nsPrintfCString comprSizePath("zram-compr-data-size/%s", name); michael@0: REPORT_WITH_CLEANUP(comprSizePath, UNITS_BYTES, comprSize, michael@0: comprSizeDesc, closedir(d)); michael@0: } michael@0: michael@0: closedir(d); michael@0: return NS_OK; michael@0: } michael@0: michael@0: #undef REPORT michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(SystemReporter, nsIMemoryReporter) michael@0: michael@0: // Keep this in sync with SystemReporter::ProcessSizeKind! michael@0: const char* SystemReporter::kindPathSuffixes[] = { michael@0: "anonymous/outside-brk", michael@0: "anonymous/brk-heap", michael@0: "shared-libraries/read-executable", michael@0: "shared-libraries/read-write", michael@0: "shared-libraries/read-only", michael@0: "shared-libraries/other", michael@0: "other-files", michael@0: "main-thread-stack", michael@0: "vdso" michael@0: }; michael@0: michael@0: void Init() michael@0: { michael@0: RegisterStrongMemoryReporter(new SystemReporter()); michael@0: } michael@0: michael@0: } // namespace SystemMemoryReporter michael@0: } // namespace mozilla