1 // Copyright 2024 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/android/pre_freeze_background_memory_trimmer.h"
6
7 #include <optional>
8 #include <string>
9
10 #include "base/android/build_info.h"
11 #include "base/android/pmf_utils.h"
12 #include "base/check.h"
13 #include "base/command_line.h"
14 #include "base/feature_list.h"
15 #include "base/functional/bind.h"
16 #include "base/logging.h"
17 #include "base/metrics/histogram_functions.h"
18 #include "base/strings/strcat.h"
19 #include "base/task/sequenced_task_runner.h"
20 #include "base/task/thread_pool.h"
21 #include "base/task/thread_pool/thread_pool_instance.h"
22 #include "base/time/time.h"
23
24 namespace base::android {
25 namespace {
26
27 // This constant is chosen arbitrarily, to allow time for the background tasks
28 // to finish running BEFORE collecting metrics.
29 const base::TimeDelta kDelayForMetrics = base::Seconds(2);
30
GetPrivateMemoryFootprint()31 std::optional<uint64_t> GetPrivateMemoryFootprint() {
32 return PmfUtils::GetPrivateMemoryFootprintForCurrentProcess();
33 }
34
BytesToMiB(uint64_t v)35 uint64_t BytesToMiB(uint64_t v) {
36 return v / 1024 / 1024;
37 }
38
GetProcessType()39 const char* GetProcessType() {
40 CHECK(base::CommandLine::InitializedForCurrentProcess());
41 const std::string type =
42 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII("type");
43 const char* process_type = type == "" ? "Browser"
44 : type == "renderer" ? "Renderer"
45 : type == "gpu-process" ? "Gpu"
46 : type == "utility" ? "Utility"
47 : "Unknown";
48 return process_type;
49 }
50
GetMetricName(const char * suffix)51 std::string GetMetricName(const char* suffix) {
52 CHECK(base::CommandLine::InitializedForCurrentProcess());
53 const char* process_type = GetProcessType();
54 return StrCat(
55 {"Memory.PreFreeze2.", process_type, ".PrivateMemoryFootprint.", suffix});
56 }
57
MaybeRecordMetric(const std::string metric_name,std::optional<uint64_t> value_bytes)58 void MaybeRecordMetric(const std::string metric_name,
59 std::optional<uint64_t> value_bytes) {
60 // Skip recording the metric if we failed to get the PMF.
61 if (!value_bytes.has_value()) {
62 return;
63 }
64 UmaHistogramMemoryMB(metric_name,
65 static_cast<int>(BytesToMiB(value_bytes.value())));
66 }
67
PmfDiff(std::optional<uint64_t> pmf_before,std::optional<uint64_t> pmf_after)68 std::optional<uint64_t> PmfDiff(std::optional<uint64_t> pmf_before,
69 std::optional<uint64_t> pmf_after) {
70 if (!pmf_before.has_value() || !pmf_before.has_value()) {
71 return std::nullopt;
72 }
73
74 const uint64_t pmf_before_value = pmf_before.value();
75 const uint64_t pmf_after_value = pmf_after.value();
76
77 return pmf_after_value < pmf_before_value ? pmf_before_value - pmf_after_value
78 : 0;
79 }
80
RecordMetrics(std::optional<uint64_t> pmf_before)81 void RecordMetrics(std::optional<uint64_t> pmf_before) {
82 CHECK(base::CommandLine::InitializedForCurrentProcess());
83
84 std::string before_name = GetMetricName("Before");
85 std::string after_name = GetMetricName("After");
86 std::string diff_name = GetMetricName("Diff");
87
88 std::optional<uint64_t> pmf_after = GetPrivateMemoryFootprint();
89
90 MaybeRecordMetric(before_name, pmf_before);
91 MaybeRecordMetric(after_name, pmf_after);
92 MaybeRecordMetric(diff_name, PmfDiff(pmf_before, pmf_after));
93 }
94
95 } // namespace
96
97 BASE_FEATURE(kOnPreFreezeMemoryTrim,
98 "OnPreFreezeMemoryTrim",
99 FEATURE_DISABLED_BY_DEFAULT);
100
PreFreezeBackgroundMemoryTrimmer()101 PreFreezeBackgroundMemoryTrimmer::PreFreezeBackgroundMemoryTrimmer()
102 : supports_modern_trim_(BuildInfo::GetInstance()->sdk_int() >=
103 SDK_VERSION_U) {}
104
105 // static
Instance()106 PreFreezeBackgroundMemoryTrimmer& PreFreezeBackgroundMemoryTrimmer::Instance() {
107 static base::NoDestructor<PreFreezeBackgroundMemoryTrimmer> instance;
108 return *instance;
109 }
110
PostMetricsTask(std::optional<uint64_t> pmf_before)111 void PreFreezeBackgroundMemoryTrimmer::PostMetricsTask(
112 std::optional<uint64_t> pmf_before) {
113 // PreFreeze is only for Android U and greater, so no need to record metrics
114 // for older versions.
115 if (!SupportsModernTrim()) {
116 return;
117 }
118
119 // We need the process type to record the metrics below, which we get from
120 // the command line. We cannot post the task below if the thread pool is not
121 // initialized yet.
122 if (!base::CommandLine::InitializedForCurrentProcess() ||
123 !base::ThreadPoolInstance::Get()) {
124 return;
125 }
126
127 // The posted task will be more likely to survive background killing in
128 // experiments that change the memory trimming behavior. Run as USER_BLOCKING
129 // to reduce this sample imbalance in experiment groups. Normally tasks
130 // collecting metrics should use BEST_EFFORT, but when running in background a
131 // number of subtle effects may influence the real delay of those tasks. The
132 // USER_BLOCKING will allow to estimate the number of better-survived tasks
133 // more precisely.
134 base::ThreadPool::PostDelayedTask(
135 FROM_HERE, {base::TaskPriority::USER_BLOCKING, MayBlock()},
136 base::BindOnce(&RecordMetrics, pmf_before), kDelayForMetrics);
137 }
138
139 // static
PostDelayedBackgroundTask(scoped_refptr<base::SequencedTaskRunner> task_runner,const base::Location & from_here,OnceCallback<void (MemoryReductionTaskContext)> task,base::TimeDelta delay)140 void PreFreezeBackgroundMemoryTrimmer::PostDelayedBackgroundTask(
141 scoped_refptr<base::SequencedTaskRunner> task_runner,
142 const base::Location& from_here,
143 OnceCallback<void(MemoryReductionTaskContext)> task,
144 base::TimeDelta delay) {
145 // Preserve previous behaviour on versions before Android U.
146 if (!SupportsModernTrim()) {
147 task_runner->PostDelayedTask(
148 from_here,
149 BindOnce(std::move(task), MemoryReductionTaskContext::kDelayExpired),
150 delay);
151 return;
152 }
153
154 Instance().PostDelayedBackgroundTaskInternal(task_runner, from_here,
155 std::move(task), delay);
156 }
157
PostDelayedBackgroundTaskInternal(scoped_refptr<base::SequencedTaskRunner> task_runner,const base::Location & from_here,OnceCallback<void (MemoryReductionTaskContext)> task,base::TimeDelta delay)158 void PreFreezeBackgroundMemoryTrimmer::PostDelayedBackgroundTaskInternal(
159 scoped_refptr<base::SequencedTaskRunner> task_runner,
160 const base::Location& from_here,
161 OnceCallback<void(MemoryReductionTaskContext)> task,
162 base::TimeDelta delay) {
163 DCHECK(SupportsModernTrim());
164
165 {
166 base::AutoLock locker(lock_);
167 did_register_task_ = true;
168 }
169 if (!base::FeatureList::IsEnabled(kOnPreFreezeMemoryTrim)) {
170 task_runner->PostDelayedTask(
171 from_here,
172 BindOnce(std::move(task), MemoryReductionTaskContext::kDelayExpired),
173 delay);
174 return;
175 }
176
177 PostDelayedBackgroundTaskModern(task_runner, from_here, std::move(task),
178 delay);
179 }
180
PostDelayedBackgroundTaskModern(scoped_refptr<base::SequencedTaskRunner> task_runner,const base::Location & from_here,OnceCallback<void (MemoryReductionTaskContext)> task,base::TimeDelta delay)181 void PreFreezeBackgroundMemoryTrimmer::PostDelayedBackgroundTaskModern(
182 scoped_refptr<base::SequencedTaskRunner> task_runner,
183 const base::Location& from_here,
184 OnceCallback<void(MemoryReductionTaskContext)> task,
185 base::TimeDelta delay) {
186 // We create a cancellable delayed task (below), which must be done on the
187 // same TaskRunner that will run the task eventually, so we may need to
188 // repost this on the correct TaskRunner.
189 if (!task_runner->RunsTasksInCurrentSequence()) {
190 // |base::Unretained(this)| is safe here because we never destroy |this|.
191 task_runner->PostTask(
192 FROM_HERE,
193 base::BindOnce(
194 &PreFreezeBackgroundMemoryTrimmer::PostDelayedBackgroundTaskModern,
195 base::Unretained(this), task_runner, from_here, std::move(task),
196 delay));
197 return;
198 }
199
200 base::AutoLock locker(lock_);
201 PostDelayedBackgroundTaskModernHelper(std::move(task_runner), from_here,
202 std::move(task), delay);
203 }
204
205 PreFreezeBackgroundMemoryTrimmer::BackgroundTask*
PostDelayedBackgroundTaskModernHelper(scoped_refptr<SequencedTaskRunner> task_runner,const Location & from_here,OnceCallback<void (MemoryReductionTaskContext)> task,TimeDelta delay)206 PreFreezeBackgroundMemoryTrimmer::PostDelayedBackgroundTaskModernHelper(
207 scoped_refptr<SequencedTaskRunner> task_runner,
208 const Location& from_here,
209 OnceCallback<void(MemoryReductionTaskContext)> task,
210 TimeDelta delay) {
211 std::unique_ptr<BackgroundTask> background_task =
212 BackgroundTask::Create(task_runner, from_here, std::move(task), delay);
213 auto* ptr = background_task.get();
214 background_tasks_.push_back(std::move(background_task));
215 return ptr;
216 }
217
218 // static
OnPreFreeze()219 void PreFreezeBackgroundMemoryTrimmer::OnPreFreeze() {
220 Instance().OnPreFreezeInternal();
221 }
222
OnPreFreezeInternal()223 void PreFreezeBackgroundMemoryTrimmer::OnPreFreezeInternal() {
224 base::AutoLock locker(lock_);
225 if (did_register_task_) {
226 PostMetricsTask(GetPrivateMemoryFootprint());
227 }
228
229 if (!ShouldUseModernTrim()) {
230 return;
231 }
232
233 // We check |num_pending_tasks-- > 0| so that we have an upper limit on the
234 // number of tasks that we run.
235 // We check |!background_tasks_.empty()| so that we exit as soon as we have
236 // no more tasks to run.
237 //
238 // This handles both the case where we have tasks that post other tasks (we
239 // won't run endlessly because of the upper limit), and the case where tasks
240 // cancel other tasks (we exit as soon as the queue is empty).
241 //
242 // Note that the current implementation may run some tasks that were posted
243 // by earlier tasks, if some other tasks are also cancelled, but we
244 // stop eventually due to the upper limit.
245 size_t num_pending_tasks = background_tasks_.size();
246 while (num_pending_tasks-- > 0 && !background_tasks_.empty()) {
247 auto background_task = std::move(background_tasks_.front());
248 background_tasks_.pop_front();
249 // We release the lock here for two reasons:
250 // (1) To avoid holding it too long while running all the background tasks.
251 // (2) To prevent a deadlock if the |background_task| needs to acquire the
252 // lock (e.g. to post another task).
253 base::AutoUnlock unlocker(lock_);
254 BackgroundTask::RunNow(std::move(background_task));
255 }
256 }
257
258 // static
UnregisterBackgroundTask(BackgroundTask * task)259 void PreFreezeBackgroundMemoryTrimmer::UnregisterBackgroundTask(
260 BackgroundTask* task) {
261 return Instance().UnregisterBackgroundTaskInternal(task);
262 }
263
UnregisterBackgroundTaskInternal(BackgroundTask * timer)264 void PreFreezeBackgroundMemoryTrimmer::UnregisterBackgroundTaskInternal(
265 BackgroundTask* timer) {
266 base::AutoLock locker(lock_);
267 std::erase_if(background_tasks_, [&](auto& t) { return t.get() == timer; });
268 }
269
270 // static
SetDidRegisterTask()271 void PreFreezeBackgroundMemoryTrimmer::SetDidRegisterTask() {
272 Instance().SetDidRegisterTaskInternal();
273 }
274
SetDidRegisterTaskInternal()275 void PreFreezeBackgroundMemoryTrimmer::SetDidRegisterTaskInternal() {
276 base::AutoLock locker(lock_);
277 did_register_task_ = true;
278 }
279
280 // static
SupportsModernTrim()281 bool PreFreezeBackgroundMemoryTrimmer::SupportsModernTrim() {
282 return Instance().supports_modern_trim_;
283 }
284
285 // static
ShouldUseModernTrim()286 bool PreFreezeBackgroundMemoryTrimmer::ShouldUseModernTrim() {
287 return SupportsModernTrim() &&
288 base::FeatureList::IsEnabled(kOnPreFreezeMemoryTrim);
289 }
290
291 // static
SetSupportsModernTrimForTesting(bool is_supported)292 void PreFreezeBackgroundMemoryTrimmer::SetSupportsModernTrimForTesting(
293 bool is_supported) {
294 Instance().supports_modern_trim_ = is_supported;
295 }
296
297 // static
SetDidRegisterTasksForTesting(bool did_register_task)298 void PreFreezeBackgroundMemoryTrimmer::SetDidRegisterTasksForTesting(
299 bool did_register_task) {
300 base::AutoLock locker(Instance().lock_);
301 Instance().did_register_task_ = did_register_task;
302 }
303
304 size_t PreFreezeBackgroundMemoryTrimmer::
GetNumberOfPendingBackgroundTasksForTesting()305 GetNumberOfPendingBackgroundTasksForTesting() {
306 base::AutoLock locker(lock_);
307 return background_tasks_.size();
308 }
309
DidRegisterTasksForTesting()310 bool PreFreezeBackgroundMemoryTrimmer::DidRegisterTasksForTesting() {
311 base::AutoLock locker(lock_);
312 return did_register_task_;
313 }
314
315 // static
RunNow(std::unique_ptr<PreFreezeBackgroundMemoryTrimmer::BackgroundTask> background_task)316 void PreFreezeBackgroundMemoryTrimmer::BackgroundTask::RunNow(
317 std::unique_ptr<PreFreezeBackgroundMemoryTrimmer::BackgroundTask>
318 background_task) {
319 if (!background_task->task_runner_->RunsTasksInCurrentSequence()) {
320 background_task->task_runner_->PostTask(
321 FROM_HERE,
322 base::BindOnce(&BackgroundTask::RunNow, std::move(background_task)));
323 return;
324 }
325
326 // We check that the task has not been run already. If it has, we do not run
327 // it again.
328 if (background_task->task_handle_.IsValid()) {
329 background_task->task_handle_.CancelTask();
330 } else {
331 return;
332 }
333
334 background_task->Run(MemoryReductionTaskContext::kProactive);
335 }
336
CancelTask()337 void PreFreezeBackgroundMemoryTrimmer::BackgroundTask::CancelTask() {
338 if (task_handle_.IsValid()) {
339 task_handle_.CancelTask();
340 PreFreezeBackgroundMemoryTrimmer::UnregisterBackgroundTask(this);
341 }
342 }
343
344 // static
345 std::unique_ptr<PreFreezeBackgroundMemoryTrimmer::BackgroundTask>
Create(scoped_refptr<base::SequencedTaskRunner> task_runner,const base::Location & from_here,OnceCallback<void (MemoryReductionTaskContext)> task,base::TimeDelta delay)346 PreFreezeBackgroundMemoryTrimmer::BackgroundTask::Create(
347 scoped_refptr<base::SequencedTaskRunner> task_runner,
348 const base::Location& from_here,
349 OnceCallback<void(MemoryReductionTaskContext)> task,
350 base::TimeDelta delay) {
351 DCHECK(task_runner->RunsTasksInCurrentSequence());
352 auto background_task = std::make_unique<BackgroundTask>(task_runner);
353 background_task->Start(from_here, delay, std::move(task));
354 return background_task;
355 }
356
BackgroundTask(scoped_refptr<base::SequencedTaskRunner> task_runner)357 PreFreezeBackgroundMemoryTrimmer::BackgroundTask::BackgroundTask(
358 scoped_refptr<base::SequencedTaskRunner> task_runner)
359 : task_runner_(task_runner) {}
360
361 PreFreezeBackgroundMemoryTrimmer::BackgroundTask::~BackgroundTask() = default;
362
Run(MemoryReductionTaskContext from_pre_freeze)363 void PreFreezeBackgroundMemoryTrimmer::BackgroundTask::Run(
364 MemoryReductionTaskContext from_pre_freeze) {
365 DCHECK(!task_handle_.IsValid());
366 std::move(task_).Run(from_pre_freeze);
367 }
368
Start(const base::Location & from_here,base::TimeDelta delay,OnceCallback<void (MemoryReductionTaskContext)> task)369 void PreFreezeBackgroundMemoryTrimmer::BackgroundTask::Start(
370 const base::Location& from_here,
371 base::TimeDelta delay,
372 OnceCallback<void(MemoryReductionTaskContext)> task) {
373 task_ = std::move(task);
374 task_handle_ = task_runner_->PostCancelableDelayedTask(
375 subtle::PostDelayedTaskPassKey(), from_here,
376 base::BindOnce(
377 [](BackgroundTask* p) {
378 p->Run(MemoryReductionTaskContext::kDelayExpired);
379 UnregisterBackgroundTask(p);
380 },
381 this),
382 delay);
383 }
384
385 } // namespace base::android
386