LCOV - code coverage report
Current view: top level - xpcom/base - AvailableMemoryTracker.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 14 28 50.0 %
Date: 2017-07-14 16:53:18 Functions: 6 15 40.0 %
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/AvailableMemoryTracker.h"
       8             : 
       9             : #if defined(XP_WIN)
      10             : #include "prinrval.h"
      11             : #include "prenv.h"
      12             : #include "nsIMemoryReporter.h"
      13             : #include "nsMemoryPressure.h"
      14             : #endif
      15             : 
      16             : #include "nsIObserver.h"
      17             : #include "nsIObserverService.h"
      18             : #include "nsIRunnable.h"
      19             : #include "nsISupports.h"
      20             : #include "nsThreadUtils.h"
      21             : 
      22             : #include "mozilla/Preferences.h"
      23             : #include "mozilla/Services.h"
      24             : 
      25             : #if defined(XP_WIN)
      26             : #   include "nsWindowsDllInterceptor.h"
      27             : #   include <windows.h>
      28             : #endif
      29             : 
      30             : #if defined(MOZ_MEMORY)
      31             : #   include "mozmemory.h"
      32             : #endif  // MOZ_MEMORY
      33             : 
      34             : using namespace mozilla;
      35             : 
      36             : namespace {
      37             : 
      38             : #if defined(_M_IX86) && defined(XP_WIN)
      39             : 
      40             : 
      41             : uint32_t sLowVirtualMemoryThreshold = 0;
      42             : uint32_t sLowCommitSpaceThreshold = 0;
      43             : uint32_t sLowPhysicalMemoryThreshold = 0;
      44             : uint32_t sLowMemoryNotificationIntervalMS = 0;
      45             : 
      46             : Atomic<uint32_t> sNumLowVirtualMemEvents;
      47             : Atomic<uint32_t> sNumLowCommitSpaceEvents;
      48             : Atomic<uint32_t> sNumLowPhysicalMemEvents;
      49             : 
      50             : WindowsDllInterceptor sKernel32Intercept;
      51             : WindowsDllInterceptor sGdi32Intercept;
      52             : 
      53             : // Has Init() been called?
      54             : bool sInitialized = false;
      55             : 
      56             : // Has Activate() been called?  The hooks don't do anything until this happens.
      57             : bool sHooksActive = false;
      58             : 
      59             : // Alas, we'd like to use mozilla::TimeStamp, but we can't, because it acquires
      60             : // a lock!
      61             : volatile bool sHasScheduledOneLowMemoryNotification = false;
      62             : volatile PRIntervalTime sLastLowMemoryNotificationTime;
      63             : 
      64             : // These are function pointers to the functions we wrap in Init().
      65             : 
      66             : void* (WINAPI* sVirtualAllocOrig)(LPVOID aAddress, SIZE_T aSize,
      67             :                                   DWORD aAllocationType, DWORD aProtect);
      68             : 
      69             : void* (WINAPI* sMapViewOfFileOrig)(HANDLE aFileMappingObject,
      70             :                                    DWORD aDesiredAccess, DWORD aFileOffsetHigh,
      71             :                                    DWORD aFileOffsetLow, SIZE_T aNumBytesToMap);
      72             : 
      73             : HBITMAP(WINAPI* sCreateDIBSectionOrig)(HDC aDC, const BITMAPINFO* aBitmapInfo,
      74             :                                        UINT aUsage, VOID** aBits,
      75             :                                        HANDLE aSection, DWORD aOffset);
      76             : 
      77             : /**
      78             :  * Fire a memory pressure event if it's been long enough since the last one we
      79             :  * fired.
      80             :  */
      81             : bool
      82             : MaybeScheduleMemoryPressureEvent()
      83             : {
      84             :   // If this interval rolls over, we may fire an extra memory pressure
      85             :   // event, but that's not a big deal.
      86             :   PRIntervalTime interval = PR_IntervalNow() - sLastLowMemoryNotificationTime;
      87             :   if (sHasScheduledOneLowMemoryNotification &&
      88             :       PR_IntervalToMilliseconds(interval) < sLowMemoryNotificationIntervalMS) {
      89             : 
      90             :     return false;
      91             :   }
      92             : 
      93             :   // There's a bit of a race condition here, since an interval may be a
      94             :   // 64-bit number, and 64-bit writes aren't atomic on x86-32.  But let's
      95             :   // not worry about it -- the races only happen when we're already
      96             :   // experiencing memory pressure and firing notifications, so the worst
      97             :   // thing that can happen is that we fire two notifications when we
      98             :   // should have fired only one.
      99             :   sHasScheduledOneLowMemoryNotification = true;
     100             :   sLastLowMemoryNotificationTime = PR_IntervalNow();
     101             : 
     102             :   NS_DispatchEventualMemoryPressure(MemPressure_New);
     103             :   return true;
     104             : }
     105             : 
     106             : void
     107             : CheckMemAvailable()
     108             : {
     109             :   if (!sHooksActive) {
     110             :     return;
     111             :   }
     112             : 
     113             :   MEMORYSTATUSEX stat;
     114             :   stat.dwLength = sizeof(stat);
     115             :   bool success = GlobalMemoryStatusEx(&stat);
     116             : 
     117             :   if (success) {
     118             :     // sLowVirtualMemoryThreshold is in MB, but ullAvailVirtual is in bytes.
     119             :     if (stat.ullAvailVirtual < sLowVirtualMemoryThreshold * 1024 * 1024) {
     120             :       // If we're running low on virtual memory, unconditionally schedule the
     121             :       // notification.  We'll probably crash if we run out of virtual memory,
     122             :       // so don't worry about firing this notification too often.
     123             :       ++sNumLowVirtualMemEvents;
     124             :       NS_DispatchEventualMemoryPressure(MemPressure_New);
     125             :     } else if (stat.ullAvailPageFile < sLowCommitSpaceThreshold * 1024 * 1024) {
     126             :       if (MaybeScheduleMemoryPressureEvent()) {
     127             :         ++sNumLowCommitSpaceEvents;
     128             :       }
     129             :     } else if (stat.ullAvailPhys < sLowPhysicalMemoryThreshold * 1024 * 1024) {
     130             :       if (MaybeScheduleMemoryPressureEvent()) {
     131             :         ++sNumLowPhysicalMemEvents;
     132             :       }
     133             :     }
     134             :   }
     135             : }
     136             : 
     137             : LPVOID WINAPI
     138             : VirtualAllocHook(LPVOID aAddress, SIZE_T aSize,
     139             :                  DWORD aAllocationType,
     140             :                  DWORD aProtect)
     141             : {
     142             :   // It's tempting to see whether we have enough free virtual address space for
     143             :   // this allocation and, if we don't, synchronously fire a low-memory
     144             :   // notification to free some before we allocate.
     145             :   //
     146             :   // Unfortunately that doesn't work, principally because code doesn't expect a
     147             :   // call to malloc could trigger a GC (or call into the other routines which
     148             :   // are triggered by a low-memory notification).
     149             :   //
     150             :   // I think the best we can do here is try to allocate the memory and check
     151             :   // afterwards how much free virtual address space we have.  If we're running
     152             :   // low, we schedule a low-memory notification to run as soon as possible.
     153             : 
     154             :   LPVOID result = sVirtualAllocOrig(aAddress, aSize, aAllocationType, aProtect);
     155             : 
     156             :   // Don't call CheckMemAvailable for MEM_RESERVE if we're not tracking low
     157             :   // virtual memory.  Similarly, don't call CheckMemAvailable for MEM_COMMIT if
     158             :   // we're not tracking low physical memory.
     159             :   if ((sLowVirtualMemoryThreshold != 0 && aAllocationType & MEM_RESERVE) ||
     160             :       (sLowPhysicalMemoryThreshold != 0 && aAllocationType & MEM_COMMIT)) {
     161             :     CheckMemAvailable();
     162             :   }
     163             : 
     164             :   return result;
     165             : }
     166             : 
     167             : LPVOID WINAPI
     168             : MapViewOfFileHook(HANDLE aFileMappingObject,
     169             :                   DWORD aDesiredAccess,
     170             :                   DWORD aFileOffsetHigh,
     171             :                   DWORD aFileOffsetLow,
     172             :                   SIZE_T aNumBytesToMap)
     173             : {
     174             :   LPVOID result = sMapViewOfFileOrig(aFileMappingObject, aDesiredAccess,
     175             :                                      aFileOffsetHigh, aFileOffsetLow,
     176             :                                      aNumBytesToMap);
     177             :   CheckMemAvailable();
     178             :   return result;
     179             : }
     180             : 
     181             : HBITMAP WINAPI
     182             : CreateDIBSectionHook(HDC aDC,
     183             :                      const BITMAPINFO* aBitmapInfo,
     184             :                      UINT aUsage,
     185             :                      VOID** aBits,
     186             :                      HANDLE aSection,
     187             :                      DWORD aOffset)
     188             : {
     189             :   // There are a lot of calls to CreateDIBSection, so we make some effort not
     190             :   // to CheckMemAvailable() for calls to CreateDIBSection which allocate only
     191             :   // a small amount of memory.
     192             : 
     193             :   // If aSection is non-null, CreateDIBSection won't allocate any new memory.
     194             :   bool doCheck = false;
     195             :   if (sHooksActive && !aSection && aBitmapInfo) {
     196             :     uint16_t bitCount = aBitmapInfo->bmiHeader.biBitCount;
     197             :     if (bitCount == 0) {
     198             :       // MSDN says bitCount == 0 means that it figures out how many bits each
     199             :       // pixel gets by examining the corresponding JPEG or PNG data.  We'll just
     200             :       // assume the worst.
     201             :       bitCount = 32;
     202             :     }
     203             : 
     204             :     // |size| contains the expected allocation size in *bits*.  Height may be
     205             :     // negative (indicating the direction the DIB is drawn in), so we take the
     206             :     // absolute value.
     207             :     int64_t size = bitCount * aBitmapInfo->bmiHeader.biWidth *
     208             :                               aBitmapInfo->bmiHeader.biHeight;
     209             :     if (size < 0) {
     210             :       size *= -1;
     211             :     }
     212             : 
     213             :     // If we're allocating more than 1MB, check how much memory is left after
     214             :     // the allocation.
     215             :     if (size > 1024 * 1024 * 8) {
     216             :       doCheck = true;
     217             :     }
     218             :   }
     219             : 
     220             :   HBITMAP result = sCreateDIBSectionOrig(aDC, aBitmapInfo, aUsage, aBits,
     221             :                                          aSection, aOffset);
     222             : 
     223             :   if (doCheck) {
     224             :     CheckMemAvailable();
     225             :   }
     226             : 
     227             :   return result;
     228             : }
     229             : 
     230             : static int64_t
     231             : LowMemoryEventsVirtualDistinguishedAmount()
     232             : {
     233             :   return sNumLowVirtualMemEvents;
     234             : }
     235             : 
     236             : static int64_t
     237             : LowMemoryEventsPhysicalDistinguishedAmount()
     238             : {
     239             :   return sNumLowPhysicalMemEvents;
     240             : }
     241             : 
     242             : class LowEventsReporter final : public nsIMemoryReporter
     243             : {
     244             :   ~LowEventsReporter() {}
     245             : 
     246             : public:
     247             :   NS_DECL_ISUPPORTS
     248             : 
     249             :   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
     250             :                             nsISupports* aData, bool aAnonymize) override
     251             :   {
     252             :     MOZ_COLLECT_REPORT(
     253             :       "low-memory-events/virtual", KIND_OTHER, UNITS_COUNT_CUMULATIVE,
     254             :       LowMemoryEventsVirtualDistinguishedAmount(),
     255             : "Number of low-virtual-memory events fired since startup. We fire such an "
     256             : "event if we notice there is less than memory.low_virtual_mem_threshold_mb of "
     257             : "virtual address space available (if zero, this behavior is disabled). The "
     258             : "process will probably crash if it runs out of virtual address space, so "
     259             : "this event is dire.");
     260             : 
     261             :     MOZ_COLLECT_REPORT(
     262             :       "low-memory-events/commit-space", KIND_OTHER, UNITS_COUNT_CUMULATIVE,
     263             :       sNumLowCommitSpaceEvents,
     264             : "Number of low-commit-space events fired since startup. We fire such an "
     265             : "event if we notice there is less than memory.low_commit_space_threshold_mb of "
     266             : "commit space available (if zero, this behavior is disabled). Windows will "
     267             : "likely kill the process if it runs out of commit space, so this event is "
     268             : "dire.");
     269             : 
     270             :     MOZ_COLLECT_REPORT(
     271             :       "low-memory-events/physical", KIND_OTHER, UNITS_COUNT_CUMULATIVE,
     272             :       LowMemoryEventsPhysicalDistinguishedAmount(),
     273             : "Number of low-physical-memory events fired since startup. We fire such an "
     274             : "event if we notice there is less than memory.low_physical_memory_threshold_mb "
     275             : "of physical memory available (if zero, this behavior is disabled).  The "
     276             : "machine will start to page if it runs out of physical memory.  This may "
     277             : "cause it to run slowly, but it shouldn't cause it to crash.");
     278             : 
     279             :     return NS_OK;
     280             :   }
     281             : };
     282             : NS_IMPL_ISUPPORTS(LowEventsReporter, nsIMemoryReporter)
     283             : 
     284             : #endif // defined(_M_IX86) && defined(XP_WIN)
     285             : 
     286             : /**
     287             :  * This runnable is executed in response to a memory-pressure event; we spin
     288             :  * the event-loop when receiving the memory-pressure event in the hope that
     289             :  * other observers will synchronously free some memory that we'll be able to
     290             :  * purge here.
     291             :  */
     292           0 : class nsJemallocFreeDirtyPagesRunnable final : public nsIRunnable
     293             : {
     294           0 :   ~nsJemallocFreeDirtyPagesRunnable() {}
     295             : 
     296             : public:
     297             :   NS_DECL_ISUPPORTS
     298             :   NS_DECL_NSIRUNNABLE
     299             : };
     300             : 
     301           0 : NS_IMPL_ISUPPORTS(nsJemallocFreeDirtyPagesRunnable, nsIRunnable)
     302             : 
     303             : NS_IMETHODIMP
     304           0 : nsJemallocFreeDirtyPagesRunnable::Run()
     305             : {
     306           0 :   MOZ_ASSERT(NS_IsMainThread());
     307             : 
     308             : #if defined(MOZ_MEMORY)
     309           0 :   jemalloc_free_dirty_pages();
     310             : #endif
     311             : 
     312           0 :   return NS_OK;
     313             : }
     314             : 
     315             : /**
     316             :  * The memory pressure watcher is used for listening to memory-pressure events
     317             :  * and reacting upon them. We use one instance per process currently only for
     318             :  * cleaning up dirty unused pages held by jemalloc.
     319             :  */
     320           3 : class nsMemoryPressureWatcher final : public nsIObserver
     321             : {
     322           0 :   ~nsMemoryPressureWatcher() {}
     323             : 
     324             : public:
     325             :   NS_DECL_ISUPPORTS
     326             :   NS_DECL_NSIOBSERVER
     327             : 
     328             :   void Init();
     329             : 
     330             : private:
     331             :   static bool sFreeDirtyPages;
     332             : };
     333             : 
     334           9 : NS_IMPL_ISUPPORTS(nsMemoryPressureWatcher, nsIObserver)
     335             : 
     336             : bool nsMemoryPressureWatcher::sFreeDirtyPages = false;
     337             : 
     338             : /**
     339             :  * Initialize and subscribe to the memory-pressure events. We subscribe to the
     340             :  * observer service in this method and not in the constructor because we need
     341             :  * to hold a strong reference to 'this' before calling the observer service.
     342             :  */
     343             : void
     344           3 : nsMemoryPressureWatcher::Init()
     345             : {
     346           6 :   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
     347             : 
     348           3 :   if (os) {
     349           3 :     os->AddObserver(this, "memory-pressure", /* ownsWeak */ false);
     350             :   }
     351             : 
     352             :   Preferences::AddBoolVarCache(&sFreeDirtyPages, "memory.free_dirty_pages",
     353           3 :                                false);
     354           3 : }
     355             : 
     356             : /**
     357             :  * Reacts to all types of memory-pressure events, launches a runnable to
     358             :  * free dirty pages held by jemalloc.
     359             :  */
     360             : NS_IMETHODIMP
     361           0 : nsMemoryPressureWatcher::Observe(nsISupports* aSubject, const char* aTopic,
     362             :                                  const char16_t* aData)
     363             : {
     364           0 :   MOZ_ASSERT(!strcmp(aTopic, "memory-pressure"), "Unknown topic");
     365             : 
     366           0 :   if (sFreeDirtyPages) {
     367           0 :     nsCOMPtr<nsIRunnable> runnable = new nsJemallocFreeDirtyPagesRunnable();
     368             : 
     369           0 :     NS_DispatchToMainThread(runnable);
     370             :   }
     371             : 
     372           0 :   return NS_OK;
     373             : }
     374             : 
     375             : } // namespace
     376             : 
     377             : namespace mozilla {
     378             : namespace AvailableMemoryTracker {
     379             : 
     380             : void
     381           3 : Activate()
     382             : {
     383             : #if defined(_M_IX86) && defined(XP_WIN)
     384             :   MOZ_ASSERT(sInitialized);
     385             :   MOZ_ASSERT(!sHooksActive);
     386             : 
     387             :   Preferences::AddUintVarCache(&sLowVirtualMemoryThreshold,
     388             :                                "memory.low_virtual_mem_threshold_mb", 256);
     389             :   Preferences::AddUintVarCache(&sLowPhysicalMemoryThreshold,
     390             :                                "memory.low_physical_memory_threshold_mb", 0);
     391             :   Preferences::AddUintVarCache(&sLowCommitSpaceThreshold,
     392             :                                "memory.low_commit_space_threshold_mb", 256);
     393             :   Preferences::AddUintVarCache(&sLowMemoryNotificationIntervalMS,
     394             :                                "memory.low_memory_notification_interval_ms",
     395             :                                10000);
     396             : 
     397             :   RegisterStrongMemoryReporter(new LowEventsReporter());
     398             :   RegisterLowMemoryEventsVirtualDistinguishedAmount(
     399             :     LowMemoryEventsVirtualDistinguishedAmount);
     400             :   RegisterLowMemoryEventsPhysicalDistinguishedAmount(
     401             :     LowMemoryEventsPhysicalDistinguishedAmount);
     402             :   sHooksActive = true;
     403             : #endif
     404             : 
     405             :   // This object is held alive by the observer service.
     406           6 :   RefPtr<nsMemoryPressureWatcher> watcher = new nsMemoryPressureWatcher();
     407           3 :   watcher->Init();
     408           3 : }
     409             : 
     410             : void
     411           3 : Init()
     412             : {
     413             :   // Do nothing on x86-64, because nsWindowsDllInterceptor is not thread-safe
     414             :   // on 64-bit.  (On 32-bit, it's probably thread-safe.)  Even if we run Init()
     415             :   // before any other of our threads are running, another process may have
     416             :   // started a remote thread which could call VirtualAlloc!
     417             :   //
     418             :   // Moreover, the benefit of this code is less clear when we're a 64-bit
     419             :   // process, because we aren't going to run out of virtual memory, and the
     420             :   // system is likely to have a fair bit of physical memory.
     421             : 
     422             : #if defined(_M_IX86) && defined(XP_WIN)
     423             :   // Don't register the hooks if we're a build instrumented for PGO: If we're
     424             :   // an instrumented build, the compiler adds function calls all over the place
     425             :   // which may call VirtualAlloc; this makes it hard to prevent
     426             :   // VirtualAllocHook from reentering itself.
     427             :   if (!PR_GetEnv("MOZ_PGO_INSTRUMENTED")) {
     428             :     sKernel32Intercept.Init("Kernel32.dll");
     429             :     sKernel32Intercept.AddHook("VirtualAlloc",
     430             :                                reinterpret_cast<intptr_t>(VirtualAllocHook),
     431             :                                reinterpret_cast<void**>(&sVirtualAllocOrig));
     432             :     sKernel32Intercept.AddHook("MapViewOfFile",
     433             :                                reinterpret_cast<intptr_t>(MapViewOfFileHook),
     434             :                                reinterpret_cast<void**>(&sMapViewOfFileOrig));
     435             : 
     436             :     sGdi32Intercept.Init("Gdi32.dll");
     437             :     sGdi32Intercept.AddHook("CreateDIBSection",
     438             :                             reinterpret_cast<intptr_t>(CreateDIBSectionHook),
     439             :                             reinterpret_cast<void**>(&sCreateDIBSectionOrig));
     440             :   }
     441             : 
     442             :   sInitialized = true;
     443             : #endif
     444           3 : }
     445             : 
     446             : } // namespace AvailableMemoryTracker
     447             : } // namespace mozilla

Generated by: LCOV version 1.13