xref: /aosp_15_r20/external/perfetto/src/traced/probes/kmem_activity_trigger.cc (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "src/traced/probes/kmem_activity_trigger.h"
18 
19 #include <unistd.h>
20 
21 #include "perfetto/base/time.h"
22 #include "perfetto/ext/base/file_utils.h"
23 #include "perfetto/ext/base/waitable_event.h"
24 #include "src/traced/probes/ftrace/ftrace_procfs.h"
25 #include "src/traced/probes/probes_producer.h"
26 
27 namespace perfetto {
28 
29 namespace {
30 constexpr uint32_t kTriggerIntervalMs = 60 * 1000;  // 1 min.
31 constexpr size_t kPerCpuTraceBufferSizeInPages = 1;
32 constexpr char kTriggerName[] = "kmem_activity";
33 
34 }  // namespace
35 
36 // This is called by traced_probes' ProbesMain().
KmemActivityTrigger()37 KmemActivityTrigger::KmemActivityTrigger()
38     : task_runner_(base::ThreadTaskRunner::CreateAndStart()) {
39   task_runner_.PostTask(
40       [this]() { worker_data_.reset(new WorkerData(&task_runner_)); });
41 }
42 
~KmemActivityTrigger()43 KmemActivityTrigger::~KmemActivityTrigger() {
44   base::WaitableEvent evt;
45   task_runner_.PostTask([this, &evt]() {
46     worker_data_.reset();  // Destroy the WorkerData object.
47     evt.Notify();
48   });
49   evt.Wait();
50 }
51 
~WorkerData()52 KmemActivityTrigger::WorkerData::~WorkerData() {
53   PERFETTO_DCHECK_THREAD(thread_checker_);
54   if (ftrace_procfs_) {
55     ftrace_procfs_->SetTracingOn(false);
56     ftrace_procfs_->ClearTrace();
57   }
58   DisarmFtraceFDWatches();
59 }
60 
WorkerData(base::TaskRunner * task_runner)61 KmemActivityTrigger::WorkerData::WorkerData(base::TaskRunner* task_runner)
62     : task_runner_(task_runner), weak_ptr_factory_(this) {
63   PERFETTO_DCHECK_THREAD(thread_checker_);
64 
65   ftrace_procfs_ =
66       FtraceProcfs::CreateGuessingMountPoint("instances/mm_events/");
67   if (!ftrace_procfs_) {
68 #if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
69     PERFETTO_DLOG(
70         "mm_events ftrace instance not found. Triggering of traces on memory "
71         "pressure will not be available on this device.");
72 #endif
73     return;
74   }
75 
76   ftrace_procfs_->SetCpuBufferSizeInPages(kPerCpuTraceBufferSizeInPages);
77 
78   // Enable mm trace events
79   ftrace_procfs_->DisableAllEvents();
80   ftrace_procfs_->EnableEvent("vmscan", "mm_vmscan_direct_reclaim_begin");
81   ftrace_procfs_->EnableEvent("compaction", "mm_compaction_begin");
82   ftrace_procfs_->SetTracingOn(true);
83 
84   num_cpus_ = ftrace_procfs_->NumberOfCpus();
85   for (size_t cpu = 0; cpu < num_cpus_; cpu++) {
86     trace_pipe_fds_.emplace_back(ftrace_procfs_->OpenPipeForCpu(cpu));
87     auto& scoped_fd = trace_pipe_fds_.back();
88     if (!scoped_fd) {
89       PERFETTO_PLOG("Failed to open trace_pipe_raw for cpu %zu", cpu);
90       // Deliberately keeping this into the |trace_pipe_fds_| array so there is
91       // a 1:1 mapping between CPU number and index in the array.
92     } else {
93       // Attempt reading from the trace pipe to detect if the CPU is disabled,
94       // since open() doesn't fail. (b/169210648, b/178929757) This doesn't
95       // block as OpenPipeForCpu() opens the pipe in non-blocking mode.
96       char ch;
97       if (base::Read(scoped_fd.get(), &ch, sizeof(char)) < 0 &&
98           errno == ENODEV) {
99         scoped_fd.reset();
100       }
101     }
102   }
103 
104   ArmFtraceFDWatches();
105 }
106 
ArmFtraceFDWatches()107 void KmemActivityTrigger::WorkerData::ArmFtraceFDWatches() {
108   PERFETTO_DCHECK_THREAD(thread_checker_);
109   auto weak_this = weak_ptr_factory_.GetWeakPtr();
110   if (fd_watches_armed_)
111     return;
112   fd_watches_armed_ = true;
113   for (size_t cpu = 0; cpu < trace_pipe_fds_.size(); cpu++) {
114     const auto& scoped_fd = trace_pipe_fds_[cpu];
115     if (!scoped_fd)
116       continue;  // Can happen if the initial open() failed (CPU hotplug).
117     ftrace_procfs_->ClearPerCpuTrace(cpu);
118     task_runner_->AddFileDescriptorWatch(scoped_fd.get(), [weak_this, cpu] {
119       if (weak_this)
120         weak_this->OnFtracePipeWakeup(cpu);
121     });
122   }
123 }
124 
DisarmFtraceFDWatches()125 void KmemActivityTrigger::WorkerData::DisarmFtraceFDWatches() {
126   PERFETTO_DCHECK_THREAD(thread_checker_);
127   if (!fd_watches_armed_)
128     return;
129   fd_watches_armed_ = false;
130   for (const base::ScopedFile& fd : trace_pipe_fds_) {
131     if (fd)
132       task_runner_->RemoveFileDescriptorWatch(fd.get());
133   }
134 }
135 
OnFtracePipeWakeup(size_t cpu)136 void KmemActivityTrigger::WorkerData::OnFtracePipeWakeup(size_t cpu) {
137   PERFETTO_DCHECK_THREAD(thread_checker_);
138   PERFETTO_DLOG("KmemActivityTrigger ftrace pipe wakeup on cpu %zu", cpu);
139   ftrace_procfs_->ClearPerCpuTrace(cpu);
140 
141   if (!fd_watches_armed_) {
142     // If false, another task for another CPU got here, disarmed the watches
143     // and posted the re-arming. Don't append another task.
144     return;
145   }
146 
147   ProbesProducer* probes_producer = ProbesProducer::GetInstance();
148   if (probes_producer)
149     probes_producer->ActivateTrigger(kTriggerName);
150 
151   // Once a ftrace pipe wakes up, disarm the poll() and re-enable only after
152   // kTriggerIntervalMs. This is to avoid spinning on the pipes if there is too
153   // much ftrace activity (b/178929757).
154 
155   DisarmFtraceFDWatches();
156 
157   auto weak_this = weak_ptr_factory_.GetWeakPtr();
158   task_runner_->PostDelayedTask(
159       [weak_this] {
160         if (weak_this)
161           weak_this->ArmFtraceFDWatches();
162       },
163       kTriggerIntervalMs);
164 }
165 
166 }  // namespace perfetto
167