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 #include "base/util/memory_pressure/system_memory_pressure_evaluator_chromeos.h"
5 
6 #include <fcntl.h>
7 #include <sys/poll.h>
8 #include <string>
9 #include <vector>
10 
11 #include "base/bind.h"
12 #include "base/files/file_util.h"
13 #include "base/metrics/histogram_macros.h"
14 #include "base/no_destructor.h"
15 #include "base/posix/eintr_wrapper.h"
16 #include "base/single_thread_task_runner.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/string_split.h"
19 #include "base/strings/string_util.h"
20 #include "base/system/sys_info.h"
21 #include "base/task/post_task.h"
22 #include "base/task/thread_pool.h"
23 #include "base/threading/scoped_blocking_call.h"
24 #include "base/threading/thread_task_runner_handle.h"
25 #include "base/time/time.h"
26 
27 namespace util {
28 namespace chromeos {
29 
30 const base::Feature kCrOSUserSpaceLowMemoryNotification{
31     "CrOSUserSpaceLowMemoryNotification", base::FEATURE_DISABLED_BY_DEFAULT};
32 
33 namespace {
34 // Pointer to the SystemMemoryPressureEvaluator used by TabManagerDelegate for
35 // chromeos to need to call into ScheduleEarlyCheck.
36 SystemMemoryPressureEvaluator* g_system_evaluator = nullptr;
37 
38 // We try not to re-notify on moderate too frequently, this time
39 // controls how frequently we will notify after our first notification.
40 constexpr base::TimeDelta kModerateMemoryPressureCooldownTime =
41     base::TimeDelta::FromSeconds(10);
42 
43 // The margin mem file contains the two memory levels, the first is the
44 // critical level and the second is the moderate level. Note, this
45 // file may contain more values but only the first two are used for
46 // memory pressure notifications in chromeos.
47 constexpr char kMarginMemFile[] = "/sys/kernel/mm/chromeos-low_mem/margin";
48 
49 // The available memory file contains the available memory as determined
50 // by the kernel.
51 constexpr char kAvailableMemFile[] =
52     "/sys/kernel/mm/chromeos-low_mem/available";
53 
54 // The reserved file cache.
55 constexpr char kMinFilelist[] = "/proc/sys/vm/min_filelist_kbytes";
56 
57 // The estimation of how well zram based swap is compressed.
58 constexpr char kRamVsSwapWeight[] =
59     "/sys/kernel/mm/chromeos-low_mem/ram_vs_swap_weight";
60 
61 // Converts an available memory value in MB to a memory pressure level.
62 base::MemoryPressureListener::MemoryPressureLevel
GetMemoryPressureLevelFromAvailable(int available_mb,int moderate_avail_mb,int critical_avail_mb)63 GetMemoryPressureLevelFromAvailable(int available_mb,
64                                     int moderate_avail_mb,
65                                     int critical_avail_mb) {
66   if (available_mb < critical_avail_mb)
67     return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL;
68   if (available_mb < moderate_avail_mb)
69     return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE;
70 
71   return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
72 }
73 
ReadFileToUint64(const base::FilePath & file)74 uint64_t ReadFileToUint64(const base::FilePath& file) {
75   std::string file_contents;
76   if (!ReadFileToString(file, &file_contents))
77     return 0;
78   TrimWhitespaceASCII(file_contents, base::TRIM_ALL, &file_contents);
79   uint64_t file_contents_uint64 = 0;
80   if (!base::StringToUint64(file_contents, &file_contents_uint64))
81     return 0;
82   return file_contents_uint64;
83 }
84 
ReadAvailableMemoryMB(int available_fd)85 uint64_t ReadAvailableMemoryMB(int available_fd) {
86   // Read the available memory.
87   char buf[32] = {};
88 
89   // kernfs/file.c:
90   // "Once poll/select indicates that the value has changed, you
91   // need to close and re-open the file, or seek to 0 and read again.
92   ssize_t bytes_read = HANDLE_EINTR(pread(available_fd, buf, sizeof(buf), 0));
93   PCHECK(bytes_read != -1);
94 
95   std::string mem_str(buf, bytes_read);
96   uint64_t available = std::numeric_limits<uint64_t>::max();
97   CHECK(base::StringToUint64(
98       base::TrimWhitespaceASCII(mem_str, base::TrimPositions::TRIM_ALL),
99       &available));
100 
101   return available;
102 }
103 
104 // This function will wait until the /sys/kernel/mm/chromeos-low_mem/available
105 // file becomes readable and then read the latest value. This file will only
106 // become readable once the available memory cross through one of the margin
107 // values specified in /sys/kernel/mm/chromeos-low_mem/margin, for more
108 // details see https://crrev.com/c/536336.
WaitForMemoryPressureChanges(int available_fd)109 bool WaitForMemoryPressureChanges(int available_fd) {
110   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
111                                                 base::BlockingType::WILL_BLOCK);
112 
113   pollfd pfd = {available_fd, POLLPRI | POLLERR, 0};
114   int res = HANDLE_EINTR(poll(&pfd, 1, -1));  // Wait indefinitely.
115   PCHECK(res != -1);
116 
117   if (pfd.revents != (POLLPRI | POLLERR)) {
118     // If we didn't receive POLLPRI | POLLERR it means we likely received
119     // POLLNVAL because the fd has been closed we will only log an error in
120     // other situations.
121     LOG_IF(ERROR, pfd.revents != POLLNVAL)
122         << "WaitForMemoryPressureChanges received unexpected revents: "
123         << pfd.revents;
124 
125     // We no longer want to wait for a kernel notification if the fd has been
126     // closed.
127     return false;
128   }
129 
130   return true;
131 }
132 
133 }  // namespace
134 
SystemMemoryPressureEvaluator(std::unique_ptr<MemoryPressureVoter> voter)135 SystemMemoryPressureEvaluator::SystemMemoryPressureEvaluator(
136     std::unique_ptr<MemoryPressureVoter> voter)
137     : SystemMemoryPressureEvaluator(
138           kMarginMemFile,
139           kAvailableMemFile,
140           base::BindRepeating(&WaitForMemoryPressureChanges),
141           /*disable_timer_for_testing*/ false,
142           base::FeatureList::IsEnabled(
143               chromeos::kCrOSUserSpaceLowMemoryNotification),
144           std::move(voter)) {}
145 
SystemMemoryPressureEvaluator(const std::string & margin_file,const std::string & available_file,base::RepeatingCallback<bool (int)> kernel_waiting_callback,bool disable_timer_for_testing,bool is_user_space_notify,std::unique_ptr<MemoryPressureVoter> voter)146 SystemMemoryPressureEvaluator::SystemMemoryPressureEvaluator(
147     const std::string& margin_file,
148     const std::string& available_file,
149     base::RepeatingCallback<bool(int)> kernel_waiting_callback,
150     bool disable_timer_for_testing,
151     bool is_user_space_notify,
152     std::unique_ptr<MemoryPressureVoter> voter)
153     : util::SystemMemoryPressureEvaluator(std::move(voter)),
154       is_user_space_notify_(is_user_space_notify),
155       weak_ptr_factory_(this) {
156   DCHECK(g_system_evaluator == nullptr);
157   g_system_evaluator = this;
158 
159   std::vector<int> margin_parts =
160       SystemMemoryPressureEvaluator::GetMarginFileParts(margin_file);
161 
162   // This class SHOULD have verified kernel support by calling
163   // SupportsKernelNotifications() before creating a new instance of this.
164   // Therefore we will check fail if we don't have multiple margin values.
165   CHECK_LE(2u, margin_parts.size());
166   critical_pressure_threshold_mb_ = margin_parts[0];
167   moderate_pressure_threshold_mb_ = margin_parts[1];
168 
169   UpdateMemoryParameters();
170 
171   if (!is_user_space_notify_) {
172     kernel_waiting_callback_ = base::BindRepeating(
173         std::move(kernel_waiting_callback), available_mem_file_.get());
174     available_mem_file_ =
175         base::ScopedFD(HANDLE_EINTR(open(available_file.c_str(), O_RDONLY)));
176     CHECK(available_mem_file_.is_valid());
177 
178     ScheduleWaitForKernelNotification();
179   }
180 
181   if (!disable_timer_for_testing) {
182     // We will check the memory pressure and report the metric
183     // (ChromeOS.MemoryPressureLevel) every 1 second.
184     checking_timer_.Start(
185         FROM_HERE, base::TimeDelta::FromSeconds(1),
186         base::BindRepeating(&SystemMemoryPressureEvaluator::
187                                 CheckMemoryPressureAndRecordStatistics,
188                             weak_ptr_factory_.GetWeakPtr()));
189   }
190 }
~SystemMemoryPressureEvaluator()191 SystemMemoryPressureEvaluator::~SystemMemoryPressureEvaluator() {
192   DCHECK(g_system_evaluator);
193   g_system_evaluator = nullptr;
194 }
195 
196 // static
Get()197 SystemMemoryPressureEvaluator* SystemMemoryPressureEvaluator::Get() {
198   return g_system_evaluator;
199 }
200 
GetMarginFileParts()201 std::vector<int> SystemMemoryPressureEvaluator::GetMarginFileParts() {
202   static const base::NoDestructor<std::vector<int>> margin_file_parts(
203       GetMarginFileParts(kMarginMemFile));
204   return *margin_file_parts;
205 }
206 
GetMarginFileParts(const std::string & file)207 std::vector<int> SystemMemoryPressureEvaluator::GetMarginFileParts(
208     const std::string& file) {
209   std::vector<int> margin_values;
210   std::string margin_contents;
211   if (base::ReadFileToString(base::FilePath(file), &margin_contents)) {
212     std::vector<std::string> margins =
213         base::SplitString(margin_contents, base::kWhitespaceASCII,
214                           base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
215     for (const auto& v : margins) {
216       int value = -1;
217       if (!base::StringToInt(v, &value)) {
218         // If any of the values weren't parseable as an int we return
219         // nothing as the file format is unexpected.
220         LOG(ERROR) << "Unable to parse margin file contents as integer: " << v;
221         return std::vector<int>();
222       }
223       margin_values.push_back(value);
224     }
225   } else {
226     LOG(ERROR) << "Unable to read margin file: " << kMarginMemFile;
227   }
228   return margin_values;
229 }
230 
231 // CalculateReservedFreeKB() calculates the reserved free memory in KiB from
232 // /proc/zoneinfo.  Reserved pages are free pages reserved for emergent kernel
233 // allocation and are not available to the user space.  It's the sum of high
234 // watermarks and max protection pages of memory zones.  It implements the same
235 // reserved pages calculation in linux kernel calculate_totalreserve_pages().
236 //
237 // /proc/zoneinfo example:
238 // ...
239 // Node 0, zone    DMA32
240 //   pages free     422432
241 //         min      16270
242 //         low      20337
243 //         high     24404
244 //         ...
245 //         protection: (0, 0, 1953, 1953)
246 //
247 // The high field is the high watermark for this zone.  The protection field is
248 // the protected pages for lower zones.  See the lowmem_reserve_ratio section in
249 // https://www.kernel.org/doc/Documentation/sysctl/vm.txt.
CalculateReservedFreeKB(const std::string & zoneinfo)250 uint64_t SystemMemoryPressureEvaluator::CalculateReservedFreeKB(
251     const std::string& zoneinfo) {
252   constexpr uint64_t kPageSizeKB = 4;
253 
254   uint64_t num_reserved_pages = 0;
255   for (const base::StringPiece& line : base::SplitStringPiece(
256            zoneinfo, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
257     std::vector<base::StringPiece> tokens = base::SplitStringPiece(
258         line, base::kWhitespaceASCII, base::TRIM_WHITESPACE,
259         base::SPLIT_WANT_NONEMPTY);
260 
261     // Skip the line if there are not enough tokens.
262     if (tokens.size() < 2) {
263       continue;
264     }
265 
266     if (tokens[0] == "high") {
267       // Parse the high watermark.
268       uint64_t high = 0;
269       if (base::StringToUint64(tokens[1], &high)) {
270         num_reserved_pages += high;
271       } else {
272         LOG(ERROR) << "Couldn't parse the high field in /proc/zoneinfo: "
273                    << tokens[1];
274       }
275     } else if (tokens[0] == "protection:") {
276       // Parse the protection pages.
277       uint64_t max = 0;
278       for (size_t i = 1; i < tokens.size(); ++i) {
279         uint64_t num = 0;
280         base::StringPiece entry;
281         if (i == 1) {
282           // Exclude the leading '(' and the trailing ','.
283           entry = tokens[i].substr(1, tokens[i].size() - 2);
284         } else {
285           // Exclude the trailing ',' or ')'.
286           entry = tokens[i].substr(0, tokens[i].size() - 1);
287         }
288         if (base::StringToUint64(entry, &num)) {
289           max = std::max(max, num);
290         } else {
291           LOG(ERROR)
292               << "Couldn't parse the protection field in /proc/zoneinfo: "
293               << entry;
294         }
295       }
296       num_reserved_pages += max;
297     }
298   }
299 
300   return num_reserved_pages * kPageSizeKB;
301 }
302 
GetReservedMemoryKB()303 uint64_t SystemMemoryPressureEvaluator::GetReservedMemoryKB() {
304   std::string file_contents;
305   if (!ReadFileToString(base::FilePath("/proc/zoneinfo"), &file_contents)) {
306     LOG(ERROR) << "Couldn't get /proc/zoneinfo";
307     return 0;
308   }
309   return CalculateReservedFreeKB(file_contents);
310 }
311 
312 // CalculateAvailableMemoryUserSpaceKB implements the same available memory
313 // calculation as kernel function get_available_mem_adj().  The available memory
314 // consists of 3 parts: the free memory, the file cache, and the swappable
315 // memory.  The available free memory is free memory minus reserved free memory.
316 // The available file cache is the total file cache minus reserved file cache
317 // (min_filelist).  Because swapping is prohibited if there is no anonymous
318 // memory or no swap free, the swappable memory is the minimal of anonymous
319 // memory and swap free.  As swapping memory is more costly than dropping file
320 // cache, only a fraction (1 / ram_swap_weight) of the swappable memory
321 // contributes to the available memory.
CalculateAvailableMemoryUserSpaceKB(const base::SystemMemoryInfoKB & info,uint64_t reserved_free,uint64_t min_filelist,uint64_t ram_swap_weight)322 uint64_t SystemMemoryPressureEvaluator::CalculateAvailableMemoryUserSpaceKB(
323     const base::SystemMemoryInfoKB& info,
324     uint64_t reserved_free,
325     uint64_t min_filelist,
326     uint64_t ram_swap_weight) {
327   const uint64_t free = info.free;
328   const uint64_t anon = info.active_anon + info.inactive_anon;
329   const uint64_t file = info.active_file + info.inactive_file;
330   const uint64_t dirty = info.dirty;
331   const uint64_t swap_free = info.swap_free;
332 
333   uint64_t available = (free > reserved_free) ? free - reserved_free : 0;
334   available += (file > dirty + min_filelist) ? file - dirty - min_filelist : 0;
335   available += std::min<uint64_t>(anon, swap_free) / ram_swap_weight;
336 
337   return available;
338 }
339 
GetAvailableMemoryKB()340 uint64_t SystemMemoryPressureEvaluator::GetAvailableMemoryKB() {
341   if (is_user_space_notify_) {
342     base::SystemMemoryInfoKB info;
343     CHECK(base::GetSystemMemoryInfo(&info));
344     return CalculateAvailableMemoryUserSpaceKB(info, reserved_free_,
345                                                min_filelist_, ram_swap_weight_);
346   } else {
347     const uint64_t available_mem_mb =
348         ReadAvailableMemoryMB(available_mem_file_.get());
349     return available_mem_mb * 1024;
350   }
351 }
352 
SupportsKernelNotifications()353 bool SystemMemoryPressureEvaluator::SupportsKernelNotifications() {
354   // Unfortunately at the moment the only way to determine if the chromeos
355   // kernel supports polling on the available file is to observe two values
356   // in the margin file, if the critical and moderate levels are specified
357   // there then we know the kernel must support polling on available.
358   return SystemMemoryPressureEvaluator::GetMarginFileParts().size() >= 2;
359 }
360 
361 // CheckMemoryPressure will get the current memory pressure level by reading
362 // the available file.
CheckMemoryPressure()363 void SystemMemoryPressureEvaluator::CheckMemoryPressure() {
364   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
365 
366   auto old_vote = current_vote();
367 
368   uint64_t mem_avail_mb = GetAvailableMemoryKB() / 1024;
369 
370   SetCurrentVote(GetMemoryPressureLevelFromAvailable(
371       mem_avail_mb, moderate_pressure_threshold_mb_,
372       critical_pressure_threshold_mb_));
373   bool notify = true;
374 
375   if (current_vote() ==
376       base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE) {
377     last_moderate_notification_ = base::TimeTicks();
378     notify = false;
379   }
380 
381   // In the case of MODERATE memory pressure we may be in this state for quite
382   // some time so we limit the rate at which we dispatch notifications.
383   else if (current_vote() ==
384            base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE) {
385     if (old_vote == current_vote()) {
386       if (base::TimeTicks::Now() - last_moderate_notification_ <
387           kModerateMemoryPressureCooldownTime) {
388         notify = false;
389       } else if (old_vote ==
390                  base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) {
391         // Reset the moderate notification time if we just crossed back.
392         last_moderate_notification_ = base::TimeTicks::Now();
393         notify = false;
394       }
395     }
396 
397     if (notify)
398       last_moderate_notification_ = base::TimeTicks::Now();
399   }
400 
401   VLOG(1) << "SystemMemoryPressureEvaluator::CheckMemoryPressure dispatching "
402              "at level: "
403           << current_vote();
404   SendCurrentVote(notify);
405 }
406 
HandleKernelNotification(bool result)407 void SystemMemoryPressureEvaluator::HandleKernelNotification(bool result) {
408   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
409 
410   // If WaitForKernelNotification returned false then the FD has been closed and
411   // we just exit without waiting again.
412   if (!result) {
413     return;
414   }
415 
416   CheckMemoryPressure();
417 
418   // Now we need to schedule back our blocking task to wait for more
419   // kernel notifications.
420   ScheduleWaitForKernelNotification();
421 }
422 
CheckMemoryPressureAndRecordStatistics()423 void SystemMemoryPressureEvaluator::CheckMemoryPressureAndRecordStatistics() {
424   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
425 
426   // Note: If we support notifications of memory pressure changes in both
427   // directions we will not have to update the cached value as it will always
428   // be correct.
429   CheckMemoryPressure();
430 
431   // Record UMA histogram statistics for the current memory pressure level, it
432   // would seem that only Memory.PressureLevel would be necessary.
433   constexpr int kNumberPressureLevels = 3;
434   UMA_HISTOGRAM_ENUMERATION("ChromeOS.MemoryPressureLevel", current_vote(),
435                             kNumberPressureLevels);
436 }
437 
ScheduleEarlyCheck()438 void SystemMemoryPressureEvaluator::ScheduleEarlyCheck() {
439   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
440 
441   base::ThreadTaskRunnerHandle::Get()->PostTask(
442       FROM_HERE,
443       base::BindOnce(&SystemMemoryPressureEvaluator::CheckMemoryPressure,
444                      weak_ptr_factory_.GetWeakPtr()));
445 }
446 
UpdateMemoryParameters()447 void SystemMemoryPressureEvaluator::UpdateMemoryParameters() {
448   if (is_user_space_notify_) {
449     reserved_free_ = GetReservedMemoryKB();
450     min_filelist_ = ReadFileToUint64(base::FilePath(kMinFilelist));
451     ram_swap_weight_ = ReadFileToUint64(base::FilePath(kRamVsSwapWeight));
452   }
453 }
454 
ScheduleWaitForKernelNotification()455 void SystemMemoryPressureEvaluator::ScheduleWaitForKernelNotification() {
456   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
457 
458   base::ThreadPool::PostTaskAndReplyWithResult(
459       FROM_HERE,
460       {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
461       base::BindOnce(kernel_waiting_callback_),
462       base::BindOnce(&SystemMemoryPressureEvaluator::HandleKernelNotification,
463                      weak_ptr_factory_.GetWeakPtr()));
464 }
465 
466 }  // namespace chromeos
467 }  // namespace util
468