blob: 4b44cab95f705bfde2b360447709741d33331e67 [file] [log] [blame]
// 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 <fcntl.h>
#include <sys/select.h>
#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/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "starboard/types.h"
namespace base {
namespace chromeos {
namespace {
// Type-safe version of |g_monitor| from base/memory/memory_pressure_monitor.cc.
MemoryPressureMonitor* g_monitor = nullptr;
// 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 than 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),
seconds_since_reporting_(0),
moderate_pressure_threshold_percent_(
GetModerateMemoryThresholdInPercent(thresholds)),
critical_pressure_threshold_percent_(
GetCriticalMemoryThresholdInPercent(thresholds)),
low_mem_file_(HANDLE_EINTR(::open(kLowMemFile, O_RDONLY))),
dispatch_callback_(
base::Bind(&MemoryPressureListener::NotifyMemoryPressure)),
weak_ptr_factory_(this) {
DCHECK(!g_monitor);
g_monitor = this;
StartObserving();
LOG_IF(ERROR,
base::SysInfo::IsRunningOnChromeOS() && !low_mem_file_.is_valid())
<< "Cannot open kernel listener";
}
MemoryPressureMonitor::~MemoryPressureMonitor() {
DCHECK(g_monitor);
g_monitor = nullptr;
StopObserving();
}
void MemoryPressureMonitor::ScheduleEarlyCheck() {
ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, BindOnce(&MemoryPressureMonitor::CheckMemoryPressure,
weak_ptr_factory_.GetWeakPtr()));
}
MemoryPressureListener::MemoryPressureLevel
MemoryPressureMonitor::GetCurrentPressureLevel() {
return current_memory_pressure_level_;
}
// static
MemoryPressureMonitor* MemoryPressureMonitor::Get() {
return g_monitor;
}
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();
if (seconds_since_reporting_++ == 5) {
seconds_since_reporting_ = 0;
RecordMemoryPressure(current_memory_pressure_level_, 1);
}
// Record UMA histogram statistics for the current memory pressure level.
// TODO(lgrey): Remove this once there's a usable history for the
// "Memory.PressureLevel" statistic
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;
dispatch_callback_.Run(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;
}
void MemoryPressureMonitor::SetDispatchCallback(
const DispatchCallback& callback) {
dispatch_callback_ = callback;
}
} // namespace chromeos
} // namespace base