/* * Copyright (C) 2018 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/ftrace_reader/ftrace_config_muxer.h" #include <stdint.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <algorithm> #include "perfetto/base/utils.h" #include "src/ftrace_reader/atrace_wrapper.h" #include "src/ftrace_reader/proto_translation_table.h" namespace perfetto { namespace { // trace_clocks in preference order. constexpr const char* kClocks[] = {"boot", "global", "local"}; constexpr int kDefaultPerCpuBufferSizeKb = 512; // 512kb constexpr int kMaxPerCpuBufferSizeKb = 64 * 1024; // 64mb std::vector<std::string> difference(const std::set<std::string>& a, const std::set<std::string>& b) { std::vector<std::string> result; result.reserve(std::max(b.size(), a.size())); std::set_difference(a.begin(), a.end(), b.begin(), b.end(), std::inserter(result, result.begin())); return result; } void AddEventGroup(const ProtoTranslationTable* table, const std::string& group, std::set<std::string>* to) { const std::vector<const Event*>* events = table->GetEventsByGroup(group); if (!events) return; for (const Event* event : *events) to->insert(event->name); } } // namespace std::set<std::string> GetFtraceEvents(const FtraceConfig& request, const ProtoTranslationTable* table) { std::set<std::string> events; events.insert(request.ftrace_events().begin(), request.ftrace_events().end()); if (RequiresAtrace(request)) { events.insert("print"); // Ideally we should keep this code in sync with: // platform/frameworks/native/cmds/atrace/atrace.cpp // It's not a disaster if they go out of sync, we can always add the ftrace // categories manually server side but this is user friendly and reduces the // size of the configs. for (const std::string& category : request.atrace_categories()) { if (category == "gfx") { AddEventGroup(table, "mdss", &events); AddEventGroup(table, "sde", &events); continue; } if (category == "sched") { events.insert("sched_switch"); events.insert("sched_wakeup"); events.insert("sched_waking"); events.insert("sched_blocked_reason"); events.insert("sched_cpu_hotplug"); AddEventGroup(table, "cgroup", &events); continue; } if (category == "irq") { AddEventGroup(table, "irq", &events); AddEventGroup(table, "ipi", &events); continue; } if (category == "irqoff") { events.insert("irq_enable"); events.insert("irq_disable"); continue; } if (category == "preemptoff") { events.insert("preempt_enable"); events.insert("preempt_disable"); continue; } if (category == "i2c") { AddEventGroup(table, "i2c", &events); continue; } if (category == "freq") { events.insert("cpu_frequency"); events.insert("clock_set_rate"); events.insert("clock_disable"); events.insert("clock_enable"); events.insert("clk_set_rate"); events.insert("clk_disable"); events.insert("clk_enable"); events.insert("cpu_frequency_limits"); continue; } if (category == "membus") { AddEventGroup(table, "memory_bus", &events); continue; } if (category == "idle") { events.insert("cpu_idle"); continue; } if (category == "disk") { events.insert("f2fs_sync_file_enter"); events.insert("f2fs_sync_file_exit"); events.insert("f2fs_write_begin"); events.insert("f2fs_write_end"); events.insert("ext4_da_write_begin"); events.insert("ext4_da_write_end"); events.insert("ext4_sync_file_enter"); events.insert("ext4_sync_file_exit"); events.insert("block_rq_issue"); events.insert("block_rq_complete"); continue; } if (category == "mmc") { AddEventGroup(table, "mmc", &events); continue; } if (category == "load") { AddEventGroup(table, "cpufreq_interactive", &events); continue; } if (category == "sync") { AddEventGroup(table, "sync", &events); continue; } if (category == "workq") { AddEventGroup(table, "workqueue", &events); continue; } if (category == "memreclaim") { events.insert("mm_vmscan_direct_reclaim_begin"); events.insert("mm_vmscan_direct_reclaim_end"); events.insert("mm_vmscan_kswapd_wake"); events.insert("mm_vmscan_kswapd_sleep"); AddEventGroup(table, "lowmemorykiller", &events); continue; } if (category == "regulators") { AddEventGroup(table, "regulator", &events); continue; } if (category == "binder_driver") { events.insert("binder_transaction"); events.insert("binder_transaction_received"); events.insert("binder_set_priority"); continue; } if (category == "binder_lock") { events.insert("binder_lock"); events.insert("binder_locked"); events.insert("binder_unlock"); continue; } if (category == "pagecache") { AddEventGroup(table, "pagecache", &events); continue; } } } return events; } // Post-conditions: // 1. result >= 1 (should have at least one page per CPU) // 2. result * 4 < kMaxTotalBufferSizeKb // 3. If input is 0 output is a good default number. size_t ComputeCpuBufferSizeInPages(size_t requested_buffer_size_kb) { if (requested_buffer_size_kb == 0) requested_buffer_size_kb = kDefaultPerCpuBufferSizeKb; if (requested_buffer_size_kb > kMaxPerCpuBufferSizeKb) { PERFETTO_ELOG( "The requested ftrace buf size (%zu KB) is too big, capping to %d KB", requested_buffer_size_kb, kMaxPerCpuBufferSizeKb); requested_buffer_size_kb = kMaxPerCpuBufferSizeKb; } size_t pages = requested_buffer_size_kb / (base::kPageSize / 1024); if (pages == 0) return 1; return pages; } FtraceConfigMuxer::FtraceConfigMuxer(FtraceProcfs* ftrace, const ProtoTranslationTable* table) : ftrace_(ftrace), table_(table), current_state_(), configs_() {} FtraceConfigMuxer::~FtraceConfigMuxer() = default; FtraceConfigId FtraceConfigMuxer::RequestConfig(const FtraceConfig& request) { FtraceConfig actual; bool is_ftrace_enabled = ftrace_->IsTracingEnabled(); if (configs_.empty()) { PERFETTO_DCHECK(!current_state_.tracing_on); // If someone outside of perfetto is using ftrace give up now. if (is_ftrace_enabled) return 0; // If we're about to turn tracing on use this opportunity do some setup: SetupClock(request); SetupBufferSize(request); } else { // Did someone turn ftrace off behind our back? If so give up. if (!is_ftrace_enabled) return 0; } std::set<std::string> events = GetFtraceEvents(request, table_); if (RequiresAtrace(request)) UpdateAtrace(request); for (auto& name : events) { const Event* event = table_->GetEventByName(name); if (!event) { PERFETTO_DLOG("Can't enable %s, event not known", name.c_str()); continue; } if (current_state_.ftrace_events.count(name) || std::string("ftrace") == event->group) { *actual.add_ftrace_events() = name; continue; } if (ftrace_->EnableEvent(event->group, event->name)) { current_state_.ftrace_events.insert(name); *actual.add_ftrace_events() = name; } else { PERFETTO_DPLOG("Failed to enable %s.", name.c_str()); } } if (configs_.empty()) { PERFETTO_DCHECK(!current_state_.tracing_on); ftrace_->EnableTracing(); current_state_.tracing_on = true; } FtraceConfigId id = ++last_id_; configs_.emplace(id, std::move(actual)); return id; } bool FtraceConfigMuxer::RemoveConfig(FtraceConfigId id) { if (!id || !configs_.erase(id)) return false; std::set<std::string> expected_ftrace_events; for (const auto& id_config : configs_) { const FtraceConfig& config = id_config.second; expected_ftrace_events.insert(config.ftrace_events().begin(), config.ftrace_events().end()); } std::vector<std::string> events_to_disable = difference(current_state_.ftrace_events, expected_ftrace_events); for (auto& name : events_to_disable) { const Event* event = table_->GetEventByName(name); if (!event) continue; if (ftrace_->DisableEvent(event->group, event->name)) current_state_.ftrace_events.erase(name); } if (configs_.empty()) { PERFETTO_DCHECK(current_state_.tracing_on); ftrace_->DisableTracing(); ftrace_->SetCpuBufferSizeInPages(0); ftrace_->DisableAllEvents(); ftrace_->ClearTrace(); current_state_.tracing_on = false; if (current_state_.atrace_on) DisableAtrace(); } return true; } const FtraceConfig* FtraceConfigMuxer::GetConfig(FtraceConfigId id) { if (!configs_.count(id)) return nullptr; return &configs_.at(id); } void FtraceConfigMuxer::SetupClock(const FtraceConfig&) { std::string current_clock = ftrace_->GetClock(); std::set<std::string> clocks = ftrace_->AvailableClocks(); for (size_t i = 0; i < base::ArraySize(kClocks); i++) { std::string clock = std::string(kClocks[i]); if (!clocks.count(clock)) continue; if (current_clock == clock) break; ftrace_->SetClock(clock); break; } } void FtraceConfigMuxer::SetupBufferSize(const FtraceConfig& request) { size_t pages = ComputeCpuBufferSizeInPages(request.buffer_size_kb()); ftrace_->SetCpuBufferSizeInPages(pages); current_state_.cpu_buffer_size_pages = pages; } void FtraceConfigMuxer::UpdateAtrace(const FtraceConfig& request) { PERFETTO_DLOG("Update atrace config..."); std::vector<std::string> args; args.push_back("atrace"); // argv0 for exec() args.push_back("--async_start"); args.push_back("--only_userspace"); for (const auto& category : request.atrace_categories()) args.push_back(category); if (!request.atrace_apps().empty()) { args.push_back("-a"); for (const auto& app : request.atrace_apps()) args.push_back(app); } if (RunAtrace(args)) current_state_.atrace_on = true; PERFETTO_DLOG("...done"); } void FtraceConfigMuxer::DisableAtrace() { PERFETTO_DCHECK(current_state_.atrace_on); PERFETTO_DLOG("Stop atrace..."); if (RunAtrace({"atrace", "--async_stop", "--only_userspace"})) current_state_.atrace_on = false; PERFETTO_DLOG("...done"); } } // namespace perfetto