blob: 4e685703bf084711001201c62e3de70191972822 [file] [log] [blame]
// Copyright 2019 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 "chromeos/memory/pressure/system_memory_pressure_evaluator.h"
#include <fcntl.h>
#include <sys/poll.h>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/posix/eintr_wrapper.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/system/sys_info.h"
#include "base/task/post_task.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "chromeos/memory/pressure/pressure.h"
namespace chromeos {
namespace memory {
namespace {
// Pointer to the SystemMemoryPressureEvaluator used by TabManagerDelegate for
// chromeos to need to call into ScheduleEarlyCheck.
SystemMemoryPressureEvaluator* g_system_evaluator = nullptr;
// We try not to re-notify on moderate too frequently, this time
// controls how frequently we will notify after our first notification.
constexpr base::TimeDelta kModerateMemoryPressureCooldownTime =
base::TimeDelta::FromSeconds(10);
// Converts an available memory value in MB to a memory pressure level.
base::MemoryPressureListener::MemoryPressureLevel
GetMemoryPressureLevelFromAvailable(uint64_t available_mb,
uint64_t moderate_avail_mb,
uint64_t critical_avail_mb) {
if (available_mb < critical_avail_mb)
return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL;
if (available_mb < moderate_avail_mb)
return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE;
return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
}
} // namespace
SystemMemoryPressureEvaluator::SystemMemoryPressureEvaluator(
std::unique_ptr<util::MemoryPressureVoter> voter)
: SystemMemoryPressureEvaluator(
/*disable_timer_for_testing*/ false,
std::move(voter)) {}
SystemMemoryPressureEvaluator::SystemMemoryPressureEvaluator(
bool disable_timer_for_testing,
std::unique_ptr<util::MemoryPressureVoter> voter)
: util::SystemMemoryPressureEvaluator(std::move(voter)),
weak_ptr_factory_(this) {
DCHECK(g_system_evaluator == nullptr);
g_system_evaluator = this;
std::pair<uint64_t, uint64_t> margins_kb =
chromeos::memory::pressure::GetMemoryMarginsKB();
critical_pressure_threshold_mb_ = margins_kb.first / 1024;
moderate_pressure_threshold_mb_ = margins_kb.second / 1024;
chromeos::memory::pressure::UpdateMemoryParameters();
if (!disable_timer_for_testing) {
// We will check the memory pressure and report the metric
// (ChromeOS.MemoryPressureLevel) every 1 second.
checking_timer_.Start(
FROM_HERE, base::TimeDelta::FromSeconds(1),
base::BindRepeating(&SystemMemoryPressureEvaluator::
CheckMemoryPressureAndRecordStatistics,
weak_ptr_factory_.GetWeakPtr()));
}
}
SystemMemoryPressureEvaluator::~SystemMemoryPressureEvaluator() {
DCHECK(g_system_evaluator);
g_system_evaluator = nullptr;
}
// static
SystemMemoryPressureEvaluator* SystemMemoryPressureEvaluator::Get() {
return g_system_evaluator;
}
// CheckMemoryPressure will get the current memory pressure level by checking
// the available memory.
void SystemMemoryPressureEvaluator::CheckMemoryPressure() {
uint64_t mem_avail_mb =
chromeos::memory::pressure::GetAvailableMemoryKB() / 1024;
CheckMemoryPressureImpl(moderate_pressure_threshold_mb_,
critical_pressure_threshold_mb_, mem_avail_mb);
}
void SystemMemoryPressureEvaluator::CheckMemoryPressureImpl(
uint64_t moderate_avail_mb,
uint64_t critical_avail_mb,
uint64_t mem_avail_mb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto old_vote = current_vote();
SetCurrentVote(GetMemoryPressureLevelFromAvailable(
mem_avail_mb, moderate_avail_mb, critical_avail_mb));
bool notify = true;
if (current_vote() ==
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE) {
last_moderate_notification_ = base::TimeTicks();
notify = false;
} else if (current_vote() ==
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE) {
// In the case of MODERATE memory pressure we may be in this state for quite
// some time so we limit the rate at which we dispatch notifications.
if (old_vote == current_vote()) {
if (base::TimeTicks::Now() - last_moderate_notification_ <
kModerateMemoryPressureCooldownTime) {
notify = false;
} else if (old_vote ==
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) {
// Reset the moderate notification time if we just crossed back.
last_moderate_notification_ = base::TimeTicks::Now();
notify = false;
}
}
if (notify)
last_moderate_notification_ = base::TimeTicks::Now();
}
VLOG(1) << "SystemMemoryPressureEvaluator::CheckMemoryPressure dispatching "
"at level: "
<< current_vote();
SendCurrentVote(notify);
}
void SystemMemoryPressureEvaluator::CheckMemoryPressureAndRecordStatistics() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Note: If we support notifications of memory pressure changes in both
// directions we will not have to update the cached value as it will always
// be correct.
CheckMemoryPressure();
// Record UMA histogram statistics for the current memory pressure level, it
// would seem that only Memory.PressureLevel would be necessary.
constexpr int kNumberPressureLevels = 3;
UMA_HISTOGRAM_ENUMERATION("ChromeOS.MemoryPressureLevel", current_vote(),
kNumberPressureLevels);
}
void SystemMemoryPressureEvaluator::ScheduleEarlyCheck() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&SystemMemoryPressureEvaluator::CheckMemoryPressure,
weak_ptr_factory_.GetWeakPtr()));
}
} // namespace memory
} // namespace chromeos