// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/memory/memory_pressure_monitor_chromeos.h" #include #include #include "base/metrics/histogram_macros.h" #include "base/posix/eintr_wrapper.h" #include "base/process/process_metrics.h" #include "base/single_thread_task_runner.h" #include "base/sys_info.h" #include "base/thread_task_runner_handle.h" #include "base/time/time.h" namespace base { namespace chromeos { namespace { // The time between memory pressure checks. While under critical pressure, this // is also the timer to repeat cleanup attempts. const int kMemoryPressureIntervalMs = 1000; // The time which should pass between two moderate memory pressure calls. const int kModerateMemoryPressureCooldownMs = 10000; // Number of event polls before the next moderate pressure event can be sent. const int kModerateMemoryPressureCooldown = kModerateMemoryPressureCooldownMs / kMemoryPressureIntervalMs; // Threshold constants to emit pressure events. const int kNormalMemoryPressureModerateThresholdPercent = 60; const int kNormalMemoryPressureCriticalThresholdPercent = 95; const int kAggressiveMemoryPressureModerateThresholdPercent = 35; const int kAggressiveMemoryPressureCriticalThresholdPercent = 70; // The possible state for memory pressure level. The values should be in line // with values in MemoryPressureListener::MemoryPressureLevel and should be // updated if more memory pressure levels are introduced. enum MemoryPressureLevelUMA { MEMORY_PRESSURE_LEVEL_NONE = 0, MEMORY_PRESSURE_LEVEL_MODERATE, MEMORY_PRESSURE_LEVEL_CRITICAL, NUM_MEMORY_PRESSURE_LEVELS }; // This is the file that will exist if low memory notification is available // on the device. Whenever it becomes readable, it signals a low memory // condition. const char kLowMemFile[] = "/dev/chromeos-low-mem"; // Converts a |MemoryPressureThreshold| value into a used memory percentage for // the moderate pressure event. int GetModerateMemoryThresholdInPercent( MemoryPressureMonitor::MemoryPressureThresholds thresholds) { return thresholds == MemoryPressureMonitor:: THRESHOLD_AGGRESSIVE_CACHE_DISCARD || thresholds == MemoryPressureMonitor::THRESHOLD_AGGRESSIVE ? kAggressiveMemoryPressureModerateThresholdPercent : kNormalMemoryPressureModerateThresholdPercent; } // Converts a |MemoryPressureThreshold| value into a used memory percentage for // the critical pressure event. int GetCriticalMemoryThresholdInPercent( MemoryPressureMonitor::MemoryPressureThresholds thresholds) { return thresholds == MemoryPressureMonitor:: THRESHOLD_AGGRESSIVE_TAB_DISCARD || thresholds == MemoryPressureMonitor::THRESHOLD_AGGRESSIVE ? kAggressiveMemoryPressureCriticalThresholdPercent : kNormalMemoryPressureCriticalThresholdPercent; } // Converts free percent of memory into a memory pressure value. MemoryPressureListener::MemoryPressureLevel GetMemoryPressureLevelFromFillLevel( int actual_fill_level, int moderate_threshold, int critical_threshold) { if (actual_fill_level < moderate_threshold) return MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE; return actual_fill_level < critical_threshold ? MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE : MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL; } // This function will be called less then once a second. It will check if // the kernel has detected a low memory situation. bool IsLowMemoryCondition(int file_descriptor) { fd_set fds; struct timeval tv; FD_ZERO(&fds); FD_SET(file_descriptor, &fds); tv.tv_sec = 0; tv.tv_usec = 0; return HANDLE_EINTR(select(file_descriptor + 1, &fds, NULL, NULL, &tv)) > 0; } } // namespace MemoryPressureMonitor::MemoryPressureMonitor( MemoryPressureThresholds thresholds) : current_memory_pressure_level_( MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE), moderate_pressure_repeat_count_(0), moderate_pressure_threshold_percent_( GetModerateMemoryThresholdInPercent(thresholds)), critical_pressure_threshold_percent_( GetCriticalMemoryThresholdInPercent(thresholds)), low_mem_file_(HANDLE_EINTR(::open(kLowMemFile, O_RDONLY))), weak_ptr_factory_(this) { StartObserving(); LOG_IF(ERROR, base::SysInfo::IsRunningOnChromeOS() && !low_mem_file_.is_valid()) << "Cannot open kernel listener"; } MemoryPressureMonitor::~MemoryPressureMonitor() { StopObserving(); } void MemoryPressureMonitor::ScheduleEarlyCheck() { ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, Bind(&MemoryPressureMonitor::CheckMemoryPressure, weak_ptr_factory_.GetWeakPtr())); } MemoryPressureListener::MemoryPressureLevel MemoryPressureMonitor::GetCurrentPressureLevel() const { return current_memory_pressure_level_; } // static MemoryPressureMonitor* MemoryPressureMonitor::Get() { return static_cast( base::MemoryPressureMonitor::Get()); } void MemoryPressureMonitor::StartObserving() { timer_.Start(FROM_HERE, TimeDelta::FromMilliseconds(kMemoryPressureIntervalMs), Bind(&MemoryPressureMonitor:: CheckMemoryPressureAndRecordStatistics, weak_ptr_factory_.GetWeakPtr())); } void MemoryPressureMonitor::StopObserving() { // If StartObserving failed, StopObserving will still get called. timer_.Stop(); } void MemoryPressureMonitor::CheckMemoryPressureAndRecordStatistics() { CheckMemoryPressure(); // Record UMA histogram statistics for the current memory pressure level. MemoryPressureLevelUMA memory_pressure_level_uma(MEMORY_PRESSURE_LEVEL_NONE); switch (current_memory_pressure_level_) { case MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE: memory_pressure_level_uma = MEMORY_PRESSURE_LEVEL_NONE; break; case MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE: memory_pressure_level_uma = MEMORY_PRESSURE_LEVEL_MODERATE; break; case MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL: memory_pressure_level_uma = MEMORY_PRESSURE_LEVEL_CRITICAL; break; } UMA_HISTOGRAM_ENUMERATION("ChromeOS.MemoryPressureLevel", memory_pressure_level_uma, NUM_MEMORY_PRESSURE_LEVELS); } void MemoryPressureMonitor::CheckMemoryPressure() { MemoryPressureListener::MemoryPressureLevel old_pressure = current_memory_pressure_level_; // If we have the kernel low memory observer, we use it's flag instead of our // own computation (for now). Note that in "simulation mode" it can be null. // TODO(skuhne): We need to add code which makes sure that the kernel and this // computation come to similar results and then remove this override again. // TODO(skuhne): Add some testing framework here to see how close the kernel // and the internal functions are. if (low_mem_file_.is_valid() && IsLowMemoryCondition(low_mem_file_.get())) { current_memory_pressure_level_ = MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL; } else { current_memory_pressure_level_ = GetMemoryPressureLevelFromFillLevel( GetUsedMemoryInPercent(), moderate_pressure_threshold_percent_, critical_pressure_threshold_percent_); // When listening to the kernel, we ignore the reported memory pressure // level from our own computation and reduce critical to moderate. if (low_mem_file_.is_valid() && current_memory_pressure_level_ == MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) { current_memory_pressure_level_ = MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE; } } // In case there is no memory pressure we do not notify. if (current_memory_pressure_level_ == MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE) { return; } if (old_pressure == current_memory_pressure_level_) { // If the memory pressure is still at the same level, we notify again for a // critical level. In case of a moderate level repeat however, we only send // a notification after a certain time has passed. if (current_memory_pressure_level_ == MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE && ++moderate_pressure_repeat_count_ < kModerateMemoryPressureCooldown) { return; } } else if (current_memory_pressure_level_ == MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE && old_pressure == MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) { // When we reducing the pressure level from critical to moderate, we // restart the timeout and do not send another notification. moderate_pressure_repeat_count_ = 0; return; } moderate_pressure_repeat_count_ = 0; MemoryPressureListener::NotifyMemoryPressure(current_memory_pressure_level_); } // Gets the used ChromeOS memory in percent. int MemoryPressureMonitor::GetUsedMemoryInPercent() { base::SystemMemoryInfoKB info; if (!base::GetSystemMemoryInfo(&info)) { VLOG(1) << "Cannot determine the free memory of the system."; return 0; } // TODO(skuhne): Instead of adding the kernel memory pressure calculation // logic here, we should have a kernel mechanism similar to the low memory // notifier in ChromeOS which offers multiple pressure states. // To track this, we have crbug.com/381196. // The available memory consists of "real" and virtual (z)ram memory. // Since swappable memory uses a non pre-deterministic compression and // the compression creates its own "dynamic" in the system, it gets // de-emphasized by the |kSwapWeight| factor. const int kSwapWeight = 4; // The total memory we have is the "real memory" plus the virtual (z)ram. int total_memory = info.total + info.swap_total / kSwapWeight; // The kernel internally uses 50MB. const int kMinFileMemory = 50 * 1024; // Most file memory can be easily reclaimed. int file_memory = info.active_file + info.inactive_file; // unless it is dirty or it's a minimal portion which is required. file_memory -= info.dirty + kMinFileMemory; // Available memory is the sum of free, swap and easy reclaimable memory. int available_memory = info.free + info.swap_free / kSwapWeight + file_memory; DCHECK(available_memory < total_memory); int percentage = ((total_memory - available_memory) * 100) / total_memory; return percentage; } } // namespace chromeos } // namespace base