blob: 641bd286b6d5d2bb0a1fbcf7ea5e91f0dbe253c2 [file] [log] [blame]
/*
* 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 "atrace_wrapper.h"
#include "perfetto/base/utils.h"
#include "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 = 2 * 1024; // 2mb
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)
requested_buffer_size_kb = kDefaultPerCpuBufferSizeKb;
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:
if (RequiresAtrace(request))
EnableAtrace(request);
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_);
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::EnableAtrace(const FtraceConfig& request) {
PERFETTO_DCHECK(!current_state_.atrace_on);
PERFETTO_DLOG("Start atrace...");
std::vector<std::string> args;
args.push_back("atrace"); // argv0 for exec()
args.push_back("--async_start");
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"}))
current_state_.atrace_on = false;
PERFETTO_DLOG("...done");
}
} // namespace perfetto