xpcom/base/SystemMemoryReporter.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include "mozilla/SystemMemoryReporter.h"
michael@0 8
michael@0 9 #include "mozilla/Attributes.h"
michael@0 10 #include "mozilla/Preferences.h"
michael@0 11 #include "mozilla/unused.h"
michael@0 12
michael@0 13 #include "nsIMemoryReporter.h"
michael@0 14 #include "nsPrintfCString.h"
michael@0 15 #include "nsString.h"
michael@0 16 #include "nsTHashtable.h"
michael@0 17 #include "nsHashKeys.h"
michael@0 18
michael@0 19 #include <dirent.h>
michael@0 20 #include <inttypes.h>
michael@0 21 #include <stdio.h>
michael@0 22 #include <sys/stat.h>
michael@0 23 #include <sys/types.h>
michael@0 24 #include <unistd.h>
michael@0 25 #include <errno.h>
michael@0 26
michael@0 27 // This file implements a Linux-specific, system-wide memory reporter. It
michael@0 28 // gathers all the useful memory measurements obtainable from the OS in a
michael@0 29 // single place, giving a high-level view of memory consumption for the entire
michael@0 30 // machine/device.
michael@0 31 //
michael@0 32 // Other memory reporters measure part of a single process's memory consumption.
michael@0 33 // This reporter is different in that it measures memory consumption of many
michael@0 34 // processes, and they end up in a single reports tree. This is a slight abuse
michael@0 35 // of the memory reporting infrastructure, and therefore the results are given
michael@0 36 // their own "process" called "System", which means they show up in about:memory
michael@0 37 // in their own section, distinct from the per-process sections.
michael@0 38
michael@0 39 namespace mozilla {
michael@0 40 namespace SystemMemoryReporter {
michael@0 41
michael@0 42 #if !defined(XP_LINUX)
michael@0 43 #error "This won't work if we're not on Linux."
michael@0 44 #endif
michael@0 45
michael@0 46 static bool
michael@0 47 EndsWithLiteral(const nsCString& aHaystack, const char* aNeedle)
michael@0 48 {
michael@0 49 int32_t idx = aHaystack.RFind(aNeedle);
michael@0 50 return idx != -1 && idx + strlen(aNeedle) == aHaystack.Length();
michael@0 51 }
michael@0 52
michael@0 53 static void
michael@0 54 GetDirname(const nsCString& aPath, nsACString& aOut)
michael@0 55 {
michael@0 56 int32_t idx = aPath.RFind("/");
michael@0 57 if (idx == -1) {
michael@0 58 aOut.Truncate();
michael@0 59 } else {
michael@0 60 aOut.Assign(Substring(aPath, 0, idx));
michael@0 61 }
michael@0 62 }
michael@0 63
michael@0 64 static void
michael@0 65 GetBasename(const nsCString& aPath, nsACString& aOut)
michael@0 66 {
michael@0 67 nsCString out;
michael@0 68 int32_t idx = aPath.RFind("/");
michael@0 69 if (idx == -1) {
michael@0 70 out.Assign(aPath);
michael@0 71 } else {
michael@0 72 out.Assign(Substring(aPath, idx + 1));
michael@0 73 }
michael@0 74
michael@0 75 // On Android, some entries in /dev/ashmem end with "(deleted)" (e.g.
michael@0 76 // "/dev/ashmem/libxul.so(deleted)"). We don't care about this modifier, so
michael@0 77 // cut it off when getting the entry's basename.
michael@0 78 if (EndsWithLiteral(out, "(deleted)")) {
michael@0 79 out.Assign(Substring(out, 0, out.RFind("(deleted)")));
michael@0 80 }
michael@0 81 out.StripChars(" ");
michael@0 82
michael@0 83 aOut.Assign(out);
michael@0 84 }
michael@0 85
michael@0 86 static bool
michael@0 87 IsNumeric(const char* s)
michael@0 88 {
michael@0 89 MOZ_ASSERT(*s); // shouldn't see empty strings
michael@0 90 while (*s) {
michael@0 91 if (!isdigit(*s)) {
michael@0 92 return false;
michael@0 93 }
michael@0 94 s++;
michael@0 95 }
michael@0 96 return true;
michael@0 97 }
michael@0 98
michael@0 99 static bool
michael@0 100 IsAnonymous(const nsACString& aName)
michael@0 101 {
michael@0 102 // Recent kernels (e.g. 3.5) have multiple [stack:nnnn] entries, where |nnnn|
michael@0 103 // is a thread ID. However, [stack:nnnn] entries count both stack memory
michael@0 104 // *and* anonymous memory because the kernel only knows about the start of
michael@0 105 // each thread stack, not its end. So we treat such entries as anonymous
michael@0 106 // memory instead of stack. This is consistent with older kernels that don't
michael@0 107 // even show [stack:nnnn] entries.
michael@0 108 return aName.IsEmpty() ||
michael@0 109 StringBeginsWith(aName, NS_LITERAL_CSTRING("[stack:"));
michael@0 110 }
michael@0 111
michael@0 112 class SystemReporter MOZ_FINAL : public nsIMemoryReporter
michael@0 113 {
michael@0 114 public:
michael@0 115 NS_DECL_THREADSAFE_ISUPPORTS
michael@0 116
michael@0 117 #define REPORT_WITH_CLEANUP(_path, _units, _amount, _desc, _cleanup) \
michael@0 118 do { \
michael@0 119 size_t amount = _amount; /* evaluate _amount only once */ \
michael@0 120 if (amount > 0) { \
michael@0 121 nsresult rv; \
michael@0 122 rv = aHandleReport->Callback(NS_LITERAL_CSTRING("System"), _path, \
michael@0 123 KIND_NONHEAP, _units, amount, _desc, \
michael@0 124 aData); \
michael@0 125 if (NS_WARN_IF(NS_FAILED(rv))) { \
michael@0 126 _cleanup; \
michael@0 127 return rv; \
michael@0 128 } \
michael@0 129 } \
michael@0 130 } while (0)
michael@0 131
michael@0 132 #define REPORT(_path, _amount, _desc) \
michael@0 133 REPORT_WITH_CLEANUP(_path, UNITS_BYTES, _amount, _desc, (void)0)
michael@0 134
michael@0 135 NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
michael@0 136 nsISupports* aData)
michael@0 137 {
michael@0 138 if (!Preferences::GetBool("memory.system_memory_reporter")) {
michael@0 139 return NS_OK;
michael@0 140 }
michael@0 141
michael@0 142 // Read relevant fields from /proc/meminfo.
michael@0 143 int64_t memTotal = 0, memFree = 0;
michael@0 144 nsresult rv = ReadMemInfo(&memTotal, &memFree);
michael@0 145
michael@0 146 // Collect per-process reports from /proc/<pid>/smaps.
michael@0 147 int64_t totalPss = 0;
michael@0 148 rv = CollectProcessReports(aHandleReport, aData, &totalPss);
michael@0 149 NS_ENSURE_SUCCESS(rv, rv);
michael@0 150
michael@0 151 // Report the non-process numbers.
michael@0 152 int64_t other = memTotal - memFree - totalPss;
michael@0 153 REPORT(NS_LITERAL_CSTRING("mem/other"), other, NS_LITERAL_CSTRING(
michael@0 154 "Memory which is neither owned by any user-space process nor free. Note that "
michael@0 155 "this includes memory holding cached files from the disk which can be "
michael@0 156 "reclaimed by the OS at any time."));
michael@0 157
michael@0 158 REPORT(NS_LITERAL_CSTRING("mem/free"), memFree, NS_LITERAL_CSTRING(
michael@0 159 "Memory which is free and not being used for any purpose."));
michael@0 160
michael@0 161 // Report reserved memory not included in memTotal.
michael@0 162 rv = CollectPmemReports(aHandleReport, aData);
michael@0 163 NS_ENSURE_SUCCESS(rv, rv);
michael@0 164
michael@0 165 // Report zram usage statistics.
michael@0 166 rv = CollectZramReports(aHandleReport, aData);
michael@0 167 NS_ENSURE_SUCCESS(rv, rv);
michael@0 168
michael@0 169 return rv;
michael@0 170 }
michael@0 171
michael@0 172 private:
michael@0 173 // Keep this in sync with SystemReporter::kindPathSuffixes!
michael@0 174 enum ProcessSizeKind {
michael@0 175 AnonymousOutsideBrk = 0,
michael@0 176 AnonymousBrkHeap = 1,
michael@0 177 SharedLibrariesRX = 2,
michael@0 178 SharedLibrariesRW = 3,
michael@0 179 SharedLibrariesR = 4,
michael@0 180 SharedLibrariesOther = 5,
michael@0 181 OtherFiles = 6,
michael@0 182 MainThreadStack = 7,
michael@0 183 Vdso = 8,
michael@0 184
michael@0 185 ProcessSizeKindLimit = 9 // must be last
michael@0 186 };
michael@0 187
michael@0 188 static const char* kindPathSuffixes[ProcessSizeKindLimit];
michael@0 189
michael@0 190 // These are the cross-cutting measurements across all processes.
michael@0 191 struct ProcessSizes
michael@0 192 {
michael@0 193 ProcessSizes() { memset(this, 0, sizeof(*this)); }
michael@0 194
michael@0 195 size_t mSizes[ProcessSizeKindLimit];
michael@0 196 };
michael@0 197
michael@0 198 nsresult ReadMemInfo(int64_t* aMemTotal, int64_t* aMemFree)
michael@0 199 {
michael@0 200 FILE* f = fopen("/proc/meminfo", "r");
michael@0 201 if (!f) {
michael@0 202 return NS_ERROR_FAILURE;
michael@0 203 }
michael@0 204
michael@0 205 int n1 = fscanf(f, "MemTotal: %" SCNd64 " kB\n", aMemTotal);
michael@0 206 int n2 = fscanf(f, "MemFree: %" SCNd64 " kB\n", aMemFree);
michael@0 207
michael@0 208 fclose(f);
michael@0 209
michael@0 210 if (n1 != 1 || n2 != 1) {
michael@0 211 return NS_ERROR_FAILURE;
michael@0 212 }
michael@0 213
michael@0 214 // Convert from KB to B.
michael@0 215 *aMemTotal *= 1024;
michael@0 216 *aMemFree *= 1024;
michael@0 217
michael@0 218 return NS_OK;
michael@0 219 }
michael@0 220
michael@0 221 nsresult CollectProcessReports(nsIHandleReportCallback* aHandleReport,
michael@0 222 nsISupports* aData,
michael@0 223 int64_t* aTotalPss)
michael@0 224 {
michael@0 225 *aTotalPss = 0;
michael@0 226 ProcessSizes processSizes;
michael@0 227
michael@0 228 DIR* d = opendir("/proc");
michael@0 229 if (NS_WARN_IF(!d)) {
michael@0 230 return NS_ERROR_FAILURE;
michael@0 231 }
michael@0 232 struct dirent* ent;
michael@0 233 while ((ent = readdir(d))) {
michael@0 234 struct stat statbuf;
michael@0 235 const char* pidStr = ent->d_name;
michael@0 236 // Don't check the return value of stat() -- it can return -1 for these
michael@0 237 // directories even when it has succeeded, apparently.
michael@0 238 stat(pidStr, &statbuf);
michael@0 239 if (S_ISDIR(statbuf.st_mode) && IsNumeric(pidStr)) {
michael@0 240 nsCString processName("process(");
michael@0 241
michael@0 242 // Get the command name from cmdline. If that fails, the pid is still
michael@0 243 // shown.
michael@0 244 nsPrintfCString cmdlinePath("/proc/%s/cmdline", pidStr);
michael@0 245 FILE* f = fopen(cmdlinePath.get(), "r");
michael@0 246 if (f) {
michael@0 247 static const size_t len = 256;
michael@0 248 char buf[len];
michael@0 249 if (fgets(buf, len, f)) {
michael@0 250 processName.Append(buf);
michael@0 251 // A hack: replace forward slashes with '\\' so they aren't treated
michael@0 252 // as path separators. Consumers of this reporter (such as
michael@0 253 // about:memory) have to undo this change.
michael@0 254 processName.ReplaceChar('/', '\\');
michael@0 255 processName.Append(", ");
michael@0 256 }
michael@0 257 fclose(f);
michael@0 258 }
michael@0 259 processName.Append("pid=");
michael@0 260 processName.Append(pidStr);
michael@0 261 processName.Append(")");
michael@0 262
michael@0 263 // Read the PSS values from the smaps file.
michael@0 264 nsPrintfCString smapsPath("/proc/%s/smaps", pidStr);
michael@0 265 f = fopen(smapsPath.get(), "r");
michael@0 266 if (!f) {
michael@0 267 // Processes can terminate between the readdir() call above and now,
michael@0 268 // so just skip if we can't open the file.
michael@0 269 continue;
michael@0 270 }
michael@0 271 while (true) {
michael@0 272 nsresult rv = ParseMapping(f, processName, aHandleReport, aData,
michael@0 273 &processSizes, aTotalPss);
michael@0 274 if (NS_FAILED(rv))
michael@0 275 break;
michael@0 276 }
michael@0 277 fclose(f);
michael@0 278 }
michael@0 279 }
michael@0 280 closedir(d);
michael@0 281
michael@0 282 // Report the "processes/" tree.
michael@0 283
michael@0 284 for (size_t i = 0; i < ProcessSizeKindLimit; i++) {
michael@0 285 nsAutoCString path("processes/");
michael@0 286 path.Append(kindPathSuffixes[i]);
michael@0 287
michael@0 288 nsAutoCString desc("This is the sum of all processes' '");
michael@0 289 desc.Append(kindPathSuffixes[i]);
michael@0 290 desc.Append("' numbers.");
michael@0 291
michael@0 292 REPORT(path, processSizes.mSizes[i], desc);
michael@0 293 }
michael@0 294
michael@0 295 return NS_OK;
michael@0 296 }
michael@0 297
michael@0 298 nsresult ParseMapping(FILE* aFile,
michael@0 299 const nsACString& aProcessName,
michael@0 300 nsIHandleReportCallback* aHandleReport,
michael@0 301 nsISupports* aData,
michael@0 302 ProcessSizes* aProcessSizes,
michael@0 303 int64_t* aTotalPss)
michael@0 304 {
michael@0 305 // The first line of an entry in /proc/<pid>/smaps looks just like an entry
michael@0 306 // in /proc/<pid>/maps:
michael@0 307 //
michael@0 308 // address perms offset dev inode pathname
michael@0 309 // 02366000-025d8000 rw-p 00000000 00:00 0 [heap]
michael@0 310
michael@0 311 const int argCount = 8;
michael@0 312
michael@0 313 unsigned long long addrStart, addrEnd;
michael@0 314 char perms[5];
michael@0 315 unsigned long long offset;
michael@0 316 // The 2.6 and 3.0 kernels allocate 12 bits for the major device number and
michael@0 317 // 20 bits for the minor device number. Future kernels might allocate more.
michael@0 318 // 64 bits ought to be enough for anybody.
michael@0 319 char devMajor[17];
michael@0 320 char devMinor[17];
michael@0 321 unsigned int inode;
michael@0 322 char path[1025];
michael@0 323
michael@0 324 // A path might not be present on this line; set it to the empty string.
michael@0 325 path[0] = '\0';
michael@0 326
michael@0 327 // This is a bit tricky. Whitespace in a scanf pattern matches *any*
michael@0 328 // whitespace, including newlines. We want this pattern to match a line
michael@0 329 // with or without a path, but we don't want to look to a new line for the
michael@0 330 // path. Thus we have %u%1024[^\n] at the end of the pattern. This will
michael@0 331 // capture into the path some leading whitespace, which we'll later trim
michael@0 332 // off.
michael@0 333 int n = fscanf(aFile,
michael@0 334 "%llx-%llx %4s %llx "
michael@0 335 "%16[0-9a-fA-F]:%16[0-9a-fA-F] %u%1024[^\n]",
michael@0 336 &addrStart, &addrEnd, perms, &offset, devMajor,
michael@0 337 devMinor, &inode, path);
michael@0 338
michael@0 339 // Eat up any whitespace at the end of this line, including the newline.
michael@0 340 unused << fscanf(aFile, " ");
michael@0 341
michael@0 342 // We might or might not have a path, but the rest of the arguments should
michael@0 343 // be there.
michael@0 344 if (n != argCount && n != argCount - 1) {
michael@0 345 return NS_ERROR_FAILURE;
michael@0 346 }
michael@0 347
michael@0 348 nsAutoCString name, description;
michael@0 349 ProcessSizeKind kind;
michael@0 350 GetReporterNameAndDescription(path, perms, name, description, &kind);
michael@0 351
michael@0 352 while (true) {
michael@0 353 size_t pss = 0;
michael@0 354 nsresult rv = ParseMapBody(aFile, aProcessName, name, description,
michael@0 355 aHandleReport, aData, &pss);
michael@0 356 if (NS_FAILED(rv))
michael@0 357 break;
michael@0 358
michael@0 359 // Increment the appropriate aProcessSizes values, and the total.
michael@0 360 aProcessSizes->mSizes[kind] += pss;
michael@0 361 *aTotalPss += pss;
michael@0 362 }
michael@0 363
michael@0 364 return NS_OK;
michael@0 365 }
michael@0 366
michael@0 367 void GetReporterNameAndDescription(const char* aPath,
michael@0 368 const char* aPerms,
michael@0 369 nsACString& aName,
michael@0 370 nsACString& aDesc,
michael@0 371 ProcessSizeKind* aProcessSizeKind)
michael@0 372 {
michael@0 373 aName.Truncate();
michael@0 374 aDesc.Truncate();
michael@0 375
michael@0 376 // If aPath points to a file, we have its absolute path, plus some
michael@0 377 // whitespace. Truncate this to its basename, and put the absolute path in
michael@0 378 // the description.
michael@0 379 nsAutoCString absPath;
michael@0 380 absPath.Append(aPath);
michael@0 381 absPath.StripChars(" ");
michael@0 382
michael@0 383 nsAutoCString basename;
michael@0 384 GetBasename(absPath, basename);
michael@0 385
michael@0 386 if (basename.EqualsLiteral("[heap]")) {
michael@0 387 aName.Append("anonymous/brk-heap");
michael@0 388 aDesc.Append("Memory in anonymous mappings within the boundaries "
michael@0 389 "defined by brk() / sbrk(). This is likely to be just "
michael@0 390 "a portion of the application's heap; the remainder "
michael@0 391 "lives in other anonymous mappings. This corresponds to "
michael@0 392 "'[heap]' in /proc/<pid>/smaps.");
michael@0 393 *aProcessSizeKind = AnonymousBrkHeap;
michael@0 394
michael@0 395 } else if (basename.EqualsLiteral("[stack]")) {
michael@0 396 aName.Append("main-thread-stack");
michael@0 397 aDesc.Append("The stack size of the process's main thread. This "
michael@0 398 "corresponds to '[stack]' in /proc/<pid>/smaps.");
michael@0 399 *aProcessSizeKind = MainThreadStack;
michael@0 400
michael@0 401 } else if (basename.EqualsLiteral("[vdso]")) {
michael@0 402 aName.Append("vdso");
michael@0 403 aDesc.Append("The virtual dynamically-linked shared object, also known "
michael@0 404 "as the 'vsyscall page'. This is a memory region mapped by "
michael@0 405 "the operating system for the purpose of allowing processes "
michael@0 406 "to perform some privileged actions without the overhead of "
michael@0 407 "a syscall.");
michael@0 408 *aProcessSizeKind = Vdso;
michael@0 409
michael@0 410 } else if (!IsAnonymous(basename)) {
michael@0 411 nsAutoCString dirname;
michael@0 412 GetDirname(absPath, dirname);
michael@0 413
michael@0 414 // Hack: A file is a shared library if the basename contains ".so" and
michael@0 415 // its dirname contains "/lib", or if the basename ends with ".so".
michael@0 416 if (EndsWithLiteral(basename, ".so") ||
michael@0 417 (basename.Find(".so") != -1 && dirname.Find("/lib") != -1)) {
michael@0 418 aName.Append("shared-libraries/");
michael@0 419
michael@0 420 if (strncmp(aPerms, "r-x", 3) == 0) {
michael@0 421 *aProcessSizeKind = SharedLibrariesRX;
michael@0 422 } else if (strncmp(aPerms, "rw-", 3) == 0) {
michael@0 423 *aProcessSizeKind = SharedLibrariesRW;
michael@0 424 } else if (strncmp(aPerms, "r--", 3) == 0) {
michael@0 425 *aProcessSizeKind = SharedLibrariesR;
michael@0 426 } else {
michael@0 427 *aProcessSizeKind = SharedLibrariesOther;
michael@0 428 }
michael@0 429
michael@0 430 } else {
michael@0 431 aName.Append("other-files/");
michael@0 432 if (EndsWithLiteral(basename, ".xpi")) {
michael@0 433 aName.Append("extensions/");
michael@0 434 } else if (dirname.Find("/fontconfig") != -1) {
michael@0 435 aName.Append("fontconfig/");
michael@0 436 }
michael@0 437 *aProcessSizeKind = OtherFiles;
michael@0 438 }
michael@0 439
michael@0 440 aName.Append(basename);
michael@0 441 aDesc.Append(absPath);
michael@0 442
michael@0 443 } else {
michael@0 444 aName.Append("anonymous/outside-brk");
michael@0 445 aDesc.Append("Memory in anonymous mappings outside the boundaries "
michael@0 446 "defined by brk() / sbrk().");
michael@0 447 *aProcessSizeKind = AnonymousOutsideBrk;
michael@0 448 }
michael@0 449
michael@0 450 aName.Append("/[");
michael@0 451 aName.Append(aPerms);
michael@0 452 aName.Append("]");
michael@0 453
michael@0 454 // Append the permissions. This is useful for non-verbose mode in
michael@0 455 // about:memory when the filename is long and goes of the right side of the
michael@0 456 // window.
michael@0 457 aDesc.Append(" [");
michael@0 458 aDesc.Append(aPerms);
michael@0 459 aDesc.Append("]");
michael@0 460 }
michael@0 461
michael@0 462 nsresult ParseMapBody(
michael@0 463 FILE* aFile,
michael@0 464 const nsACString& aProcessName,
michael@0 465 const nsACString& aName,
michael@0 466 const nsACString& aDescription,
michael@0 467 nsIHandleReportCallback* aHandleReport,
michael@0 468 nsISupports* aData,
michael@0 469 size_t* aPss)
michael@0 470 {
michael@0 471 // Most of the lines in the body look like this:
michael@0 472 //
michael@0 473 // Size: 132 kB
michael@0 474 // Rss: 20 kB
michael@0 475 // Pss: 20 kB
michael@0 476 //
michael@0 477 // We're only interested in Pss. In newer kernels, the last line in the
michael@0 478 // body has a different form:
michael@0 479 //
michael@0 480 // VmFlags: rd wr mr mw me dw ac
michael@0 481 //
michael@0 482 // The strings after "VmFlags: " vary.
michael@0 483
michael@0 484 char desc[1025];
michael@0 485 int64_t sizeKB;
michael@0 486 int n = fscanf(aFile, "%1024[a-zA-Z_]: %" SCNd64 " kB\n", desc, &sizeKB);
michael@0 487 if (n == EOF || n == 0) {
michael@0 488 return NS_ERROR_FAILURE;
michael@0 489 } else if (n == 1 && strcmp(desc, "VmFlags") == 0) {
michael@0 490 // This is the "VmFlags:" line. Chew up the rest of it.
michael@0 491 fscanf(aFile, "%*1024[a-z ]\n");
michael@0 492 return NS_ERROR_FAILURE;
michael@0 493 }
michael@0 494
michael@0 495 // Only report "Pss" values.
michael@0 496 if (strcmp(desc, "Pss") == 0) {
michael@0 497 *aPss = sizeKB * 1024;
michael@0 498
michael@0 499 // Don't report zero values.
michael@0 500 if (*aPss == 0) {
michael@0 501 return NS_OK;
michael@0 502 }
michael@0 503
michael@0 504 nsAutoCString path("mem/processes/");
michael@0 505 path.Append(aProcessName);
michael@0 506 path.Append("/");
michael@0 507 path.Append(aName);
michael@0 508
michael@0 509 REPORT(path, *aPss, aDescription);
michael@0 510 } else {
michael@0 511 *aPss = 0;
michael@0 512 }
michael@0 513
michael@0 514 return NS_OK;
michael@0 515 }
michael@0 516
michael@0 517 nsresult CollectPmemReports(nsIHandleReportCallback* aHandleReport,
michael@0 518 nsISupports* aData)
michael@0 519 {
michael@0 520 // The pmem subsystem allocates physically contiguous memory for
michael@0 521 // interfacing with hardware. In order to ensure availability,
michael@0 522 // this memory is reserved during boot, and allocations are made
michael@0 523 // within these regions at runtime.
michael@0 524 //
michael@0 525 // There are typically several of these pools allocated at boot.
michael@0 526 // The /sys/kernel/pmem_regions directory contains a subdirectory
michael@0 527 // for each one. Within each subdirectory, the files we care
michael@0 528 // about are "size" (the total amount of physical memory) and
michael@0 529 // "mapped_regions" (a list of the current allocations within that
michael@0 530 // area).
michael@0 531 DIR* d = opendir("/sys/kernel/pmem_regions");
michael@0 532 if (!d) {
michael@0 533 if (NS_WARN_IF(errno != ENOENT)) {
michael@0 534 return NS_ERROR_FAILURE;
michael@0 535 }
michael@0 536 // If ENOENT, system doesn't use pmem.
michael@0 537 return NS_OK;
michael@0 538 }
michael@0 539
michael@0 540 struct dirent* ent;
michael@0 541 while ((ent = readdir(d))) {
michael@0 542 const char* name = ent->d_name;
michael@0 543 uint64_t size;
michael@0 544 int scanned;
michael@0 545
michael@0 546 // Skip "." and ".." (and any other dotfiles).
michael@0 547 if (name[0] == '.') {
michael@0 548 continue;
michael@0 549 }
michael@0 550
michael@0 551 // Read the total size. The file gives the size in decimal and
michael@0 552 // hex, in the form "13631488(0xd00000)"; we parse the former.
michael@0 553 nsPrintfCString sizePath("/sys/kernel/pmem_regions/%s/size", name);
michael@0 554 FILE* sizeFile = fopen(sizePath.get(), "r");
michael@0 555 if (NS_WARN_IF(!sizeFile)) {
michael@0 556 continue;
michael@0 557 }
michael@0 558 scanned = fscanf(sizeFile, "%" SCNu64, &size);
michael@0 559 if (NS_WARN_IF(scanned != 1)) {
michael@0 560 continue;
michael@0 561 }
michael@0 562 fclose(sizeFile);
michael@0 563
michael@0 564 // Read mapped regions; format described below.
michael@0 565 uint64_t freeSize = size;
michael@0 566 nsPrintfCString regionsPath("/sys/kernel/pmem_regions/%s/mapped_regions",
michael@0 567 name);
michael@0 568 FILE* regionsFile = fopen(regionsPath.get(), "r");
michael@0 569 if (regionsFile) {
michael@0 570 static const size_t bufLen = 4096;
michael@0 571 char buf[bufLen];
michael@0 572 while (fgets(buf, bufLen, regionsFile)) {
michael@0 573 int pid;
michael@0 574
michael@0 575 // Skip header line.
michael@0 576 if (strncmp(buf, "pid #", 5) == 0) {
michael@0 577 continue;
michael@0 578 }
michael@0 579 // Line format: "pid N:" + zero or more "(Start,Len) ".
michael@0 580 // N is decimal; Start and Len are in hex.
michael@0 581 scanned = sscanf(buf, "pid %d", &pid);
michael@0 582 if (NS_WARN_IF(scanned != 1)) {
michael@0 583 continue;
michael@0 584 }
michael@0 585 for (const char* nextParen = strchr(buf, '(');
michael@0 586 nextParen != nullptr;
michael@0 587 nextParen = strchr(nextParen + 1, '(')) {
michael@0 588 uint64_t mapStart, mapLen;
michael@0 589
michael@0 590 scanned = sscanf(nextParen + 1, "%" SCNx64 ",%" SCNx64,
michael@0 591 &mapStart, &mapLen);
michael@0 592 if (NS_WARN_IF(scanned != 2)) {
michael@0 593 break;
michael@0 594 }
michael@0 595
michael@0 596 nsPrintfCString path("mem/pmem/used/%s/segment(pid=%d, "
michael@0 597 "offset=0x%" PRIx64 ")", name, pid, mapStart);
michael@0 598 nsPrintfCString desc("Physical memory reserved for the \"%s\" pool "
michael@0 599 "and allocated to a buffer.", name);
michael@0 600 REPORT_WITH_CLEANUP(path, UNITS_BYTES, mapLen, desc,
michael@0 601 (fclose(regionsFile), closedir(d)));
michael@0 602 freeSize -= mapLen;
michael@0 603 }
michael@0 604 }
michael@0 605 fclose(regionsFile);
michael@0 606 }
michael@0 607
michael@0 608 nsPrintfCString path("mem/pmem/free/%s", name);
michael@0 609 nsPrintfCString desc("Physical memory reserved for the \"%s\" pool and "
michael@0 610 "unavailable to the rest of the system, but not "
michael@0 611 "currently allocated.", name);
michael@0 612 REPORT_WITH_CLEANUP(path, UNITS_BYTES, freeSize, desc, closedir(d));
michael@0 613 }
michael@0 614 closedir(d);
michael@0 615 return NS_OK;
michael@0 616 }
michael@0 617
michael@0 618 uint64_t
michael@0 619 ReadSizeFromFile(const char* aFilename)
michael@0 620 {
michael@0 621 FILE* sizeFile = fopen(aFilename, "r");
michael@0 622 if (NS_WARN_IF(!sizeFile)) {
michael@0 623 return 0;
michael@0 624 }
michael@0 625
michael@0 626 uint64_t size = 0;
michael@0 627 fscanf(sizeFile, "%" SCNu64, &size);
michael@0 628 fclose(sizeFile);
michael@0 629
michael@0 630 return size;
michael@0 631 }
michael@0 632
michael@0 633 nsresult
michael@0 634 CollectZramReports(nsIHandleReportCallback* aHandleReport,
michael@0 635 nsISupports* aData)
michael@0 636 {
michael@0 637 // zram usage stats files can be found under:
michael@0 638 // /sys/block/zram<id>
michael@0 639 // |--> disksize - Maximum amount of uncompressed data that can be
michael@0 640 // stored on the disk (bytes)
michael@0 641 // |--> orig_data_size - Uncompressed size of data in the disk (bytes)
michael@0 642 // |--> compr_data_size - Compressed size of the data in the disk (bytes)
michael@0 643 // |--> num_reads - Number of attempted reads to the disk (count)
michael@0 644 // |--> num_writes - Number of attempted writes to the disk (count)
michael@0 645 //
michael@0 646 // Each file contains a single integer value in decimal form.
michael@0 647
michael@0 648 DIR* d = opendir("/sys/block");
michael@0 649 if (!d) {
michael@0 650 if (NS_WARN_IF(errno != ENOENT)) {
michael@0 651 return NS_ERROR_FAILURE;
michael@0 652 }
michael@0 653
michael@0 654 return NS_OK;
michael@0 655 }
michael@0 656
michael@0 657 struct dirent* ent;
michael@0 658 while ((ent = readdir(d))) {
michael@0 659 const char* name = ent->d_name;
michael@0 660
michael@0 661 // Skip non-zram entries.
michael@0 662 if (strncmp("zram", name, 4) != 0) {
michael@0 663 continue;
michael@0 664 }
michael@0 665
michael@0 666 // Report disk size statistics.
michael@0 667 nsPrintfCString diskSizeFile("/sys/block/%s/disksize", name);
michael@0 668 nsPrintfCString origSizeFile("/sys/block/%s/orig_data_size", name);
michael@0 669
michael@0 670 uint64_t diskSize = ReadSizeFromFile(diskSizeFile.get());
michael@0 671 uint64_t origSize = ReadSizeFromFile(origSizeFile.get());
michael@0 672 uint64_t unusedSize = diskSize - origSize;
michael@0 673
michael@0 674 nsPrintfCString diskUsedPath("zram-disksize/%s/used", name);
michael@0 675 nsPrintfCString diskUsedDesc(
michael@0 676 "The uncompressed size of data stored in \"%s.\" "
michael@0 677 "This excludes zero-filled pages since "
michael@0 678 "no memory is allocated for them.", name);
michael@0 679 REPORT_WITH_CLEANUP(diskUsedPath, UNITS_BYTES, origSize,
michael@0 680 diskUsedDesc, closedir(d));
michael@0 681
michael@0 682 nsPrintfCString diskUnusedPath("zram-disksize/%s/unused", name);
michael@0 683 nsPrintfCString diskUnusedDesc(
michael@0 684 "The amount of uncompressed data that can still be "
michael@0 685 "be stored in \"%s\"", name);
michael@0 686 REPORT_WITH_CLEANUP(diskUnusedPath, UNITS_BYTES, unusedSize,
michael@0 687 diskUnusedDesc, closedir(d));
michael@0 688
michael@0 689 // Report disk accesses.
michael@0 690 nsPrintfCString readsFile("/sys/block/%s/num_reads", name);
michael@0 691 nsPrintfCString writesFile("/sys/block/%s/num_writes", name);
michael@0 692
michael@0 693 uint64_t reads = ReadSizeFromFile(readsFile.get());
michael@0 694 uint64_t writes = ReadSizeFromFile(writesFile.get());
michael@0 695
michael@0 696 nsPrintfCString readsDesc(
michael@0 697 "The number of reads (failed or successful) done on "
michael@0 698 "\"%s\"", name);
michael@0 699 nsPrintfCString readsPath("zram-accesses/%s/reads", name);
michael@0 700 REPORT_WITH_CLEANUP(readsPath, UNITS_COUNT_CUMULATIVE, reads,
michael@0 701 readsDesc, closedir(d));
michael@0 702
michael@0 703 nsPrintfCString writesDesc(
michael@0 704 "The number of writes (failed or successful) done "
michael@0 705 "on \"%s\"", name);
michael@0 706 nsPrintfCString writesPath("zram-accesses/%s/writes", name);
michael@0 707 REPORT_WITH_CLEANUP(writesPath, UNITS_COUNT_CUMULATIVE, writes,
michael@0 708 writesDesc, closedir(d));
michael@0 709
michael@0 710 // Report compressed data size.
michael@0 711 nsPrintfCString comprSizeFile("/sys/block/%s/compr_data_size", name);
michael@0 712 uint64_t comprSize = ReadSizeFromFile(comprSizeFile.get());
michael@0 713
michael@0 714 nsPrintfCString comprSizeDesc(
michael@0 715 "The compressed size of data stored in \"%s\"",
michael@0 716 name);
michael@0 717 nsPrintfCString comprSizePath("zram-compr-data-size/%s", name);
michael@0 718 REPORT_WITH_CLEANUP(comprSizePath, UNITS_BYTES, comprSize,
michael@0 719 comprSizeDesc, closedir(d));
michael@0 720 }
michael@0 721
michael@0 722 closedir(d);
michael@0 723 return NS_OK;
michael@0 724 }
michael@0 725
michael@0 726 #undef REPORT
michael@0 727 };
michael@0 728
michael@0 729 NS_IMPL_ISUPPORTS(SystemReporter, nsIMemoryReporter)
michael@0 730
michael@0 731 // Keep this in sync with SystemReporter::ProcessSizeKind!
michael@0 732 const char* SystemReporter::kindPathSuffixes[] = {
michael@0 733 "anonymous/outside-brk",
michael@0 734 "anonymous/brk-heap",
michael@0 735 "shared-libraries/read-executable",
michael@0 736 "shared-libraries/read-write",
michael@0 737 "shared-libraries/read-only",
michael@0 738 "shared-libraries/other",
michael@0 739 "other-files",
michael@0 740 "main-thread-stack",
michael@0 741 "vdso"
michael@0 742 };
michael@0 743
michael@0 744 void Init()
michael@0 745 {
michael@0 746 RegisterStrongMemoryReporter(new SystemReporter());
michael@0 747 }
michael@0 748
michael@0 749 } // namespace SystemMemoryReporter
michael@0 750 } // namespace mozilla

mercurial