1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/xpcom/base/SystemMemoryReporter.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,750 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "mozilla/SystemMemoryReporter.h" 1.11 + 1.12 +#include "mozilla/Attributes.h" 1.13 +#include "mozilla/Preferences.h" 1.14 +#include "mozilla/unused.h" 1.15 + 1.16 +#include "nsIMemoryReporter.h" 1.17 +#include "nsPrintfCString.h" 1.18 +#include "nsString.h" 1.19 +#include "nsTHashtable.h" 1.20 +#include "nsHashKeys.h" 1.21 + 1.22 +#include <dirent.h> 1.23 +#include <inttypes.h> 1.24 +#include <stdio.h> 1.25 +#include <sys/stat.h> 1.26 +#include <sys/types.h> 1.27 +#include <unistd.h> 1.28 +#include <errno.h> 1.29 + 1.30 +// This file implements a Linux-specific, system-wide memory reporter. It 1.31 +// gathers all the useful memory measurements obtainable from the OS in a 1.32 +// single place, giving a high-level view of memory consumption for the entire 1.33 +// machine/device. 1.34 +// 1.35 +// Other memory reporters measure part of a single process's memory consumption. 1.36 +// This reporter is different in that it measures memory consumption of many 1.37 +// processes, and they end up in a single reports tree. This is a slight abuse 1.38 +// of the memory reporting infrastructure, and therefore the results are given 1.39 +// their own "process" called "System", which means they show up in about:memory 1.40 +// in their own section, distinct from the per-process sections. 1.41 + 1.42 +namespace mozilla { 1.43 +namespace SystemMemoryReporter { 1.44 + 1.45 +#if !defined(XP_LINUX) 1.46 +#error "This won't work if we're not on Linux." 1.47 +#endif 1.48 + 1.49 +static bool 1.50 +EndsWithLiteral(const nsCString& aHaystack, const char* aNeedle) 1.51 +{ 1.52 + int32_t idx = aHaystack.RFind(aNeedle); 1.53 + return idx != -1 && idx + strlen(aNeedle) == aHaystack.Length(); 1.54 +} 1.55 + 1.56 +static void 1.57 +GetDirname(const nsCString& aPath, nsACString& aOut) 1.58 +{ 1.59 + int32_t idx = aPath.RFind("/"); 1.60 + if (idx == -1) { 1.61 + aOut.Truncate(); 1.62 + } else { 1.63 + aOut.Assign(Substring(aPath, 0, idx)); 1.64 + } 1.65 +} 1.66 + 1.67 +static void 1.68 +GetBasename(const nsCString& aPath, nsACString& aOut) 1.69 +{ 1.70 + nsCString out; 1.71 + int32_t idx = aPath.RFind("/"); 1.72 + if (idx == -1) { 1.73 + out.Assign(aPath); 1.74 + } else { 1.75 + out.Assign(Substring(aPath, idx + 1)); 1.76 + } 1.77 + 1.78 + // On Android, some entries in /dev/ashmem end with "(deleted)" (e.g. 1.79 + // "/dev/ashmem/libxul.so(deleted)"). We don't care about this modifier, so 1.80 + // cut it off when getting the entry's basename. 1.81 + if (EndsWithLiteral(out, "(deleted)")) { 1.82 + out.Assign(Substring(out, 0, out.RFind("(deleted)"))); 1.83 + } 1.84 + out.StripChars(" "); 1.85 + 1.86 + aOut.Assign(out); 1.87 +} 1.88 + 1.89 +static bool 1.90 +IsNumeric(const char* s) 1.91 +{ 1.92 + MOZ_ASSERT(*s); // shouldn't see empty strings 1.93 + while (*s) { 1.94 + if (!isdigit(*s)) { 1.95 + return false; 1.96 + } 1.97 + s++; 1.98 + } 1.99 + return true; 1.100 +} 1.101 + 1.102 +static bool 1.103 +IsAnonymous(const nsACString& aName) 1.104 +{ 1.105 + // Recent kernels (e.g. 3.5) have multiple [stack:nnnn] entries, where |nnnn| 1.106 + // is a thread ID. However, [stack:nnnn] entries count both stack memory 1.107 + // *and* anonymous memory because the kernel only knows about the start of 1.108 + // each thread stack, not its end. So we treat such entries as anonymous 1.109 + // memory instead of stack. This is consistent with older kernels that don't 1.110 + // even show [stack:nnnn] entries. 1.111 + return aName.IsEmpty() || 1.112 + StringBeginsWith(aName, NS_LITERAL_CSTRING("[stack:")); 1.113 +} 1.114 + 1.115 +class SystemReporter MOZ_FINAL : public nsIMemoryReporter 1.116 +{ 1.117 +public: 1.118 + NS_DECL_THREADSAFE_ISUPPORTS 1.119 + 1.120 +#define REPORT_WITH_CLEANUP(_path, _units, _amount, _desc, _cleanup) \ 1.121 + do { \ 1.122 + size_t amount = _amount; /* evaluate _amount only once */ \ 1.123 + if (amount > 0) { \ 1.124 + nsresult rv; \ 1.125 + rv = aHandleReport->Callback(NS_LITERAL_CSTRING("System"), _path, \ 1.126 + KIND_NONHEAP, _units, amount, _desc, \ 1.127 + aData); \ 1.128 + if (NS_WARN_IF(NS_FAILED(rv))) { \ 1.129 + _cleanup; \ 1.130 + return rv; \ 1.131 + } \ 1.132 + } \ 1.133 + } while (0) 1.134 + 1.135 +#define REPORT(_path, _amount, _desc) \ 1.136 + REPORT_WITH_CLEANUP(_path, UNITS_BYTES, _amount, _desc, (void)0) 1.137 + 1.138 + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, 1.139 + nsISupports* aData) 1.140 + { 1.141 + if (!Preferences::GetBool("memory.system_memory_reporter")) { 1.142 + return NS_OK; 1.143 + } 1.144 + 1.145 + // Read relevant fields from /proc/meminfo. 1.146 + int64_t memTotal = 0, memFree = 0; 1.147 + nsresult rv = ReadMemInfo(&memTotal, &memFree); 1.148 + 1.149 + // Collect per-process reports from /proc/<pid>/smaps. 1.150 + int64_t totalPss = 0; 1.151 + rv = CollectProcessReports(aHandleReport, aData, &totalPss); 1.152 + NS_ENSURE_SUCCESS(rv, rv); 1.153 + 1.154 + // Report the non-process numbers. 1.155 + int64_t other = memTotal - memFree - totalPss; 1.156 + REPORT(NS_LITERAL_CSTRING("mem/other"), other, NS_LITERAL_CSTRING( 1.157 +"Memory which is neither owned by any user-space process nor free. Note that " 1.158 +"this includes memory holding cached files from the disk which can be " 1.159 +"reclaimed by the OS at any time.")); 1.160 + 1.161 + REPORT(NS_LITERAL_CSTRING("mem/free"), memFree, NS_LITERAL_CSTRING( 1.162 +"Memory which is free and not being used for any purpose.")); 1.163 + 1.164 + // Report reserved memory not included in memTotal. 1.165 + rv = CollectPmemReports(aHandleReport, aData); 1.166 + NS_ENSURE_SUCCESS(rv, rv); 1.167 + 1.168 + // Report zram usage statistics. 1.169 + rv = CollectZramReports(aHandleReport, aData); 1.170 + NS_ENSURE_SUCCESS(rv, rv); 1.171 + 1.172 + return rv; 1.173 + } 1.174 + 1.175 +private: 1.176 + // Keep this in sync with SystemReporter::kindPathSuffixes! 1.177 + enum ProcessSizeKind { 1.178 + AnonymousOutsideBrk = 0, 1.179 + AnonymousBrkHeap = 1, 1.180 + SharedLibrariesRX = 2, 1.181 + SharedLibrariesRW = 3, 1.182 + SharedLibrariesR = 4, 1.183 + SharedLibrariesOther = 5, 1.184 + OtherFiles = 6, 1.185 + MainThreadStack = 7, 1.186 + Vdso = 8, 1.187 + 1.188 + ProcessSizeKindLimit = 9 // must be last 1.189 + }; 1.190 + 1.191 + static const char* kindPathSuffixes[ProcessSizeKindLimit]; 1.192 + 1.193 + // These are the cross-cutting measurements across all processes. 1.194 + struct ProcessSizes 1.195 + { 1.196 + ProcessSizes() { memset(this, 0, sizeof(*this)); } 1.197 + 1.198 + size_t mSizes[ProcessSizeKindLimit]; 1.199 + }; 1.200 + 1.201 + nsresult ReadMemInfo(int64_t* aMemTotal, int64_t* aMemFree) 1.202 + { 1.203 + FILE* f = fopen("/proc/meminfo", "r"); 1.204 + if (!f) { 1.205 + return NS_ERROR_FAILURE; 1.206 + } 1.207 + 1.208 + int n1 = fscanf(f, "MemTotal: %" SCNd64 " kB\n", aMemTotal); 1.209 + int n2 = fscanf(f, "MemFree: %" SCNd64 " kB\n", aMemFree); 1.210 + 1.211 + fclose(f); 1.212 + 1.213 + if (n1 != 1 || n2 != 1) { 1.214 + return NS_ERROR_FAILURE; 1.215 + } 1.216 + 1.217 + // Convert from KB to B. 1.218 + *aMemTotal *= 1024; 1.219 + *aMemFree *= 1024; 1.220 + 1.221 + return NS_OK; 1.222 + } 1.223 + 1.224 + nsresult CollectProcessReports(nsIHandleReportCallback* aHandleReport, 1.225 + nsISupports* aData, 1.226 + int64_t* aTotalPss) 1.227 + { 1.228 + *aTotalPss = 0; 1.229 + ProcessSizes processSizes; 1.230 + 1.231 + DIR* d = opendir("/proc"); 1.232 + if (NS_WARN_IF(!d)) { 1.233 + return NS_ERROR_FAILURE; 1.234 + } 1.235 + struct dirent* ent; 1.236 + while ((ent = readdir(d))) { 1.237 + struct stat statbuf; 1.238 + const char* pidStr = ent->d_name; 1.239 + // Don't check the return value of stat() -- it can return -1 for these 1.240 + // directories even when it has succeeded, apparently. 1.241 + stat(pidStr, &statbuf); 1.242 + if (S_ISDIR(statbuf.st_mode) && IsNumeric(pidStr)) { 1.243 + nsCString processName("process("); 1.244 + 1.245 + // Get the command name from cmdline. If that fails, the pid is still 1.246 + // shown. 1.247 + nsPrintfCString cmdlinePath("/proc/%s/cmdline", pidStr); 1.248 + FILE* f = fopen(cmdlinePath.get(), "r"); 1.249 + if (f) { 1.250 + static const size_t len = 256; 1.251 + char buf[len]; 1.252 + if (fgets(buf, len, f)) { 1.253 + processName.Append(buf); 1.254 + // A hack: replace forward slashes with '\\' so they aren't treated 1.255 + // as path separators. Consumers of this reporter (such as 1.256 + // about:memory) have to undo this change. 1.257 + processName.ReplaceChar('/', '\\'); 1.258 + processName.Append(", "); 1.259 + } 1.260 + fclose(f); 1.261 + } 1.262 + processName.Append("pid="); 1.263 + processName.Append(pidStr); 1.264 + processName.Append(")"); 1.265 + 1.266 + // Read the PSS values from the smaps file. 1.267 + nsPrintfCString smapsPath("/proc/%s/smaps", pidStr); 1.268 + f = fopen(smapsPath.get(), "r"); 1.269 + if (!f) { 1.270 + // Processes can terminate between the readdir() call above and now, 1.271 + // so just skip if we can't open the file. 1.272 + continue; 1.273 + } 1.274 + while (true) { 1.275 + nsresult rv = ParseMapping(f, processName, aHandleReport, aData, 1.276 + &processSizes, aTotalPss); 1.277 + if (NS_FAILED(rv)) 1.278 + break; 1.279 + } 1.280 + fclose(f); 1.281 + } 1.282 + } 1.283 + closedir(d); 1.284 + 1.285 + // Report the "processes/" tree. 1.286 + 1.287 + for (size_t i = 0; i < ProcessSizeKindLimit; i++) { 1.288 + nsAutoCString path("processes/"); 1.289 + path.Append(kindPathSuffixes[i]); 1.290 + 1.291 + nsAutoCString desc("This is the sum of all processes' '"); 1.292 + desc.Append(kindPathSuffixes[i]); 1.293 + desc.Append("' numbers."); 1.294 + 1.295 + REPORT(path, processSizes.mSizes[i], desc); 1.296 + } 1.297 + 1.298 + return NS_OK; 1.299 + } 1.300 + 1.301 + nsresult ParseMapping(FILE* aFile, 1.302 + const nsACString& aProcessName, 1.303 + nsIHandleReportCallback* aHandleReport, 1.304 + nsISupports* aData, 1.305 + ProcessSizes* aProcessSizes, 1.306 + int64_t* aTotalPss) 1.307 + { 1.308 + // The first line of an entry in /proc/<pid>/smaps looks just like an entry 1.309 + // in /proc/<pid>/maps: 1.310 + // 1.311 + // address perms offset dev inode pathname 1.312 + // 02366000-025d8000 rw-p 00000000 00:00 0 [heap] 1.313 + 1.314 + const int argCount = 8; 1.315 + 1.316 + unsigned long long addrStart, addrEnd; 1.317 + char perms[5]; 1.318 + unsigned long long offset; 1.319 + // The 2.6 and 3.0 kernels allocate 12 bits for the major device number and 1.320 + // 20 bits for the minor device number. Future kernels might allocate more. 1.321 + // 64 bits ought to be enough for anybody. 1.322 + char devMajor[17]; 1.323 + char devMinor[17]; 1.324 + unsigned int inode; 1.325 + char path[1025]; 1.326 + 1.327 + // A path might not be present on this line; set it to the empty string. 1.328 + path[0] = '\0'; 1.329 + 1.330 + // This is a bit tricky. Whitespace in a scanf pattern matches *any* 1.331 + // whitespace, including newlines. We want this pattern to match a line 1.332 + // with or without a path, but we don't want to look to a new line for the 1.333 + // path. Thus we have %u%1024[^\n] at the end of the pattern. This will 1.334 + // capture into the path some leading whitespace, which we'll later trim 1.335 + // off. 1.336 + int n = fscanf(aFile, 1.337 + "%llx-%llx %4s %llx " 1.338 + "%16[0-9a-fA-F]:%16[0-9a-fA-F] %u%1024[^\n]", 1.339 + &addrStart, &addrEnd, perms, &offset, devMajor, 1.340 + devMinor, &inode, path); 1.341 + 1.342 + // Eat up any whitespace at the end of this line, including the newline. 1.343 + unused << fscanf(aFile, " "); 1.344 + 1.345 + // We might or might not have a path, but the rest of the arguments should 1.346 + // be there. 1.347 + if (n != argCount && n != argCount - 1) { 1.348 + return NS_ERROR_FAILURE; 1.349 + } 1.350 + 1.351 + nsAutoCString name, description; 1.352 + ProcessSizeKind kind; 1.353 + GetReporterNameAndDescription(path, perms, name, description, &kind); 1.354 + 1.355 + while (true) { 1.356 + size_t pss = 0; 1.357 + nsresult rv = ParseMapBody(aFile, aProcessName, name, description, 1.358 + aHandleReport, aData, &pss); 1.359 + if (NS_FAILED(rv)) 1.360 + break; 1.361 + 1.362 + // Increment the appropriate aProcessSizes values, and the total. 1.363 + aProcessSizes->mSizes[kind] += pss; 1.364 + *aTotalPss += pss; 1.365 + } 1.366 + 1.367 + return NS_OK; 1.368 + } 1.369 + 1.370 + void GetReporterNameAndDescription(const char* aPath, 1.371 + const char* aPerms, 1.372 + nsACString& aName, 1.373 + nsACString& aDesc, 1.374 + ProcessSizeKind* aProcessSizeKind) 1.375 + { 1.376 + aName.Truncate(); 1.377 + aDesc.Truncate(); 1.378 + 1.379 + // If aPath points to a file, we have its absolute path, plus some 1.380 + // whitespace. Truncate this to its basename, and put the absolute path in 1.381 + // the description. 1.382 + nsAutoCString absPath; 1.383 + absPath.Append(aPath); 1.384 + absPath.StripChars(" "); 1.385 + 1.386 + nsAutoCString basename; 1.387 + GetBasename(absPath, basename); 1.388 + 1.389 + if (basename.EqualsLiteral("[heap]")) { 1.390 + aName.Append("anonymous/brk-heap"); 1.391 + aDesc.Append("Memory in anonymous mappings within the boundaries " 1.392 + "defined by brk() / sbrk(). This is likely to be just " 1.393 + "a portion of the application's heap; the remainder " 1.394 + "lives in other anonymous mappings. This corresponds to " 1.395 + "'[heap]' in /proc/<pid>/smaps."); 1.396 + *aProcessSizeKind = AnonymousBrkHeap; 1.397 + 1.398 + } else if (basename.EqualsLiteral("[stack]")) { 1.399 + aName.Append("main-thread-stack"); 1.400 + aDesc.Append("The stack size of the process's main thread. This " 1.401 + "corresponds to '[stack]' in /proc/<pid>/smaps."); 1.402 + *aProcessSizeKind = MainThreadStack; 1.403 + 1.404 + } else if (basename.EqualsLiteral("[vdso]")) { 1.405 + aName.Append("vdso"); 1.406 + aDesc.Append("The virtual dynamically-linked shared object, also known " 1.407 + "as the 'vsyscall page'. This is a memory region mapped by " 1.408 + "the operating system for the purpose of allowing processes " 1.409 + "to perform some privileged actions without the overhead of " 1.410 + "a syscall."); 1.411 + *aProcessSizeKind = Vdso; 1.412 + 1.413 + } else if (!IsAnonymous(basename)) { 1.414 + nsAutoCString dirname; 1.415 + GetDirname(absPath, dirname); 1.416 + 1.417 + // Hack: A file is a shared library if the basename contains ".so" and 1.418 + // its dirname contains "/lib", or if the basename ends with ".so". 1.419 + if (EndsWithLiteral(basename, ".so") || 1.420 + (basename.Find(".so") != -1 && dirname.Find("/lib") != -1)) { 1.421 + aName.Append("shared-libraries/"); 1.422 + 1.423 + if (strncmp(aPerms, "r-x", 3) == 0) { 1.424 + *aProcessSizeKind = SharedLibrariesRX; 1.425 + } else if (strncmp(aPerms, "rw-", 3) == 0) { 1.426 + *aProcessSizeKind = SharedLibrariesRW; 1.427 + } else if (strncmp(aPerms, "r--", 3) == 0) { 1.428 + *aProcessSizeKind = SharedLibrariesR; 1.429 + } else { 1.430 + *aProcessSizeKind = SharedLibrariesOther; 1.431 + } 1.432 + 1.433 + } else { 1.434 + aName.Append("other-files/"); 1.435 + if (EndsWithLiteral(basename, ".xpi")) { 1.436 + aName.Append("extensions/"); 1.437 + } else if (dirname.Find("/fontconfig") != -1) { 1.438 + aName.Append("fontconfig/"); 1.439 + } 1.440 + *aProcessSizeKind = OtherFiles; 1.441 + } 1.442 + 1.443 + aName.Append(basename); 1.444 + aDesc.Append(absPath); 1.445 + 1.446 + } else { 1.447 + aName.Append("anonymous/outside-brk"); 1.448 + aDesc.Append("Memory in anonymous mappings outside the boundaries " 1.449 + "defined by brk() / sbrk()."); 1.450 + *aProcessSizeKind = AnonymousOutsideBrk; 1.451 + } 1.452 + 1.453 + aName.Append("/["); 1.454 + aName.Append(aPerms); 1.455 + aName.Append("]"); 1.456 + 1.457 + // Append the permissions. This is useful for non-verbose mode in 1.458 + // about:memory when the filename is long and goes of the right side of the 1.459 + // window. 1.460 + aDesc.Append(" ["); 1.461 + aDesc.Append(aPerms); 1.462 + aDesc.Append("]"); 1.463 + } 1.464 + 1.465 + nsresult ParseMapBody( 1.466 + FILE* aFile, 1.467 + const nsACString& aProcessName, 1.468 + const nsACString& aName, 1.469 + const nsACString& aDescription, 1.470 + nsIHandleReportCallback* aHandleReport, 1.471 + nsISupports* aData, 1.472 + size_t* aPss) 1.473 + { 1.474 + // Most of the lines in the body look like this: 1.475 + // 1.476 + // Size: 132 kB 1.477 + // Rss: 20 kB 1.478 + // Pss: 20 kB 1.479 + // 1.480 + // We're only interested in Pss. In newer kernels, the last line in the 1.481 + // body has a different form: 1.482 + // 1.483 + // VmFlags: rd wr mr mw me dw ac 1.484 + // 1.485 + // The strings after "VmFlags: " vary. 1.486 + 1.487 + char desc[1025]; 1.488 + int64_t sizeKB; 1.489 + int n = fscanf(aFile, "%1024[a-zA-Z_]: %" SCNd64 " kB\n", desc, &sizeKB); 1.490 + if (n == EOF || n == 0) { 1.491 + return NS_ERROR_FAILURE; 1.492 + } else if (n == 1 && strcmp(desc, "VmFlags") == 0) { 1.493 + // This is the "VmFlags:" line. Chew up the rest of it. 1.494 + fscanf(aFile, "%*1024[a-z ]\n"); 1.495 + return NS_ERROR_FAILURE; 1.496 + } 1.497 + 1.498 + // Only report "Pss" values. 1.499 + if (strcmp(desc, "Pss") == 0) { 1.500 + *aPss = sizeKB * 1024; 1.501 + 1.502 + // Don't report zero values. 1.503 + if (*aPss == 0) { 1.504 + return NS_OK; 1.505 + } 1.506 + 1.507 + nsAutoCString path("mem/processes/"); 1.508 + path.Append(aProcessName); 1.509 + path.Append("/"); 1.510 + path.Append(aName); 1.511 + 1.512 + REPORT(path, *aPss, aDescription); 1.513 + } else { 1.514 + *aPss = 0; 1.515 + } 1.516 + 1.517 + return NS_OK; 1.518 + } 1.519 + 1.520 + nsresult CollectPmemReports(nsIHandleReportCallback* aHandleReport, 1.521 + nsISupports* aData) 1.522 + { 1.523 + // The pmem subsystem allocates physically contiguous memory for 1.524 + // interfacing with hardware. In order to ensure availability, 1.525 + // this memory is reserved during boot, and allocations are made 1.526 + // within these regions at runtime. 1.527 + // 1.528 + // There are typically several of these pools allocated at boot. 1.529 + // The /sys/kernel/pmem_regions directory contains a subdirectory 1.530 + // for each one. Within each subdirectory, the files we care 1.531 + // about are "size" (the total amount of physical memory) and 1.532 + // "mapped_regions" (a list of the current allocations within that 1.533 + // area). 1.534 + DIR* d = opendir("/sys/kernel/pmem_regions"); 1.535 + if (!d) { 1.536 + if (NS_WARN_IF(errno != ENOENT)) { 1.537 + return NS_ERROR_FAILURE; 1.538 + } 1.539 + // If ENOENT, system doesn't use pmem. 1.540 + return NS_OK; 1.541 + } 1.542 + 1.543 + struct dirent* ent; 1.544 + while ((ent = readdir(d))) { 1.545 + const char* name = ent->d_name; 1.546 + uint64_t size; 1.547 + int scanned; 1.548 + 1.549 + // Skip "." and ".." (and any other dotfiles). 1.550 + if (name[0] == '.') { 1.551 + continue; 1.552 + } 1.553 + 1.554 + // Read the total size. The file gives the size in decimal and 1.555 + // hex, in the form "13631488(0xd00000)"; we parse the former. 1.556 + nsPrintfCString sizePath("/sys/kernel/pmem_regions/%s/size", name); 1.557 + FILE* sizeFile = fopen(sizePath.get(), "r"); 1.558 + if (NS_WARN_IF(!sizeFile)) { 1.559 + continue; 1.560 + } 1.561 + scanned = fscanf(sizeFile, "%" SCNu64, &size); 1.562 + if (NS_WARN_IF(scanned != 1)) { 1.563 + continue; 1.564 + } 1.565 + fclose(sizeFile); 1.566 + 1.567 + // Read mapped regions; format described below. 1.568 + uint64_t freeSize = size; 1.569 + nsPrintfCString regionsPath("/sys/kernel/pmem_regions/%s/mapped_regions", 1.570 + name); 1.571 + FILE* regionsFile = fopen(regionsPath.get(), "r"); 1.572 + if (regionsFile) { 1.573 + static const size_t bufLen = 4096; 1.574 + char buf[bufLen]; 1.575 + while (fgets(buf, bufLen, regionsFile)) { 1.576 + int pid; 1.577 + 1.578 + // Skip header line. 1.579 + if (strncmp(buf, "pid #", 5) == 0) { 1.580 + continue; 1.581 + } 1.582 + // Line format: "pid N:" + zero or more "(Start,Len) ". 1.583 + // N is decimal; Start and Len are in hex. 1.584 + scanned = sscanf(buf, "pid %d", &pid); 1.585 + if (NS_WARN_IF(scanned != 1)) { 1.586 + continue; 1.587 + } 1.588 + for (const char* nextParen = strchr(buf, '('); 1.589 + nextParen != nullptr; 1.590 + nextParen = strchr(nextParen + 1, '(')) { 1.591 + uint64_t mapStart, mapLen; 1.592 + 1.593 + scanned = sscanf(nextParen + 1, "%" SCNx64 ",%" SCNx64, 1.594 + &mapStart, &mapLen); 1.595 + if (NS_WARN_IF(scanned != 2)) { 1.596 + break; 1.597 + } 1.598 + 1.599 + nsPrintfCString path("mem/pmem/used/%s/segment(pid=%d, " 1.600 + "offset=0x%" PRIx64 ")", name, pid, mapStart); 1.601 + nsPrintfCString desc("Physical memory reserved for the \"%s\" pool " 1.602 + "and allocated to a buffer.", name); 1.603 + REPORT_WITH_CLEANUP(path, UNITS_BYTES, mapLen, desc, 1.604 + (fclose(regionsFile), closedir(d))); 1.605 + freeSize -= mapLen; 1.606 + } 1.607 + } 1.608 + fclose(regionsFile); 1.609 + } 1.610 + 1.611 + nsPrintfCString path("mem/pmem/free/%s", name); 1.612 + nsPrintfCString desc("Physical memory reserved for the \"%s\" pool and " 1.613 + "unavailable to the rest of the system, but not " 1.614 + "currently allocated.", name); 1.615 + REPORT_WITH_CLEANUP(path, UNITS_BYTES, freeSize, desc, closedir(d)); 1.616 + } 1.617 + closedir(d); 1.618 + return NS_OK; 1.619 + } 1.620 + 1.621 + uint64_t 1.622 + ReadSizeFromFile(const char* aFilename) 1.623 + { 1.624 + FILE* sizeFile = fopen(aFilename, "r"); 1.625 + if (NS_WARN_IF(!sizeFile)) { 1.626 + return 0; 1.627 + } 1.628 + 1.629 + uint64_t size = 0; 1.630 + fscanf(sizeFile, "%" SCNu64, &size); 1.631 + fclose(sizeFile); 1.632 + 1.633 + return size; 1.634 + } 1.635 + 1.636 + nsresult 1.637 + CollectZramReports(nsIHandleReportCallback* aHandleReport, 1.638 + nsISupports* aData) 1.639 + { 1.640 + // zram usage stats files can be found under: 1.641 + // /sys/block/zram<id> 1.642 + // |--> disksize - Maximum amount of uncompressed data that can be 1.643 + // stored on the disk (bytes) 1.644 + // |--> orig_data_size - Uncompressed size of data in the disk (bytes) 1.645 + // |--> compr_data_size - Compressed size of the data in the disk (bytes) 1.646 + // |--> num_reads - Number of attempted reads to the disk (count) 1.647 + // |--> num_writes - Number of attempted writes to the disk (count) 1.648 + // 1.649 + // Each file contains a single integer value in decimal form. 1.650 + 1.651 + DIR* d = opendir("/sys/block"); 1.652 + if (!d) { 1.653 + if (NS_WARN_IF(errno != ENOENT)) { 1.654 + return NS_ERROR_FAILURE; 1.655 + } 1.656 + 1.657 + return NS_OK; 1.658 + } 1.659 + 1.660 + struct dirent* ent; 1.661 + while ((ent = readdir(d))) { 1.662 + const char* name = ent->d_name; 1.663 + 1.664 + // Skip non-zram entries. 1.665 + if (strncmp("zram", name, 4) != 0) { 1.666 + continue; 1.667 + } 1.668 + 1.669 + // Report disk size statistics. 1.670 + nsPrintfCString diskSizeFile("/sys/block/%s/disksize", name); 1.671 + nsPrintfCString origSizeFile("/sys/block/%s/orig_data_size", name); 1.672 + 1.673 + uint64_t diskSize = ReadSizeFromFile(diskSizeFile.get()); 1.674 + uint64_t origSize = ReadSizeFromFile(origSizeFile.get()); 1.675 + uint64_t unusedSize = diskSize - origSize; 1.676 + 1.677 + nsPrintfCString diskUsedPath("zram-disksize/%s/used", name); 1.678 + nsPrintfCString diskUsedDesc( 1.679 + "The uncompressed size of data stored in \"%s.\" " 1.680 + "This excludes zero-filled pages since " 1.681 + "no memory is allocated for them.", name); 1.682 + REPORT_WITH_CLEANUP(diskUsedPath, UNITS_BYTES, origSize, 1.683 + diskUsedDesc, closedir(d)); 1.684 + 1.685 + nsPrintfCString diskUnusedPath("zram-disksize/%s/unused", name); 1.686 + nsPrintfCString diskUnusedDesc( 1.687 + "The amount of uncompressed data that can still be " 1.688 + "be stored in \"%s\"", name); 1.689 + REPORT_WITH_CLEANUP(diskUnusedPath, UNITS_BYTES, unusedSize, 1.690 + diskUnusedDesc, closedir(d)); 1.691 + 1.692 + // Report disk accesses. 1.693 + nsPrintfCString readsFile("/sys/block/%s/num_reads", name); 1.694 + nsPrintfCString writesFile("/sys/block/%s/num_writes", name); 1.695 + 1.696 + uint64_t reads = ReadSizeFromFile(readsFile.get()); 1.697 + uint64_t writes = ReadSizeFromFile(writesFile.get()); 1.698 + 1.699 + nsPrintfCString readsDesc( 1.700 + "The number of reads (failed or successful) done on " 1.701 + "\"%s\"", name); 1.702 + nsPrintfCString readsPath("zram-accesses/%s/reads", name); 1.703 + REPORT_WITH_CLEANUP(readsPath, UNITS_COUNT_CUMULATIVE, reads, 1.704 + readsDesc, closedir(d)); 1.705 + 1.706 + nsPrintfCString writesDesc( 1.707 + "The number of writes (failed or successful) done " 1.708 + "on \"%s\"", name); 1.709 + nsPrintfCString writesPath("zram-accesses/%s/writes", name); 1.710 + REPORT_WITH_CLEANUP(writesPath, UNITS_COUNT_CUMULATIVE, writes, 1.711 + writesDesc, closedir(d)); 1.712 + 1.713 + // Report compressed data size. 1.714 + nsPrintfCString comprSizeFile("/sys/block/%s/compr_data_size", name); 1.715 + uint64_t comprSize = ReadSizeFromFile(comprSizeFile.get()); 1.716 + 1.717 + nsPrintfCString comprSizeDesc( 1.718 + "The compressed size of data stored in \"%s\"", 1.719 + name); 1.720 + nsPrintfCString comprSizePath("zram-compr-data-size/%s", name); 1.721 + REPORT_WITH_CLEANUP(comprSizePath, UNITS_BYTES, comprSize, 1.722 + comprSizeDesc, closedir(d)); 1.723 + } 1.724 + 1.725 + closedir(d); 1.726 + return NS_OK; 1.727 + } 1.728 + 1.729 +#undef REPORT 1.730 +}; 1.731 + 1.732 +NS_IMPL_ISUPPORTS(SystemReporter, nsIMemoryReporter) 1.733 + 1.734 +// Keep this in sync with SystemReporter::ProcessSizeKind! 1.735 +const char* SystemReporter::kindPathSuffixes[] = { 1.736 + "anonymous/outside-brk", 1.737 + "anonymous/brk-heap", 1.738 + "shared-libraries/read-executable", 1.739 + "shared-libraries/read-write", 1.740 + "shared-libraries/read-only", 1.741 + "shared-libraries/other", 1.742 + "other-files", 1.743 + "main-thread-stack", 1.744 + "vdso" 1.745 +}; 1.746 + 1.747 +void Init() 1.748 +{ 1.749 + RegisterStrongMemoryReporter(new SystemReporter()); 1.750 +} 1.751 + 1.752 +} // namespace SystemMemoryReporter 1.753 +} // namespace mozilla