/* * 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 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(); TraceConfig trace_config; trace_config.add_buffers()->set_size_kb(1024); 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(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_->IsTracingEnabled(); }, "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(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(); TraceConfig trace_config; trace_config.add_buffers()->set_size_kb(1024); 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.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(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; trace_config.add_buffers()->set_size_kb(32); trace_config.set_duration_ms(1); 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_process_fork"); // 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.WaitForTracingDisabled(kDefaultTestTimeoutMs); helper.ReadData(); helper.WaitForReadData(); const auto& packets = helper.trace(); ASSERT_GT(packets.size(), 0u); base::Optional 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