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