File: system_memory_pressure_evaluator_mac.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (223 lines) | stat: -rw-r--r-- 8,864 bytes parent folder | download | duplicates (3)
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