blob: 4791b0d60003d0c027f31cc4fa9b8b68cbbf02c4 [file] [log] [blame]
/*
* Copyright (C) 2022 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/base/build_config.h"
#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX)
#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/scoped_file.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/temp_file.h"
#include "perfetto/ext/base/utils.h"
#include "perfetto/ext/traced/traced.h"
#include "perfetto/ext/tracing/core/commit_data_request.h"
#include "perfetto/ext/tracing/core/trace_packet.h"
#include "perfetto/ext/tracing/core/tracing_service.h"
#include "perfetto/protozero/scattered_heap_buffer.h"
#include "perfetto/tracing/core/tracing_service_state.h"
#include "src/base/test/test_task_runner.h"
#include "src/base/test/utils.h"
#include "src/traced/probes/ftrace/ftrace_controller.h"
#include "src/traced/probes/ftrace/ftrace_procfs.h"
#include "test/gtest_and_gmock.h"
#include "test/test_helper.h"
#include "protos/perfetto/config/test_config.gen.h"
#include "protos/perfetto/config/trace_config.gen.h"
#include "protos/perfetto/trace/ftrace/ftrace.gen.h"
#include "protos/perfetto/trace/ftrace/ftrace_event.gen.h"
#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h"
#include "protos/perfetto/trace/ftrace/ftrace_stats.gen.h"
#include "protos/perfetto/trace/perfetto/tracing_service_event.gen.h"
#include "protos/perfetto/trace/test_event.gen.h"
#include "protos/perfetto/trace/trace.gen.h"
#include "protos/perfetto/trace/trace_packet.gen.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
#if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
#include "test/android_test_utils.h"
#endif
namespace perfetto {
namespace {
using ::testing::ContainsRegex;
using ::testing::Each;
using ::testing::ElementsAreArray;
using ::testing::HasSubstr;
using ::testing::Property;
using ::testing::SizeIs;
using ::testing::UnorderedElementsAreArray;
class PerfettoFtraceIntegrationTest : public ::testing::Test {
public:
void SetUp() override {
ftrace_procfs_ = FtraceProcfs::CreateGuessingMountPoint();
// On android we do expect that tracefs is accessible, both in the case of
// running as part of traced/probes system daemons and shell. On Linux this is
// up to the system admin, don't hard fail.
#if !PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
if (!ftrace_procfs_) {
PERFETTO_ELOG(
"Cannot acces tracefs. On Linux you need to manually run `sudo chown "
"-R $USER /sys/kernel/tracing` to enable these tests. Skipping");
GTEST_SKIP();
} else {
// Recent kernels set tracing_on=1 by default. On Android this is
// disabled by initrc scripts. Be tolerant on Linux where we don't have
// that and force disable ftrace.
ftrace_procfs_->SetTracingOn(false);
}
#endif
}
std::unique_ptr<FtraceProcfs> ftrace_procfs_;
};
} // namespace
TEST_F(PerfettoFtraceIntegrationTest, TestFtraceProducer) {
base::TestTaskRunner task_runner;
TestHelper helper(&task_runner);
helper.StartServiceIfRequired();
#if PERFETTO_BUILDFLAG(PERFETTO_START_DAEMONS)
ProbesProducerThread probes(GetTestProducerSockName());
probes.Connect();
#endif
helper.ConnectConsumer();
helper.WaitForConsumerConnect();
helper.WaitForDataSourceConnected("linux.ftrace");
TraceConfig trace_config;
trace_config.add_buffers()->set_size_kb(64);
trace_config.set_duration_ms(3000);
auto* ds_config = trace_config.add_data_sources()->mutable_config();
ds_config->set_name("linux.ftrace");
ds_config->set_target_buffer(0);
protos::gen::FtraceConfig ftrace_config;
ftrace_config.add_ftrace_events("sched_switch");
ftrace_config.add_ftrace_events("bar");
ds_config->set_ftrace_config_raw(ftrace_config.SerializeAsString());
helper.StartTracing(trace_config);
helper.WaitForTracingDisabled();
helper.ReadData();
helper.WaitForReadData();
const auto& packets = helper.trace();
ASSERT_GT(packets.size(), 0u);
for (const auto& packet : packets) {
for (int ev = 0; ev < packet.ftrace_events().event_size(); ev++) {
ASSERT_TRUE(packet.ftrace_events()
.event()[static_cast<size_t>(ev)]
.has_sched_switch());
}
}
}
TEST_F(PerfettoFtraceIntegrationTest, TestFtraceFlush) {
base::TestTaskRunner task_runner;
TestHelper helper(&task_runner);
helper.StartServiceIfRequired();
#if PERFETTO_BUILDFLAG(PERFETTO_START_DAEMONS)
ProbesProducerThread probes(GetTestProducerSockName());
probes.Connect();
#endif
helper.ConnectConsumer();
helper.WaitForConsumerConnect();
// Wait for the traced_probes service to connect. We want to start tracing
// only after it connects, otherwise we'll start a tracing session with 0
// producers connected (which is valid but not what we want here).
helper.WaitForDataSourceConnected("linux.ftrace");
TraceConfig trace_config;
trace_config.add_buffers()->set_size_kb(32);
trace_config.set_duration_ms(kDefaultTestTimeoutMs);
auto* ds_config = trace_config.add_data_sources()->mutable_config();
ds_config->set_name("linux.ftrace");
protos::gen::FtraceConfig ftrace_config;
ftrace_config.add_ftrace_events("print");
ds_config->set_ftrace_config_raw(ftrace_config.SerializeAsString());
helper.StartTracing(trace_config);
// Wait for traced_probes to start.
helper.WaitFor([&] { return ftrace_procfs_->GetTracingOn(); }, "ftrace");
// Do a first flush just to synchronize with the producer. The problem here
// is that, on a Linux workstation, the producer can take several seconds just
// to get to the point where it is fully ready. We use the flush ack as a
// synchronization point.
helper.FlushAndWait(kDefaultTestTimeoutMs);
const char kMarker[] = "just_one_event";
EXPECT_TRUE(ftrace_procfs_->WriteTraceMarker(kMarker));
// This is the real flush we are testing.
helper.FlushAndWait(kDefaultTestTimeoutMs);
helper.DisableTracing();
helper.WaitForTracingDisabled(kDefaultTestTimeoutMs);
helper.ReadData();
helper.WaitForReadData();
int marker_found = 0;
for (const auto& packet : helper.trace()) {
for (int i = 0; i < packet.ftrace_events().event_size(); i++) {
const auto& ev = packet.ftrace_events().event()[static_cast<size_t>(i)];
if (ev.has_print() && ev.print().buf().find(kMarker) != std::string::npos)
marker_found++;
}
}
ASSERT_EQ(marker_found, 1);
}
// Disable this test:
// 1. On cuttlefish (x86-kvm). It's too slow when running on GCE (b/171771440).
// We cannot change the length of the production code in
// CanReadKernelSymbolAddresses() to deal with it.
// 2. On user (i.e. non-userdebug) builds. As that doesn't work there by design.
// 3. On ARM builds, because they fail on our CI.
#if (PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD) && defined(__i386__)) || \
defined(__arm__)
#define MAYBE_KernelAddressSymbolization DISABLED_KernelAddressSymbolization
#else
#define MAYBE_KernelAddressSymbolization KernelAddressSymbolization
#endif
TEST_F(PerfettoFtraceIntegrationTest, MAYBE_KernelAddressSymbolization) {
// On Android in-tree builds (TreeHugger): this test must always run to
// prevent selinux / property-related regressions. However it can run only on
// userdebug.
// On standalone builds and Linux, this can be optionally skipped because
// there it requires root to lower kptr_restrict.
#if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
if (!IsDebuggableBuild())
GTEST_SKIP();
#else
if (geteuid() != 0)
GTEST_SKIP();
#endif
base::TestTaskRunner task_runner;
TestHelper helper(&task_runner);
helper.StartServiceIfRequired();
#if PERFETTO_BUILDFLAG(PERFETTO_START_DAEMONS)
ProbesProducerThread probes(GetTestProducerSockName());
probes.Connect();
#endif
helper.ConnectConsumer();
helper.WaitForConsumerConnect();
helper.WaitForDataSourceConnected("linux.ftrace");
TraceConfig trace_config;
trace_config.add_buffers()->set_size_kb(64);
auto* ds_config = trace_config.add_data_sources()->mutable_config();
ds_config->set_name("linux.ftrace");
protos::gen::FtraceConfig ftrace_cfg;
ftrace_cfg.set_symbolize_ksyms(true);
ftrace_cfg.set_initialize_ksyms_synchronously_for_testing(true);
ds_config->set_ftrace_config_raw(ftrace_cfg.SerializeAsString());
helper.StartTracing(trace_config);
// Synchronize with the ftrace data source. The kernel symbol map is loaded
// at this point.
helper.WaitForAllDataSourceStarted();
helper.FlushAndWait(kDefaultTestTimeoutMs);
helper.DisableTracing();
helper.WaitForTracingDisabled();
helper.ReadData();
helper.WaitForReadData();
const auto& packets = helper.trace();
ASSERT_GT(packets.size(), 0u);
int symbols_parsed = -1;
for (const auto& packet : packets) {
if (!packet.has_ftrace_stats())
continue;
if (packet.ftrace_stats().phase() != protos::gen::FtraceStats::END_OF_TRACE)
continue;
symbols_parsed =
static_cast<int>(packet.ftrace_stats().kernel_symbols_parsed());
}
ASSERT_GT(symbols_parsed, 100);
}
TEST_F(PerfettoFtraceIntegrationTest, ReportFtraceFailuresInStats) {
base::TestTaskRunner task_runner;
TestHelper helper(&task_runner);
helper.StartServiceIfRequired();
#if PERFETTO_BUILDFLAG(PERFETTO_START_DAEMONS)
ProbesProducerThread probes(GetTestProducerSockName());
probes.Connect();
#endif
helper.ConnectConsumer();
helper.WaitForConsumerConnect();
// Wait for the traced_probes service to connect. We want to start tracing
// only after it connects, otherwise we'll start a tracing session with 0
// producers connected (which is valid but not what we want here).
helper.WaitForDataSourceConnected("linux.ftrace");
TraceConfig trace_config;
TraceConfig::BufferConfig* buf = trace_config.add_buffers();
buf->set_size_kb(32);
buf->set_fill_policy(TraceConfig::BufferConfig::DISCARD);
auto* ds_config = trace_config.add_data_sources()->mutable_config();
ds_config->set_name("linux.ftrace");
protos::gen::FtraceConfig ftrace_config;
ftrace_config.add_ftrace_events("sched/sched_switch"); // Good.
ftrace_config.add_ftrace_events("sched/does_not_exist"); // Bad.
ftrace_config.add_ftrace_events("foobar/i_just_made_this_up"); // Bad.
ftrace_config.add_atrace_categories("madeup_atrace_cat"); // Bad.
ds_config->set_ftrace_config_raw(ftrace_config.SerializeAsString());
helper.StartTracing(trace_config);
helper.WaitForAllDataSourceStarted(kDefaultTestTimeoutMs);
helper.FlushAndWait(kDefaultTestTimeoutMs);
helper.DisableTracing();
helper.WaitForTracingDisabled(kDefaultTestTimeoutMs);
helper.ReadData();
helper.WaitForReadData();
const auto& packets = helper.trace();
ASSERT_GT(packets.size(), 0u);
std::optional<protos::gen::FtraceStats> stats;
for (const auto& packet : packets) {
if (!packet.has_ftrace_stats() ||
packet.ftrace_stats().phase() !=
protos::gen::FtraceStats::START_OF_TRACE) {
continue;
}
stats = packet.ftrace_stats();
}
ASSERT_TRUE(stats.has_value());
EXPECT_THAT(stats->unknown_ftrace_events(),
UnorderedElementsAreArray(
{"sched/does_not_exist", "foobar/i_just_made_this_up"}));
// Atrace is not available on Linux and on the O-based emulator on the CI.
if (base::FileExists("/system/bin/atrace")) {
EXPECT_THAT(stats->atrace_errors(), HasSubstr("madeup_atrace_cat"));
}
}
} // namespace perfetto
#endif // OS_ANDROID || OS_LINUX