| /* |
| * Copyright (C) 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "src/traced/probes/kmem_activity_trigger.h" |
| |
| #include <unistd.h> |
| |
| #include "perfetto/base/time.h" |
| #include "perfetto/ext/base/file_utils.h" |
| #include "perfetto/ext/base/waitable_event.h" |
| #include "src/traced/probes/ftrace/ftrace_procfs.h" |
| #include "src/traced/probes/probes_producer.h" |
| |
| namespace perfetto { |
| |
| namespace { |
| constexpr uint32_t kTriggerIntervalMs = 60 * 1000; // 1 min. |
| constexpr size_t kPerCpuTraceBufferSizeInPages = 1; |
| constexpr char kTriggerName[] = "kmem_activity"; |
| |
| } // namespace |
| |
| // This is called by traced_probes' ProbesMain(). |
| KmemActivityTrigger::KmemActivityTrigger() |
| : task_runner_(base::ThreadTaskRunner::CreateAndStart()) { |
| task_runner_.PostTask( |
| [this]() { worker_data_.reset(new WorkerData(&task_runner_)); }); |
| } |
| |
| KmemActivityTrigger::~KmemActivityTrigger() { |
| base::WaitableEvent evt; |
| task_runner_.PostTask([this, &evt]() { |
| worker_data_.reset(); // Destroy the WorkerData object. |
| evt.Notify(); |
| }); |
| evt.Wait(); |
| } |
| |
| KmemActivityTrigger::WorkerData::~WorkerData() { |
| PERFETTO_DCHECK_THREAD(thread_checker_); |
| if (ftrace_procfs_) { |
| ftrace_procfs_->DisableTracing(); |
| ftrace_procfs_->ClearTrace(); |
| } |
| DisarmFtraceFDWatches(); |
| } |
| |
| KmemActivityTrigger::WorkerData::WorkerData(base::TaskRunner* task_runner) |
| : task_runner_(task_runner), weak_ptr_factory_(this) { |
| PERFETTO_DCHECK_THREAD(thread_checker_); |
| |
| ftrace_procfs_ = |
| FtraceProcfs::CreateGuessingMountPoint("instances/mm_events/"); |
| if (!ftrace_procfs_) { |
| #if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD) |
| PERFETTO_LOG( |
| "mm_events ftrace instance not found. Triggering of traces on memory " |
| "pressure will not be available on this device."); |
| #endif |
| return; |
| } |
| |
| ftrace_procfs_->SetCpuBufferSizeInPages(kPerCpuTraceBufferSizeInPages); |
| |
| // Enable mm trace events |
| ftrace_procfs_->DisableAllEvents(); |
| ftrace_procfs_->EnableEvent("vmscan", "mm_vmscan_direct_reclaim_begin"); |
| ftrace_procfs_->EnableEvent("compaction", "mm_compaction_begin"); |
| ftrace_procfs_->EnableTracing(); |
| |
| num_cpus_ = ftrace_procfs_->NumberOfCpus(); |
| for (size_t cpu = 0; cpu < num_cpus_; cpu++) { |
| trace_pipe_fds_.emplace_back(ftrace_procfs_->OpenPipeForCpu(cpu)); |
| auto& scoped_fd = trace_pipe_fds_.back(); |
| if (!scoped_fd) { |
| PERFETTO_PLOG("Failed to open trace_pipe_raw for cpu %zu", cpu); |
| // Deliberately keeping this into the |trace_pipe_fds_| array so there is |
| // a 1:1 mapping between CPU number and index in the array. |
| } else { |
| // Attempt reading from the trace pipe to detect if the CPU is disabled, |
| // since open() doesn't fail. (b/169210648, b/178929757) This doesn't block |
| // as OpenPipeForCpu() opens the pipe in non-blocking mode. |
| char ch; |
| if (base::Read(scoped_fd.get(), &ch, sizeof(char)) < 0 && errno == ENODEV) { |
| scoped_fd.reset(); |
| } |
| } |
| } |
| |
| ArmFtraceFDWatches(); |
| } |
| |
| void KmemActivityTrigger::WorkerData::ArmFtraceFDWatches() { |
| PERFETTO_DCHECK_THREAD(thread_checker_); |
| auto weak_this = weak_ptr_factory_.GetWeakPtr(); |
| if (fd_watches_armed_) |
| return; |
| fd_watches_armed_ = true; |
| for (size_t cpu = 0; cpu < trace_pipe_fds_.size(); cpu++) { |
| const auto& scoped_fd = trace_pipe_fds_[cpu]; |
| if (!scoped_fd) |
| continue; // Can happen if the initial open() failed (CPU hotplug). |
| ftrace_procfs_->ClearPerCpuTrace(cpu); |
| task_runner_->AddFileDescriptorWatch(scoped_fd.get(), [weak_this, cpu] { |
| if (weak_this) |
| weak_this->OnFtracePipeWakeup(cpu); |
| }); |
| } |
| } |
| |
| void KmemActivityTrigger::WorkerData::DisarmFtraceFDWatches() { |
| PERFETTO_DCHECK_THREAD(thread_checker_); |
| if (!fd_watches_armed_) |
| return; |
| fd_watches_armed_ = false; |
| for (const base::ScopedFile& fd : trace_pipe_fds_) { |
| if (fd) |
| task_runner_->RemoveFileDescriptorWatch(fd.get()); |
| } |
| } |
| |
| void KmemActivityTrigger::WorkerData::OnFtracePipeWakeup(size_t cpu) { |
| PERFETTO_DCHECK_THREAD(thread_checker_); |
| PERFETTO_DLOG("KmemActivityTrigger ftrace pipe wakeup on cpu %zu", cpu); |
| ftrace_procfs_->ClearPerCpuTrace(cpu); |
| |
| if (!fd_watches_armed_) { |
| // If false, another task for another CPU got here, disarmed the watches |
| // and posted the re-arming. Don't append another task. |
| return; |
| } |
| |
| ProbesProducer* probes_producer = ProbesProducer::GetInstance(); |
| if (probes_producer) |
| probes_producer->ActivateTrigger(kTriggerName); |
| |
| // Once a ftrace pipe wakes up, disarm the poll() and re-enable only after |
| // kTriggerIntervalMs. This is to avoid spinning on the pipes if there is too |
| // much ftrace activity (b/178929757). |
| |
| DisarmFtraceFDWatches(); |
| |
| auto weak_this = weak_ptr_factory_.GetWeakPtr(); |
| task_runner_->PostDelayedTask( |
| [weak_this] { |
| if (weak_this) |
| weak_this->ArmFtraceFDWatches(); |
| }, |
| kTriggerIntervalMs); |
| } |
| |
| } // namespace perfetto |