1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "src/profiling/perf/event_config.h"
18 
19 #include <linux/perf_event.h>
20 #include <stdint.h>
21 
22 #include "perfetto/base/logging.h"
23 #include "perfetto/ext/base/optional.h"
24 #include "test/gtest_and_gmock.h"
25 
26 #include "protos/perfetto/common/perf_events.gen.h"
27 #include "protos/perfetto/config/data_source_config.gen.h"
28 #include "protos/perfetto/config/profiling/perf_event_config.gen.h"
29 
30 using ::testing::UnorderedElementsAreArray;
31 
32 namespace perfetto {
33 namespace profiling {
34 namespace {
35 
IsPowerOfTwo(size_t v)36 bool IsPowerOfTwo(size_t v) {
37   return (v != 0 && ((v & (v - 1)) == 0));
38 }
39 
AsDataSourceConfig(const protos::gen::PerfEventConfig & perf_cfg)40 static DataSourceConfig AsDataSourceConfig(
41     const protos::gen::PerfEventConfig& perf_cfg) {
42   protos::gen::DataSourceConfig ds_cfg;
43   ds_cfg.set_perf_event_config_raw(perf_cfg.SerializeAsString());
44   return ds_cfg;
45 }
46 
TEST(EventConfigTest,AttrStructConstructed)47 TEST(EventConfigTest, AttrStructConstructed) {
48   protos::gen::PerfEventConfig cfg;
49   base::Optional<EventConfig> event_config =
50       EventConfig::Create(AsDataSourceConfig(cfg));
51 
52   ASSERT_TRUE(event_config.has_value());
53   ASSERT_TRUE(event_config->perf_attr() != nullptr);
54 }
55 
TEST(EventConfigTest,RingBufferPagesValidated)56 TEST(EventConfigTest, RingBufferPagesValidated) {
57   {  // if unset, a default is used
58     protos::gen::PerfEventConfig cfg;
59     base::Optional<EventConfig> event_config =
60         EventConfig::Create(AsDataSourceConfig(cfg));
61 
62     ASSERT_TRUE(event_config.has_value());
63     ASSERT_GT(event_config->ring_buffer_pages(), 0u);
64     ASSERT_TRUE(IsPowerOfTwo(event_config->ring_buffer_pages()));
65   }
66   {  // power of two pages accepted
67     uint32_t num_pages = 128;
68     protos::gen::PerfEventConfig cfg;
69     cfg.set_ring_buffer_pages(num_pages);
70     base::Optional<EventConfig> event_config =
71         EventConfig::Create(AsDataSourceConfig(cfg));
72 
73     ASSERT_TRUE(event_config.has_value());
74     ASSERT_EQ(event_config->ring_buffer_pages(), num_pages);
75   }
76   {  // entire config rejected if not a power of two of pages
77     protos::gen::PerfEventConfig cfg;
78     cfg.set_ring_buffer_pages(7);
79     base::Optional<EventConfig> event_config =
80         EventConfig::Create(AsDataSourceConfig(cfg));
81 
82     ASSERT_FALSE(event_config.has_value());
83   }
84 }
85 
TEST(EventConfigTest,ReadTickPeriodDefaultedIfUnset)86 TEST(EventConfigTest, ReadTickPeriodDefaultedIfUnset) {
87   {  // if unset, a default is used
88     protos::gen::PerfEventConfig cfg;
89     base::Optional<EventConfig> event_config =
90         EventConfig::Create(AsDataSourceConfig(cfg));
91 
92     ASSERT_TRUE(event_config.has_value());
93     ASSERT_GT(event_config->read_tick_period_ms(), 0u);
94   }
95   {  // otherwise, given value used
96     uint32_t period_ms = 250;
97     protos::gen::PerfEventConfig cfg;
98     cfg.set_ring_buffer_read_period_ms(period_ms);
99     base::Optional<EventConfig> event_config =
100         EventConfig::Create(AsDataSourceConfig(cfg));
101 
102     ASSERT_TRUE(event_config.has_value());
103     ASSERT_EQ(event_config->read_tick_period_ms(), period_ms);
104   }
105 }
106 
TEST(EventConfigTest,RemotePeriodTimeoutDefaultedIfUnset)107 TEST(EventConfigTest, RemotePeriodTimeoutDefaultedIfUnset) {
108   {  // if unset, a default is used
109     protos::gen::PerfEventConfig cfg;
110     base::Optional<EventConfig> event_config =
111         EventConfig::Create(AsDataSourceConfig(cfg));
112 
113     ASSERT_TRUE(event_config.has_value());
114     ASSERT_GT(event_config->remote_descriptor_timeout_ms(), 0u);
115   }
116   {  // otherwise, given value used
117     uint32_t timeout_ms = 300;
118     protos::gen::PerfEventConfig cfg;
119     cfg.set_remote_descriptor_timeout_ms(timeout_ms);
120     base::Optional<EventConfig> event_config =
121         EventConfig::Create(AsDataSourceConfig(cfg));
122 
123     ASSERT_TRUE(event_config.has_value());
124     ASSERT_EQ(event_config->remote_descriptor_timeout_ms(), timeout_ms);
125   }
126 }
127 
TEST(EventConfigTest,EnableKernelFrames)128 TEST(EventConfigTest, EnableKernelFrames) {
129   {
130     protos::gen::PerfEventConfig cfg;
131     cfg.mutable_callstack_sampling()->set_kernel_frames(true);
132     base::Optional<EventConfig> event_config =
133         EventConfig::Create(AsDataSourceConfig(cfg));
134 
135     ASSERT_TRUE(event_config.has_value());
136     EXPECT_TRUE(event_config->kernel_frames());
137   }
138   {  // legacy config:
139     protos::gen::PerfEventConfig cfg;
140     cfg.set_all_cpus(true);  // used to detect compat mode
141     cfg.set_kernel_frames(true);
142     base::Optional<EventConfig> event_config =
143         EventConfig::Create(AsDataSourceConfig(cfg));
144 
145     ASSERT_TRUE(event_config.has_value());
146     EXPECT_TRUE(event_config->kernel_frames());
147   }
148   {  // default is false
149     protos::gen::PerfEventConfig cfg;
150     base::Optional<EventConfig> event_config =
151         EventConfig::Create(AsDataSourceConfig(cfg));
152 
153     ASSERT_TRUE(event_config.has_value());
154     EXPECT_FALSE(event_config->kernel_frames());
155   }
156 }
157 
TEST(EventConfigTest,SelectSamplingInterval)158 TEST(EventConfigTest, SelectSamplingInterval) {
159   {  // period:
160     protos::gen::PerfEventConfig cfg;
161     cfg.mutable_timebase()->set_period(100);
162     base::Optional<EventConfig> event_config =
163         EventConfig::Create(AsDataSourceConfig(cfg));
164 
165     ASSERT_TRUE(event_config.has_value());
166     EXPECT_FALSE(event_config->perf_attr()->freq);
167     EXPECT_EQ(event_config->perf_attr()->sample_period, 100u);
168   }
169   {  // frequency:
170     protos::gen::PerfEventConfig cfg;
171     cfg.mutable_timebase()->set_frequency(4000);
172     base::Optional<EventConfig> event_config =
173         EventConfig::Create(AsDataSourceConfig(cfg));
174 
175     ASSERT_TRUE(event_config.has_value());
176     EXPECT_TRUE(event_config->perf_attr()->freq);
177     EXPECT_EQ(event_config->perf_attr()->sample_freq, 4000u);
178   }
179   {  // legacy frequency field:
180     protos::gen::PerfEventConfig cfg;
181     cfg.set_sampling_frequency(5000);
182     base::Optional<EventConfig> event_config =
183         EventConfig::Create(AsDataSourceConfig(cfg));
184 
185     ASSERT_TRUE(event_config.has_value());
186     EXPECT_TRUE(event_config->perf_attr()->freq);
187     EXPECT_EQ(event_config->perf_attr()->sample_freq, 5000u);
188   }
189   {  // default is 10 Hz (implementation-defined)
190     protos::gen::PerfEventConfig cfg;
191     base::Optional<EventConfig> event_config =
192         EventConfig::Create(AsDataSourceConfig(cfg));
193 
194     ASSERT_TRUE(event_config.has_value());
195     EXPECT_TRUE(event_config->perf_attr()->freq);
196     EXPECT_EQ(event_config->perf_attr()->sample_freq, 10u);
197   }
198 }
199 
TEST(EventConfigTest,SelectTimebaseEvent)200 TEST(EventConfigTest, SelectTimebaseEvent) {
201   auto id_lookup = [](const std::string& group, const std::string& name) {
202     return (group == "sched" && name == "sched_switch") ? 42 : 0;
203   };
204 
205   {
206     protos::gen::PerfEventConfig cfg;
207     protos::gen::PerfEvents::Tracepoint* mutable_tracepoint =
208         cfg.mutable_timebase()->mutable_tracepoint();
209     mutable_tracepoint->set_name("sched:sched_switch");
210 
211     base::Optional<EventConfig> event_config =
212         EventConfig::Create(AsDataSourceConfig(cfg), id_lookup);
213 
214     ASSERT_TRUE(event_config.has_value());
215     EXPECT_EQ(event_config->perf_attr()->type, PERF_TYPE_TRACEPOINT);
216     EXPECT_EQ(event_config->perf_attr()->config, 42u);
217   }
218   {  // default is the CPU timer:
219     protos::gen::PerfEventConfig cfg;
220     cfg.mutable_timebase()->set_frequency(1000);
221     base::Optional<EventConfig> event_config =
222         EventConfig::Create(AsDataSourceConfig(cfg));
223 
224     ASSERT_TRUE(event_config.has_value());
225     EXPECT_EQ(event_config->perf_attr()->type, PERF_TYPE_SOFTWARE);
226     EXPECT_EQ(event_config->perf_attr()->config, PERF_COUNT_SW_CPU_CLOCK);
227   }
228 }
229 
TEST(EventConfigTest,ParseTargetfilter)230 TEST(EventConfigTest, ParseTargetfilter) {
231   {
232     protos::gen::PerfEventConfig cfg;
233     auto* mutable_scope = cfg.mutable_callstack_sampling()->mutable_scope();
234     mutable_scope->add_target_pid(42);
235     mutable_scope->add_target_cmdline("traced_probes");
236     mutable_scope->add_target_cmdline("traced");
237     mutable_scope->set_additional_cmdline_count(3);
238     mutable_scope->add_exclude_cmdline("heapprofd");
239 
240     base::Optional<EventConfig> event_config =
241         EventConfig::Create(AsDataSourceConfig(cfg));
242 
243     ASSERT_TRUE(event_config.has_value());
244     const auto& filter = event_config->filter();
245     EXPECT_THAT(filter.pids, UnorderedElementsAreArray({42}));
246     EXPECT_THAT(filter.cmdlines,
247                 UnorderedElementsAreArray({"traced_probes", "traced"}));
248     EXPECT_EQ(filter.additional_cmdline_count, 3u);
249     EXPECT_TRUE(filter.exclude_pids.empty());
250     EXPECT_THAT(filter.exclude_cmdlines,
251                 UnorderedElementsAreArray({"heapprofd"}));
252   }
253   {  // legacy:
254     protos::gen::PerfEventConfig cfg;
255     cfg.set_all_cpus(true);
256     cfg.add_target_pid(42);
257     cfg.add_target_cmdline("traced_probes");
258     cfg.add_target_cmdline("traced");
259     cfg.set_additional_cmdline_count(3);
260     cfg.add_exclude_cmdline("heapprofd");
261 
262     base::Optional<EventConfig> event_config =
263         EventConfig::Create(AsDataSourceConfig(cfg));
264 
265     ASSERT_TRUE(event_config.has_value());
266     const auto& filter = event_config->filter();
267     EXPECT_THAT(filter.pids, UnorderedElementsAreArray({42}));
268     EXPECT_THAT(filter.cmdlines,
269                 UnorderedElementsAreArray({"traced_probes", "traced"}));
270     EXPECT_EQ(filter.additional_cmdline_count, 3u);
271     EXPECT_TRUE(filter.exclude_pids.empty());
272     EXPECT_THAT(filter.exclude_cmdlines,
273                 UnorderedElementsAreArray({"heapprofd"}));
274   }
275 }
276 
TEST(EventConfigTest,CounterOnlyModeDetection)277 TEST(EventConfigTest, CounterOnlyModeDetection) {
278   {  // hardware counter:
279     protos::gen::PerfEventConfig cfg;
280     auto* mutable_timebase = cfg.mutable_timebase();
281     mutable_timebase->set_period(500);
282     mutable_timebase->set_counter(protos::gen::PerfEvents::HW_CPU_CYCLES);
283 
284     base::Optional<EventConfig> event_config =
285         EventConfig::Create(AsDataSourceConfig(cfg));
286 
287     ASSERT_TRUE(event_config.has_value());
288     EXPECT_EQ(event_config->perf_attr()->type, PERF_TYPE_HARDWARE);
289     EXPECT_EQ(event_config->perf_attr()->config, PERF_COUNT_HW_CPU_CYCLES);
290     EXPECT_EQ(event_config->perf_attr()->sample_type &
291                   (PERF_SAMPLE_STACK_USER | PERF_SAMPLE_REGS_USER),
292               0u);
293   }
294   {  // software counter:
295     protos::gen::PerfEventConfig cfg;
296     auto* mutable_timebase = cfg.mutable_timebase();
297     mutable_timebase->set_period(500);
298     mutable_timebase->set_counter(protos::gen::PerfEvents::SW_PAGE_FAULTS);
299 
300     base::Optional<EventConfig> event_config =
301         EventConfig::Create(AsDataSourceConfig(cfg));
302 
303     ASSERT_TRUE(event_config.has_value());
304     EXPECT_EQ(event_config->perf_attr()->type, PERF_TYPE_SOFTWARE);
305     EXPECT_EQ(event_config->perf_attr()->config, PERF_COUNT_SW_PAGE_FAULTS);
306     EXPECT_EQ(event_config->perf_attr()->sample_type &
307                   (PERF_SAMPLE_STACK_USER | PERF_SAMPLE_REGS_USER),
308               0u);
309   }
310 }
311 
TEST(EventConfigTest,CallstackSamplingModeDetection)312 TEST(EventConfigTest, CallstackSamplingModeDetection) {
313   {  // set-but-empty |callstack_sampling| field enables callstacks
314     protos::gen::PerfEventConfig cfg;
315     cfg.mutable_callstack_sampling();  // set field
316 
317     base::Optional<EventConfig> event_config =
318         EventConfig::Create(AsDataSourceConfig(cfg));
319 
320     ASSERT_TRUE(event_config.has_value());
321     EXPECT_EQ(event_config->perf_attr()->sample_type &
322                   (PERF_SAMPLE_STACK_USER | PERF_SAMPLE_REGS_USER),
323               PERF_SAMPLE_STACK_USER | PERF_SAMPLE_REGS_USER);
324 
325     EXPECT_NE(event_config->perf_attr()->sample_regs_user, 0u);
326     EXPECT_NE(event_config->perf_attr()->sample_stack_user, 0u);
327   }
328 }
329 
330 }  // namespace
331 }  // namespace profiling
332 }  // namespace perfetto
333