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