xref: /aosp_15_r20/external/cronet/base/android/pre_freeze_background_memory_trimmer.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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