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_->DisableTracing();
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_LOG(
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_->EnableTracing();
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 block
95 // as OpenPipeForCpu() opens the pipe in non-blocking mode.
96 char ch;
97 if (base::Read(scoped_fd.get(), &ch, sizeof(char)) < 0 && errno == ENODEV) {
98 scoped_fd.reset();
99 }
100 }
101 }
102
103 ArmFtraceFDWatches();
104 }
105
ArmFtraceFDWatches()106 void KmemActivityTrigger::WorkerData::ArmFtraceFDWatches() {
107 PERFETTO_DCHECK_THREAD(thread_checker_);
108 auto weak_this = weak_ptr_factory_.GetWeakPtr();
109 if (fd_watches_armed_)
110 return;
111 fd_watches_armed_ = true;
112 for (size_t cpu = 0; cpu < trace_pipe_fds_.size(); cpu++) {
113 const auto& scoped_fd = trace_pipe_fds_[cpu];
114 if (!scoped_fd)
115 continue; // Can happen if the initial open() failed (CPU hotplug).
116 ftrace_procfs_->ClearPerCpuTrace(cpu);
117 task_runner_->AddFileDescriptorWatch(scoped_fd.get(), [weak_this, cpu] {
118 if (weak_this)
119 weak_this->OnFtracePipeWakeup(cpu);
120 });
121 }
122 }
123
DisarmFtraceFDWatches()124 void KmemActivityTrigger::WorkerData::DisarmFtraceFDWatches() {
125 PERFETTO_DCHECK_THREAD(thread_checker_);
126 if (!fd_watches_armed_)
127 return;
128 fd_watches_armed_ = false;
129 for (const base::ScopedFile& fd : trace_pipe_fds_) {
130 if (fd)
131 task_runner_->RemoveFileDescriptorWatch(fd.get());
132 }
133 }
134
OnFtracePipeWakeup(size_t cpu)135 void KmemActivityTrigger::WorkerData::OnFtracePipeWakeup(size_t cpu) {
136 PERFETTO_DCHECK_THREAD(thread_checker_);
137 PERFETTO_DLOG("KmemActivityTrigger ftrace pipe wakeup on cpu %zu", cpu);
138 ftrace_procfs_->ClearPerCpuTrace(cpu);
139
140 if (!fd_watches_armed_) {
141 // If false, another task for another CPU got here, disarmed the watches
142 // and posted the re-arming. Don't append another task.
143 return;
144 }
145
146 ProbesProducer* probes_producer = ProbesProducer::GetInstance();
147 if (probes_producer)
148 probes_producer->ActivateTrigger(kTriggerName);
149
150 // Once a ftrace pipe wakes up, disarm the poll() and re-enable only after
151 // kTriggerIntervalMs. This is to avoid spinning on the pipes if there is too
152 // much ftrace activity (b/178929757).
153
154 DisarmFtraceFDWatches();
155
156 auto weak_this = weak_ptr_factory_.GetWeakPtr();
157 task_runner_->PostDelayedTask(
158 [weak_this] {
159 if (weak_this)
160 weak_this->ArmFtraceFDWatches();
161 },
162 kTriggerIntervalMs);
163 }
164
165 } // namespace perfetto
166