forked from mirrors/gecko-dev
		
	 df91a347b4
			
		
	
	
		df91a347b4
		
	
	
	
	
		
			
			This introduces a low memory watcher that dispatches an offthread read of /proc/meminfo every 5000/1000ms depending on memory levels, then determines which information to act on. It works like this: - Get a percentage of `MemAvailable` versus `MemTotal`. - If memory drops below 5% availability, we are in a memory pressure scenario - If `MemAvailable` is not large enough to accommodate a content process, we are in a memory pressure scenario - If we are in a memory pressure scenario, notify the observers from the main thread. The value I decided to use to represent a content process was based on observation and should be adjusted if it is not representative of what we consider a "typical" content process. Differential Revision: https://phabricator.services.mozilla.com/D117972
		
			
				
	
	
		
			180 lines
		
	
	
	
		
			5.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			180 lines
		
	
	
	
		
			5.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "AvailableMemoryWatcher.h"
 | |
| 
 | |
| #include "mozilla/ClearOnShutdown.h"
 | |
| #include "mozilla/dom/Promise.h"
 | |
| #include "mozilla/ErrorResult.h"
 | |
| #include "mozilla/RefPtr.h"
 | |
| #include "mozilla/Services.h"
 | |
| #include "mozilla/StaticPtr.h"
 | |
| #include "mozilla/Telemetry.h"
 | |
| #include "nsExceptionHandler.h"
 | |
| #include "nsMemoryPressure.h"
 | |
| #include "nsXULAppAPI.h"
 | |
| 
 | |
