xpcom/base/AvailableMemoryTracker.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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/AvailableMemoryTracker.h"
     9 #if defined(XP_WIN)
    10 #include "prinrval.h"
    11 #include "prenv.h"
    12 #include "nsIMemoryReporter.h"
    13 #include "nsMemoryPressure.h"
    14 #endif
    16 #include "nsIObserver.h"
    17 #include "nsIObserverService.h"
    18 #include "nsIRunnable.h"
    19 #include "nsISupports.h"
    20 #include "nsThreadUtils.h"
    22 #include "mozilla/Preferences.h"
    23 #include "mozilla/Services.h"
    25 #if defined(XP_WIN)
    26 #   include "nsWindowsDllInterceptor.h"
    27 #   include <windows.h>
    28 #endif
    30 #if defined(MOZ_MEMORY)
    31 #   include "mozmemory.h"
    32 #endif  // MOZ_MEMORY
    34 using namespace mozilla;
    36 namespace {
    38 #if defined(XP_WIN)
    40 // We don't want our diagnostic functions to call malloc, because that could
    41 // call VirtualAlloc, and we'd end up back in here!  So here are a few simple
    42 // debugging macros (modeled on jemalloc's), which hopefully won't allocate.
    44 // #define LOGGING_ENABLED
    46 #ifdef LOGGING_ENABLED
    48 #define LOG(msg)       \
    49   do {                 \
    50     safe_write(msg);   \
    51     safe_write("\n");  \
    52   } while(0)
    54 #define LOG2(m1, m2)   \
    55   do {                 \
    56     safe_write(m1);    \
    57     safe_write(m2);    \
    58     safe_write("\n");  \
    59   } while(0)
    61 #define LOG3(m1, m2, m3) \
    62   do {                   \
    63     safe_write(m1);      \
    64     safe_write(m2);      \
    65     safe_write(m3);      \
    66     safe_write("\n");    \
    67   } while(0)
    69 #define LOG4(m1, m2, m3, m4) \
    70   do {                       \
    71     safe_write(m1);          \
    72     safe_write(m2);          \
    73     safe_write(m3);          \
    74     safe_write(m4);          \
    75     safe_write("\n");        \
    76   } while(0)
    78 #else
    80 #define LOG(msg)
    81 #define LOG2(m1, m2)
    82 #define LOG3(m1, m2, m3)
    83 #define LOG4(m1, m2, m3, m4)
    85 #endif
    87 void safe_write(const char *a)
    88 {
    89   // Well, puts isn't exactly "safe", but at least it doesn't call malloc...
    90   fputs(a, stdout);
    91 }
    93 void safe_write(uint64_t x)
    94 {
    95   // 2^64 is 20 decimal digits.
    96   const unsigned int max_len = 21;
    97   char buf[max_len];
    98   buf[max_len - 1] = '\0';
   100   uint32_t i;
   101   for (i = max_len - 2; i < max_len && x > 0; i--)
   102   {
   103     buf[i] = "0123456789"[x % 10];
   104     x /= 10;
   105   }
   107   safe_write(&buf[i + 1]);
   108 }
   110 #ifdef DEBUG
   111 #define DEBUG_WARN_IF_FALSE(cond, msg)          \
   112   do {                                          \
   113     if (!(cond)) {                              \
   114       safe_write(__FILE__);                     \
   115       safe_write(":");                          \
   116       safe_write(__LINE__);                     \
   117       safe_write(" ");                          \
   118       safe_write(msg);                          \
   119       safe_write("\n");                         \
   120     }                                           \
   121   } while(0)
   122 #else
   123 #define DEBUG_WARN_IF_FALSE(cond, msg)
   124 #endif
   126 uint32_t sLowVirtualMemoryThreshold = 0;
   127 uint32_t sLowCommitSpaceThreshold = 0;
   128 uint32_t sLowPhysicalMemoryThreshold = 0;
   129 uint32_t sLowMemoryNotificationIntervalMS = 0;
   131 Atomic<uint32_t> sNumLowVirtualMemEvents;
   132 Atomic<uint32_t> sNumLowCommitSpaceEvents;
   133 Atomic<uint32_t> sNumLowPhysicalMemEvents;
   135 WindowsDllInterceptor sKernel32Intercept;
   136 WindowsDllInterceptor sGdi32Intercept;
   138 // Has Init() been called?
   139 bool sInitialized = false;
   141 // Has Activate() been called?  The hooks don't do anything until this happens.
   142 bool sHooksActive = false;
   144 // Alas, we'd like to use mozilla::TimeStamp, but we can't, because it acquires
   145 // a lock!
   146 volatile bool sHasScheduledOneLowMemoryNotification = false;
   147 volatile PRIntervalTime sLastLowMemoryNotificationTime;
   149 // These are function pointers to the functions we wrap in Init().
   151 void* (WINAPI *sVirtualAllocOrig)
   152   (LPVOID aAddress, SIZE_T aSize, DWORD aAllocationType, DWORD aProtect);
   154 void* (WINAPI *sMapViewOfFileOrig)
   155   (HANDLE aFileMappingObject, DWORD aDesiredAccess,
   156    DWORD aFileOffsetHigh, DWORD aFileOffsetLow,
   157    SIZE_T aNumBytesToMap);
   159 HBITMAP (WINAPI *sCreateDIBSectionOrig)
   160   (HDC aDC, const BITMAPINFO *aBitmapInfo,
   161    UINT aUsage, VOID **aBits,
   162    HANDLE aSection, DWORD aOffset);
   164 /**
   165  * Fire a memory pressure event if it's been long enough since the last one we
   166  * fired.
   167  */
   168 bool MaybeScheduleMemoryPressureEvent()
   169 {
   170   // If this interval rolls over, we may fire an extra memory pressure
   171   // event, but that's not a big deal.
   172   PRIntervalTime interval = PR_IntervalNow() - sLastLowMemoryNotificationTime;
   173   if (sHasScheduledOneLowMemoryNotification &&
   174       PR_IntervalToMilliseconds(interval) < sLowMemoryNotificationIntervalMS) {
   176     LOG("Not scheduling low physical memory notification, "
   177         "because not enough time has elapsed since last one.");
   178     return false;
   179   }
   181   // There's a bit of a race condition here, since an interval may be a
   182   // 64-bit number, and 64-bit writes aren't atomic on x86-32.  But let's
   183   // not worry about it -- the races only happen when we're already
   184   // experiencing memory pressure and firing notifications, so the worst
   185   // thing that can happen is that we fire two notifications when we
   186   // should have fired only one.
   187   sHasScheduledOneLowMemoryNotification = true;
   188   sLastLowMemoryNotificationTime = PR_IntervalNow();
   190   LOG("Scheduling memory pressure notification.");
   191   NS_DispatchEventualMemoryPressure(MemPressure_New);
   192   return true;
   193 }
   195 void CheckMemAvailable()
   196 {
   197   if (!sHooksActive) {
   198     return;
   199   }
   201   MEMORYSTATUSEX stat;
   202   stat.dwLength = sizeof(stat);
   203   bool success = GlobalMemoryStatusEx(&stat);
   205   DEBUG_WARN_IF_FALSE(success, "GlobalMemoryStatusEx failed.");
   207   if (success)
   208   {
   209     // sLowVirtualMemoryThreshold is in MB, but ullAvailVirtual is in bytes.
   210     if (stat.ullAvailVirtual < sLowVirtualMemoryThreshold * 1024 * 1024) {
   211       // If we're running low on virtual memory, unconditionally schedule the
   212       // notification.  We'll probably crash if we run out of virtual memory,
   213       // so don't worry about firing this notification too often.
   214       LOG("Detected low virtual memory.");
   215       ++sNumLowVirtualMemEvents;
   216       NS_DispatchEventualMemoryPressure(MemPressure_New);
   217     }
   218     else if (stat.ullAvailPageFile < sLowCommitSpaceThreshold * 1024 * 1024) {
   219       LOG("Detected low available page file space.");
   220       if (MaybeScheduleMemoryPressureEvent()) {
   221         ++sNumLowCommitSpaceEvents;
   222       }
   223     }
   224     else if (stat.ullAvailPhys < sLowPhysicalMemoryThreshold * 1024 * 1024) {
   225       LOG("Detected low physical memory.");
   226       if (MaybeScheduleMemoryPressureEvent()) {
   227         ++sNumLowPhysicalMemEvents;
   228       }
   229     }
   230   }
   231 }
   233 LPVOID WINAPI
   234 VirtualAllocHook(LPVOID aAddress, SIZE_T aSize,
   235                  DWORD aAllocationType,
   236                  DWORD aProtect)
   237 {
   238   // It's tempting to see whether we have enough free virtual address space for
   239   // this allocation and, if we don't, synchronously fire a low-memory
   240   // notification to free some before we allocate.
   241   //
   242   // Unfortunately that doesn't work, principally because code doesn't expect a
   243   // call to malloc could trigger a GC (or call into the other routines which
   244   // are triggered by a low-memory notification).
   245   //
   246   // I think the best we can do here is try to allocate the memory and check
   247   // afterwards how much free virtual address space we have.  If we're running
   248   // low, we schedule a low-memory notification to run as soon as possible.
   250   LPVOID result = sVirtualAllocOrig(aAddress, aSize, aAllocationType, aProtect);
   252   // Don't call CheckMemAvailable for MEM_RESERVE if we're not tracking low
   253   // virtual memory.  Similarly, don't call CheckMemAvailable for MEM_COMMIT if
   254   // we're not tracking low physical memory.
   255   if ((sLowVirtualMemoryThreshold != 0 && aAllocationType & MEM_RESERVE) ||
   256       (sLowPhysicalMemoryThreshold != 0 && aAllocationType & MEM_COMMIT)) {
   257     LOG3("VirtualAllocHook(size=", aSize, ")");
   258     CheckMemAvailable();
   259   }
   261   return result;
   262 }
   264 LPVOID WINAPI
   265 MapViewOfFileHook(HANDLE aFileMappingObject,
   266                   DWORD aDesiredAccess,
   267                   DWORD aFileOffsetHigh,
   268                   DWORD aFileOffsetLow,
   269                   SIZE_T aNumBytesToMap)
   270 {
   271   LPVOID result = sMapViewOfFileOrig(aFileMappingObject, aDesiredAccess,
   272                                      aFileOffsetHigh, aFileOffsetLow,
   273                                      aNumBytesToMap);
   274   LOG("MapViewOfFileHook");
   275   CheckMemAvailable();
   276   return result;
   277 }
   279 HBITMAP WINAPI
   280 CreateDIBSectionHook(HDC aDC,
   281                      const BITMAPINFO *aBitmapInfo,
   282                      UINT aUsage,
   283                      VOID **aBits,
   284                      HANDLE aSection,
   285                      DWORD aOffset)
   286 {
   287   // There are a lot of calls to CreateDIBSection, so we make some effort not
   288   // to CheckMemAvailable() for calls to CreateDIBSection which allocate only
   289   // a small amount of memory.
   291   // If aSection is non-null, CreateDIBSection won't allocate any new memory.
   292   bool doCheck = false;
   293   if (sHooksActive && !aSection && aBitmapInfo) {
   294     uint16_t bitCount = aBitmapInfo->bmiHeader.biBitCount;
   295     if (bitCount == 0) {
   296       // MSDN says bitCount == 0 means that it figures out how many bits each
   297       // pixel gets by examining the corresponding JPEG or PNG data.  We'll just
   298       // assume the worst.
   299       bitCount = 32;
   300     }
   302     // |size| contains the expected allocation size in *bits*.  Height may be
   303     // negative (indicating the direction the DIB is drawn in), so we take the
   304     // absolute value.
   305     int64_t size = bitCount * aBitmapInfo->bmiHeader.biWidth *
   306                               aBitmapInfo->bmiHeader.biHeight;
   307     if (size < 0)
   308       size *= -1;
   310     // If we're allocating more than 1MB, check how much memory is left after
   311     // the allocation.
   312     if (size > 1024 * 1024 * 8) {
   313       LOG3("CreateDIBSectionHook: Large allocation (size=", size, ")");
   314       doCheck = true;
   315     }
   316   }
   318   HBITMAP result = sCreateDIBSectionOrig(aDC, aBitmapInfo, aUsage, aBits,
   319                                          aSection, aOffset);
   321   if (doCheck) {
   322     CheckMemAvailable();
   323   }
   325   return result;
   326 }
   328 static int64_t
   329 LowMemoryEventsVirtualDistinguishedAmount()
   330 {
   331   return sNumLowVirtualMemEvents;
   332 }
   334 static int64_t
   335 LowMemoryEventsPhysicalDistinguishedAmount()
   336 {
   337   return sNumLowPhysicalMemEvents;
   338 }
   340 class LowEventsReporter MOZ_FINAL : public nsIMemoryReporter
   341 {
   342 public:
   343   NS_DECL_ISUPPORTS
   345   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
   346                             nsISupports* aData)
   347   {
   348     nsresult rv;
   350     // We only do virtual-memory tracking on 32-bit builds.
   351     if (sizeof(void*) == 4) {
   352       rv = MOZ_COLLECT_REPORT(
   353         "low-memory-events/virtual", KIND_OTHER, UNITS_COUNT_CUMULATIVE,
   354         LowMemoryEventsVirtualDistinguishedAmount(),
   355 "Number of low-virtual-memory events fired since startup. We fire such an "
   356 "event if we notice there is less than memory.low_virtual_mem_threshold_mb of "
   357 "virtual address space available (if zero, this behavior is disabled). The "
   358 "process will probably crash if it runs out of virtual address space, so "
   359 "this event is dire.");
   360       NS_ENSURE_SUCCESS(rv, rv);
   361     }
   363     rv = MOZ_COLLECT_REPORT(
   364       "low-commit-space-events", KIND_OTHER, UNITS_COUNT_CUMULATIVE,
   365       sNumLowCommitSpaceEvents,
   366 "Number of low-commit-space events fired since startup. We fire such an "
   367 "event if we notice there is less than memory.low_commit_space_threshold_mb of "
   368 "commit space available (if zero, this behavior is disabled). Windows will "
   369 "likely kill the process if it runs out of commit space, so this event is "
   370 "dire.");
   371     NS_ENSURE_SUCCESS(rv, rv);
   373     rv = MOZ_COLLECT_REPORT(
   374       "low-memory-events/physical", KIND_OTHER, UNITS_COUNT_CUMULATIVE,
   375       LowMemoryEventsPhysicalDistinguishedAmount(),
   376 "Number of low-physical-memory events fired since startup. We fire such an "
   377 "event if we notice there is less than memory.low_physical_memory_threshold_mb "
   378 "of physical memory available (if zero, this behavior is disabled).  The "
   379 "machine will start to page if it runs out of physical memory.  This may "
   380 "cause it to run slowly, but it shouldn't cause it to crash.");
   381     NS_ENSURE_SUCCESS(rv, rv);
   383     return NS_OK;
   384   }
   385 };
   386 NS_IMPL_ISUPPORTS(LowEventsReporter, nsIMemoryReporter)
   388 #endif // defined(XP_WIN)
   390 /**
   391  * This runnable is executed in response to a memory-pressure event; we spin
   392  * the event-loop when receiving the memory-pressure event in the hope that
   393  * other observers will synchronously free some memory that we'll be able to
   394  * purge here.
   395  */
   396 class nsJemallocFreeDirtyPagesRunnable MOZ_FINAL : public nsIRunnable
   397 {
   398 public:
   399   NS_DECL_ISUPPORTS
   400   NS_DECL_NSIRUNNABLE
   401 };
   403 NS_IMPL_ISUPPORTS(nsJemallocFreeDirtyPagesRunnable, nsIRunnable)
   405 NS_IMETHODIMP
   406 nsJemallocFreeDirtyPagesRunnable::Run()
   407 {
   408   MOZ_ASSERT(NS_IsMainThread());
   410 #if defined(MOZ_MEMORY)
   411   jemalloc_free_dirty_pages();
   412 #endif
   414   return NS_OK;
   415 }
   417 /**
   418  * The memory pressure watcher is used for listening to memory-pressure events
   419  * and reacting upon them. We use one instance per process currently only for
   420  * cleaning up dirty unused pages held by jemalloc.
   421  */
   422 class nsMemoryPressureWatcher MOZ_FINAL : public nsIObserver
   423 {
   424 public:
   425   NS_DECL_ISUPPORTS
   426   NS_DECL_NSIOBSERVER
   428   void Init();
   430 private:
   431   static bool sFreeDirtyPages;
   432 };
   434 NS_IMPL_ISUPPORTS(nsMemoryPressureWatcher, nsIObserver)
   436 bool nsMemoryPressureWatcher::sFreeDirtyPages = false;
   438 /**
   439  * Initialize and subscribe to the memory-pressure events. We subscribe to the
   440  * observer service in this method and not in the constructor because we need
   441  * to hold a strong reference to 'this' before calling the observer service.
   442  */
   443 void
   444 nsMemoryPressureWatcher::Init()
   445 {
   446   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
   448   if (os) {
   449     os->AddObserver(this, "memory-pressure", /* ownsWeak */ false);
   450   }
   452   Preferences::AddBoolVarCache(&sFreeDirtyPages, "memory.free_dirty_pages",
   453                                false);
   454 }
   456 /**
   457  * Reacts to all types of memory-pressure events, launches a runnable to
   458  * free dirty pages held by jemalloc.
   459  */
   460 NS_IMETHODIMP
   461 nsMemoryPressureWatcher::Observe(nsISupports *subject, const char *topic,
   462                                  const char16_t *data)
   463 {
   464   MOZ_ASSERT(!strcmp(topic, "memory-pressure"), "Unknown topic");
   466   if (sFreeDirtyPages) {
   467     nsRefPtr<nsIRunnable> runnable = new nsJemallocFreeDirtyPagesRunnable();
   469     NS_DispatchToMainThread(runnable);
   470   }
   472   return NS_OK;
   473 }
   475 } // anonymous namespace
   477 namespace mozilla {
   478 namespace AvailableMemoryTracker {
   480 void Activate()
   481 {
   482 #if defined(_M_IX86) && defined(XP_WIN)
   483   MOZ_ASSERT(sInitialized);
   484   MOZ_ASSERT(!sHooksActive);
   486   // On 64-bit systems, hardcode sLowVirtualMemoryThreshold to 0 -- we assume
   487   // we're not going to run out of virtual memory!
   488   if (sizeof(void*) > 4) {
   489     sLowVirtualMemoryThreshold = 0;
   490   }
   491   else {
   492     Preferences::AddUintVarCache(&sLowVirtualMemoryThreshold,
   493         "memory.low_virtual_mem_threshold_mb", 128);
   494   }
   496   Preferences::AddUintVarCache(&sLowPhysicalMemoryThreshold,
   497       "memory.low_physical_memory_threshold_mb", 0);
   498   Preferences::AddUintVarCache(&sLowCommitSpaceThreshold,
   499       "memory.low_commit_space_threshold_mb", 128);
   500   Preferences::AddUintVarCache(&sLowMemoryNotificationIntervalMS,
   501       "memory.low_memory_notification_interval_ms", 10000);
   503   RegisterStrongMemoryReporter(new LowEventsReporter());
   504   RegisterLowMemoryEventsVirtualDistinguishedAmount(LowMemoryEventsVirtualDistinguishedAmount);
   505   RegisterLowMemoryEventsPhysicalDistinguishedAmount(LowMemoryEventsPhysicalDistinguishedAmount);
   506   sHooksActive = true;
   507 #endif
   509   // This object is held alive by the observer service.
   510   nsRefPtr<nsMemoryPressureWatcher> watcher = new nsMemoryPressureWatcher();
   511   watcher->Init();
   512 }
   514 void Init()
   515 {
   516   // Do nothing on x86-64, because nsWindowsDllInterceptor is not thread-safe
   517   // on 64-bit.  (On 32-bit, it's probably thread-safe.)  Even if we run Init()
   518   // before any other of our threads are running, another process may have
   519   // started a remote thread which could call VirtualAlloc!
   520   //
   521   // Moreover, the benefit of this code is less clear when we're a 64-bit
   522   // process, because we aren't going to run out of virtual memory, and the
   523   // system is likely to have a fair bit of physical memory.
   525 #if defined(_M_IX86) && defined(XP_WIN)
   526   // Don't register the hooks if we're a build instrumented for PGO: If we're
   527   // an instrumented build, the compiler adds function calls all over the place
   528   // which may call VirtualAlloc; this makes it hard to prevent
   529   // VirtualAllocHook from reentering itself.
   530   if (!PR_GetEnv("MOZ_PGO_INSTRUMENTED")) {
   531     sKernel32Intercept.Init("Kernel32.dll");
   532     sKernel32Intercept.AddHook("VirtualAlloc",
   533       reinterpret_cast<intptr_t>(VirtualAllocHook),
   534       (void**) &sVirtualAllocOrig);
   535     sKernel32Intercept.AddHook("MapViewOfFile",
   536       reinterpret_cast<intptr_t>(MapViewOfFileHook),
   537       (void**) &sMapViewOfFileOrig);
   539     sGdi32Intercept.Init("Gdi32.dll");
   540     sGdi32Intercept.AddHook("CreateDIBSection",
   541       reinterpret_cast<intptr_t>(CreateDIBSectionHook),
   542       (void**) &sCreateDIBSectionOrig);
   543   }
   545   sInitialized = true;
   546 #endif
   547 }
   549 } // namespace AvailableMemoryTracker
   550 } // namespace mozilla

mercurial