| /* |
| * 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 |