1 // Copyright 2014 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/memory/memory_pressure_monitor_chromeos.h"
6 
7 #include <fcntl.h>
8 #include <sys/select.h>
9 
10 #include "base/metrics/histogram_macros.h"
11 #include "base/posix/eintr_wrapper.h"
12 #include "base/process/process_metrics.h"
13 #include "base/single_thread_task_runner.h"
14 #include "base/sys_info.h"
15 #include "base/threading/thread_task_runner_handle.h"
16 #include "base/time/time.h"
17 
18 namespace base {
19 namespace chromeos {
20 
21 namespace {
22 
23 // The time between memory pressure checks. While under critical pressure, this
24 // is also the timer to repeat cleanup attempts.
25 const int kMemoryPressureIntervalMs = 1000;
26 
27 // The time which should pass between two moderate memory pressure calls.
28 const int kModerateMemoryPressureCooldownMs = 10000;
29 
30 // Number of event polls before the next moderate pressure event can be sent.
31 const int kModerateMemoryPressureCooldown =
32     kModerateMemoryPressureCooldownMs / kMemoryPressureIntervalMs;
33 
34 // Threshold constants to emit pressure events.
35 const int kNormalMemoryPressureModerateThresholdPercent = 60;
36 const int kNormalMemoryPressureCriticalThresholdPercent = 95;
37 const int kAggressiveMemoryPressureModerateThresholdPercent = 35;
38 const int kAggressiveMemoryPressureCriticalThresholdPercent = 70;
39 
40 // The possible state for memory pressure level. The values should be in line
41 // with values in MemoryPressureListener::MemoryPressureLevel and should be
42 // updated if more memory pressure levels are introduced.
43 enum MemoryPressureLevelUMA {
44   MEMORY_PRESSURE_LEVEL_NONE = 0,
45   MEMORY_PRESSURE_LEVEL_MODERATE,
46   MEMORY_PRESSURE_LEVEL_CRITICAL,
47   NUM_MEMORY_PRESSURE_LEVELS
48 };
49 
50 // This is the file that will exist if low memory notification is available
51 // on the device.  Whenever it becomes readable, it signals a low memory
52 // condition.
53 const char kLowMemFile[] = "/dev/chromeos-low-mem";
54 
55 // Converts a |MemoryPressureThreshold| value into a used memory percentage for
56 // the moderate pressure event.
GetModerateMemoryThresholdInPercent(MemoryPressureMonitor::MemoryPressureThresholds thresholds)57 int GetModerateMemoryThresholdInPercent(
58     MemoryPressureMonitor::MemoryPressureThresholds thresholds) {
59   return thresholds == MemoryPressureMonitor::
60                            THRESHOLD_AGGRESSIVE_CACHE_DISCARD ||
61          thresholds == MemoryPressureMonitor::THRESHOLD_AGGRESSIVE
62              ? kAggressiveMemoryPressureModerateThresholdPercent
63              : kNormalMemoryPressureModerateThresholdPercent;
64 }
65 
66 // Converts a |MemoryPressureThreshold| value into a used memory percentage for
67 // the critical pressure event.
GetCriticalMemoryThresholdInPercent(MemoryPressureMonitor::MemoryPressureThresholds thresholds)68 int GetCriticalMemoryThresholdInPercent(
69     MemoryPressureMonitor::MemoryPressureThresholds thresholds) {
70   return thresholds == MemoryPressureMonitor::
71                            THRESHOLD_AGGRESSIVE_TAB_DISCARD ||
72          thresholds == MemoryPressureMonitor::THRESHOLD_AGGRESSIVE
73              ? kAggressiveMemoryPressureCriticalThresholdPercent
74              : kNormalMemoryPressureCriticalThresholdPercent;
75 }
76 
77 // Converts free percent of memory into a memory pressure value.
GetMemoryPressureLevelFromFillLevel(int actual_fill_level,int moderate_threshold,int critical_threshold)78 MemoryPressureListener::MemoryPressureLevel GetMemoryPressureLevelFromFillLevel(
79     int actual_fill_level,
80     int moderate_threshold,
81     int critical_threshold) {
82   if (actual_fill_level < moderate_threshold)
83     return MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
84   return actual_fill_level < critical_threshold
85              ? MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE
86              : MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL;
87 }
88 
89 // This function will be called less than once a second. It will check if
90 // the kernel has detected a low memory situation.
IsLowMemoryCondition(int file_descriptor)91 bool IsLowMemoryCondition(int file_descriptor) {
92   fd_set fds;
93   struct timeval tv;
94 
95   FD_ZERO(&fds);
96   FD_SET(file_descriptor, &fds);
97 
98   tv.tv_sec = 0;
99   tv.tv_usec = 0;
100 
101   return HANDLE_EINTR(select(file_descriptor + 1, &fds, NULL, NULL, &tv)) > 0;
102 }
103 
104 }  // namespace
105 
MemoryPressureMonitor(MemoryPressureThresholds thresholds)106 MemoryPressureMonitor::MemoryPressureMonitor(
107     MemoryPressureThresholds thresholds)
108     : current_memory_pressure_level_(
109           MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE),
110       moderate_pressure_repeat_count_(0),
111       seconds_since_reporting_(0),
112       moderate_pressure_threshold_percent_(
113           GetModerateMemoryThresholdInPercent(thresholds)),
114       critical_pressure_threshold_percent_(
115           GetCriticalMemoryThresholdInPercent(thresholds)),
116       low_mem_file_(HANDLE_EINTR(::open(kLowMemFile, O_RDONLY))),
117       dispatch_callback_(
118           base::Bind(&MemoryPressureListener::NotifyMemoryPressure)),
119       weak_ptr_factory_(this) {
120   StartObserving();
121   LOG_IF(ERROR,
122          base::SysInfo::IsRunningOnChromeOS() && !low_mem_file_.is_valid())
123       << "Cannot open kernel listener";
124 }
125 
~MemoryPressureMonitor()126 MemoryPressureMonitor::~MemoryPressureMonitor() {
127   StopObserving();
128 }
129 
ScheduleEarlyCheck()130 void MemoryPressureMonitor::ScheduleEarlyCheck() {
131   ThreadTaskRunnerHandle::Get()->PostTask(
132       FROM_HERE, BindOnce(&MemoryPressureMonitor::CheckMemoryPressure,
133                           weak_ptr_factory_.GetWeakPtr()));
134 }
135 
136 MemoryPressureListener::MemoryPressureLevel
GetCurrentPressureLevel()137 MemoryPressureMonitor::GetCurrentPressureLevel() {
138   return current_memory_pressure_level_;
139 }
140 
141 // static
Get()142 MemoryPressureMonitor* MemoryPressureMonitor::Get() {
143   return static_cast<MemoryPressureMonitor*>(
144       base::MemoryPressureMonitor::Get());
145 }
146 
StartObserving()147 void MemoryPressureMonitor::StartObserving() {
148   timer_.Start(FROM_HERE,
149                TimeDelta::FromMilliseconds(kMemoryPressureIntervalMs),
150                Bind(&MemoryPressureMonitor::
151                         CheckMemoryPressureAndRecordStatistics,
152                     weak_ptr_factory_.GetWeakPtr()));
153 }
154 
StopObserving()155 void MemoryPressureMonitor::StopObserving() {
156   // If StartObserving failed, StopObserving will still get called.
157   timer_.Stop();
158 }
159 
CheckMemoryPressureAndRecordStatistics()160 void MemoryPressureMonitor::CheckMemoryPressureAndRecordStatistics() {
161   CheckMemoryPressure();
162   if (seconds_since_reporting_++ == 5) {
163     seconds_since_reporting_ = 0;
164     RecordMemoryPressure(current_memory_pressure_level_, 1);
165   }
166   // Record UMA histogram statistics for the current memory pressure level.
167   // TODO(lgrey): Remove this once there's a usable history for the
168   // "Memory.PressureLevel" statistic
169   MemoryPressureLevelUMA memory_pressure_level_uma(MEMORY_PRESSURE_LEVEL_NONE);
170   switch (current_memory_pressure_level_) {
171     case MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE:
172       memory_pressure_level_uma = MEMORY_PRESSURE_LEVEL_NONE;
173       break;
174     case MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE:
175       memory_pressure_level_uma = MEMORY_PRESSURE_LEVEL_MODERATE;
176       break;
177     case MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL:
178       memory_pressure_level_uma = MEMORY_PRESSURE_LEVEL_CRITICAL;
179       break;
180   }
181 
182   UMA_HISTOGRAM_ENUMERATION("ChromeOS.MemoryPressureLevel",
183                             memory_pressure_level_uma,
184                             NUM_MEMORY_PRESSURE_LEVELS);
185 }
186 
CheckMemoryPressure()187 void MemoryPressureMonitor::CheckMemoryPressure() {
188   MemoryPressureListener::MemoryPressureLevel old_pressure =
189       current_memory_pressure_level_;
190 
191   // If we have the kernel low memory observer, we use it's flag instead of our
192   // own computation (for now). Note that in "simulation mode" it can be null.
193   // TODO(skuhne): We need to add code which makes sure that the kernel and this
194   // computation come to similar results and then remove this override again.
195   // TODO(skuhne): Add some testing framework here to see how close the kernel
196   // and the internal functions are.
197   if (low_mem_file_.is_valid() && IsLowMemoryCondition(low_mem_file_.get())) {
198     current_memory_pressure_level_ =
199         MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL;
200   } else {
201     current_memory_pressure_level_ = GetMemoryPressureLevelFromFillLevel(
202         GetUsedMemoryInPercent(),
203         moderate_pressure_threshold_percent_,
204         critical_pressure_threshold_percent_);
205 
206     // When listening to the kernel, we ignore the reported memory pressure
207     // level from our own computation and reduce critical to moderate.
208     if (low_mem_file_.is_valid() &&
209         current_memory_pressure_level_ ==
210         MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) {
211       current_memory_pressure_level_ =
212           MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE;
213     }
214   }
215 
216   // In case there is no memory pressure we do not notify.
217   if (current_memory_pressure_level_ ==
218       MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE) {
219     return;
220   }
221   if (old_pressure == current_memory_pressure_level_) {
222     // If the memory pressure is still at the same level, we notify again for a
223     // critical level. In case of a moderate level repeat however, we only send
224     // a notification after a certain time has passed.
225     if (current_memory_pressure_level_ ==
226         MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE &&
227           ++moderate_pressure_repeat_count_ <
228               kModerateMemoryPressureCooldown) {
229       return;
230     }
231   } else if (current_memory_pressure_level_ ==
232                MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE &&
233              old_pressure ==
234                MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) {
235     // When we reducing the pressure level from critical to moderate, we
236     // restart the timeout and do not send another notification.
237     moderate_pressure_repeat_count_ = 0;
238     return;
239   }
240   moderate_pressure_repeat_count_ = 0;
241   dispatch_callback_.Run(current_memory_pressure_level_);
242 }
243 
244 // Gets the used ChromeOS memory in percent.
GetUsedMemoryInPercent()245 int MemoryPressureMonitor::GetUsedMemoryInPercent() {
246   base::SystemMemoryInfoKB info;
247   if (!base::GetSystemMemoryInfo(&info)) {
248     VLOG(1) << "Cannot determine the free memory of the system.";
249     return 0;
250   }
251   // TODO(skuhne): Instead of adding the kernel memory pressure calculation
252   // logic here, we should have a kernel mechanism similar to the low memory
253   // notifier in ChromeOS which offers multiple pressure states.
254   // To track this, we have crbug.com/381196.
255 
256   // The available memory consists of "real" and virtual (z)ram memory.
257   // Since swappable memory uses a non pre-deterministic compression and
258   // the compression creates its own "dynamic" in the system, it gets
259   // de-emphasized by the |kSwapWeight| factor.
260   const int kSwapWeight = 4;
261 
262   // The total memory we have is the "real memory" plus the virtual (z)ram.
263   int total_memory = info.total + info.swap_total / kSwapWeight;
264 
265   // The kernel internally uses 50MB.
266   const int kMinFileMemory = 50 * 1024;
267 
268   // Most file memory can be easily reclaimed.
269   int file_memory = info.active_file + info.inactive_file;
270   // unless it is dirty or it's a minimal portion which is required.
271   file_memory -= info.dirty + kMinFileMemory;
272 
273   // Available memory is the sum of free, swap and easy reclaimable memory.
274   int available_memory =
275       info.free + info.swap_free / kSwapWeight + file_memory;
276 
277   DCHECK(available_memory < total_memory);
278   int percentage = ((total_memory - available_memory) * 100) / total_memory;
279   return percentage;
280 }
281 
SetDispatchCallback(const DispatchCallback & callback)282 void MemoryPressureMonitor::SetDispatchCallback(
283     const DispatchCallback& callback) {
284   dispatch_callback_ = callback;
285 }
286 
287 }  // namespace chromeos
288 }  // namespace base
289