1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
|
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/memory_pressure/system_memory_pressure_evaluator_mac.h"
#include <CoreFoundation/CoreFoundation.h>
#include <dlfcn.h>
#include <stddef.h>
#include <sys/sysctl.h>
#include <algorithm>
#include <cmath>
#include "base/check_op.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/mac/mac_util.h"
#include "base/memory/memory_pressure_monitor.h"
#include "base/path_service.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
namespace memory_pressure::mac {
namespace {
// When enabled, the moderate memory pressure signals on macOS are ignored and
// treated as 'none'. This is to experiment with the idea that the 'warn'
// level signal from the OS is not always an accurate or useful signal.
BASE_FEATURE(kSkipModerateMemoryPressureLevelMac,
"SkipModerateMemoryPressureLevelMac",
base::FEATURE_DISABLED_BY_DEFAULT);
// This feature controls the critical memory pressure signal based on low disk
// space. Disabling this feature turns off the disk space check entirely.
BASE_FEATURE(kMacCriticalDiskSpacePressure,
"MacCriticalDiskSpacePressure",
base::FEATURE_DISABLED_BY_DEFAULT);
// The default threshold in megabytes for the critical disk space pressure
// signal.
constexpr int kDefaultCriticalDiskSpaceMb = 250;
const int64_t kBytesPerMb = 1024 * 1024;
// Defines the threshold in megabytes for the critical disk space pressure
// signal. This is a parameter for the kMacCriticalDiskSpacePressure feature.
BASE_FEATURE_PARAM(int,
kMacCriticalDiskSpacePressureThresholdMB,
&kMacCriticalDiskSpacePressure,
"MacCriticalDiskSpacePressureThresholdMB",
kDefaultCriticalDiskSpaceMb);
// How often to check for free disk space.
constexpr base::TimeDelta kDiskSpaceCheckPeriod = base::Seconds(5);
} // namespace
base::MemoryPressureListener::MemoryPressureLevel
SystemMemoryPressureEvaluator::MemoryPressureLevelForMacMemoryPressureLevel(
int mac_memory_pressure_level) {
switch (mac_memory_pressure_level) {
case DISPATCH_MEMORYPRESSURE_NORMAL:
return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
case DISPATCH_MEMORYPRESSURE_WARN:
if (base::FeatureList::IsEnabled(kSkipModerateMemoryPressureLevelMac)) {
return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
}
return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE;
case DISPATCH_MEMORYPRESSURE_CRITICAL:
return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL;
}
return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
}
SystemMemoryPressureEvaluator::SystemMemoryPressureEvaluator(
std::unique_ptr<MemoryPressureVoter> voter)
: memory_pressure::SystemMemoryPressureEvaluator(std::move(voter)),
memory_level_event_source_(dispatch_source_create(
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE,
0,
DISPATCH_MEMORYPRESSURE_WARN | DISPATCH_MEMORYPRESSURE_CRITICAL |
DISPATCH_MEMORYPRESSURE_NORMAL,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0))),
renotify_current_vote_timer_(
FROM_HERE,
kRenotifyVotePeriod,
base::BindRepeating(&SystemMemoryPressureEvaluator::SendCurrentVote,
base::Unretained(this),
/*notify=*/true)),
disk_check_task_runner_(
base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})),
weak_ptr_factory_(this) {
// A check for available disk space is necessary to generate a
// low-disk-space pressure signal.
//
// To ensure the correct disk volume is checked, this implementation uses
// the user's home directory path, retrieved via `base::PathService`. On
// macOS, the browser's data directory is a subdirectory of home, so this
// correctly targets the volume most relevant to browser performance.
base::PathService::Get(base::DIR_HOME, &user_data_dir_);
// WeakPtr needed because there is no guarantee that |this| is still be alive
// when the task posted to the TaskRunner or event handler runs.
base::WeakPtr<SystemMemoryPressureEvaluator> weak_this =
weak_ptr_factory_.GetWeakPtr();
scoped_refptr<base::TaskRunner> task_runner =
base::SequencedTaskRunner::GetCurrentDefault();
// Attach an event handler to the memory pressure event source.
if (memory_level_event_source_.get()) {
dispatch_source_set_event_handler(memory_level_event_source_.get(), ^{
task_runner->PostTask(
FROM_HERE,
base::BindRepeating(
&SystemMemoryPressureEvaluator::OnMemoryPressureChanged,
weak_this));
});
// Start monitoring the event source.
dispatch_resume(memory_level_event_source_.get());
}
if (base::FeatureList::IsEnabled(kMacCriticalDiskSpacePressure)) {
disk_space_check_timer_.Start(
FROM_HERE, kDiskSpaceCheckPeriod,
base::BindRepeating(&SystemMemoryPressureEvaluator::CheckDiskSpace,
weak_this));
// Perform an initial check on startup.
CheckDiskSpace();
}
}
SystemMemoryPressureEvaluator::~SystemMemoryPressureEvaluator() {
// Remove the memory pressure event source.
if (memory_level_event_source_.get()) {
dispatch_source_cancel(memory_level_event_source_.get());
}
}
int SystemMemoryPressureEvaluator::GetMacMemoryPressureLevel() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Get the raw memory pressure level from macOS.
int mac_memory_pressure_level;
size_t length = sizeof(int);
sysctlbyname("kern.memorystatus_vm_pressure_level",
&mac_memory_pressure_level, &length, nullptr, 0);
return mac_memory_pressure_level;
}
void SystemMemoryPressureEvaluator::UpdatePressureLevel() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Get the current macOS pressure level and convert to the corresponding
// Chrome pressure level.
auto os_pressure_level =
MemoryPressureLevelForMacMemoryPressureLevel(GetMacMemoryPressureLevel());
// The effective pressure level is the most severe of the OS-reported level
// and our disk-space-derived level. If the disk pressure feature is disabled,
// `disk_pressure_vote_` will always be `NONE`.
auto effective_pressure_level =
std::max(os_pressure_level, disk_pressure_vote_);
SetCurrentVote(effective_pressure_level);
}
void SystemMemoryPressureEvaluator::OnMemoryPressureChanged() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
UpdatePressureAndManageNotifications();
}
void SystemMemoryPressureEvaluator::CheckDiskSpace() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
disk_check_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&base::SysInfo::AmountOfFreeDiskSpace, user_data_dir_),
base::BindOnce(&SystemMemoryPressureEvaluator::OnDiskSpaceCheckComplete,
weak_ptr_factory_.GetWeakPtr()));
}
void SystemMemoryPressureEvaluator::OnDiskSpaceCheckComplete(
int64_t free_bytes) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::MemoryPressureListener::MemoryPressureLevel new_disk_vote =
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
const int64_t threshold_mb = kMacCriticalDiskSpacePressureThresholdMB.Get();
// The minimum free disk space in MB before dispatching a critical memory
// pressure signal.
const int64_t critical_disk_space_bytes = threshold_mb * kBytesPerMb;
if (free_bytes != -1 && free_bytes < critical_disk_space_bytes) {
new_disk_vote =
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL;
}
if (disk_pressure_vote_ != new_disk_vote) {
disk_pressure_vote_ = new_disk_vote;
UpdatePressureAndManageNotifications();
}
}
void SystemMemoryPressureEvaluator::UpdatePressureAndManageNotifications() {
// The OS has sent a notification that the memory pressure level has changed.
// Go through the normal memory pressure level checking mechanism so that
// |current_vote_| and UMA get updated to the current value.
UpdatePressureLevel();
// Run the callback that's waiting on memory pressure change notifications.
// The convention is to not send notifiations on memory pressure returning to
// normal.
bool notify = current_vote() !=
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
SendCurrentVote(notify);
if (notify) {
renotify_current_vote_timer_.Reset();
} else {
renotify_current_vote_timer_.Stop();
}
}
} // namespace memory_pressure::mac
|