blob: 2e2ec579f3eddf3cbccbc69620360edea223ea0c [file] [log] [blame]
/*
* Copyright (C) 2017 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 "perfetto/ftrace_reader/ftrace_controller.h"
#include <fcntl.h>
#include <stdint.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <array>
#include <string>
#include "cpu_reader.h"
#include "event_info.h"
#include "ftrace_procfs.h"
#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
#include "perfetto/base/utils.h"
#include "proto_translation_table.h"
#include "perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
namespace perfetto {
namespace {
#if BUILDFLAG(OS_ANDROID)
const char* kTracingPaths[] = {
"/sys/kernel/tracing/", "/sys/kernel/debug/tracing/", nullptr,
};
#else
const char* kTracingPaths[] = {
"/sys/kernel/debug/tracing/", nullptr,
};
#endif
const int kDefaultDrainPeriodMs = 100;
const int kMinDrainPeriodMs = 1;
const int kMaxDrainPeriodMs = 1000 * 60;
const int kDefaultTotalBufferSizeKb = 1024 * 4; // 4mb
const int kMaxTotalBufferSizeKb = 1024 * 8; // 8mb
uint32_t ClampDrainPeriodMs(uint32_t drain_period_ms) {
if (drain_period_ms == 0) {
return kDefaultDrainPeriodMs;
}
if (drain_period_ms < kMinDrainPeriodMs ||
kMaxDrainPeriodMs < drain_period_ms) {
PERFETTO_LOG("drain_period_ms was %u should be between %u and %u",
drain_period_ms, kMinDrainPeriodMs, kMaxDrainPeriodMs);
return kDefaultDrainPeriodMs;
}
return drain_period_ms;
}
// 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(uint32_t requested_buffer_size_kb) {
if (requested_buffer_size_kb == 0)
requested_buffer_size_kb = kDefaultTotalBufferSizeKb;
if (requested_buffer_size_kb > kMaxTotalBufferSizeKb)
requested_buffer_size_kb = kDefaultTotalBufferSizeKb;
size_t pages = requested_buffer_size_kb / (base::kPageSize / 1024);
if (pages == 0)
return 1;
return pages;
}
bool RunAtrace(std::vector<std::string> args) {
int status = 1;
std::vector<char*> argv;
// args, and then a null.
argv.reserve(1 + args.size());
for (const auto& arg : args)
argv.push_back(const_cast<char*>(arg.c_str()));
argv.push_back(nullptr);
pid_t pid = fork();
PERFETTO_CHECK(pid >= 0);
if (pid == 0) {
execv("/system/bin/atrace", &argv[0]);
// Reached only if execv fails.
_exit(1);
}
waitpid(pid, &status, 0);
return status == 0;
}
} // namespace
// static
// TODO(taylori): Add a test for tracing paths in integration tests.
std::unique_ptr<FtraceController> FtraceController::Create(
base::TaskRunner* runner) {
size_t index = 0;
std::unique_ptr<FtraceProcfs> ftrace_procfs = nullptr;
while (!ftrace_procfs && kTracingPaths[index]) {
ftrace_procfs = FtraceProcfs::Create(kTracingPaths[index++]);
}
if (!ftrace_procfs) {
return nullptr;
}
auto table = ProtoTranslationTable::Create(
ftrace_procfs.get(), GetStaticEventInfo(), GetStaticCommonFieldsInfo());
return std::unique_ptr<FtraceController>(
new FtraceController(std::move(ftrace_procfs), runner, std::move(table)));
}
FtraceController::FtraceController(std::unique_ptr<FtraceProcfs> ftrace_procfs,
base::TaskRunner* task_runner,
std::unique_ptr<ProtoTranslationTable> table)
: ftrace_procfs_(std::move(ftrace_procfs)),
task_runner_(task_runner),
enabled_count_(table->largest_id() + 1),
table_(std::move(table)),
weak_factory_(this) {}
FtraceController::~FtraceController() {
for (size_t id = 1; id <= table_->largest_id(); id++) {
if (enabled_count_[id]) {
const Event* event = table_->GetEventById(id);
ftrace_procfs_->DisableEvent(event->group, event->name);
}
}
if (listening_for_raw_trace_data_) {
sinks_.clear();
StopIfNeeded();
}
}
// static
void FtraceController::PeriodicDrainCPU(
base::WeakPtr<FtraceController> weak_this,
size_t generation,
int cpu) {
// The controller might be gone.
if (!weak_this)
return;
// We might have stopped caring about events.
if (!weak_this->listening_for_raw_trace_data_)
return;
// We might have stopped tracing then quickly re-enabled it, in this case
// we don't want to end up with two periodic tasks for each CPU:
if (weak_this->generation_ != generation)
return;
bool has_more = weak_this->OnRawFtraceDataAvailable(cpu);
weak_this->task_runner_->PostDelayedTask(
std::bind(&FtraceController::PeriodicDrainCPU, weak_this, generation,
cpu),
has_more ? 0 : weak_this->GetDrainPeriodMs());
}
void FtraceController::StartIfNeeded() {
if (sinks_.size() > 1)
return;
PERFETTO_CHECK(sinks_.size() != 0);
PERFETTO_CHECK(!listening_for_raw_trace_data_);
listening_for_raw_trace_data_ = true;
ftrace_procfs_->SetCpuBufferSizeInPages(GetCpuBufferSizeInPages());
ftrace_procfs_->EnableTracing();
generation_++;
for (size_t cpu = 0; cpu < ftrace_procfs_->NumberOfCpus(); cpu++) {
base::WeakPtr<FtraceController> weak_this = weak_factory_.GetWeakPtr();
task_runner_->PostDelayedTask(std::bind(&FtraceController::PeriodicDrainCPU,
weak_this, generation_, cpu),
GetDrainPeriodMs());
}
}
uint32_t FtraceController::GetDrainPeriodMs() {
if (sinks_.size() == 0)
return kDefaultDrainPeriodMs;
uint32_t min_drain_period_ms = kMaxDrainPeriodMs + 1;
for (const FtraceSink* sink : sinks_) {
if (sink->config().drain_period_ms() < min_drain_period_ms)
min_drain_period_ms = sink->config().drain_period_ms();
}
return ClampDrainPeriodMs(min_drain_period_ms);
}
uint32_t FtraceController::GetCpuBufferSizeInPages() {
uint32_t max_buffer_size_kb = 0;
for (const FtraceSink* sink : sinks_) {
if (sink->config().buffer_size_kb() > max_buffer_size_kb)
max_buffer_size_kb = sink->config().buffer_size_kb();
}
return ComputeCpuBufferSizeInPages(max_buffer_size_kb);
}
void FtraceController::ClearTrace() {
ftrace_procfs_->ClearTrace();
}
void FtraceController::DisableAllEvents() {
ftrace_procfs_->DisableAllEvents();
}
void FtraceController::WriteTraceMarker(const std::string& s) {
ftrace_procfs_->WriteTraceMarker(s);
}
void FtraceController::StopIfNeeded() {
if (sinks_.size() != 0)
return;
PERFETTO_CHECK(listening_for_raw_trace_data_);
listening_for_raw_trace_data_ = false;
readers_.clear();
ftrace_procfs_->DisableTracing();
}
bool FtraceController::OnRawFtraceDataAvailable(size_t cpu) {
CpuReader* reader = GetCpuReader(cpu);
using BundleHandle =
protozero::ProtoZeroMessageHandle<protos::pbzero::FtraceEventBundle>;
std::array<const EventFilter*, kMaxSinks> filters{};
std::array<BundleHandle, kMaxSinks> bundles{};
size_t sink_count = sinks_.size();
size_t i = 0;
for (FtraceSink* sink : sinks_) {
filters[i] = sink->get_event_filter();
bundles[i++] = sink->GetBundleForCpu(cpu);
}
bool res = reader->Drain(filters, bundles);
i = 0;
for (FtraceSink* sink : sinks_)
sink->OnBundleComplete(cpu, std::move(bundles[i++]));
PERFETTO_DCHECK(sinks_.size() == sink_count);
return res;
}
CpuReader* FtraceController::GetCpuReader(size_t cpu) {
PERFETTO_CHECK(cpu < ftrace_procfs_->NumberOfCpus());
if (!readers_.count(cpu)) {
readers_.emplace(
cpu, std::unique_ptr<CpuReader>(new CpuReader(
table_.get(), cpu, ftrace_procfs_->OpenPipeForCpu(cpu))));
}
return readers_.at(cpu).get();
}
std::unique_ptr<FtraceSink> FtraceController::CreateSink(
FtraceConfig config,
FtraceSink::Delegate* delegate) {
PERFETTO_DCHECK_THREAD(thread_checker_);
if (sinks_.size() >= kMaxSinks)
return nullptr;
if (!ValidConfig(config))
return nullptr;
auto controller_weak = weak_factory_.GetWeakPtr();
auto filter = std::unique_ptr<EventFilter>(
new EventFilter(*table_.get(), FtraceEventsAsSet(config)));
for (const std::string& event : config.event_names())
PERFETTO_LOG("%s", event.c_str());
auto sink = std::unique_ptr<FtraceSink>(new FtraceSink(
std::move(controller_weak), config, std::move(filter), delegate));
Register(sink.get());
return sink;
}
void FtraceController::Register(FtraceSink* sink) {
PERFETTO_DCHECK_THREAD(thread_checker_);
auto it_and_inserted = sinks_.insert(sink);
PERFETTO_DCHECK(it_and_inserted.second);
if (RequiresAtrace(sink->config()))
StartAtrace(sink->config());
StartIfNeeded();
for (const std::string& name : sink->enabled_events())
RegisterForEvent(name);
}
void FtraceController::RegisterForEvent(const std::string& name) {
PERFETTO_DCHECK_THREAD(thread_checker_);
const Event* event = table_->GetEventByName(name);
if (!event) {
PERFETTO_DLOG("Can't enable %s, event not known", name.c_str());
return;
}
size_t& count = enabled_count_.at(event->ftrace_event_id);
if (count == 0)
ftrace_procfs_->EnableEvent(event->group, event->name);
count += 1;
}
void FtraceController::UnregisterForEvent(const std::string& name) {
PERFETTO_DCHECK_THREAD(thread_checker_);
const Event* event = table_->GetEventByName(name);
if (!event)
return;
size_t& count = enabled_count_.at(event->ftrace_event_id);
PERFETTO_CHECK(count > 0);
if (--count == 0)
ftrace_procfs_->DisableEvent(event->group, event->name);
}
void FtraceController::Unregister(FtraceSink* sink) {
PERFETTO_DCHECK_THREAD(thread_checker_);
size_t removed = sinks_.erase(sink);
PERFETTO_DCHECK(removed == 1);
for (const std::string& name : sink->enabled_events())
UnregisterForEvent(name);
if (RequiresAtrace(sink->config()))
StopAtrace();
StopIfNeeded();
}
void FtraceController::StartAtrace(const FtraceConfig& config) {
PERFETTO_CHECK(atrace_running_ == false);
atrace_running_ = true;
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 : config.atrace_categories())
args.push_back(category);
if (!config.atrace_apps().empty()) {
args.push_back("-a");
for (const auto& app : config.atrace_apps())
args.push_back(app);
}
PERFETTO_CHECK(RunAtrace(std::move(args)));
PERFETTO_DLOG("...done");
}
void FtraceController::StopAtrace() {
PERFETTO_CHECK(atrace_running_ == true);
atrace_running_ = false;
PERFETTO_DLOG("Stop atrace...");
PERFETTO_CHECK(
RunAtrace(std::vector<std::string>({"atrace", "--async_stop"})));
PERFETTO_DLOG("...done");
}
FtraceSink::FtraceSink(base::WeakPtr<FtraceController> controller_weak,
FtraceConfig config,
std::unique_ptr<EventFilter> filter,
Delegate* delegate)
: controller_weak_(std::move(controller_weak)),
config_(config),
filter_(std::move(filter)),
delegate_(delegate){};
FtraceSink::~FtraceSink() {
if (controller_weak_)
controller_weak_->Unregister(this);
};
const std::set<std::string>& FtraceSink::enabled_events() {
return filter_->enabled_names();
}
} // namespace perfetto