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