xpcom/base/SystemMemoryReporter.cpp

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

mercurial