| namespace mozilla {
 | |
| 
 | |
| // Use this class as the initial value of
 | |
| // nsAvailableMemoryWatcherBase::mCallback until RegisterCallback() is called
 | |
| // so that nsAvailableMemoryWatcherBase does not have to check if its callback
 | |
| // object is valid or not.
 | |
| class NullTabUnloader final : public nsITabUnloader {
 | |
|   ~NullTabUnloader() = default;
 | |
| 
 | |
|  public:
 | |
|   NullTabUnloader() = default;
 | |
| 
 | |
|   NS_DECL_ISUPPORTS
 | |
|   NS_DECL_NSITABUNLOADER
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(NullTabUnloader, nsITabUnloader)
 | |
| 
 | |
| NS_IMETHODIMP NullTabUnloader::UnloadTabAsync() {
 | |
|   return NS_ERROR_NOT_IMPLEMENTED;
 | |
| }
 | |
| 
 | |
| StaticRefPtr<nsAvailableMemoryWatcherBase>
 | |
|     nsAvailableMemoryWatcherBase::sSingleton;
 | |
| 
 | |
| /*static*/
 | |
| already_AddRefed<nsAvailableMemoryWatcherBase>
 | |
| nsAvailableMemoryWatcherBase::GetSingleton() {
 | |
|   if (!sSingleton) {
 | |
|     sSingleton = CreateAvailableMemoryWatcher();
 | |
|     ClearOnShutdown(&sSingleton);
 | |
|   }
 | |
| 
 | |
|   return do_AddRef(sSingleton);
 | |
| }
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(nsAvailableMemoryWatcherBase, nsIAvailableMemoryWatcherBase);
 | |
| 
 | |
| nsAvailableMemoryWatcherBase::nsAvailableMemoryWatcherBase()
 | |
|     : mNumOfTabUnloading(0),
 | |
|       mNumOfMemoryPressure(0),
 | |
|       mTabUnloader(new NullTabUnloader),
 | |
|       mInteracting(false) {
 | |
|   MOZ_ASSERT(XRE_IsParentProcess(),
 | |
|              "Watching memory only in the main process.");
 | |
| }
 | |
| 
 | |
| const char* const nsAvailableMemoryWatcherBase::kObserverTopics[] = {
 | |
|     // Use this shutdown phase to make sure the instance is destroyed in GTest
 | |
|     "xpcom-shutdown",
 | |
|     "user-interaction-active",
 | |
|     "user-interaction-inactive",
 | |
| };
 | |
| 
 | |
| nsresult nsAvailableMemoryWatcherBase::Init() {
 | |
|   MOZ_ASSERT(NS_IsMainThread(),
 | |
|              "nsAvailableMemoryWatcherBase needs to be initialized "
 | |
|              "in the main thread.");
 | |
| 
 | |
|   if (mObserverSvc) {
 | |
|     return NS_ERROR_ALREADY_INITIALIZED;
 | |
|   }
 | |
| 
 | |
|   mObserverSvc = services::GetObserverService();
 | |
|   MOZ_ASSERT(mObserverSvc);
 | |
| 
 | |
|   for (auto topic : kObserverTopics) {
 | |
|     nsresult rv = mObserverSvc->AddObserver(this, topic,
 | |
|                                             /* ownsWeak */ false);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsAvailableMemoryWatcherBase::Shutdown() {
 | |
|   for (auto topic : kObserverTopics) {
 | |
|     mObserverSvc->RemoveObserver(this, topic);
 | |
|   }
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsAvailableMemoryWatcherBase::Observe(nsISupports* aSubject, const char* aTopic,
 | |
|                                       const char16_t* aData) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   if (strcmp(aTopic, "xpcom-shutdown") == 0) {
 | |
|     Shutdown();
 | |
|   } else if (strcmp(aTopic, "user-interaction-inactive") == 0) {
 | |
|     mInteracting = false;
 | |
| #ifdef MOZ_CRASHREPORTER
 | |
|     CrashReporter::SetInactiveStateStart();
 | |
| #endif
 | |
|   } else if (strcmp(aTopic, "user-interaction-active") == 0) {
 | |
|     mInteracting = true;
 | |
| #ifdef MOZ_CRASHREPORTER
 | |
|     CrashReporter::ClearInactiveStateStart();
 | |
| #endif
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult nsAvailableMemoryWatcherBase::RegisterTabUnloader(
 | |
|     nsITabUnloader* aTabUnloader) {
 | |
|   mTabUnloader = aTabUnloader;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult nsAvailableMemoryWatcherBase::OnUnloadAttemptCompleted(
 | |
|     nsresult aResult) {
 | |
|   switch (aResult) {
 | |
|     // A tab was unloaded successfully.
 | |
|     case NS_OK:
 | |
|       ++mNumOfTabUnloading;
 | |
|       break;
 | |
| 
 | |
|     // There was no unloadable tab.
 | |
|     case NS_ERROR_NOT_AVAILABLE:
 | |
|       ++mNumOfMemoryPressure;
 | |
|       NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory);
 | |
|       break;
 | |
| 
 | |
|     // There was a pending task to unload a tab.
 | |
|     case NS_ERROR_ABORT:
 | |
|       break;
 | |
| 
 | |
|     default:
 | |
|       MOZ_ASSERT_UNREACHABLE("Unexpected aResult");
 | |
|       break;
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsAvailableMemoryWatcherBase::UpdateLowMemoryTimeStamp() {
 | |
|   if (mLowMemoryStart.IsNull()) {
 | |
|     mLowMemoryStart = TimeStamp::NowLoRes();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsAvailableMemoryWatcherBase::RecordTelemetryEventOnHighMemory() {
 | |
|   Telemetry::SetEventRecordingEnabled("memory_watcher"_ns, true);
 | |
|   Telemetry::RecordEvent(
 | |
|       Telemetry::EventID::Memory_watcher_OnHighMemory_Stats,
 | |
|       Some(nsPrintfCString(
 | |
|           "%u,%u,%f", mNumOfTabUnloading, mNumOfMemoryPressure,
 | |
|           (TimeStamp::NowLoRes() - mLowMemoryStart).ToSeconds())),
 | |
|       Nothing());
 | |
|   mNumOfTabUnloading = mNumOfMemoryPressure = 0;
 | |
|   mLowMemoryStart = TimeStamp();
 | |
| }
 | |
| 
 | |
| // Define the fallback method for a platform for which a platform-specific
 | |
| // CreateAvailableMemoryWatcher() is not defined.
 | |
| #if defined(ANDROID) || \
 | |
|     !defined(XP_WIN) && !defined(XP_MACOSX) && !defined(XP_LINUX)
 | |
| already_AddRefed<nsAvailableMemoryWatcherBase> CreateAvailableMemoryWatcher() {
 | |
|   RefPtr instance(new nsAvailableMemoryWatcherBase);
 | |
|   return do_AddRef(instance);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| }  // namespace mozilla
 |