LCOV - code coverage report
Current view: top level - xpcom/base - SystemMemoryReporter.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 5 407 1.2 %
Date: 2017-07-14 16:53:18 Functions: 5 30 16.7 %
Legend: Lines: hit not hit

          Line data    Source code
       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/. */
       6             : 
       7             : #include "mozilla/SystemMemoryReporter.h"
       8             : 
       9             : #include "mozilla/Attributes.h"
      10             : #include "mozilla/LinuxUtils.h"
      11             : #include "mozilla/PodOperations.h"
      12             : #include "mozilla/Preferences.h"
      13             : #include "mozilla/TaggedAnonymousMemory.h"
      14             : #include "mozilla/Unused.h"
      15             : 
      16             : #include "nsDataHashtable.h"
      17             : #include "nsIMemoryReporter.h"
      18             : #include "nsPrintfCString.h"
      19             : #include "nsString.h"
      20             : #include "nsTHashtable.h"
      21             : #include "nsHashKeys.h"
      22             : 
      23             : #include <dirent.h>
      24             : #include <inttypes.h>
      25             : #include <stdio.h>
      26             : #include <sys/stat.h>
      27             : #include <sys/types.h>
      28             : #include <unistd.h>
      29             : #include <errno.h>
      30             : 
      31             : // This file implements a Linux-specific, system-wide memory reporter.  It
      32             : // gathers all the useful memory measurements obtainable from the OS in a
      33             : // single place, giving a high-level view of memory consumption for the entire
      34             : // machine/device.
      35             : //
      36             : // Other memory reporters measure part of a single process's memory consumption.
      37             : // This reporter is different in that it measures memory consumption of many
      38             : // processes, and they end up in a single reports tree.  This is a slight abuse
      39             : // of the memory reporting infrastructure, and therefore the results are given
      40             : // their own "process" called "System", which means they show up in about:memory
      41             : // in their own section, distinct from the per-process sections.
      42             : 
      43             : namespace mozilla {
      44             : namespace SystemMemoryReporter {
      45             : 
      46             : #if !defined(XP_LINUX)
      47             : #error "This won't work if we're not on Linux."
      48             : #endif
      49             : 
      50             : /**
      51             :  * RAII helper that will close an open DIR handle.
      52             :  */
      53             : struct MOZ_STACK_CLASS AutoDir
      54             : {
      55           0 :   explicit AutoDir(DIR* aDir) : mDir(aDir) {}
      56           0 :   ~AutoDir() { if (mDir) closedir(mDir); };
      57             :   DIR* mDir;
      58             : };
      59             : 
      60             : /**
      61             :  * RAII helper that will close an open FILE handle.
      62             :  */
      63             : struct MOZ_STACK_CLASS AutoFile
      64             : {
      65           0 :   explicit AutoFile(FILE* aFile) : mFile(aFile) {}
      66           0 :   ~AutoFile() { if (mFile) fclose(mFile); }
      67             :   FILE* mFile;
      68             : };
      69             : 
      70             : static bool
      71           0 : EndsWithLiteral(const nsCString& aHaystack, const char* aNeedle)
      72             : {
      73           0 :   int32_t idx = aHaystack.RFind(aNeedle);
      74           0 :   return idx != -1 && idx + strlen(aNeedle) == aHaystack.Length();
      75             : }
      76             : 
      77             : static void
      78           0 : GetDirname(const nsCString& aPath, nsACString& aOut)
      79             : {
      80           0 :   int32_t idx = aPath.RFind("/");
      81           0 :   if (idx == -1) {
      82           0 :     aOut.Truncate();
      83             :   } else {
      84           0 :     aOut.Assign(Substring(aPath, 0, idx));
      85             :   }
      86           0 : }
      87             : 
      88             : static void
      89           0 : GetBasename(const nsCString& aPath, nsACString& aOut)
      90             : {
      91           0 :   nsCString out;
      92           0 :   int32_t idx = aPath.RFind("/");
      93           0 :   if (idx == -1) {
      94           0 :     out.Assign(aPath);
      95             :   } else {
      96           0 :     out.Assign(Substring(aPath, idx + 1));
      97             :   }
      98             : 
      99             :   // On Android, some entries in /dev/ashmem end with "(deleted)" (e.g.
     100             :   // "/dev/ashmem/libxul.so(deleted)").  We don't care about this modifier, so
     101             :   // cut it off when getting the entry's basename.
     102           0 :   if (EndsWithLiteral(out, "(deleted)")) {
     103           0 :     out.Assign(Substring(out, 0, out.RFind("(deleted)")));
     104             :   }
     105           0 :   out.StripChars(" ");
     106             : 
     107           0 :   aOut.Assign(out);
     108           0 : }
     109             : 
     110             : static bool
     111           0 : IsNumeric(const char* aStr)
     112             : {
     113           0 :   MOZ_ASSERT(*aStr);  // shouldn't see empty strings
     114           0 :   while (*aStr) {
     115           0 :     if (!isdigit(*aStr)) {
     116           0 :       return false;
     117             :     }
     118           0 :     ++aStr;
     119             :   }
     120           0 :   return true;
     121             : }
     122             : 
     123             : static bool
     124           0 : IsAnonymous(const nsACString& aName)
     125             : {
     126             :   // Recent kernels have multiple [stack:nnnn] entries, where |nnnn| is a
     127             :   // thread ID.  However, the entire virtual memory area containing a thread's
     128             :   // stack pointer is considered the stack for that thread, even if it was
     129             :   // merged with an adjacent area containing non-stack data.  So we treat them
     130             :   // as regular anonymous memory.  However, see below about tagged anonymous
     131             :   // memory.
     132           0 :   return aName.IsEmpty() ||
     133           0 :          StringBeginsWith(aName, NS_LITERAL_CSTRING("[stack:"));
     134             : }
     135             : 
     136           1 : class SystemReporter final : public nsIMemoryReporter
     137             : {
     138           0 :   ~SystemReporter() {}
     139             : 
     140             : public:
     141             :   NS_DECL_THREADSAFE_ISUPPORTS
     142             : 
     143             : #define REPORT(_path, _units, _amount, _desc)                                 \
     144             :   do {                                                                        \
     145             :     size_t __amount = _amount;  /* evaluate _amount only once */              \
     146             :     if (__amount > 0) {                                                       \
     147             :       aHandleReport->Callback(NS_LITERAL_CSTRING("System"), _path,            \
     148             :                               KIND_OTHER, _units, __amount, _desc, aData);    \
     149             :     }                                                                         \
     150             :   } while (0)
     151             : 
     152           0 :   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
     153             :                             nsISupports* aData, bool aAnonymize) override
     154             :   {
     155             :     // There is lots of privacy-sensitive data in /proc. Just skip this
     156             :     // reporter entirely when anonymization is required.
     157           0 :     if (aAnonymize) {
     158           0 :       return NS_OK;
     159             :     }
     160             : 
     161           0 :     if (!Preferences::GetBool("memory.system_memory_reporter")) {
     162           0 :       return NS_OK;
     163             :     }
     164             : 
     165             :     // Read relevant fields from /proc/meminfo.
     166           0 :     int64_t memTotal = 0, memFree = 0;
     167           0 :     nsresult rv1 = ReadMemInfo(&memTotal, &memFree);
     168             : 
     169             :     // Collect per-process reports from /proc/<pid>/smaps.
     170           0 :     int64_t totalPss = 0;
     171           0 :     nsresult rv2 = CollectProcessReports(aHandleReport, aData, &totalPss);
     172             : 
     173             :     // Report the non-process numbers.
     174           0 :     if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2)) {
     175           0 :       int64_t other = memTotal - memFree - totalPss;
     176           0 :       REPORT(NS_LITERAL_CSTRING("mem/other"), UNITS_BYTES, other,
     177             :              NS_LITERAL_CSTRING(
     178             : "Memory which is neither owned by any user-space process nor free. Note that "
     179             : "this includes memory holding cached files from the disk which can be "
     180             : "reclaimed by the OS at any time."));
     181             : 
     182           0 :       REPORT(NS_LITERAL_CSTRING("mem/free"), UNITS_BYTES, memFree,
     183             :              NS_LITERAL_CSTRING(
     184             : "Memory which is free and not being used for any purpose."));
     185             :     }
     186             : 
     187             :     // Report reserved memory not included in memTotal.
     188           0 :     CollectPmemReports(aHandleReport, aData);
     189             : 
     190             :     // Report zram usage statistics.
     191           0 :     CollectZramReports(aHandleReport, aData);
     192             : 
     193             :     // Report kgsl graphics memory usage.
     194           0 :     CollectKgslReports(aHandleReport, aData);
     195             : 
     196             :     // Report ION memory usage.
     197           0 :     CollectIonReports(aHandleReport, aData);
     198             : 
     199           0 :     return NS_OK;
     200             :   }
     201             : 
     202             : private:
     203             :   // These are the cross-cutting measurements across all processes.
     204           0 :   class ProcessSizes
     205             :   {
     206             :   public:
     207           0 :     void Add(const nsACString& aKey, size_t aSize)
     208             :     {
     209           0 :       mTagged.Put(aKey, mTagged.Get(aKey) + aSize);
     210           0 :     }
     211             : 
     212           0 :     void Report(nsIHandleReportCallback* aHandleReport, nsISupports* aData)
     213             :     {
     214           0 :       for (auto iter = mTagged.Iter(); !iter.Done(); iter.Next()) {
     215           0 :         nsCStringHashKey::KeyType key = iter.Key();
     216           0 :         size_t amount = iter.UserData();
     217             : 
     218           0 :         nsAutoCString path("processes/");
     219           0 :         path.Append(key);
     220             : 
     221           0 :         nsAutoCString desc("This is the sum of all processes' '");
     222           0 :         desc.Append(key);
     223           0 :         desc.AppendLiteral("' numbers.");
     224             : 
     225           0 :         REPORT(path, UNITS_BYTES, amount, desc);
     226             :       }
     227           0 :     }
     228             : 
     229             :   private:
     230             :     nsDataHashtable<nsCStringHashKey, size_t> mTagged;
     231             :   };
     232             : 
     233           0 :   nsresult ReadMemInfo(int64_t* aMemTotal, int64_t* aMemFree)
     234             :   {
     235           0 :     FILE* f = fopen("/proc/meminfo", "r");
     236           0 :     if (!f) {
     237           0 :       return NS_ERROR_FAILURE;
     238             :     }
     239             : 
     240           0 :     int n1 = fscanf(f, "MemTotal: %" SCNd64 " kB\n", aMemTotal);
     241           0 :     int n2 = fscanf(f, "MemFree: %"  SCNd64 " kB\n", aMemFree);
     242             : 
     243           0 :     fclose(f);
     244             : 
     245           0 :     if (n1 != 1 || n2 != 1) {
     246           0 :       return NS_ERROR_FAILURE;
     247             :     }
     248             : 
     249             :     // Convert from KB to B.
     250           0 :     *aMemTotal *= 1024;
     251           0 :     *aMemFree  *= 1024;
     252             : 
     253           0 :     return NS_OK;
     254             :   }
     255             : 
     256           0 :   nsresult CollectProcessReports(nsIHandleReportCallback* aHandleReport,
     257             :                                  nsISupports* aData,
     258             :                                  int64_t* aTotalPss)
     259             :   {
     260           0 :     *aTotalPss = 0;
     261           0 :     ProcessSizes processSizes;
     262             : 
     263           0 :     DIR* d = opendir("/proc");
     264           0 :     if (NS_WARN_IF(!d)) {
     265           0 :       return NS_ERROR_FAILURE;
     266             :     }
     267             :     struct dirent* ent;
     268           0 :     while ((ent = readdir(d))) {
     269             :       struct stat statbuf;
     270           0 :       const char* pidStr = ent->d_name;
     271             :       // Don't check the return value of stat() -- it can return -1 for these
     272             :       // directories even when it has succeeded, apparently.
     273           0 :       stat(pidStr, &statbuf);
     274           0 :       if (S_ISDIR(statbuf.st_mode) && IsNumeric(pidStr)) {
     275           0 :         nsCString processName("process(");
     276             : 
     277             :         // Get the command name from cmdline.  If that fails, the pid is still
     278             :         // shown.
     279           0 :         nsPrintfCString cmdlinePath("/proc/%s/cmdline", pidStr);
     280           0 :         FILE* f = fopen(cmdlinePath.get(), "r");
     281           0 :         if (f) {
     282             :           static const size_t len = 256;
     283             :           char buf[len];
     284           0 :           if (fgets(buf, len, f)) {
     285           0 :             processName.Append(buf);
     286             :             // A hack: replace forward slashes with '\\' so they aren't treated
     287             :             // as path separators.  Consumers of this reporter (such as
     288             :             // about:memory) have to undo this change.
     289           0 :             processName.ReplaceChar('/', '\\');
     290           0 :             processName.AppendLiteral(", ");
     291             :           }
     292           0 :           fclose(f);
     293             :         }
     294           0 :         processName.AppendLiteral("pid=");
     295           0 :         processName.Append(pidStr);
     296           0 :         processName.Append(')');
     297             : 
     298             :         // Read the PSS values from the smaps file.
     299           0 :         nsPrintfCString smapsPath("/proc/%s/smaps", pidStr);
     300           0 :         f = fopen(smapsPath.get(), "r");
     301           0 :         if (!f) {
     302             :           // Processes can terminate between the readdir() call above and now,
     303             :           // so just skip if we can't open the file.
     304           0 :           continue;
     305             :         }
     306             :         ParseMappings(f, processName, aHandleReport, aData, &processSizes,
     307           0 :                       aTotalPss);
     308           0 :         fclose(f);
     309             : 
     310             :         // Report the open file descriptors for this process.
     311           0 :         nsPrintfCString procFdPath("/proc/%s/fd", pidStr);
     312           0 :         CollectOpenFileReports(aHandleReport, aData, procFdPath, processName);
     313             :       }
     314             :     }
     315           0 :     closedir(d);
     316             : 
     317             :     // Report the "processes/" tree.
     318           0 :     processSizes.Report(aHandleReport, aData);
     319             : 
     320           0 :     return NS_OK;
     321             :   }
     322             : 
     323           0 :   void ParseMappings(FILE* aFile,
     324             :                      const nsACString& aProcessName,
     325             :                      nsIHandleReportCallback* aHandleReport,
     326             :                      nsISupports* aData,
     327             :                      ProcessSizes* aProcessSizes,
     328             :                      int64_t* aTotalPss)
     329             :   {
     330             :     // The first line of an entry in /proc/<pid>/smaps looks just like an entry
     331             :     // in /proc/<pid>/maps:
     332             :     //
     333             :     //   address           perms offset  dev   inode  pathname
     334             :     //   02366000-025d8000 rw-p 00000000 00:00 0      [heap]
     335             :     //
     336             :     // Each of the following lines contains a key and a value, separated
     337             :     // by ": ", where the key does not contain either of those characters.
     338             :     // Assuming more than this about the structure of those lines has
     339             :     // failed to be future-proof in the past, so we avoid doing so.
     340             :     //
     341             :     // This makes it difficult to detect the start of a new entry
     342             :     // until it's been removed from the stdio buffer, so we just loop
     343             :     // over all lines in the file in this routine.
     344             : 
     345           0 :     const int argCount = 8;
     346             : 
     347             :     unsigned long long addrStart, addrEnd;
     348             :     char perms[5];
     349             :     unsigned long long offset;
     350             :     // The 2.6 and 3.0 kernels allocate 12 bits for the major device number and
     351             :     // 20 bits for the minor device number.  Future kernels might allocate more.
     352             :     // 64 bits ought to be enough for anybody.
     353             :     char devMajor[17];
     354             :     char devMinor[17];
     355             :     unsigned int inode;
     356             :     char line[1025];
     357             : 
     358             :     // This variable holds the path of the current entry, or is void
     359             :     // if we're scanning for the start of a new entry.
     360           0 :     nsAutoCString currentPath;
     361             :     int pathOffset;
     362             : 
     363           0 :     currentPath.SetIsVoid(true);
     364           0 :     while (fgets(line, sizeof(line), aFile)) {
     365           0 :       if (currentPath.IsVoid()) {
     366             :         int n = sscanf(line,
     367             :                        "%llx-%llx %4s %llx "
     368             :                        "%16[0-9a-fA-F]:%16[0-9a-fA-F] %u %n",
     369             :                        &addrStart, &addrEnd, perms, &offset, devMajor,
     370           0 :                        devMinor, &inode, &pathOffset);
     371             : 
     372           0 :         if (n >= argCount - 1) {
     373           0 :           currentPath.Assign(line + pathOffset);
     374           0 :           currentPath.StripChars("\n");
     375             :         }
     376           0 :         continue;
     377             :       }
     378             : 
     379             :       // Now that we have a name and other metadata, scan for the PSS.
     380             :       size_t pss_kb;
     381           0 :       int n = sscanf(line, "Pss: %zu", &pss_kb);
     382           0 :       if (n < 1) {
     383           0 :         continue;
     384             :       }
     385             : 
     386           0 :       size_t pss = pss_kb * 1024;
     387           0 :       if (pss > 0) {
     388           0 :         nsAutoCString name, description, tag;
     389           0 :         GetReporterNameAndDescription(currentPath.get(), perms, name, description, tag);
     390             : 
     391           0 :         nsAutoCString processMemPath("mem/processes/");
     392           0 :         processMemPath.Append(aProcessName);
     393           0 :         processMemPath.Append('/');
     394           0 :         processMemPath.Append(name);
     395             : 
     396           0 :         REPORT(processMemPath, UNITS_BYTES, pss, description);
     397             : 
     398             :         // Increment the appropriate aProcessSizes values, and the total.
     399           0 :         aProcessSizes->Add(tag, pss);
     400           0 :         *aTotalPss += pss;
     401             :       }
     402             : 
     403             :       // Now that we've seen the PSS, we're done with this entry.
     404           0 :       currentPath.SetIsVoid(true);
     405             :     }
     406           0 :   }
     407             : 
     408           0 :   void GetReporterNameAndDescription(const char* aPath,
     409             :                                      const char* aPerms,
     410             :                                      nsACString& aName,
     411             :                                      nsACString& aDesc,
     412             :                                      nsACString& aTag)
     413             :   {
     414           0 :     aName.Truncate();
     415           0 :     aDesc.Truncate();
     416           0 :     aTag.Truncate();
     417             : 
     418             :     // If aPath points to a file, we have its absolute path; it might
     419             :     // also be a bracketed pseudo-name (see below).  In either case
     420             :     // there is also some whitespace to trim.
     421           0 :     nsAutoCString absPath;
     422           0 :     absPath.Append(aPath);
     423           0 :     absPath.StripChars(" ");
     424             : 
     425           0 :     if (absPath.EqualsLiteral("[heap]")) {
     426           0 :       aName.AppendLiteral("anonymous/brk-heap");
     427             :       aDesc.AppendLiteral(
     428             :         "Memory in anonymous mappings within the boundaries defined by "
     429             :         "brk() / sbrk().  This is likely to be just a portion of the "
     430             :         "application's heap; the remainder lives in other anonymous mappings. "
     431           0 :         "This corresponds to '[heap]' in /proc/<pid>/smaps.");
     432           0 :       aTag = aName;
     433           0 :     } else if (absPath.EqualsLiteral("[stack]")) {
     434           0 :       aName.AppendLiteral("stack/main-thread");
     435             :       aDesc.AppendPrintf(
     436             :         "The stack size of the process's main thread.  This corresponds to "
     437           0 :         "'[stack]' in /proc/<pid>/smaps.");
     438           0 :       aTag = aName;
     439           0 :     } else if (MozTaggedMemoryIsSupported() &&
     440           0 :                StringBeginsWith(absPath, NS_LITERAL_CSTRING("[stack:"))) {
     441             :       // If tagged memory is supported, we can be reasonably sure that
     442             :       // the virtual memory area containing the stack hasn't been
     443             :       // merged with unrelated heap memory.  (This prevents the
     444             :       // "[stack:" entries from reaching the IsAnonymous case below.)
     445           0 :       pid_t tid = atoi(absPath.get() + 7);
     446           0 :       nsAutoCString threadName, escapedThreadName;
     447           0 :       LinuxUtils::GetThreadName(tid, threadName);
     448           0 :       if (threadName.IsEmpty()) {
     449           0 :         threadName.AssignLiteral("<unknown>");
     450             :       }
     451           0 :       escapedThreadName.Assign(threadName);
     452           0 :       escapedThreadName.StripChars("()");
     453           0 :       escapedThreadName.ReplaceChar('/', '\\');
     454             : 
     455           0 :       aName.AppendLiteral("stack/non-main-thread");
     456           0 :       aName.AppendLiteral("/name(");
     457           0 :       aName.Append(escapedThreadName);
     458           0 :       aName.Append(')');
     459           0 :       aTag = aName;
     460           0 :       aName.AppendPrintf("/thread(%d)", tid);
     461             : 
     462           0 :       aDesc.AppendPrintf("The stack size of a non-main thread named '%s' with "
     463             :                          "thread ID %d.  This corresponds to '[stack:%d]' "
     464           0 :                          "in /proc/%d/smaps.", threadName.get(), tid, tid, tid);
     465           0 :     } else if (absPath.EqualsLiteral("[vdso]")) {
     466           0 :       aName.AppendLiteral("vdso");
     467             :       aDesc.AppendLiteral(
     468             :         "The virtual dynamically-linked shared object, also known as the "
     469             :         "'vsyscall page'. This is a memory region mapped by the operating "
     470             :         "system for the purpose of allowing processes to perform some "
     471           0 :         "privileged actions without the overhead of a syscall.");
     472           0 :       aTag = aName;
     473           0 :     } else if (StringBeginsWith(absPath, NS_LITERAL_CSTRING("[anon:")) &&
     474           0 :                EndsWithLiteral(absPath, "]")) {
     475             :       // It's tagged memory; see also "mfbt/TaggedAnonymousMemory.h".
     476           0 :       nsAutoCString tag(Substring(absPath, 6, absPath.Length() - 7));
     477             : 
     478           0 :       aName.AppendLiteral("anonymous/");
     479           0 :       aName.Append(tag);
     480           0 :       aTag = aName;
     481           0 :       aDesc.AppendLiteral("Memory in anonymous mappings tagged with '");
     482           0 :       aDesc.Append(tag);
     483           0 :       aDesc.Append('\'');
     484           0 :     } else if (!IsAnonymous(absPath)) {
     485             :       // We now know it's an actual file.  Truncate this to its
     486             :       // basename, and put the absolute path in the description.
     487           0 :       nsAutoCString basename, dirname;
     488           0 :       GetBasename(absPath, basename);
     489           0 :       GetDirname(absPath, dirname);
     490             : 
     491             :       // Hack: A file is a shared library if the basename contains ".so" and
     492             :       // its dirname contains "/lib", or if the basename ends with ".so".
     493           0 :       if (EndsWithLiteral(basename, ".so") ||
     494           0 :           (basename.Find(".so") != -1 && dirname.Find("/lib") != -1)) {
     495           0 :         aName.AppendLiteral("shared-libraries/");
     496           0 :         aTag = aName;
     497             : 
     498           0 :         if (strncmp(aPerms, "r-x", 3) == 0) {
     499           0 :           aTag.AppendLiteral("read-executable");
     500           0 :         } else if (strncmp(aPerms, "rw-", 3) == 0) {
     501           0 :           aTag.AppendLiteral("read-write");
     502           0 :         } else if (strncmp(aPerms, "r--", 3) == 0) {
     503           0 :           aTag.AppendLiteral("read-only");
     504             :         } else {
     505           0 :           aTag.AppendLiteral("other");
     506             :         }
     507             : 
     508             :       } else {
     509           0 :         aName.AppendLiteral("other-files");
     510           0 :         if (EndsWithLiteral(basename, ".xpi")) {
     511           0 :           aName.AppendLiteral("/extensions");
     512           0 :         } else if (dirname.Find("/fontconfig") != -1) {
     513           0 :           aName.AppendLiteral("/fontconfig");
     514             :         } else {
     515           0 :           aName.AppendLiteral("/misc");
     516             :         }
     517           0 :         aTag = aName;
     518           0 :         aName.Append('/');
     519             :       }
     520             : 
     521           0 :       aName.Append(basename);
     522           0 :       aDesc.Append(absPath);
     523             :     } else {
     524           0 :       if (MozTaggedMemoryIsSupported()) {
     525           0 :         aName.AppendLiteral("anonymous/untagged");
     526           0 :         aDesc.AppendLiteral("Memory in untagged anonymous mappings.");
     527           0 :         aTag = aName;
     528             :       } else {
     529           0 :         aName.AppendLiteral("anonymous/outside-brk");
     530             :         aDesc.AppendLiteral("Memory in anonymous mappings outside the "
     531           0 :                             "boundaries defined by brk() / sbrk().");
     532           0 :         aTag = aName;
     533             :       }
     534             :     }
     535             : 
     536           0 :     aName.AppendLiteral("/[");
     537           0 :     aName.Append(aPerms);
     538           0 :     aName.Append(']');
     539             : 
     540             :     // Append the permissions.  This is useful for non-verbose mode in
     541             :     // about:memory when the filename is long and goes of the right side of the
     542             :     // window.
     543           0 :     aDesc.AppendLiteral(" [");
     544           0 :     aDesc.Append(aPerms);
     545           0 :     aDesc.Append(']');
     546           0 :   }
     547             : 
     548           0 :   void CollectPmemReports(nsIHandleReportCallback* aHandleReport,
     549             :                           nsISupports* aData)
     550             :   {
     551             :     // The pmem subsystem allocates physically contiguous memory for
     552             :     // interfacing with hardware.  In order to ensure availability,
     553             :     // this memory is reserved during boot, and allocations are made
     554             :     // within these regions at runtime.
     555             :     //
     556             :     // There are typically several of these pools allocated at boot.
     557             :     // The /sys/kernel/pmem_regions directory contains a subdirectory
     558             :     // for each one.  Within each subdirectory, the files we care
     559             :     // about are "size" (the total amount of physical memory) and
     560             :     // "mapped_regions" (a list of the current allocations within that
     561             :     // area).
     562           0 :     DIR* d = opendir("/sys/kernel/pmem_regions");
     563           0 :     if (!d) {
     564           0 :       return;
     565             :     }
     566             : 
     567             :     struct dirent* ent;
     568           0 :     while ((ent = readdir(d))) {
     569           0 :       const char* name = ent->d_name;
     570             :       uint64_t size;
     571             :       int scanned;
     572             : 
     573             :       // Skip "." and ".." (and any other dotfiles).
     574           0 :       if (name[0] == '.') {
     575           0 :         continue;
     576             :       }
     577             : 
     578             :       // Read the total size.  The file gives the size in decimal and
     579             :       // hex, in the form "13631488(0xd00000)"; we parse the former.
     580           0 :       nsPrintfCString sizePath("/sys/kernel/pmem_regions/%s/size", name);
     581           0 :       FILE* sizeFile = fopen(sizePath.get(), "r");
     582           0 :       if (NS_WARN_IF(!sizeFile)) {
     583           0 :         continue;
     584             :       }
     585           0 :       scanned = fscanf(sizeFile, "%" SCNu64, &size);
     586           0 :       fclose(sizeFile);
     587           0 :       if (NS_WARN_IF(scanned != 1)) {
     588           0 :         continue;
     589             :       }
     590             : 
     591             :       // Read mapped regions; format described below.
     592           0 :       uint64_t freeSize = size;
     593             :       nsPrintfCString regionsPath("/sys/kernel/pmem_regions/%s/mapped_regions",
     594           0 :                                   name);
     595           0 :       FILE* regionsFile = fopen(regionsPath.get(), "r");
     596           0 :       if (regionsFile) {
     597             :         static const size_t bufLen = 4096;
     598             :         char buf[bufLen];
     599           0 :         while (fgets(buf, bufLen, regionsFile)) {
     600             :           int pid;
     601             : 
     602             :           // Skip header line.
     603           0 :           if (strncmp(buf, "pid #", 5) == 0) {
     604           0 :             continue;
     605             :           }
     606             :           // Line format: "pid N:" + zero or more "(Start,Len) ".
     607             :           // N is decimal; Start and Len are in hex.
     608           0 :           scanned = sscanf(buf, "pid %d", &pid);
     609           0 :           if (NS_WARN_IF(scanned != 1)) {
     610           0 :             continue;
     611             :           }
     612           0 :           for (const char* nextParen = strchr(buf, '(');
     613           0 :                nextParen != nullptr;
     614           0 :                nextParen = strchr(nextParen + 1, '(')) {
     615             :             uint64_t mapStart, mapLen;
     616             : 
     617           0 :             scanned = sscanf(nextParen + 1, "%" SCNx64 ",%" SCNx64,
     618           0 :                              &mapStart, &mapLen);
     619           0 :             if (NS_WARN_IF(scanned != 2)) {
     620           0 :               break;
     621             :             }
     622             : 
     623             :             nsPrintfCString path("mem/pmem/used/%s/segment(pid=%d, "
     624           0 :                                  "offset=0x%" PRIx64 ")", name, pid, mapStart);
     625             :             nsPrintfCString desc("Physical memory reserved for the \"%s\" pool "
     626           0 :                                  "and allocated to a buffer.", name);
     627           0 :             REPORT(path, UNITS_BYTES, mapLen, desc);
     628           0 :             freeSize -= mapLen;
     629             :           }
     630             :         }
     631           0 :         fclose(regionsFile);
     632             :       }
     633             : 
     634           0 :       nsPrintfCString path("mem/pmem/free/%s", name);
     635             :       nsPrintfCString desc("Physical memory reserved for the \"%s\" pool and "
     636             :                            "unavailable to the rest of the system, but not "
     637           0 :                            "currently allocated.", name);
     638           0 :       REPORT(path, UNITS_BYTES, freeSize, desc);
     639             :     }
     640           0 :     closedir(d);
     641             :   }
     642             : 
     643             :   void
     644           0 :   CollectIonReports(nsIHandleReportCallback* aHandleReport,
     645             :                     nsISupports* aData)
     646             :   {
     647             :     // ION is a replacement for PMEM (and other similar allocators).
     648             :     //
     649             :     // More details from http://lwn.net/Articles/480055/
     650             :     //  "Like its PMEM-like predecessors, ION manages one or more memory pools,
     651             :     //   some of which are set aside at boot time to combat fragmentation or to
     652             :     //   serve special hardware needs. GPUs, display controllers, and cameras
     653             :     //   are some of the hardware blocks that may have special memory
     654             :     //   requirements."
     655             :     //
     656             :     // The file format starts as follows:
     657             :     //     client              pid             size
     658             :     //     ----------------------------------------------------
     659             :     //     adsprpc-smd                1             4096
     660             :     //     fd900000.qcom,mdss_mdp     1          1658880
     661             :     //     ----------------------------------------------------
     662             :     //     orphaned allocations (info is from last known client):
     663             :     //     Homescreen            24100           294912 0 1
     664             :     //     b2g                   23987          1658880 0 1
     665             :     //     mdss_fb0                401          1658880 0 1
     666             :     //     b2g                   23987             4096 0 1
     667             :     //     Built-in Keyboa       24205            61440 0 1
     668             :     //     ----------------------------------------------------
     669             :     //     <other stuff>
     670             :     //
     671             :     // For our purposes we only care about the first portion of the file noted
     672             :     // above which contains memory alloations (both sections). The term
     673             :     // "orphaned" is misleading, it appears that every allocation not by the
     674             :     // first process is considered orphaned on FxOS devices.
     675             : 
     676             :     // The first three fields of each entry interest us:
     677             :     //   1) client - Essentially the process name. We limit client names to 63
     678             :     //               characters, in theory they should never be greater than 15
     679             :     //               due to thread name length limitations.
     680             :     //   2) pid    - The ID of the allocating process, read as a uint32_t.
     681             :     //   3) size   - The size of the allocation in bytes, read as as a uint64_t.
     682           0 :     const char* const kFormatString = "%63s %" SCNu32 " %" SCNu64;
     683           0 :     const int kNumFields = 3;
     684           0 :     const size_t kStringSize = 64;
     685           0 :     const char* const kIonIommuPath = "/sys/kernel/debug/ion/iommu";
     686             : 
     687           0 :     FILE* iommu = fopen(kIonIommuPath, "r");
     688           0 :     if (!iommu) {
     689           0 :       return;
     690             :     }
     691             : 
     692           0 :     AutoFile iommuGuard(iommu);
     693             : 
     694           0 :     const size_t kBufferLen = 256;
     695             :     char buffer[kBufferLen];
     696             :     char client[kStringSize];
     697             :     uint32_t pid;
     698             :     uint64_t size;
     699             : 
     700             :     // Ignore the header line.
     701           0 :     Unused << fgets(buffer, kBufferLen, iommu);
     702             : 
     703             :     // Ignore the separator line.
     704           0 :     Unused << fgets(buffer, kBufferLen, iommu);
     705             : 
     706           0 :     const char* const kSep = "----";
     707           0 :     const size_t kSepLen = 4;
     708             : 
     709             :     // Read non-orphaned entries.
     710           0 :     while (fgets(buffer, kBufferLen, iommu) &&
     711           0 :            strncmp(kSep, buffer, kSepLen) != 0) {
     712           0 :       if (sscanf(buffer, kFormatString, client, &pid, &size) == kNumFields) {
     713           0 :         nsPrintfCString entryPath("ion-memory/%s (pid=%d)", client, pid);
     714           0 :         REPORT(entryPath, UNITS_BYTES, size,
     715             :                NS_LITERAL_CSTRING("An ION kernel memory allocation."));
     716             :       }
     717             :     }
     718             : 
     719             :     // Ignore the orphaned header.
     720           0 :     Unused << fgets(buffer, kBufferLen, iommu);
     721             : 
     722             :     // Read orphaned entries.
     723           0 :     while (fgets(buffer, kBufferLen, iommu) &&
     724           0 :            strncmp(kSep, buffer, kSepLen) != 0) {
     725           0 :       if (sscanf(buffer, kFormatString, client, &pid, &size) == kNumFields) {
     726           0 :         nsPrintfCString entryPath("ion-memory/%s (pid=%d)", client, pid);
     727           0 :         REPORT(entryPath, UNITS_BYTES, size,
     728             :                NS_LITERAL_CSTRING("An ION kernel memory allocation."));
     729             :       }
     730             :     }
     731             : 
     732             :     // Ignore the rest of the file.
     733             :   }
     734             : 
     735             :   uint64_t
     736           0 :   ReadSizeFromFile(const char* aFilename)
     737             :   {
     738           0 :     FILE* sizeFile = fopen(aFilename, "r");
     739           0 :     if (NS_WARN_IF(!sizeFile)) {
     740           0 :       return 0;
     741             :     }
     742             : 
     743           0 :     uint64_t size = 0;
     744           0 :     Unused << fscanf(sizeFile, "%" SCNu64, &size);
     745           0 :     fclose(sizeFile);
     746             : 
     747           0 :     return size;
     748             :   }
     749             : 
     750             :   void
     751           0 :   CollectZramReports(nsIHandleReportCallback* aHandleReport,
     752             :                      nsISupports* aData)
     753             :   {
     754             :     // zram usage stats files can be found under:
     755             :     //  /sys/block/zram<id>
     756             :     //  |--> disksize        - Maximum amount of uncompressed data that can be
     757             :     //                         stored on the disk (bytes)
     758             :     //  |--> orig_data_size  - Uncompressed size of data in the disk (bytes)
     759             :     //  |--> compr_data_size - Compressed size of the data in the disk (bytes)
     760             :     //  |--> num_reads       - Number of attempted reads to the disk (count)
     761             :     //  |--> num_writes      - Number of attempted writes to the disk (count)
     762             :     //
     763             :     // Each file contains a single integer value in decimal form.
     764             : 
     765           0 :     DIR* d = opendir("/sys/block");
     766           0 :     if (!d) {
     767           0 :       return;
     768             :     }
     769             : 
     770             :     struct dirent* ent;
     771           0 :     while ((ent = readdir(d))) {
     772           0 :       const char* name = ent->d_name;
     773             : 
     774             :       // Skip non-zram entries.
     775           0 :       if (strncmp("zram", name, 4) != 0) {
     776           0 :         continue;
     777             :       }
     778             : 
     779             :       // Report disk size statistics.
     780           0 :       nsPrintfCString diskSizeFile("/sys/block/%s/disksize", name);
     781           0 :       nsPrintfCString origSizeFile("/sys/block/%s/orig_data_size", name);
     782             : 
     783           0 :       uint64_t diskSize = ReadSizeFromFile(diskSizeFile.get());
     784           0 :       uint64_t origSize = ReadSizeFromFile(origSizeFile.get());
     785           0 :       uint64_t unusedSize = diskSize - origSize;
     786             : 
     787           0 :       nsPrintfCString diskUsedPath("zram-disksize/%s/used", name);
     788             :       nsPrintfCString diskUsedDesc(
     789             :         "The uncompressed size of data stored in \"%s.\" "
     790             :         "This excludes zero-filled pages since "
     791           0 :         "no memory is allocated for them.", name);
     792           0 :       REPORT(diskUsedPath, UNITS_BYTES, origSize, diskUsedDesc);
     793             : 
     794           0 :       nsPrintfCString diskUnusedPath("zram-disksize/%s/unused", name);
     795             :       nsPrintfCString diskUnusedDesc(
     796             :         "The amount of uncompressed data that can still be "
     797           0 :         "be stored in \"%s\"", name);
     798           0 :       REPORT(diskUnusedPath, UNITS_BYTES, unusedSize, diskUnusedDesc);
     799             : 
     800             :       // Report disk accesses.
     801           0 :       nsPrintfCString readsFile("/sys/block/%s/num_reads", name);
     802           0 :       nsPrintfCString writesFile("/sys/block/%s/num_writes", name);
     803             : 
     804           0 :       uint64_t reads = ReadSizeFromFile(readsFile.get());
     805           0 :       uint64_t writes = ReadSizeFromFile(writesFile.get());
     806             : 
     807             :       nsPrintfCString readsDesc(
     808             :         "The number of reads (failed or successful) done on "
     809           0 :         "\"%s\"", name);
     810           0 :       nsPrintfCString readsPath("zram-accesses/%s/reads", name);
     811           0 :       REPORT(readsPath, UNITS_COUNT_CUMULATIVE, reads, readsDesc);
     812             : 
     813             :       nsPrintfCString writesDesc(
     814             :         "The number of writes (failed or successful) done "
     815           0 :         "on \"%s\"", name);
     816           0 :       nsPrintfCString writesPath("zram-accesses/%s/writes", name);
     817           0 :       REPORT(writesPath, UNITS_COUNT_CUMULATIVE, writes, writesDesc);
     818             : 
     819             :       // Report compressed data size.
     820           0 :       nsPrintfCString comprSizeFile("/sys/block/%s/compr_data_size", name);
     821           0 :       uint64_t comprSize = ReadSizeFromFile(comprSizeFile.get());
     822             : 
     823             :       nsPrintfCString comprSizeDesc(
     824             :         "The compressed size of data stored in \"%s\"",
     825           0 :         name);
     826           0 :       nsPrintfCString comprSizePath("zram-compr-data-size/%s", name);
     827           0 :       REPORT(comprSizePath, UNITS_BYTES, comprSize, comprSizeDesc);
     828             :     }
     829             : 
     830           0 :     closedir(d);
     831             :   }
     832             : 
     833             :   void
     834           0 :   CollectOpenFileReports(nsIHandleReportCallback* aHandleReport,
     835             :                          nsISupports* aData,
     836             :                          const nsACString& aProcPath,
     837             :                          const nsACString& aProcessName)
     838             :   {
     839             :     // All file descriptors opened by a process are listed under
     840             :     // /proc/<pid>/fd/<numerical_fd>. Each entry is a symlink that points to the
     841             :     // path that was opened. This can be an actual file, a socket, a pipe, an
     842             :     // anon_inode, or possibly an uncategorized device.
     843           0 :     const char kFilePrefix[] = "/";
     844           0 :     const char kSocketPrefix[] = "socket:";
     845           0 :     const char kPipePrefix[] = "pipe:";
     846           0 :     const char kAnonInodePrefix[] = "anon_inode:";
     847             : 
     848           0 :     const nsCString procPath(aProcPath);
     849           0 :     DIR* d = opendir(procPath.get());
     850           0 :     if (!d) {
     851           0 :       return;
     852             :     }
     853             : 
     854             :     char linkPath[PATH_MAX + 1];
     855             :     struct dirent* ent;
     856           0 :     while ((ent = readdir(d))) {
     857           0 :       const char* fd = ent->d_name;
     858             : 
     859             :       // Skip "." and ".." (and any other dotfiles).
     860           0 :       if (fd[0] == '.') {
     861           0 :         continue;
     862             :       }
     863             : 
     864           0 :       nsPrintfCString fullPath("%s/%s", procPath.get(), fd);
     865           0 :       ssize_t linkPathSize = readlink(fullPath.get(), linkPath, PATH_MAX);
     866           0 :       if (linkPathSize > 0) {
     867           0 :         linkPath[linkPathSize] = '\0';
     868             : 
     869             : #define CHECK_PREFIX(prefix) \
     870             :   (strncmp(linkPath, prefix, sizeof(prefix) - 1) == 0)
     871             : 
     872           0 :         const char* category = nullptr;
     873           0 :         const char* descriptionPrefix = nullptr;
     874             : 
     875           0 :         if (CHECK_PREFIX(kFilePrefix)) {
     876           0 :           category = "files"; // No trailing slash, the file path will have one
     877           0 :           descriptionPrefix = "An open";
     878           0 :         } else if (CHECK_PREFIX(kSocketPrefix)) {
     879           0 :           category = "sockets/";
     880           0 :           descriptionPrefix = "A socket";
     881           0 :         } else if (CHECK_PREFIX(kPipePrefix)) {
     882           0 :           category = "pipes/";
     883           0 :           descriptionPrefix = "A pipe";
     884           0 :         } else if (CHECK_PREFIX(kAnonInodePrefix)) {
     885           0 :           category = "anon_inodes/";
     886           0 :           descriptionPrefix = "An anon_inode";
     887             :         } else {
     888           0 :           category = "";
     889           0 :           descriptionPrefix = "An uncategorized";
     890             :         }
     891             : 
     892             : #undef CHECK_PREFIX
     893             : 
     894           0 :         const nsCString processName(aProcessName);
     895             :         nsPrintfCString entryPath("open-fds/%s/%s%s/%s",
     896           0 :                                   processName.get(), category, linkPath, fd);
     897             :         nsPrintfCString entryDescription("%s file descriptor opened by the process",
     898           0 :                                          descriptionPrefix);
     899           0 :         REPORT(entryPath, UNITS_COUNT, 1, entryDescription);
     900             :       }
     901             :     }
     902             : 
     903           0 :     closedir(d);
     904             :   }
     905             : 
     906             :   void
     907           0 :   CollectKgslReports(nsIHandleReportCallback* aHandleReport,
     908             :                      nsISupports* aData)
     909             :   {
     910             :     // Each process that uses kgsl memory will have an entry under
     911             :     // /sys/kernel/debug/kgsl/proc/<pid>/mem. This file format includes a
     912             :     // header and then entries with types as follows:
     913             :     //   gpuaddr useraddr size id  flags type usage sglen
     914             :     //   hexaddr hexaddr  int  int str   str  str   int
     915             :     // We care primarily about the usage and size.
     916             : 
     917             :     // For simplicity numbers will be uint64_t, strings 63 chars max.
     918             :     const char* const kScanFormat =
     919             :       "%" SCNx64 " %" SCNx64 " %" SCNu64 " %" SCNu64
     920           0 :       " %63s %63s %63s %" SCNu64;
     921           0 :     const int kNumFields = 8;
     922           0 :     const size_t kStringSize = 64;
     923             : 
     924           0 :     DIR* d = opendir("/sys/kernel/debug/kgsl/proc/");
     925           0 :     if (!d) {
     926           0 :       return;
     927             :     }
     928             : 
     929           0 :     AutoDir dirGuard(d);
     930             : 
     931             :     struct dirent* ent;
     932           0 :     while ((ent = readdir(d))) {
     933           0 :       const char* pid = ent->d_name;
     934             : 
     935             :       // Skip "." and ".." (and any other dotfiles).
     936           0 :       if (pid[0] == '.') {
     937           0 :         continue;
     938             :       }
     939             : 
     940           0 :       nsPrintfCString memPath("/sys/kernel/debug/kgsl/proc/%s/mem", pid);
     941           0 :       FILE* memFile = fopen(memPath.get(), "r");
     942           0 :       if (NS_WARN_IF(!memFile)) {
     943           0 :         continue;
     944             :       }
     945             : 
     946           0 :       AutoFile fileGuard(memFile);
     947             : 
     948             :       // Attempt to map the pid to a more useful name.
     949           0 :       nsAutoCString procName;
     950           0 :       LinuxUtils::GetThreadName(atoi(pid), procName);
     951             : 
     952           0 :       if (procName.IsEmpty()) {
     953           0 :         procName.Append("pid=");
     954           0 :         procName.Append(pid);
     955             :       } else {
     956           0 :         procName.Append(" (pid=");
     957           0 :         procName.Append(pid);
     958           0 :         procName.Append(")");
     959             :       }
     960             : 
     961             :       uint64_t gpuaddr, useraddr, size, id, sglen;
     962             :       char flags[kStringSize];
     963             :       char type[kStringSize];
     964             :       char usage[kStringSize];
     965             : 
     966             :       // Bypass the header line.
     967             :       char buff[1024];
     968           0 :       Unused << fgets(buff, 1024, memFile);
     969             : 
     970           0 :       while (fscanf(memFile, kScanFormat, &gpuaddr, &useraddr, &size, &id,
     971             :                     flags, type, usage, &sglen) == kNumFields) {
     972           0 :         nsPrintfCString entryPath("kgsl-memory/%s/%s", procName.get(), usage);
     973           0 :         REPORT(entryPath, UNITS_BYTES, size,
     974             :                NS_LITERAL_CSTRING("A kgsl graphics memory allocation."));
     975             :       }
     976             :     }
     977             :   }
     978             : };
     979             : 
     980          13 : NS_IMPL_ISUPPORTS(SystemReporter, nsIMemoryReporter)
     981             : 
     982             : void
     983           1 : Init()
     984             : {
     985           1 :   RegisterStrongMemoryReporter(new SystemReporter());
     986           1 : }
     987             : 
     988             : } // namespace SystemMemoryReporter
     989             : } // namespace mozilla

Generated by: LCOV version 1.13