1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/util/memory_pressure/system_memory_pressure_evaluator_win.h"
6
7 #include <windows.h>
8 #include <memory>
9
10 #include "base/bind.h"
11 #include "base/metrics/histogram_functions.h"
12 #include "base/single_thread_task_runner.h"
13 #include "base/system/sys_info.h"
14 #include "base/threading/sequenced_task_runner_handle.h"
15 #include "base/time/time.h"
16 #include "base/util/memory_pressure/multi_source_memory_pressure_monitor.h"
17 #include "base/win/object_watcher.h"
18
19 namespace util {
20 namespace win {
21
22 namespace {
23
24 static const DWORDLONG kMBBytes = 1024 * 1024;
25
26 // Implements ObjectWatcher::Delegate by forwarding to a provided callback.
27 class MemoryPressureWatcherDelegate
28 : public base::win::ObjectWatcher::Delegate {
29 public:
30 MemoryPressureWatcherDelegate(base::win::ScopedHandle handle,
31 base::OnceClosure callback);
32 ~MemoryPressureWatcherDelegate() override;
33 MemoryPressureWatcherDelegate(const MemoryPressureWatcherDelegate& other) =
34 delete;
35 MemoryPressureWatcherDelegate& operator=(
36 const MemoryPressureWatcherDelegate&) = delete;
37
38 void ReplaceWatchedHandleForTesting(base::win::ScopedHandle handle);
SetCallbackForTesting(base::OnceClosure callback)39 void SetCallbackForTesting(base::OnceClosure callback) {
40 callback_ = std::move(callback);
41 }
42
43 private:
44 void OnObjectSignaled(HANDLE handle) override;
45
46 base::win::ScopedHandle handle_;
47 base::win::ObjectWatcher watcher_;
48 base::OnceClosure callback_;
49 };
50
MemoryPressureWatcherDelegate(base::win::ScopedHandle handle,base::OnceClosure callback)51 MemoryPressureWatcherDelegate::MemoryPressureWatcherDelegate(
52 base::win::ScopedHandle handle,
53 base::OnceClosure callback)
54 : handle_(std::move(handle)), callback_(std::move(callback)) {
55 DCHECK(handle_.IsValid());
56 CHECK(watcher_.StartWatchingOnce(handle_.Get(), this));
57 }
58
59 MemoryPressureWatcherDelegate::~MemoryPressureWatcherDelegate() = default;
60
ReplaceWatchedHandleForTesting(base::win::ScopedHandle handle)61 void MemoryPressureWatcherDelegate::ReplaceWatchedHandleForTesting(
62 base::win::ScopedHandle handle) {
63 if (watcher_.IsWatching())
64 watcher_.StopWatching();
65 handle_ = std::move(handle);
66 CHECK(watcher_.StartWatchingOnce(handle_.Get(), this));
67 }
68
OnObjectSignaled(HANDLE handle)69 void MemoryPressureWatcherDelegate::OnObjectSignaled(HANDLE handle) {
70 DCHECK_EQ(handle, handle_.Get());
71 std::move(callback_).Run();
72 }
73
74 } // namespace
75
76 // The following constants have been lifted from similar values in the ChromeOS
77 // memory pressure monitor. The values were determined experimentally to ensure
78 // sufficient responsiveness of the memory pressure subsystem, and minimal
79 // overhead.
80 const base::TimeDelta SystemMemoryPressureEvaluator::kModeratePressureCooldown =
81 base::TimeDelta::FromSeconds(10);
82
83 // TODO(chrisha): Explore the following constants further with an experiment.
84
85 // A system is considered 'high memory' if it has more than 1.5GB of system
86 // memory available for use by the memory manager (not reserved for hardware
87 // and drivers). This is a fuzzy version of the ~2GB discussed below.
88 const int SystemMemoryPressureEvaluator::kLargeMemoryThresholdMb = 1536;
89
90 // These are the default thresholds used for systems with < ~2GB of physical
91 // memory. Such systems have been observed to always maintain ~100MB of
92 // available memory, paging until that is the case. To try to avoid paging a
93 // threshold slightly above this is chosen. The moderate threshold is slightly
94 // less grounded in reality and chosen as 2.5x critical.
95 const int
96 SystemMemoryPressureEvaluator::kSmallMemoryDefaultModerateThresholdMb = 500;
97 const int
98 SystemMemoryPressureEvaluator::kSmallMemoryDefaultCriticalThresholdMb = 200;
99
100 // These are the default thresholds used for systems with >= ~2GB of physical
101 // memory. Such systems have been observed to always maintain ~300MB of
102 // available memory, paging until that is the case.
103 const int
104 SystemMemoryPressureEvaluator::kLargeMemoryDefaultModerateThresholdMb =
105 1000;
106 const int
107 SystemMemoryPressureEvaluator::kLargeMemoryDefaultCriticalThresholdMb = 400;
108
109 // A memory pressure evaluator that receives memory pressure notifications from
110 // the OS and forwards them to the memory pressure monitor.
111 class SystemMemoryPressureEvaluator::OSSignalsMemoryPressureEvaluator {
112 public:
113 using MemoryPressureLevel = base::MemoryPressureListener::MemoryPressureLevel;
114
115 explicit OSSignalsMemoryPressureEvaluator(
116 std::unique_ptr<MemoryPressureVoter> voter);
117 ~OSSignalsMemoryPressureEvaluator();
118 OSSignalsMemoryPressureEvaluator(
119 const OSSignalsMemoryPressureEvaluator& other) = delete;
120 OSSignalsMemoryPressureEvaluator& operator=(
121 const OSSignalsMemoryPressureEvaluator&) = delete;
122
123 // Creates the watcher used to receive the low and high memory notifications.
124 void Start();
125
GetWatcherForTesting() const126 MemoryPressureWatcherDelegate* GetWatcherForTesting() const {
127 return memory_notification_watcher_.get();
128 }
129 void WaitForHighMemoryNotificationForTesting(base::OnceClosure closure);
130
131 private:
132 // Called when receiving a low/high memory notification.
133 void OnLowMemoryNotification();
134 void OnHighMemoryNotification();
135
136 void StartLowMemoryNotificationWatcher();
137 void StartHighMemoryNotificationWatcher();
138
139 // The period of the critical pressure notification timer.
140 static constexpr base::TimeDelta kHighPressureNotificationInterval =
141 base::TimeDelta::FromSeconds(2);
142
143 // The voter used to cast the votes.
144 std::unique_ptr<MemoryPressureVoter> voter_;
145
146 // The memory notification watcher.
147 std::unique_ptr<MemoryPressureWatcherDelegate> memory_notification_watcher_;
148
149 // Timer that will re-emit the critical memory pressure signal until the
150 // memory gets high again.
151 base::RepeatingTimer critical_pressure_notification_timer_;
152
153 // Beginning of the critical memory pressure session.
154 base::TimeTicks critical_pressure_session_begin_;
155
156 // Ensures that this object is used from a single sequence.
157 SEQUENCE_CHECKER(sequence_checker_);
158 };
159
SystemMemoryPressureEvaluator(std::unique_ptr<MemoryPressureVoter> voter)160 SystemMemoryPressureEvaluator::SystemMemoryPressureEvaluator(
161 std::unique_ptr<MemoryPressureVoter> voter)
162 : util::SystemMemoryPressureEvaluator(std::move(voter)),
163 moderate_threshold_mb_(0),
164 critical_threshold_mb_(0),
165 moderate_pressure_repeat_count_(0) {
166 InferThresholds();
167 StartObserving();
168 }
169
SystemMemoryPressureEvaluator(int moderate_threshold_mb,int critical_threshold_mb,std::unique_ptr<MemoryPressureVoter> voter)170 SystemMemoryPressureEvaluator::SystemMemoryPressureEvaluator(
171 int moderate_threshold_mb,
172 int critical_threshold_mb,
173 std::unique_ptr<MemoryPressureVoter> voter)
174 : util::SystemMemoryPressureEvaluator(std::move(voter)),
175 moderate_threshold_mb_(moderate_threshold_mb),
176 critical_threshold_mb_(critical_threshold_mb),
177 moderate_pressure_repeat_count_(0) {
178 DCHECK_GE(moderate_threshold_mb_, critical_threshold_mb_);
179 DCHECK_LE(0, critical_threshold_mb_);
180 StartObserving();
181 }
182
~SystemMemoryPressureEvaluator()183 SystemMemoryPressureEvaluator::~SystemMemoryPressureEvaluator() {
184 StopObserving();
185 }
186
CheckMemoryPressureSoon()187 void SystemMemoryPressureEvaluator::CheckMemoryPressureSoon() {
188 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
189
190 base::SequencedTaskRunnerHandle::Get()->PostTask(
191 FROM_HERE, BindOnce(&SystemMemoryPressureEvaluator::CheckMemoryPressure,
192 weak_ptr_factory_.GetWeakPtr()));
193 }
194
CreateOSSignalPressureEvaluator(std::unique_ptr<MemoryPressureVoter> voter)195 void SystemMemoryPressureEvaluator::CreateOSSignalPressureEvaluator(
196 std::unique_ptr<MemoryPressureVoter> voter) {
197 os_signals_evaluator_ =
198 std::make_unique<OSSignalsMemoryPressureEvaluator>(std::move(voter));
199 os_signals_evaluator_->Start();
200 }
201
ReplaceWatchedHandleForTesting(base::win::ScopedHandle handle)202 void SystemMemoryPressureEvaluator::ReplaceWatchedHandleForTesting(
203 base::win::ScopedHandle handle) {
204 os_signals_evaluator_->GetWatcherForTesting()->ReplaceWatchedHandleForTesting(
205 std::move(handle));
206 }
207
WaitForHighMemoryNotificationForTesting(base::OnceClosure closure)208 void SystemMemoryPressureEvaluator::WaitForHighMemoryNotificationForTesting(
209 base::OnceClosure closure) {
210 os_signals_evaluator_->WaitForHighMemoryNotificationForTesting(
211 std::move(closure));
212 }
213
InferThresholds()214 void SystemMemoryPressureEvaluator::InferThresholds() {
215 // Default to a 'high' memory situation, which uses more conservative
216 // thresholds.
217 bool high_memory = true;
218 MEMORYSTATUSEX mem_status = {};
219 if (GetSystemMemoryStatus(&mem_status)) {
220 static const DWORDLONG kLargeMemoryThresholdBytes =
221 static_cast<DWORDLONG>(kLargeMemoryThresholdMb) * kMBBytes;
222 high_memory = mem_status.ullTotalPhys >= kLargeMemoryThresholdBytes;
223 }
224
225 if (high_memory) {
226 moderate_threshold_mb_ = kLargeMemoryDefaultModerateThresholdMb;
227 critical_threshold_mb_ = kLargeMemoryDefaultCriticalThresholdMb;
228 } else {
229 moderate_threshold_mb_ = kSmallMemoryDefaultModerateThresholdMb;
230 critical_threshold_mb_ = kSmallMemoryDefaultCriticalThresholdMb;
231 }
232 }
233
StartObserving()234 void SystemMemoryPressureEvaluator::StartObserving() {
235 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
236
237 timer_.Start(
238 FROM_HERE, base::MemoryPressureMonitor::kUMAMemoryPressureLevelPeriod,
239 BindRepeating(&SystemMemoryPressureEvaluator::CheckMemoryPressure,
240 weak_ptr_factory_.GetWeakPtr()));
241 }
242
StopObserving()243 void SystemMemoryPressureEvaluator::StopObserving() {
244 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
245
246 // If StartObserving failed, StopObserving will still get called.
247 timer_.Stop();
248 weak_ptr_factory_.InvalidateWeakPtrs();
249 }
250
CheckMemoryPressure()251 void SystemMemoryPressureEvaluator::CheckMemoryPressure() {
252 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
253
254 // Get the previous pressure level and update the current one.
255 MemoryPressureLevel old_vote = current_vote();
256 SetCurrentVote(CalculateCurrentPressureLevel());
257
258 // |notify| will be set to true if MemoryPressureListeners need to be
259 // notified of a memory pressure level state change.
260 bool notify = false;
261 switch (current_vote()) {
262 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE:
263 break;
264
265 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE:
266 if (old_vote != current_vote()) {
267 // This is a new transition to moderate pressure so notify.
268 moderate_pressure_repeat_count_ = 0;
269 notify = true;
270 } else {
271 // Already in moderate pressure, only notify if sustained over the
272 // cooldown period.
273 const int kModeratePressureCooldownCycles =
274 kModeratePressureCooldown /
275 base::MemoryPressureMonitor::kUMAMemoryPressureLevelPeriod;
276 if (++moderate_pressure_repeat_count_ ==
277 kModeratePressureCooldownCycles) {
278 moderate_pressure_repeat_count_ = 0;
279 notify = true;
280 }
281 }
282 break;
283
284 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL:
285 // Always notify of critical pressure levels.
286 notify = true;
287 break;
288 }
289
290 SendCurrentVote(notify);
291 }
292
293 base::MemoryPressureListener::MemoryPressureLevel
CalculateCurrentPressureLevel()294 SystemMemoryPressureEvaluator::CalculateCurrentPressureLevel() {
295 MEMORYSTATUSEX mem_status = {};
296 if (!GetSystemMemoryStatus(&mem_status))
297 return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
298
299 // How much system memory is actively available for use right now, in MBs.
300 int phys_free = static_cast<int>(mem_status.ullAvailPhys / kMBBytes);
301
302 // TODO(chrisha): This should eventually care about address space pressure,
303 // but the browser process (where this is running) effectively never runs out
304 // of address space. Renderers occasionally do, but it does them no good to
305 // have the browser process monitor address space pressure. Long term,
306 // renderers should run their own address space pressure monitors and act
307 // accordingly, with the browser making cross-process decisions based on
308 // system memory pressure.
309
310 // Determine if the physical memory is under critical memory pressure.
311 if (phys_free <= critical_threshold_mb_)
312 return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL;
313
314 // Determine if the physical memory is under moderate memory pressure.
315 if (phys_free <= moderate_threshold_mb_)
316 return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE;
317
318 // No memory pressure was detected.
319 return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
320 }
321
GetSystemMemoryStatus(MEMORYSTATUSEX * mem_status)322 bool SystemMemoryPressureEvaluator::GetSystemMemoryStatus(
323 MEMORYSTATUSEX* mem_status) {
324 DCHECK(mem_status);
325 mem_status->dwLength = sizeof(*mem_status);
326 if (!::GlobalMemoryStatusEx(mem_status))
327 return false;
328 return true;
329 }
330
331 SystemMemoryPressureEvaluator::OSSignalsMemoryPressureEvaluator::
OSSignalsMemoryPressureEvaluator(std::unique_ptr<MemoryPressureVoter> voter)332 OSSignalsMemoryPressureEvaluator(std::unique_ptr<MemoryPressureVoter> voter)
333 : voter_(std::move(voter)) {}
334
335 SystemMemoryPressureEvaluator::OSSignalsMemoryPressureEvaluator::
336 ~OSSignalsMemoryPressureEvaluator() = default;
337
Start()338 void SystemMemoryPressureEvaluator::OSSignalsMemoryPressureEvaluator::Start() {
339 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
340 // Start by observing the low memory notifications. If the system is already
341 // under pressure this will run the |OnLowMemoryNotification| callback and
342 // automatically switch to waiting for the high memory notification/
343 StartLowMemoryNotificationWatcher();
344 }
345
346 void SystemMemoryPressureEvaluator::OSSignalsMemoryPressureEvaluator::
OnLowMemoryNotification()347 OnLowMemoryNotification() {
348 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
349
350 critical_pressure_session_begin_ = base::TimeTicks::Now();
351
352 base::UmaHistogramEnumeration(
353 "Discarding.WinOSPressureSignals.PressureLevelOnLowMemoryNotification",
354 base::MemoryPressureMonitor::Get()->GetCurrentPressureLevel());
355
356 base::UmaHistogramMemoryMB(
357 "Discarding.WinOSPressureSignals."
358 "AvailableMemoryMbOnLowMemoryNotification",
359 base::SysInfo::AmountOfAvailablePhysicalMemory() / 1024 / 1024);
360
361 voter_->SetVote(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL,
362 /* notify = */ true);
363
364 // Start a timer to repeat the notification at regular interval until
365 // OnHighMemoryNotification gets called.
366 critical_pressure_notification_timer_.Start(
367 FROM_HERE, kHighPressureNotificationInterval,
368 base::BindRepeating(
369 &MemoryPressureVoter::SetVote, base::Unretained(voter_.get()),
370 base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL,
371 /* notify = */ true));
372
373 // Start the high memory notification watcher to be notified when the system
374 // exits memory pressure.
375 StartHighMemoryNotificationWatcher();
376 }
377
378 void SystemMemoryPressureEvaluator::OSSignalsMemoryPressureEvaluator::
OnHighMemoryNotification()379 OnHighMemoryNotification() {
380 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
381
382 base::UmaHistogramMediumTimes(
383 "Discarding.WinOSPressureSignals.LowMemorySessionLength",
384 base::TimeTicks::Now() - critical_pressure_session_begin_);
385 critical_pressure_session_begin_ = base::TimeTicks();
386
387 critical_pressure_notification_timer_.Stop();
388 voter_->SetVote(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE,
389 /* notify = */ false);
390
391 // Start the low memory notification watcher to be notified the next time the
392 // system hits memory pressure.
393 StartLowMemoryNotificationWatcher();
394 }
395
396 void SystemMemoryPressureEvaluator::OSSignalsMemoryPressureEvaluator::
StartLowMemoryNotificationWatcher()397 StartLowMemoryNotificationWatcher() {
398 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
399
400 DCHECK(base::SequencedTaskRunnerHandle::IsSet());
401 memory_notification_watcher_ =
402 std::make_unique<MemoryPressureWatcherDelegate>(
403 base::win::ScopedHandle(::CreateMemoryResourceNotification(
404 ::LowMemoryResourceNotification)),
405 base::BindOnce(
406 &SystemMemoryPressureEvaluator::OSSignalsMemoryPressureEvaluator::
407 OnLowMemoryNotification,
408 base::Unretained(this)));
409 }
410
411 void SystemMemoryPressureEvaluator::OSSignalsMemoryPressureEvaluator::
StartHighMemoryNotificationWatcher()412 StartHighMemoryNotificationWatcher() {
413 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
414
415 memory_notification_watcher_ =
416 std::make_unique<MemoryPressureWatcherDelegate>(
417 base::win::ScopedHandle(::CreateMemoryResourceNotification(
418 ::HighMemoryResourceNotification)),
419 base::BindOnce(
420 &SystemMemoryPressureEvaluator::OSSignalsMemoryPressureEvaluator::
421 OnHighMemoryNotification,
422 base::Unretained(this)));
423 }
424
425 void SystemMemoryPressureEvaluator::OSSignalsMemoryPressureEvaluator::
WaitForHighMemoryNotificationForTesting(base::OnceClosure closure)426 WaitForHighMemoryNotificationForTesting(base::OnceClosure closure) {
427 // If the timer isn't running then it means that the high memory notification
428 // has already been received.
429 if (!critical_pressure_notification_timer_.IsRunning()) {
430 std::move(closure).Run();
431 return;
432 }
433
434 memory_notification_watcher_->SetCallbackForTesting(base::BindOnce(
435 [](SystemMemoryPressureEvaluator::OSSignalsMemoryPressureEvaluator*
436 evaluator,
437 base::OnceClosure closure) {
438 evaluator->OnHighMemoryNotification();
439 std::move(closure).Run();
440 },
441 base::Unretained(this), std::move(closure)));
442 }
443
444 } // namespace win
445 } // namespace util
446