Commit fd1b1e39 authored by Sebastien Marchand's avatar Sebastien Marchand Committed by Commit Bot

[win] Add a memory pressure voter that uses the OS provided signals

This adds a second voter to the Win system memory pressure evaluator
that will base its vote on the OS Memory Pressure signals. This voter
works in parallel to the default voter (based on heuristics) and so
the pressure level will always be based on worst of these 2 signals.

This code is behind a feature flag, some feature param will later be
added to test some variations of this solution (e.g. only use the
OS-signals based voter for the critical pressure notifications).

Change-Id: I52fa235df42efcdf9f14d592de8e0dc1d91395e5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2321524Reviewed-by: default avatarChris Hamilton <chrisha@chromium.org>
Commit-Queue: Sébastien Marchand <sebmarchand@chromium.org>
Cr-Commit-Position: refs/heads/master@{#795927}
parent fb092e50
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "base/util/memory_pressure/system_memory_pressure_evaluator.h" #include "base/util/memory_pressure/system_memory_pressure_evaluator.h"
#include "base/feature_list.h"
#include "build/build_config.h" #include "build/build_config.h"
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
...@@ -16,10 +17,16 @@ ...@@ -16,10 +17,16 @@
#include "base/util/memory_pressure/system_memory_pressure_evaluator_mac.h" #include "base/util/memory_pressure/system_memory_pressure_evaluator_mac.h"
#elif defined(OS_WIN) #elif defined(OS_WIN)
#include "base/util/memory_pressure/system_memory_pressure_evaluator_win.h" #include "base/util/memory_pressure/system_memory_pressure_evaluator_win.h"
#include "base/win/windows_version.h"
#endif #endif
namespace util { namespace util {
#if defined(OS_WIN)
constexpr base::Feature kUseWinOSMemoryPressureSignals{
"UseWinOSMemoryPressureSignals", base::FEATURE_DISABLED_BY_DEFAULT};
#endif
// static // static
std::unique_ptr<SystemMemoryPressureEvaluator> std::unique_ptr<SystemMemoryPressureEvaluator>
SystemMemoryPressureEvaluator::CreateDefaultSystemEvaluator( SystemMemoryPressureEvaluator::CreateDefaultSystemEvaluator(
...@@ -40,8 +47,15 @@ SystemMemoryPressureEvaluator::CreateDefaultSystemEvaluator( ...@@ -40,8 +47,15 @@ SystemMemoryPressureEvaluator::CreateDefaultSystemEvaluator(
return std::make_unique<util::mac::SystemMemoryPressureEvaluator>( return std::make_unique<util::mac::SystemMemoryPressureEvaluator>(
monitor->CreateVoter()); monitor->CreateVoter());
#elif defined(OS_WIN) #elif defined(OS_WIN)
return std::make_unique<util::win::SystemMemoryPressureEvaluator>( auto evaluator = std::make_unique<util::win::SystemMemoryPressureEvaluator>(
monitor->CreateVoter()); monitor->CreateVoter());
// Also subscribe to the OS signals if they're available and the feature is
// enabled.
if (base::FeatureList::IsEnabled(kUseWinOSMemoryPressureSignals) &&
base::win::GetVersion() >= base::win::Version::WIN8_1) {
evaluator->CreateOSSignalPressureEvaluator(monitor->CreateVoter());
}
return evaluator;
#endif #endif
return nullptr; return nullptr;
} }
......
...@@ -5,12 +5,14 @@ ...@@ -5,12 +5,14 @@
#include "base/util/memory_pressure/system_memory_pressure_evaluator_win.h" #include "base/util/memory_pressure/system_memory_pressure_evaluator_win.h"
#include <windows.h> #include <windows.h>
#include <memory>
#include "base/bind.h" #include "base/bind.h"
#include "base/single_thread_task_runner.h" #include "base/single_thread_task_runner.h"
#include "base/threading/sequenced_task_runner_handle.h" #include "base/threading/sequenced_task_runner_handle.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "base/util/memory_pressure/multi_source_memory_pressure_monitor.h" #include "base/util/memory_pressure/multi_source_memory_pressure_monitor.h"
#include "base/win/object_watcher.h"
namespace util { namespace util {
namespace win { namespace win {
...@@ -19,6 +21,58 @@ namespace { ...@@ -19,6 +21,58 @@ namespace {
static const DWORDLONG kMBBytes = 1024 * 1024; static const DWORDLONG kMBBytes = 1024 * 1024;
// Implements ObjectWatcher::Delegate by forwarding to a provided callback.
class MemoryPressureWatcherDelegate
: public base::win::ObjectWatcher::Delegate {
public:
MemoryPressureWatcherDelegate(base::win::ScopedHandle handle,
base::OnceClosure callback);
~MemoryPressureWatcherDelegate() override;
MemoryPressureWatcherDelegate(const MemoryPressureWatcherDelegate& other) =
delete;
MemoryPressureWatcherDelegate& operator=(
const MemoryPressureWatcherDelegate&) = delete;
void ReplaceWatchedHandleForTesting(base::win::ScopedHandle handle);
void SetCallbackForTesting(base::OnceClosure callback) {
callback_ = std::move(callback);
}
private:
void OnObjectSignaled(HANDLE handle) override;
base::win::ScopedHandle handle_;
base::win::ObjectWatcher watcher_;
base::OnceClosure callback_;
};
MemoryPressureWatcherDelegate::MemoryPressureWatcherDelegate(
base::win::ScopedHandle handle,
base::OnceClosure callback)
: handle_(std::move(handle)), callback_(std::move(callback)) {
DCHECK(handle_.IsValid());
CHECK(watcher_.StartWatchingOnce(handle_.Get(), this));
}
MemoryPressureWatcherDelegate::~MemoryPressureWatcherDelegate() {
if (watcher_.IsWatching())
watcher_.StopWatching();
handle_.Close();
}
void MemoryPressureWatcherDelegate::ReplaceWatchedHandleForTesting(
base::win::ScopedHandle handle) {
if (watcher_.IsWatching())
watcher_.StopWatching();
handle_.Close();
handle_ = std::move(handle);
CHECK(watcher_.StartWatchingOnce(handle_.Get(), this));
}
void MemoryPressureWatcherDelegate::OnObjectSignaled(HANDLE object) {
std::move(callback_).Run();
}
} // namespace } // namespace
// The following constants have been lifted from similar values in the ChromeOS // The following constants have been lifted from similar values in the ChromeOS
...@@ -53,6 +107,54 @@ const int ...@@ -53,6 +107,54 @@ const int
const int const int
SystemMemoryPressureEvaluator::kLargeMemoryDefaultCriticalThresholdMb = 400; SystemMemoryPressureEvaluator::kLargeMemoryDefaultCriticalThresholdMb = 400;
// A memory pressure evaluator that receives memory pressure notifications from
// the OS and forwards them to the memory pressure monitor.
class SystemMemoryPressureEvaluator::OSSignalsMemoryPressureEvaluator {
public:
using MemoryPressureLevel = base::MemoryPressureListener::MemoryPressureLevel;
explicit OSSignalsMemoryPressureEvaluator(
std::unique_ptr<MemoryPressureVoter> voter);
~OSSignalsMemoryPressureEvaluator();
OSSignalsMemoryPressureEvaluator(
const OSSignalsMemoryPressureEvaluator& other) = delete;
OSSignalsMemoryPressureEvaluator& operator=(
const OSSignalsMemoryPressureEvaluator&) = delete;
// Creates the watcher used to receive the low and high memory notifications.
void Start();
MemoryPressureWatcherDelegate* GetWatcherForTesting() const {
return memory_notification_watcher_.get();
}
void WaitForHighMemoryNotificationForTesting(base::OnceClosure closure);
private:
// Called when receiving a low/high memory notification.
void OnLowMemoryNotification();
void OnHighMemoryNotification();
void StartLowMemoryNotificationWatcher();
void StartHighMemoryNotificationWatcher();
// The period of the critical pressure notification timer.
static constexpr base::TimeDelta kHighPressureNotificationInterval =
base::TimeDelta::FromSeconds(2);
// The voter used to cast the votes.
std::unique_ptr<MemoryPressureVoter> voter_;
// The memory notification watcher.
std::unique_ptr<MemoryPressureWatcherDelegate> memory_notification_watcher_;
// Timer that will re-emit the critical memory pressure signal until the
// memory gets high again.
base::RepeatingTimer critical_pressure_notification_timer_;
// Ensures that this object is used from a single sequence.
SEQUENCE_CHECKER(sequence_checker_);
};
SystemMemoryPressureEvaluator::SystemMemoryPressureEvaluator( SystemMemoryPressureEvaluator::SystemMemoryPressureEvaluator(
std::unique_ptr<MemoryPressureVoter> voter) std::unique_ptr<MemoryPressureVoter> voter)
: util::SystemMemoryPressureEvaluator(std::move(voter)), : util::SystemMemoryPressureEvaluator(std::move(voter)),
...@@ -88,6 +190,25 @@ void SystemMemoryPressureEvaluator::CheckMemoryPressureSoon() { ...@@ -88,6 +190,25 @@ void SystemMemoryPressureEvaluator::CheckMemoryPressureSoon() {
weak_ptr_factory_.GetWeakPtr())); weak_ptr_factory_.GetWeakPtr()));
} }
void SystemMemoryPressureEvaluator::CreateOSSignalPressureEvaluator(
std::unique_ptr<MemoryPressureVoter> voter) {
os_signals_evaluator_ =
std::make_unique<OSSignalsMemoryPressureEvaluator>(std::move(voter));
os_signals_evaluator_->Start();
}
void SystemMemoryPressureEvaluator::ReplaceWatchedHandleForTesting(
base::win::ScopedHandle handle) {
os_signals_evaluator_->GetWatcherForTesting()->ReplaceWatchedHandleForTesting(
std::move(handle));
}
void SystemMemoryPressureEvaluator::WaitForHighMemoryNotificationForTesting(
base::OnceClosure closure) {
os_signals_evaluator_->WaitForHighMemoryNotificationForTesting(
std::move(closure));
}
void SystemMemoryPressureEvaluator::InferThresholds() { void SystemMemoryPressureEvaluator::InferThresholds() {
// Default to a 'high' memory situation, which uses more conservative // Default to a 'high' memory situation, which uses more conservative
// thresholds. // thresholds.
...@@ -199,12 +320,102 @@ SystemMemoryPressureEvaluator::CalculateCurrentPressureLevel() { ...@@ -199,12 +320,102 @@ SystemMemoryPressureEvaluator::CalculateCurrentPressureLevel() {
bool SystemMemoryPressureEvaluator::GetSystemMemoryStatus( bool SystemMemoryPressureEvaluator::GetSystemMemoryStatus(
MEMORYSTATUSEX* mem_status) { MEMORYSTATUSEX* mem_status) {
DCHECK(mem_status != nullptr); DCHECK(mem_status);
mem_status->dwLength = sizeof(*mem_status); mem_status->dwLength = sizeof(*mem_status);
if (!::GlobalMemoryStatusEx(mem_status)) if (!::GlobalMemoryStatusEx(mem_status))
return false; return false;
return true; return true;
} }
SystemMemoryPressureEvaluator::OSSignalsMemoryPressureEvaluator::
OSSignalsMemoryPressureEvaluator(std::unique_ptr<MemoryPressureVoter> voter)
: voter_(std::move(voter)) {}
SystemMemoryPressureEvaluator::OSSignalsMemoryPressureEvaluator::
~OSSignalsMemoryPressureEvaluator() = default;
void SystemMemoryPressureEvaluator::OSSignalsMemoryPressureEvaluator::Start() {
// Start by observing the low memory notifications. If the system is already
// under pressure this will run the |OnLowMemoryNotification| callback and
// automatically switch to waiting for the high memory notification/
StartLowMemoryNotificationWatcher();
}
void SystemMemoryPressureEvaluator::OSSignalsMemoryPressureEvaluator::
OnLowMemoryNotification() {
// TODO(sebmarchand): Emit some histogram that compares the level detected by
// the default evaluator to this one.
voter_->SetVote(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL,
/* notify = */ true);
// Start a timer to repeat the notification at regular interval until
// OnHighMemoryNotification gets called.
critical_pressure_notification_timer_.Start(
FROM_HERE, kHighPressureNotificationInterval,
base::BindRepeating(
&MemoryPressureVoter::SetVote, base::Unretained(voter_.get()),
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL,
/* notify = */ true));
// Start the high memory notification watcher to be notified when the system
// exits memory pressure.
StartHighMemoryNotificationWatcher();
}
void SystemMemoryPressureEvaluator::OSSignalsMemoryPressureEvaluator::
OnHighMemoryNotification() {
critical_pressure_notification_timer_.Stop();
voter_->SetVote(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE,
/* notify = */ false);
// Start the low memory notification watcher to be notified the next time the
// system hits memory pressure.
StartLowMemoryNotificationWatcher();
}
void SystemMemoryPressureEvaluator::OSSignalsMemoryPressureEvaluator::
StartLowMemoryNotificationWatcher() {
DCHECK(base::SequencedTaskRunnerHandle::IsSet());
memory_notification_watcher_ =
std::make_unique<MemoryPressureWatcherDelegate>(
base::win::ScopedHandle(::CreateMemoryResourceNotification(
::LowMemoryResourceNotification)),
base::BindOnce(
&SystemMemoryPressureEvaluator::OSSignalsMemoryPressureEvaluator::
OnLowMemoryNotification,
base::Unretained(this)));
}
void SystemMemoryPressureEvaluator::OSSignalsMemoryPressureEvaluator::
StartHighMemoryNotificationWatcher() {
memory_notification_watcher_ =
std::make_unique<MemoryPressureWatcherDelegate>(
base::win::ScopedHandle(::CreateMemoryResourceNotification(
::HighMemoryResourceNotification)),
base::BindOnce(
&SystemMemoryPressureEvaluator::OSSignalsMemoryPressureEvaluator::
OnHighMemoryNotification,
base::Unretained(this)));
}
void SystemMemoryPressureEvaluator::OSSignalsMemoryPressureEvaluator::
WaitForHighMemoryNotificationForTesting(base::OnceClosure closure) {
// If the timer isn't running then it means that the high memory notification
// has already been received.
if (!critical_pressure_notification_timer_.IsRunning()) {
std::move(closure).Run();
return;
}
memory_notification_watcher_->SetCallbackForTesting(base::BindOnce(
[](SystemMemoryPressureEvaluator::OSSignalsMemoryPressureEvaluator*
evaluator,
base::OnceClosure closure) {
evaluator->OnHighMemoryNotification();
std::move(closure).Run();
},
base::Unretained(this), std::move(closure)));
}
} // namespace win } // namespace win
} // namespace util } // namespace util
...@@ -6,13 +6,16 @@ ...@@ -6,13 +6,16 @@
#define BASE_UTIL_MEMORY_PRESSURE_SYSTEM_MEMORY_PRESSURE_EVALUATOR_WIN_H_ #define BASE_UTIL_MEMORY_PRESSURE_SYSTEM_MEMORY_PRESSURE_EVALUATOR_WIN_H_
#include "base/base_export.h" #include "base/base_export.h"
#include "base/callback_forward.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/memory_pressure_listener.h" #include "base/memory/memory_pressure_listener.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h" #include "base/sequence_checker.h"
#include "base/time/time.h"
#include "base/timer/timer.h" #include "base/timer/timer.h"
#include "base/util/memory_pressure/memory_pressure_voter.h" #include "base/util/memory_pressure/memory_pressure_voter.h"
#include "base/util/memory_pressure/system_memory_pressure_evaluator.h" #include "base/util/memory_pressure/system_memory_pressure_evaluator.h"
#include "base/win/scoped_handle.h"
// To not pull in windows.h. // To not pull in windows.h.
typedef struct _MEMORYSTATUSEX MEMORYSTATUSEX; typedef struct _MEMORYSTATUSEX MEMORYSTATUSEX;
...@@ -20,8 +23,8 @@ typedef struct _MEMORYSTATUSEX MEMORYSTATUSEX; ...@@ -20,8 +23,8 @@ typedef struct _MEMORYSTATUSEX MEMORYSTATUSEX;
namespace util { namespace util {
namespace win { namespace win {
// Windows memory pressure voter. Because there is no OS provided signal this // Windows memory pressure voter that checks the amount of RAM left at a low
// polls at a low frequency, and applies internal hysteresis. // frequency and applies internal hysteresis.
class SystemMemoryPressureEvaluator class SystemMemoryPressureEvaluator
: public util::SystemMemoryPressureEvaluator { : public util::SystemMemoryPressureEvaluator {
public: public:
...@@ -68,6 +71,26 @@ class SystemMemoryPressureEvaluator ...@@ -68,6 +71,26 @@ class SystemMemoryPressureEvaluator
// Returns the critical pressure level free memory threshold, in MB. // Returns the critical pressure level free memory threshold, in MB.
int critical_threshold_mb() const { return critical_threshold_mb_; } int critical_threshold_mb() const { return critical_threshold_mb_; }
// Create a nested evaluator that will use the signals provided by the OS to
// detect memory pressure. This evaluator will take ownership of |voter| and
// use it to cast its vote to the monitor.
//
// This evaluator will subscribe to the OS signals via a call to
// CreateMemoryResourceNotification and will use the following mapping:
// - MEMORY_PRESSURE_LEVEL_CRITICAL: LowMemoryResourceNotification.
// - MEMORY_PRESSURE_LEVEL_MODERATE: Not measured by this evaluator.
// - MEMORY_PRESSURE_LEVEL_NONE: HighMemoryResourceNotification.
//
// MEMORY_PRESSURE_LEVEL_CRITICAL signals will be emitted as soon as the low
// memory notification is received and at regular interval until receiving a
// HighMemoryResourceNotification.
void CreateOSSignalPressureEvaluator(
std::unique_ptr<MemoryPressureVoter> voter);
// Testing seams for the OSSignalPressureEvaluator.
void ReplaceWatchedHandleForTesting(base::win::ScopedHandle handle);
void WaitForHighMemoryNotificationForTesting(base::OnceClosure closure);
protected: protected:
// Internals are exposed for unittests. // Internals are exposed for unittests.
...@@ -102,6 +125,8 @@ class SystemMemoryPressureEvaluator ...@@ -102,6 +125,8 @@ class SystemMemoryPressureEvaluator
virtual bool GetSystemMemoryStatus(MEMORYSTATUSEX* mem_status); virtual bool GetSystemMemoryStatus(MEMORYSTATUSEX* mem_status);
private: private:
class OSSignalsMemoryPressureEvaluator;
// Threshold amounts of available memory that trigger pressure levels. See // Threshold amounts of available memory that trigger pressure levels. See
// memory_pressure_monitor.cc for a discussion of reasonable values for these. // memory_pressure_monitor.cc for a discussion of reasonable values for these.
int moderate_threshold_mb_; int moderate_threshold_mb_;
...@@ -116,6 +141,10 @@ class SystemMemoryPressureEvaluator ...@@ -116,6 +141,10 @@ class SystemMemoryPressureEvaluator
// |CalculateCurrentPressureLevel|. // |CalculateCurrentPressureLevel|.
int moderate_pressure_repeat_count_; int moderate_pressure_repeat_count_;
// Optional companion evaluator that will receive the OS memory pressure
// notifications, created by |CreateOSSignalPressureEvaluator|.
std::unique_ptr<OSSignalsMemoryPressureEvaluator> os_signals_evaluator_;
// Ensures that this object is used from a single sequence. // Ensures that this object is used from a single sequence.
SEQUENCE_CHECKER(sequence_checker_); SEQUENCE_CHECKER(sequence_checker_);
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/test/bind_test_util.h"
#include "base/test/task_environment.h" #include "base/test/task_environment.h"
#include "base/util/memory_pressure/multi_source_memory_pressure_monitor.h" #include "base/util/memory_pressure/multi_source_memory_pressure_monitor.h"
#include "build/build_config.h" #include "build/build_config.h"
...@@ -318,5 +319,75 @@ TEST_F(WinSystemMemoryPressureEvaluatorTest, CheckMemoryPressure) { ...@@ -318,5 +319,75 @@ TEST_F(WinSystemMemoryPressureEvaluatorTest, CheckMemoryPressure) {
testing::Mock::VerifyAndClearExpectations(&evaluator); testing::Mock::VerifyAndClearExpectations(&evaluator);
} }
TEST_F(WinSystemMemoryPressureEvaluatorTest, OSSignalsMemoryPressureEvaluator) {
MultiSourceMemoryPressureMonitor monitor;
monitor.ResetSystemEvaluatorForTesting();
testing::StrictMock<TestSystemMemoryPressureEvaluator> evaluator(
true, monitor.CreateVoter());
evaluator.CreateOSSignalPressureEvaluator(monitor.CreateVoter());
// Mock function used to ensure that the proper memory pressure signals are
// emitted.
testing::MockFunction<void(base::MemoryPressureListener::MemoryPressureLevel)>
mock_listener_function;
base::MemoryPressureListener listener(
FROM_HERE,
base::BindLambdaForTesting(
[&](base::MemoryPressureListener::MemoryPressureLevel level) {
mock_listener_function.Call(level);
}));
{
base::RunLoop run_loop;
auto quit_closure = run_loop.QuitClosure();
EXPECT_CALL(
mock_listener_function,
Call(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL))
.WillOnce(::testing::Invoke([&]() { std::move(quit_closure).Run(); }));
// A manual-reset event that is not yet signaled.
base::win::ScopedHandle event_low_memory(
CreateEvent(nullptr, TRUE, FALSE, nullptr));
auto* handle = event_low_memory.Get();
// Replace the handle watched by the evaluator to be able to simulate a low
// pressure OS notification.
evaluator.ReplaceWatchedHandleForTesting(std::move(event_low_memory));
::SetEvent(handle);
run_loop.Run();
testing::Mock::VerifyAndClearExpectations(&mock_listener_function);
// |event_low_memory| will be automatically closed by the pressure
// evaluator, no need to call CloseEvent on it.
}
{
base::RunLoop run_loop;
// The evaluator will automatically start watching for a high memory
// notification after receiving the previous low memory notification, wait
// for it to arrive.
evaluator.WaitForHighMemoryNotificationForTesting(run_loop.QuitClosure());
run_loop.Run();
// There should be no MEMORY_PRESSURE_LEVEL_NONE notification emitted.
testing::Mock::VerifyAndClearExpectations(&mock_listener_function);
}
// Do another low memory notification test to make sure that the evaluator
// can detect several critical pressure sessions.
{
base::RunLoop run_loop;
auto quit_closure = run_loop.QuitClosure();
EXPECT_CALL(
mock_listener_function,
Call(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL))
.WillOnce(::testing::Invoke([&]() { std::move(quit_closure).Run(); }));
base::win::ScopedHandle event_low_memory(
CreateEvent(nullptr, TRUE, FALSE, nullptr));
auto* handle = event_low_memory.Get();
evaluator.ReplaceWatchedHandleForTesting(std::move(event_low_memory));
::SetEvent(handle);
run_loop.Run();
testing::Mock::VerifyAndClearExpectations(&mock_listener_function);
}
}
} // namespace win } // namespace win
} // namespace util } // namespace util
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment