/* * Copyright (C) 2019 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/profiling/perf/event_config.h" #include #include #include "perfetto/base/logging.h" #include "perfetto/ext/base/optional.h" #include "test/gtest_and_gmock.h" #include "protos/perfetto/common/perf_events.gen.h" #include "protos/perfetto/config/data_source_config.gen.h" #include "protos/perfetto/config/profiling/perf_event_config.gen.h" using ::testing::UnorderedElementsAreArray; namespace perfetto { namespace profiling { namespace { bool IsPowerOfTwo(size_t v) { return (v != 0 && ((v & (v - 1)) == 0)); } static DataSourceConfig AsDataSourceConfig( const protos::gen::PerfEventConfig& perf_cfg) { protos::gen::DataSourceConfig ds_cfg; ds_cfg.set_perf_event_config_raw(perf_cfg.SerializeAsString()); return ds_cfg; } TEST(EventConfigTest, AttrStructConstructed) { protos::gen::PerfEventConfig cfg; base::Optional event_config = EventConfig::Create(AsDataSourceConfig(cfg)); ASSERT_TRUE(event_config.has_value()); ASSERT_TRUE(event_config->perf_attr() != nullptr); } TEST(EventConfigTest, RingBufferPagesValidated) { { // if unset, a default is used protos::gen::PerfEventConfig cfg; base::Optional event_config = EventConfig::Create(AsDataSourceConfig(cfg)); ASSERT_TRUE(event_config.has_value()); ASSERT_GT(event_config->ring_buffer_pages(), 0u); ASSERT_TRUE(IsPowerOfTwo(event_config->ring_buffer_pages())); } { // power of two pages accepted uint32_t num_pages = 128; protos::gen::PerfEventConfig cfg; cfg.set_ring_buffer_pages(num_pages); base::Optional event_config = EventConfig::Create(AsDataSourceConfig(cfg)); ASSERT_TRUE(event_config.has_value()); ASSERT_EQ(event_config->ring_buffer_pages(), num_pages); } { // entire config rejected if not a power of two of pages protos::gen::PerfEventConfig cfg; cfg.set_ring_buffer_pages(7); base::Optional event_config = EventConfig::Create(AsDataSourceConfig(cfg)); ASSERT_FALSE(event_config.has_value()); } } TEST(EventConfigTest, ReadTickPeriodDefaultedIfUnset) { { // if unset, a default is used protos::gen::PerfEventConfig cfg; base::Optional event_config = EventConfig::Create(AsDataSourceConfig(cfg)); ASSERT_TRUE(event_config.has_value()); ASSERT_GT(event_config->read_tick_period_ms(), 0u); } { // otherwise, given value used uint32_t period_ms = 250; protos::gen::PerfEventConfig cfg; cfg.set_ring_buffer_read_period_ms(period_ms); base::Optional event_config = EventConfig::Create(AsDataSourceConfig(cfg)); ASSERT_TRUE(event_config.has_value()); ASSERT_EQ(event_config->read_tick_period_ms(), period_ms); } } TEST(EventConfigTest, RemotePeriodTimeoutDefaultedIfUnset) { { // if unset, a default is used protos::gen::PerfEventConfig cfg; base::Optional event_config = EventConfig::Create(AsDataSourceConfig(cfg)); ASSERT_TRUE(event_config.has_value()); ASSERT_GT(event_config->remote_descriptor_timeout_ms(), 0u); } { // otherwise, given value used uint32_t timeout_ms = 300; protos::gen::PerfEventConfig cfg; cfg.set_remote_descriptor_timeout_ms(timeout_ms); base::Optional event_config = EventConfig::Create(AsDataSourceConfig(cfg)); ASSERT_TRUE(event_config.has_value()); ASSERT_EQ(event_config->remote_descriptor_timeout_ms(), timeout_ms); } } TEST(EventConfigTest, EnableKernelFrames) { { protos::gen::PerfEventConfig cfg; cfg.mutable_callstack_sampling()->set_kernel_frames(true); base::Optional event_config = EventConfig::Create(AsDataSourceConfig(cfg)); ASSERT_TRUE(event_config.has_value()); EXPECT_TRUE(event_config->kernel_frames()); } { // legacy config: protos::gen::PerfEventConfig cfg; cfg.set_all_cpus(true); // used to detect compat mode cfg.set_kernel_frames(true); base::Optional event_config = EventConfig::Create(AsDataSourceConfig(cfg)); ASSERT_TRUE(event_config.has_value()); EXPECT_TRUE(event_config->kernel_frames()); } { // default is false protos::gen::PerfEventConfig cfg; base::Optional event_config = EventConfig::Create(AsDataSourceConfig(cfg)); ASSERT_TRUE(event_config.has_value()); EXPECT_FALSE(event_config->kernel_frames()); } } TEST(EventConfigTest, SelectSamplingInterval) { { // period: protos::gen::PerfEventConfig cfg; cfg.mutable_timebase()->set_period(100); base::Optional event_config = EventConfig::Create(AsDataSourceConfig(cfg)); ASSERT_TRUE(event_config.has_value()); EXPECT_FALSE(event_config->perf_attr()->freq); EXPECT_EQ(event_config->perf_attr()->sample_period, 100u); } { // frequency: protos::gen::PerfEventConfig cfg; cfg.mutable_timebase()->set_frequency(4000); base::Optional event_config = EventConfig::Create(AsDataSourceConfig(cfg)); ASSERT_TRUE(event_config.has_value()); EXPECT_TRUE(event_config->perf_attr()->freq); EXPECT_EQ(event_config->perf_attr()->sample_freq, 4000u); } { // legacy frequency field: protos::gen::PerfEventConfig cfg; cfg.set_sampling_frequency(5000); base::Optional event_config = EventConfig::Create(AsDataSourceConfig(cfg)); ASSERT_TRUE(event_config.has_value()); EXPECT_TRUE(event_config->perf_attr()->freq); EXPECT_EQ(event_config->perf_attr()->sample_freq, 5000u); } { // default is 10 Hz (implementation-defined) protos::gen::PerfEventConfig cfg; base::Optional event_config = EventConfig::Create(AsDataSourceConfig(cfg)); ASSERT_TRUE(event_config.has_value()); EXPECT_TRUE(event_config->perf_attr()->freq); EXPECT_EQ(event_config->perf_attr()->sample_freq, 10u); } } TEST(EventConfigTest, SelectTimebaseEvent) { auto id_lookup = [](const std::string& group, const std::string& name) { return (group == "sched" && name == "sched_switch") ? 42 : 0; }; { protos::gen::PerfEventConfig cfg; protos::gen::PerfEvents::Tracepoint* mutable_tracepoint = cfg.mutable_timebase()->mutable_tracepoint(); mutable_tracepoint->set_name("sched:sched_switch"); base::Optional event_config = EventConfig::Create(AsDataSourceConfig(cfg), id_lookup); ASSERT_TRUE(event_config.has_value()); EXPECT_EQ(event_config->perf_attr()->type, PERF_TYPE_TRACEPOINT); EXPECT_EQ(event_config->perf_attr()->config, 42u); } { // default is the CPU timer: protos::gen::PerfEventConfig cfg; cfg.mutable_timebase()->set_frequency(1000); base::Optional event_config = EventConfig::Create(AsDataSourceConfig(cfg)); ASSERT_TRUE(event_config.has_value()); EXPECT_EQ(event_config->perf_attr()->type, PERF_TYPE_SOFTWARE); EXPECT_EQ(event_config->perf_attr()->config, PERF_COUNT_SW_CPU_CLOCK); } } TEST(EventConfigTest, ParseTargetfilter) { { protos::gen::PerfEventConfig cfg; auto* mutable_scope = cfg.mutable_callstack_sampling()->mutable_scope(); mutable_scope->add_target_pid(42); mutable_scope->add_target_cmdline("traced_probes"); mutable_scope->add_target_cmdline("traced"); mutable_scope->set_additional_cmdline_count(3); mutable_scope->add_exclude_cmdline("heapprofd"); base::Optional event_config = EventConfig::Create(AsDataSourceConfig(cfg)); ASSERT_TRUE(event_config.has_value()); const auto& filter = event_config->filter(); EXPECT_THAT(filter.pids, UnorderedElementsAreArray({42})); EXPECT_THAT(filter.cmdlines, UnorderedElementsAreArray({"traced_probes", "traced"})); EXPECT_EQ(filter.additional_cmdline_count, 3u); EXPECT_TRUE(filter.exclude_pids.empty()); EXPECT_THAT(filter.exclude_cmdlines, UnorderedElementsAreArray({"heapprofd"})); } { // legacy: protos::gen::PerfEventConfig cfg; cfg.set_all_cpus(true); cfg.add_target_pid(42); cfg.add_target_cmdline("traced_probes"); cfg.add_target_cmdline("traced"); cfg.set_additional_cmdline_count(3); cfg.add_exclude_cmdline("heapprofd"); base::Optional event_config = EventConfig::Create(AsDataSourceConfig(cfg)); ASSERT_TRUE(event_config.has_value()); const auto& filter = event_config->filter(); EXPECT_THAT(filter.pids, UnorderedElementsAreArray({42})); EXPECT_THAT(filter.cmdlines, UnorderedElementsAreArray({"traced_probes", "traced"})); EXPECT_EQ(filter.additional_cmdline_count, 3u); EXPECT_TRUE(filter.exclude_pids.empty()); EXPECT_THAT(filter.exclude_cmdlines, UnorderedElementsAreArray({"heapprofd"})); } } TEST(EventConfigTest, CounterOnlyModeDetection) { { // hardware counter: protos::gen::PerfEventConfig cfg; auto* mutable_timebase = cfg.mutable_timebase(); mutable_timebase->set_period(500); mutable_timebase->set_counter(protos::gen::PerfEvents::HW_CPU_CYCLES); base::Optional event_config = EventConfig::Create(AsDataSourceConfig(cfg)); ASSERT_TRUE(event_config.has_value()); EXPECT_EQ(event_config->perf_attr()->type, PERF_TYPE_HARDWARE); EXPECT_EQ(event_config->perf_attr()->config, PERF_COUNT_HW_CPU_CYCLES); EXPECT_EQ(event_config->perf_attr()->sample_type & (PERF_SAMPLE_STACK_USER | PERF_SAMPLE_REGS_USER), 0u); } { // software counter: protos::gen::PerfEventConfig cfg; auto* mutable_timebase = cfg.mutable_timebase(); mutable_timebase->set_period(500); mutable_timebase->set_counter(protos::gen::PerfEvents::SW_PAGE_FAULTS); base::Optional event_config = EventConfig::Create(AsDataSourceConfig(cfg)); ASSERT_TRUE(event_config.has_value()); EXPECT_EQ(event_config->perf_attr()->type, PERF_TYPE_SOFTWARE); EXPECT_EQ(event_config->perf_attr()->config, PERF_COUNT_SW_PAGE_FAULTS); EXPECT_EQ(event_config->perf_attr()->sample_type & (PERF_SAMPLE_STACK_USER | PERF_SAMPLE_REGS_USER), 0u); } } TEST(EventConfigTest, CallstackSamplingModeDetection) { { // set-but-empty |callstack_sampling| field enables callstacks protos::gen::PerfEventConfig cfg; cfg.mutable_callstack_sampling(); // set field base::Optional event_config = EventConfig::Create(AsDataSourceConfig(cfg)); ASSERT_TRUE(event_config.has_value()); EXPECT_EQ(event_config->perf_attr()->sample_type & (PERF_SAMPLE_STACK_USER | PERF_SAMPLE_REGS_USER), PERF_SAMPLE_STACK_USER | PERF_SAMPLE_REGS_USER); EXPECT_NE(event_config->perf_attr()->sample_regs_user, 0u); EXPECT_NE(event_config->perf_attr()->sample_stack_user, 0u); } } } // namespace } // namespace profiling } // namespace perfetto