1 /*
2  * Copyright (C) 2018 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/traced/probes/ftrace/ftrace_config_muxer.h"
18 
19 #include <memory>
20 
21 #include "src/traced/probes/ftrace/atrace_wrapper.h"
22 #include "src/traced/probes/ftrace/compact_sched.h"
23 #include "src/traced/probes/ftrace/ftrace_procfs.h"
24 #include "src/traced/probes/ftrace/proto_translation_table.h"
25 #include "test/gtest_and_gmock.h"
26 
27 using testing::_;
28 using testing::AnyNumber;
29 using testing::MatchesRegex;
30 using testing::Contains;
31 using testing::ElementsAreArray;
32 using testing::Eq;
33 using testing::IsEmpty;
34 using testing::NiceMock;
35 using testing::Not;
36 using testing::Return;
37 
38 namespace perfetto {
39 namespace {
40 
41 class MockFtraceProcfs : public FtraceProcfs {
42  public:
MockFtraceProcfs()43   MockFtraceProcfs() : FtraceProcfs("/root/") {
44     ON_CALL(*this, NumberOfCpus()).WillByDefault(Return(1));
45     ON_CALL(*this, WriteToFile(_, _)).WillByDefault(Return(true));
46     ON_CALL(*this, ClearFile(_)).WillByDefault(Return(true));
47     EXPECT_CALL(*this, NumberOfCpus()).Times(AnyNumber());
48   }
49 
50   MOCK_METHOD2(WriteToFile,
51                bool(const std::string& path, const std::string& str));
52   MOCK_METHOD2(AppendToFile,
53                bool(const std::string& path, const std::string& str));
54   MOCK_METHOD1(ReadOneCharFromFile, char(const std::string& path));
55   MOCK_METHOD1(ClearFile, bool(const std::string& path));
56   MOCK_CONST_METHOD1(ReadFileIntoString, std::string(const std::string& path));
57   MOCK_CONST_METHOD0(NumberOfCpus, size_t());
58   MOCK_CONST_METHOD1(GetEventNamesForGroup,
59                      const std::set<std::string>(const std::string& path));
60 };
61 
62 struct MockRunAtrace {
MockRunAtraceperfetto::__anon24109b1a0111::MockRunAtrace63   MockRunAtrace() {
64     static MockRunAtrace* instance;
65     instance = this;
66     SetRunAtraceForTesting([](const std::vector<std::string>& args) {
67       return instance->RunAtrace(args);
68     });
69   }
70 
~MockRunAtraceperfetto::__anon24109b1a0111::MockRunAtrace71   ~MockRunAtrace() { SetRunAtraceForTesting(nullptr); }
72 
73   MOCK_METHOD1(RunAtrace, bool(const std::vector<std::string>&));
74 };
75 
76 class MockProtoTranslationTable : public ProtoTranslationTable {
77  public:
MockProtoTranslationTable(NiceMock<MockFtraceProcfs> * ftrace_procfs,const std::vector<Event> & events,std::vector<Field> common_fields,FtracePageHeaderSpec ftrace_page_header_spec,CompactSchedEventFormat compact_sched_format)78   MockProtoTranslationTable(NiceMock<MockFtraceProcfs>* ftrace_procfs,
79                             const std::vector<Event>& events,
80                             std::vector<Field> common_fields,
81                             FtracePageHeaderSpec ftrace_page_header_spec,
82                             CompactSchedEventFormat compact_sched_format)
83       : ProtoTranslationTable(ftrace_procfs,
84                               events,
85                               common_fields,
86                               ftrace_page_header_spec,
87                               compact_sched_format) {}
88   MOCK_METHOD1(GetOrCreateEvent, Event*(const GroupAndName& group_and_name));
89   MOCK_CONST_METHOD1(GetEvent,
90                      const Event*(const GroupAndName& group_and_name));
91 };
92 
93 class FtraceConfigMuxerTest : public ::testing::Test {
94  protected:
GetMockTable()95   std::unique_ptr<MockProtoTranslationTable> GetMockTable() {
96     std::vector<Field> common_fields;
97     std::vector<Event> events;
98     return std::unique_ptr<MockProtoTranslationTable>(
99         new MockProtoTranslationTable(
100             &table_procfs_, events, std::move(common_fields),
101             ProtoTranslationTable::DefaultPageHeaderSpecForTesting(),
102             InvalidCompactSchedEventFormatForTesting()));
103   }
104 
105   static constexpr int kFakeSchedSwitchEventId = 1;
106   static constexpr int kCgroupMkdirEventId = 12;
107   static constexpr int kFakePrintEventId = 20;
108 
CreateFakeTable(CompactSchedEventFormat compact_format=InvalidCompactSchedEventFormatForTesting ())109   std::unique_ptr<ProtoTranslationTable> CreateFakeTable(
110       CompactSchedEventFormat compact_format =
111           InvalidCompactSchedEventFormatForTesting()) {
112     std::vector<Field> common_fields;
113     std::vector<Event> events;
114     {
115       Event event;
116       event.name = "sched_switch";
117       event.group = "sched";
118       event.ftrace_event_id = kFakeSchedSwitchEventId;
119       events.push_back(event);
120     }
121 
122     {
123       Event event;
124       event.name = "sched_wakeup";
125       event.group = "sched";
126       event.ftrace_event_id = 10;
127       events.push_back(event);
128     }
129 
130     {
131       Event event;
132       event.name = "sched_new";
133       event.group = "sched";
134       event.ftrace_event_id = 11;
135       events.push_back(event);
136     }
137 
138     {
139       Event event;
140       event.name = "cgroup_mkdir";
141       event.group = "cgroup";
142       event.ftrace_event_id = kCgroupMkdirEventId;
143       events.push_back(event);
144     }
145 
146     {
147       Event event;
148       event.name = "mm_vmscan_direct_reclaim_begin";
149       event.group = "vmscan";
150       event.ftrace_event_id = 13;
151       events.push_back(event);
152     }
153 
154     {
155       Event event;
156       event.name = "lowmemory_kill";
157       event.group = "lowmemorykiller";
158       event.ftrace_event_id = 14;
159       events.push_back(event);
160     }
161 
162     {
163       Event event;
164       event.name = "print";
165       event.group = "ftrace";
166       event.ftrace_event_id = kFakePrintEventId;
167       events.push_back(event);
168     }
169 
170     return std::unique_ptr<ProtoTranslationTable>(new ProtoTranslationTable(
171         &table_procfs_, events, std::move(common_fields),
172         ProtoTranslationTable::DefaultPageHeaderSpecForTesting(),
173         compact_format));
174   }
175 
176   NiceMock<MockFtraceProcfs> table_procfs_;
177   std::unique_ptr<ProtoTranslationTable> table_ = CreateFakeTable();
178 };
179 
TEST_F(FtraceConfigMuxerTest,ComputeCpuBufferSizeInPages)180 TEST_F(FtraceConfigMuxerTest, ComputeCpuBufferSizeInPages) {
181   static constexpr size_t kMaxBufSizeInPages = 16 * 1024u;
182   // No buffer size given: good default (128 pages = 2mb).
183   EXPECT_EQ(ComputeCpuBufferSizeInPages(0), 512u);
184   // Buffer size given way too big: good default.
185   EXPECT_EQ(ComputeCpuBufferSizeInPages(10 * 1024 * 1024), kMaxBufSizeInPages);
186   // The limit is 64mb per CPU, 512mb is too much.
187   EXPECT_EQ(ComputeCpuBufferSizeInPages(512 * 1024), kMaxBufSizeInPages);
188   // Your size ends up with less than 1 page per cpu -> 1 page.
189   EXPECT_EQ(ComputeCpuBufferSizeInPages(3), 1u);
190   // You picked a good size -> your size rounded to nearest page.
191   EXPECT_EQ(ComputeCpuBufferSizeInPages(42), 10u);
192 }
193 
TEST_F(FtraceConfigMuxerTest,AddGenericEvent)194 TEST_F(FtraceConfigMuxerTest, AddGenericEvent) {
195   auto mock_table = GetMockTable();
196   MockFtraceProcfs ftrace;
197 
198   FtraceConfig config = CreateFtraceConfig({"power/cpu_frequency"});
199 
200   FtraceConfigMuxer model(&ftrace, mock_table.get(), {});
201 
202   ON_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
203       .WillByDefault(Return("[local] global boot"));
204   EXPECT_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
205       .Times(AnyNumber());
206 
207   EXPECT_CALL(ftrace, ReadOneCharFromFile("/root/tracing_on"))
208       .Times(2)
209       .WillRepeatedly(Return('0'));
210   EXPECT_CALL(ftrace, WriteToFile("/root/buffer_size_kb", _));
211   EXPECT_CALL(ftrace, WriteToFile("/root/trace_clock", "boot"));
212   EXPECT_CALL(ftrace, WriteToFile("/root/tracing_on", "1"));
213   EXPECT_CALL(ftrace,
214               WriteToFile("/root/events/power/cpu_frequency/enable", "1"));
215   EXPECT_CALL(*mock_table, GetEvent(GroupAndName("power", "cpu_frequency")))
216       .Times(AnyNumber());
217 
218   static constexpr int kExpectedEventId = 77;
219   Event event_to_return;
220   event_to_return.name = "cpu_frequency";
221   event_to_return.group = "power";
222   event_to_return.ftrace_event_id = kExpectedEventId;
223   ON_CALL(*mock_table, GetOrCreateEvent(GroupAndName("power", "cpu_frequency")))
224       .WillByDefault(Return(&event_to_return));
225   EXPECT_CALL(*mock_table,
226               GetOrCreateEvent(GroupAndName("power", "cpu_frequency")));
227 
228   FtraceConfigId id = model.SetupConfig(config);
229   ASSERT_TRUE(model.ActivateConfig(id));
230 
231   const FtraceDataSourceConfig* ds_config = model.GetDataSourceConfig(id);
232   ASSERT_TRUE(ds_config);
233   ASSERT_THAT(ds_config->event_filter.GetEnabledEvents(),
234               ElementsAreArray({kExpectedEventId}));
235 
236   const EventFilter* central_filter = model.GetCentralEventFilterForTesting();
237   ASSERT_THAT(central_filter->GetEnabledEvents(),
238               ElementsAreArray({kExpectedEventId}));
239 }
240 
TEST_F(FtraceConfigMuxerTest,AddSameNameEvents)241 TEST_F(FtraceConfigMuxerTest, AddSameNameEvents) {
242   auto mock_table = GetMockTable();
243   NiceMock<MockFtraceProcfs> ftrace;
244 
245   FtraceConfig config = CreateFtraceConfig({"group_one/foo", "group_two/foo"});
246 
247   FtraceConfigMuxer model(&ftrace, mock_table.get(), {});
248 
249   static constexpr int kEventId1 = 1;
250   Event event1;
251   event1.name = "foo";
252   event1.group = "group_one";
253   event1.ftrace_event_id = kEventId1;
254   ON_CALL(*mock_table, GetOrCreateEvent(GroupAndName("group_one", "foo")))
255       .WillByDefault(Return(&event1));
256   EXPECT_CALL(*mock_table, GetOrCreateEvent(GroupAndName("group_one", "foo")));
257 
258   static constexpr int kEventId2 = 2;
259   Event event2;
260   event2.name = "foo";
261   event2.group = "group_two";
262   event2.ftrace_event_id = kEventId2;
263   ON_CALL(*mock_table, GetOrCreateEvent(GroupAndName("group_two", "foo")))
264       .WillByDefault(Return(&event2));
265   EXPECT_CALL(*mock_table, GetOrCreateEvent(GroupAndName("group_two", "foo")));
266 
267   FtraceConfigId id = model.SetupConfig(config);
268   ASSERT_TRUE(model.ActivateConfig(id));
269 
270   const FtraceDataSourceConfig* ds_config = model.GetDataSourceConfig(id);
271   ASSERT_THAT(ds_config->event_filter.GetEnabledEvents(),
272               ElementsAreArray({kEventId1, kEventId2}));
273 
274   const EventFilter* central_filter = model.GetCentralEventFilterForTesting();
275   ASSERT_THAT(central_filter->GetEnabledEvents(),
276               ElementsAreArray({kEventId1, kEventId2}));
277 }
278 
TEST_F(FtraceConfigMuxerTest,AddAllEvents)279 TEST_F(FtraceConfigMuxerTest, AddAllEvents) {
280   auto mock_table = GetMockTable();
281   MockFtraceProcfs ftrace;
282 
283   FtraceConfig config = CreateFtraceConfig({"sched/*"});
284 
285   ON_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
286       .WillByDefault(Return("[local] global boot"));
287   EXPECT_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
288       .Times(AnyNumber());
289 
290   EXPECT_CALL(ftrace, ReadOneCharFromFile("/root/tracing_on"))
291       .Times(2)
292       .WillRepeatedly(Return('0'));
293   EXPECT_CALL(ftrace, WriteToFile("/root/buffer_size_kb", _));
294   EXPECT_CALL(ftrace, WriteToFile("/root/trace_clock", "boot"));
295   EXPECT_CALL(ftrace, WriteToFile("/root/tracing_on", "1"));
296   EXPECT_CALL(ftrace,
297               WriteToFile("/root/events/sched/sched_switch/enable", "1"));
298   EXPECT_CALL(ftrace,
299               WriteToFile("/root/events/sched/sched_new_event/enable", "1"));
300 
301   FtraceConfigMuxer model(&ftrace, mock_table.get(), {});
302   std::set<std::string> n = {"sched_switch", "sched_new_event"};
303   ON_CALL(ftrace, GetEventNamesForGroup("events/sched"))
304       .WillByDefault(Return(n));
305   EXPECT_CALL(ftrace, GetEventNamesForGroup("events/sched")).Times(1);
306 
307   // Non-generic event.
308   static constexpr int kSchedSwitchEventId = 1;
309   Event sched_switch = {"sched_switch", "sched", {}, 0, 0, 0};
310   sched_switch.ftrace_event_id = kSchedSwitchEventId;
311   ON_CALL(*mock_table, GetOrCreateEvent(GroupAndName("sched", "sched_switch")))
312       .WillByDefault(Return(&sched_switch));
313   EXPECT_CALL(*mock_table,
314               GetOrCreateEvent(GroupAndName("sched", "sched_switch")))
315       .Times(AnyNumber());
316 
317   // Generic event.
318   static constexpr int kGenericEventId = 2;
319   Event event_to_return;
320   event_to_return.name = "sched_new_event";
321   event_to_return.group = "sched";
322   event_to_return.ftrace_event_id = kGenericEventId;
323   ON_CALL(*mock_table,
324           GetOrCreateEvent(GroupAndName("sched", "sched_new_event")))
325       .WillByDefault(Return(&event_to_return));
326   EXPECT_CALL(*mock_table,
327               GetOrCreateEvent(GroupAndName("sched", "sched_new_event")));
328 
329   FtraceConfigId id = model.SetupConfig(config);
330   ASSERT_TRUE(id);
331   ASSERT_TRUE(model.ActivateConfig(id));
332 
333   const FtraceDataSourceConfig* ds_config = model.GetDataSourceConfig(id);
334   ASSERT_TRUE(ds_config);
335   ASSERT_THAT(ds_config->event_filter.GetEnabledEvents(),
336               ElementsAreArray({kSchedSwitchEventId, kGenericEventId}));
337 
338   const EventFilter* central_filter = model.GetCentralEventFilterForTesting();
339   ASSERT_THAT(central_filter->GetEnabledEvents(),
340               ElementsAreArray({kSchedSwitchEventId, kGenericEventId}));
341 }
342 
TEST_F(FtraceConfigMuxerTest,TwoWildcardGroups)343 TEST_F(FtraceConfigMuxerTest, TwoWildcardGroups) {
344   auto mock_table = GetMockTable();
345   NiceMock<MockFtraceProcfs> ftrace;
346 
347   FtraceConfig config = CreateFtraceConfig({"group_one/*", "group_two/*"});
348 
349   FtraceConfigMuxer model(&ftrace, mock_table.get(), {});
350 
351   std::set<std::string> event_names = {"foo"};
352   ON_CALL(ftrace, GetEventNamesForGroup("events/group_one"))
353       .WillByDefault(Return(event_names));
354   EXPECT_CALL(ftrace, GetEventNamesForGroup("events/group_one"))
355       .Times(AnyNumber());
356 
357   ON_CALL(ftrace, GetEventNamesForGroup("events/group_two"))
358       .WillByDefault(Return(event_names));
359   EXPECT_CALL(ftrace, GetEventNamesForGroup("events/group_two"))
360       .Times(AnyNumber());
361 
362   static constexpr int kEventId1 = 1;
363   Event event1;
364   event1.name = "foo";
365   event1.group = "group_one";
366   event1.ftrace_event_id = kEventId1;
367   ON_CALL(*mock_table, GetOrCreateEvent(GroupAndName("group_one", "foo")))
368       .WillByDefault(Return(&event1));
369   EXPECT_CALL(*mock_table, GetOrCreateEvent(GroupAndName("group_one", "foo")));
370 
371   static constexpr int kEventId2 = 2;
372   Event event2;
373   event2.name = "foo";
374   event2.group = "group_two";
375   event2.ftrace_event_id = kEventId2;
376   ON_CALL(*mock_table, GetOrCreateEvent(GroupAndName("group_two", "foo")))
377       .WillByDefault(Return(&event2));
378   EXPECT_CALL(*mock_table, GetOrCreateEvent(GroupAndName("group_two", "foo")));
379 
380   FtraceConfigId id = model.SetupConfig(config);
381   ASSERT_TRUE(model.ActivateConfig(id));
382 
383   const FtraceDataSourceConfig* ds_config = model.GetDataSourceConfig(id);
384   ASSERT_TRUE(ds_config);
385   ASSERT_THAT(ds_config->event_filter.GetEnabledEvents(),
386               ElementsAreArray({kEventId1, kEventId2}));
387 
388   const EventFilter* central_filter = model.GetCentralEventFilterForTesting();
389   ASSERT_THAT(central_filter->GetEnabledEvents(),
390               ElementsAreArray({kEventId1, kEventId2}));
391 }
392 
TEST_F(FtraceConfigMuxerTest,TurnFtraceOnOff)393 TEST_F(FtraceConfigMuxerTest, TurnFtraceOnOff) {
394   MockFtraceProcfs ftrace;
395 
396   FtraceConfig config = CreateFtraceConfig({"sched_switch", "foo"});
397 
398   FtraceConfigMuxer model(&ftrace, table_.get(), {});
399 
400   ON_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
401       .WillByDefault(Return("[local] global boot"));
402   EXPECT_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
403       .Times(AnyNumber());
404 
405   EXPECT_CALL(ftrace, ReadOneCharFromFile("/root/tracing_on"))
406       .Times(2)
407       .WillRepeatedly(Return('0'));
408   EXPECT_CALL(ftrace, WriteToFile("/root/buffer_size_kb", _));
409   EXPECT_CALL(ftrace, WriteToFile("/root/trace_clock", "boot"));
410   EXPECT_CALL(ftrace, WriteToFile("/root/tracing_on", "1"));
411   EXPECT_CALL(ftrace,
412               WriteToFile("/root/events/sched/sched_switch/enable", "1"));
413 
414   FtraceConfigId id = model.SetupConfig(config);
415   ASSERT_TRUE(id);
416   ASSERT_TRUE(model.ActivateConfig(id));
417 
418   const FtraceDataSourceConfig* ds_config = model.GetDataSourceConfig(id);
419   ASSERT_TRUE(ds_config);
420   ASSERT_THAT(
421       ds_config->event_filter.GetEnabledEvents(),
422       ElementsAreArray({FtraceConfigMuxerTest::kFakeSchedSwitchEventId}));
423 
424   const EventFilter* central_filter = model.GetCentralEventFilterForTesting();
425   ASSERT_THAT(
426       central_filter->GetEnabledEvents(),
427       ElementsAreArray({FtraceConfigMuxerTest::kFakeSchedSwitchEventId}));
428 
429   ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(&ftrace));
430   EXPECT_CALL(ftrace, NumberOfCpus()).Times(AnyNumber());
431 
432   EXPECT_CALL(ftrace, WriteToFile("/root/tracing_on", "0"));
433   EXPECT_CALL(ftrace, WriteToFile("/root/buffer_size_kb", "4"));
434   EXPECT_CALL(ftrace, WriteToFile("/root/events/enable", "0"));
435   EXPECT_CALL(ftrace,
436               WriteToFile("/root/events/sched/sched_switch/enable", "0"));
437   EXPECT_CALL(ftrace, ClearFile("/root/trace"));
438   EXPECT_CALL(ftrace, ClearFile(MatchesRegex("/root/per_cpu/cpu[0-9]/trace")));
439 
440   ASSERT_TRUE(model.RemoveConfig(id));
441 }
442 
TEST_F(FtraceConfigMuxerTest,FtraceIsAlreadyOn)443 TEST_F(FtraceConfigMuxerTest, FtraceIsAlreadyOn) {
444   MockFtraceProcfs ftrace;
445 
446   FtraceConfig config = CreateFtraceConfig({"sched/sched_switch"});
447 
448   FtraceConfigMuxer model(&ftrace, table_.get(), {});
449 
450   // If someone is using ftrace already don't stomp on what they are doing.
451   EXPECT_CALL(ftrace, ReadOneCharFromFile("/root/tracing_on"))
452       .WillOnce(Return('1'));
453   FtraceConfigId id = model.SetupConfig(config);
454   ASSERT_FALSE(id);
455 }
456 
TEST_F(FtraceConfigMuxerTest,Atrace)457 TEST_F(FtraceConfigMuxerTest, Atrace) {
458   NiceMock<MockFtraceProcfs> ftrace;
459   MockRunAtrace atrace;
460 
461   FtraceConfig config = CreateFtraceConfig({"sched/sched_switch"});
462   *config.add_atrace_categories() = "sched";
463 
464   FtraceConfigMuxer model(&ftrace, table_.get(), {});
465 
466   EXPECT_CALL(ftrace, ReadOneCharFromFile("/root/tracing_on"))
467       .WillOnce(Return('0'));
468   EXPECT_CALL(atrace,
469               RunAtrace(ElementsAreArray(
470                   {"atrace", "--async_start", "--only_userspace", "sched"})))
471       .WillOnce(Return(true));
472 
473   FtraceConfigId id = model.SetupConfig(config);
474   ASSERT_TRUE(id);
475 
476   // "ftrace" group events are always enabled, and therefore the "print" event
477   // will show up in the per data source event filter (as we want to record it),
478   // but not the central filter (as we're not enabling/disabling it).
479   const FtraceDataSourceConfig* ds_config = model.GetDataSourceConfig(id);
480   ASSERT_TRUE(ds_config);
481   EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
482               Contains(FtraceConfigMuxerTest::kFakeSchedSwitchEventId));
483   EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
484               Contains(FtraceConfigMuxerTest::kFakePrintEventId));
485 
486   const EventFilter* central_filter = model.GetCentralEventFilterForTesting();
487   EXPECT_THAT(central_filter->GetEnabledEvents(),
488               Contains(FtraceConfigMuxerTest::kFakeSchedSwitchEventId));
489 
490   EXPECT_CALL(atrace, RunAtrace(ElementsAreArray(
491                           {"atrace", "--async_stop", "--only_userspace"})))
492       .WillOnce(Return(true));
493   ASSERT_TRUE(model.RemoveConfig(id));
494 }
495 
TEST_F(FtraceConfigMuxerTest,AtraceTwoApps)496 TEST_F(FtraceConfigMuxerTest, AtraceTwoApps) {
497   NiceMock<MockFtraceProcfs> ftrace;
498   MockRunAtrace atrace;
499 
500   FtraceConfig config = CreateFtraceConfig({});
501   *config.add_atrace_apps() = "com.google.android.gms.persistent";
502   *config.add_atrace_apps() = "com.google.android.gms";
503 
504   FtraceConfigMuxer model(&ftrace, table_.get(), {});
505 
506   EXPECT_CALL(ftrace, ReadOneCharFromFile("/root/tracing_on"))
507       .WillOnce(Return('0'));
508   EXPECT_CALL(
509       atrace,
510       RunAtrace(ElementsAreArray(
511           {"atrace", "--async_start", "--only_userspace", "-a",
512            "com.google.android.gms,com.google.android.gms.persistent"})))
513       .WillOnce(Return(true));
514 
515   FtraceConfigId id = model.SetupConfig(config);
516   ASSERT_TRUE(id);
517 
518   const FtraceDataSourceConfig* ds_config = model.GetDataSourceConfig(id);
519   ASSERT_TRUE(ds_config);
520   ASSERT_THAT(ds_config->event_filter.GetEnabledEvents(),
521               Contains(FtraceConfigMuxerTest::kFakePrintEventId));
522 
523   EXPECT_CALL(atrace, RunAtrace(ElementsAreArray(
524                           {"atrace", "--async_stop", "--only_userspace"})))
525       .WillOnce(Return(true));
526   ASSERT_TRUE(model.RemoveConfig(id));
527 }
528 
TEST_F(FtraceConfigMuxerTest,AtraceMultipleConfigs)529 TEST_F(FtraceConfigMuxerTest, AtraceMultipleConfigs) {
530   NiceMock<MockFtraceProcfs> ftrace;
531   MockRunAtrace atrace;
532 
533   FtraceConfig config_a = CreateFtraceConfig({});
534   *config_a.add_atrace_apps() = "app_a";
535   *config_a.add_atrace_categories() = "cat_a";
536 
537   FtraceConfig config_b = CreateFtraceConfig({});
538   *config_b.add_atrace_apps() = "app_b";
539   *config_b.add_atrace_categories() = "cat_b";
540 
541   FtraceConfig config_c = CreateFtraceConfig({});
542   *config_c.add_atrace_apps() = "app_c";
543   *config_c.add_atrace_categories() = "cat_c";
544 
545   FtraceConfigMuxer model(&ftrace, table_.get(), {});
546 
547   EXPECT_CALL(atrace, RunAtrace(ElementsAreArray({"atrace", "--async_start",
548                                                   "--only_userspace", "cat_a",
549                                                   "-a", "app_a"})))
550       .WillOnce(Return(true));
551   FtraceConfigId id_a = model.SetupConfig(config_a);
552   ASSERT_TRUE(id_a);
553 
554   EXPECT_CALL(
555       atrace,
556       RunAtrace(ElementsAreArray({"atrace", "--async_start", "--only_userspace",
557                                   "cat_a", "cat_b", "-a", "app_a,app_b"})))
558       .WillOnce(Return(true));
559   FtraceConfigId id_b = model.SetupConfig(config_b);
560   ASSERT_TRUE(id_b);
561 
562   EXPECT_CALL(atrace,
563               RunAtrace(ElementsAreArray({"atrace", "--async_start",
564                                           "--only_userspace", "cat_a", "cat_b",
565                                           "cat_c", "-a", "app_a,app_b,app_c"})))
566       .WillOnce(Return(true));
567   FtraceConfigId id_c = model.SetupConfig(config_c);
568   ASSERT_TRUE(id_c);
569 
570   EXPECT_CALL(
571       atrace,
572       RunAtrace(ElementsAreArray({"atrace", "--async_start", "--only_userspace",
573                                   "cat_a", "cat_c", "-a", "app_a,app_c"})))
574       .WillOnce(Return(true));
575   ASSERT_TRUE(model.RemoveConfig(id_b));
576 
577   EXPECT_CALL(atrace, RunAtrace(ElementsAreArray({"atrace", "--async_start",
578                                                   "--only_userspace", "cat_c",
579                                                   "-a", "app_c"})))
580       .WillOnce(Return(true));
581   ASSERT_TRUE(model.RemoveConfig(id_a));
582 
583   EXPECT_CALL(atrace, RunAtrace(ElementsAreArray(
584                           {"atrace", "--async_stop", "--only_userspace"})))
585       .WillOnce(Return(true));
586   ASSERT_TRUE(model.RemoveConfig(id_c));
587 }
588 
TEST_F(FtraceConfigMuxerTest,AtraceFailedConfig)589 TEST_F(FtraceConfigMuxerTest, AtraceFailedConfig) {
590   NiceMock<MockFtraceProcfs> ftrace;
591   MockRunAtrace atrace;
592 
593   FtraceConfig config_a = CreateFtraceConfig({});
594   *config_a.add_atrace_apps() = "app_1";
595   *config_a.add_atrace_apps() = "app_2";
596   *config_a.add_atrace_categories() = "cat_1";
597   *config_a.add_atrace_categories() = "cat_2";
598 
599   FtraceConfig config_b = CreateFtraceConfig({});
600   *config_b.add_atrace_apps() = "app_fail";
601   *config_b.add_atrace_categories() = "cat_fail";
602 
603   FtraceConfig config_c = CreateFtraceConfig({});
604   *config_c.add_atrace_apps() = "app_1";
605   *config_c.add_atrace_apps() = "app_3";
606   *config_c.add_atrace_categories() = "cat_1";
607   *config_c.add_atrace_categories() = "cat_3";
608 
609   FtraceConfigMuxer model(&ftrace, table_.get(), {});
610 
611   EXPECT_CALL(
612       atrace,
613       RunAtrace(ElementsAreArray({"atrace", "--async_start", "--only_userspace",
614                                   "cat_1", "cat_2", "-a", "app_1,app_2"})))
615       .WillOnce(Return(true));
616   FtraceConfigId id_a = model.SetupConfig(config_a);
617   ASSERT_TRUE(id_a);
618 
619   EXPECT_CALL(atrace,
620               RunAtrace(ElementsAreArray(
621                   {"atrace", "--async_start", "--only_userspace", "cat_1",
622                    "cat_2", "cat_fail", "-a", "app_1,app_2,app_fail"})))
623       .WillOnce(Return(false));
624   FtraceConfigId id_b = model.SetupConfig(config_b);
625   ASSERT_TRUE(id_b);
626 
627   EXPECT_CALL(atrace,
628               RunAtrace(ElementsAreArray({"atrace", "--async_start",
629                                           "--only_userspace", "cat_1", "cat_2",
630                                           "cat_3", "-a", "app_1,app_2,app_3"})))
631       .WillOnce(Return(true));
632   FtraceConfigId id_c = model.SetupConfig(config_c);
633   ASSERT_TRUE(id_c);
634 
635   EXPECT_CALL(
636       atrace,
637       RunAtrace(ElementsAreArray({"atrace", "--async_start", "--only_userspace",
638                                   "cat_1", "cat_2", "-a", "app_1,app_2"})))
639       .WillOnce(Return(true));
640   ASSERT_TRUE(model.RemoveConfig(id_c));
641 
642   // Removing the config we failed to enable doesn't change the atrace state
643   // so we don't expect a call here.
644   ASSERT_TRUE(model.RemoveConfig(id_b));
645 
646   EXPECT_CALL(atrace, RunAtrace(ElementsAreArray(
647                           {"atrace", "--async_stop", "--only_userspace"})))
648       .WillOnce(Return(true));
649   ASSERT_TRUE(model.RemoveConfig(id_a));
650 }
651 
TEST_F(FtraceConfigMuxerTest,AtraceDuplicateConfigs)652 TEST_F(FtraceConfigMuxerTest, AtraceDuplicateConfigs) {
653   NiceMock<MockFtraceProcfs> ftrace;
654   MockRunAtrace atrace;
655 
656   FtraceConfig config_a = CreateFtraceConfig({});
657   *config_a.add_atrace_apps() = "app_1";
658   *config_a.add_atrace_categories() = "cat_1";
659 
660   FtraceConfig config_b = CreateFtraceConfig({});
661   *config_b.add_atrace_apps() = "app_1";
662   *config_b.add_atrace_categories() = "cat_1";
663 
664   FtraceConfigMuxer model(&ftrace, table_.get(), {});
665 
666   EXPECT_CALL(atrace, RunAtrace(ElementsAreArray({"atrace", "--async_start",
667                                                   "--only_userspace", "cat_1",
668                                                   "-a", "app_1"})))
669       .WillOnce(Return(true));
670   FtraceConfigId id_a = model.SetupConfig(config_a);
671   ASSERT_TRUE(id_a);
672 
673   FtraceConfigId id_b = model.SetupConfig(config_b);
674   ASSERT_TRUE(id_b);
675 
676   ASSERT_TRUE(model.RemoveConfig(id_a));
677 
678   EXPECT_CALL(atrace, RunAtrace(ElementsAreArray(
679                           {"atrace", "--async_stop", "--only_userspace"})))
680       .WillOnce(Return(true));
681   ASSERT_TRUE(model.RemoveConfig(id_b));
682 }
683 
TEST_F(FtraceConfigMuxerTest,AtraceAndFtraceConfigs)684 TEST_F(FtraceConfigMuxerTest, AtraceAndFtraceConfigs) {
685   NiceMock<MockFtraceProcfs> ftrace;
686   MockRunAtrace atrace;
687 
688   FtraceConfig config_a = CreateFtraceConfig({"sched/sched_cpu_hotplug"});
689 
690   FtraceConfig config_b = CreateFtraceConfig({"sched/sched_switch"});
691   *config_b.add_atrace_categories() = "b";
692 
693   FtraceConfig config_c = CreateFtraceConfig({"sched/sched_switch"});
694 
695   FtraceConfig config_d = CreateFtraceConfig({"sched/sched_cpu_hotplug"});
696   *config_d.add_atrace_categories() = "d";
697 
698   FtraceConfigMuxer model(&ftrace, table_.get(), {});
699 
700   FtraceConfigId id_a = model.SetupConfig(config_a);
701   ASSERT_TRUE(id_a);
702 
703   EXPECT_CALL(atrace, RunAtrace(ElementsAreArray({"atrace", "--async_start",
704                                                   "--only_userspace", "b"})))
705       .WillOnce(Return(true));
706   FtraceConfigId id_b = model.SetupConfig(config_b);
707   ASSERT_TRUE(id_b);
708 
709   FtraceConfigId id_c = model.SetupConfig(config_c);
710   ASSERT_TRUE(id_c);
711 
712   EXPECT_CALL(atrace,
713               RunAtrace(ElementsAreArray(
714                   {"atrace", "--async_start", "--only_userspace", "b", "d"})))
715       .WillOnce(Return(true));
716   FtraceConfigId id_d = model.SetupConfig(config_d);
717   ASSERT_TRUE(id_d);
718 
719   EXPECT_CALL(atrace, RunAtrace(ElementsAreArray({"atrace", "--async_start",
720                                                   "--only_userspace", "b"})))
721       .WillOnce(Return(true));
722   ASSERT_TRUE(model.RemoveConfig(id_d));
723 
724   ASSERT_TRUE(model.RemoveConfig(id_c));
725 
726   EXPECT_CALL(atrace, RunAtrace(ElementsAreArray(
727                           {"atrace", "--async_stop", "--only_userspace"})))
728       .WillOnce(Return(true));
729   ASSERT_TRUE(model.RemoveConfig(id_b));
730 
731   ASSERT_TRUE(model.RemoveConfig(id_a));
732 }
733 
TEST_F(FtraceConfigMuxerTest,SetupClockForTesting)734 TEST_F(FtraceConfigMuxerTest, SetupClockForTesting) {
735   MockFtraceProcfs ftrace;
736   FtraceConfig config;
737 
738   FtraceConfigMuxer model(&ftrace, table_.get(), {});
739 
740   EXPECT_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
741       .Times(AnyNumber());
742 
743   ON_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
744       .WillByDefault(Return("[local] global boot"));
745   EXPECT_CALL(ftrace, WriteToFile("/root/trace_clock", "boot"));
746   model.SetupClockForTesting(config);
747 
748   ON_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
749       .WillByDefault(Return("[local] global"));
750   EXPECT_CALL(ftrace, WriteToFile("/root/trace_clock", "global"));
751   model.SetupClockForTesting(config);
752 
753   ON_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
754       .WillByDefault(Return(""));
755   model.SetupClockForTesting(config);
756 
757   ON_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
758       .WillByDefault(Return("local [global]"));
759   model.SetupClockForTesting(config);
760 }
761 
TEST_F(FtraceConfigMuxerTest,GetFtraceEvents)762 TEST_F(FtraceConfigMuxerTest, GetFtraceEvents) {
763   MockFtraceProcfs ftrace;
764   FtraceConfigMuxer model(&ftrace, table_.get(), {});
765 
766   FtraceConfig config = CreateFtraceConfig({"sched/sched_switch"});
767   std::set<GroupAndName> events =
768       model.GetFtraceEventsForTesting(config, table_.get());
769 
770   EXPECT_THAT(events, Contains(GroupAndName("sched", "sched_switch")));
771   EXPECT_THAT(events, Not(Contains(GroupAndName("ftrace", "print"))));
772 }
773 
TEST_F(FtraceConfigMuxerTest,GetFtraceEventsAtrace)774 TEST_F(FtraceConfigMuxerTest, GetFtraceEventsAtrace) {
775   MockFtraceProcfs ftrace;
776   FtraceConfigMuxer model(&ftrace, table_.get(), {});
777 
778   FtraceConfig config = CreateFtraceConfig({});
779   *config.add_atrace_categories() = "sched";
780   std::set<GroupAndName> events =
781       model.GetFtraceEventsForTesting(config, table_.get());
782 
783   EXPECT_THAT(events, Contains(GroupAndName("sched", "sched_switch")));
784   EXPECT_THAT(events, Contains(GroupAndName("sched", "sched_cpu_hotplug")));
785   EXPECT_THAT(events, Contains(GroupAndName("ftrace", "print")));
786 }
787 
TEST_F(FtraceConfigMuxerTest,GetFtraceEventsAtraceCategories)788 TEST_F(FtraceConfigMuxerTest, GetFtraceEventsAtraceCategories) {
789   MockFtraceProcfs ftrace;
790   FtraceConfigMuxer model(&ftrace, table_.get(), {});
791 
792   FtraceConfig config = CreateFtraceConfig({});
793   *config.add_atrace_categories() = "sched";
794   *config.add_atrace_categories() = "memreclaim";
795   std::set<GroupAndName> events =
796       model.GetFtraceEventsForTesting(config, table_.get());
797 
798   EXPECT_THAT(events, Contains(GroupAndName("sched", "sched_switch")));
799   EXPECT_THAT(events, Contains(GroupAndName("sched", "sched_cpu_hotplug")));
800   EXPECT_THAT(events, Contains(GroupAndName("cgroup", "cgroup_mkdir")));
801   EXPECT_THAT(events, Contains(GroupAndName("vmscan",
802                                             "mm_vmscan_direct_reclaim_begin")));
803   EXPECT_THAT(events,
804               Contains(GroupAndName("lowmemorykiller", "lowmemory_kill")));
805   EXPECT_THAT(events, Contains(GroupAndName("ftrace", "print")));
806 }
807 
808 // Tests the enabling fallback logic that tries to use the "set_event" interface
809 // if writing the individual xxx/enable file fails.
TEST_F(FtraceConfigMuxerTest,FallbackOnSetEvent)810 TEST_F(FtraceConfigMuxerTest, FallbackOnSetEvent) {
811   MockFtraceProcfs ftrace;
812   FtraceConfig config =
813       CreateFtraceConfig({"sched/sched_switch", "cgroup/cgroup_mkdir"});
814   FtraceConfigMuxer model(&ftrace, table_.get(), {});
815 
816   ON_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
817       .WillByDefault(Return("[local] global boot"));
818   EXPECT_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
819       .Times(AnyNumber());
820 
821   EXPECT_CALL(ftrace, ReadOneCharFromFile("/root/tracing_on"))
822       .Times(2)
823       .WillRepeatedly(Return('0'));
824   EXPECT_CALL(ftrace, WriteToFile("/root/buffer_size_kb", _));
825   EXPECT_CALL(ftrace, WriteToFile("/root/trace_clock", "boot"));
826   EXPECT_CALL(ftrace, WriteToFile("/root/tracing_on", "1"));
827   EXPECT_CALL(ftrace,
828               WriteToFile("/root/events/sched/sched_switch/enable", "1"));
829   EXPECT_CALL(ftrace,
830               WriteToFile("/root/events/cgroup/cgroup_mkdir/enable", "1"))
831       .WillOnce(Return(false));
832   EXPECT_CALL(ftrace, AppendToFile("/root/set_event", "cgroup:cgroup_mkdir"))
833       .WillOnce(Return(true));
834   FtraceConfigId id = model.SetupConfig(config);
835   ASSERT_TRUE(id);
836   ASSERT_TRUE(model.ActivateConfig(id));
837 
838   const FtraceDataSourceConfig* ds_config = model.GetDataSourceConfig(id);
839   ASSERT_TRUE(ds_config);
840   EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
841               Contains(FtraceConfigMuxerTest::kFakeSchedSwitchEventId));
842   EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
843               Contains(FtraceConfigMuxerTest::kCgroupMkdirEventId));
844 
845   const EventFilter* central_filter = model.GetCentralEventFilterForTesting();
846   EXPECT_THAT(central_filter->GetEnabledEvents(),
847               Contains(FtraceConfigMuxerTest::kFakeSchedSwitchEventId));
848   EXPECT_THAT(central_filter->GetEnabledEvents(),
849               Contains(FtraceConfigMuxerTest::kCgroupMkdirEventId));
850 
851   EXPECT_CALL(ftrace, WriteToFile("/root/tracing_on", "0"));
852   EXPECT_CALL(ftrace, WriteToFile("/root/buffer_size_kb", "4"));
853   EXPECT_CALL(ftrace, WriteToFile("/root/events/enable", "0"));
854   EXPECT_CALL(ftrace,
855               WriteToFile("/root/events/sched/sched_switch/enable", "0"));
856   EXPECT_CALL(ftrace,
857               WriteToFile("/root/events/cgroup/cgroup_mkdir/enable", "0"))
858       .WillOnce(Return(false));
859   EXPECT_CALL(ftrace, AppendToFile("/root/set_event", "!cgroup:cgroup_mkdir"))
860       .WillOnce(Return(true));
861   EXPECT_CALL(ftrace, ClearFile("/root/trace"));
862   EXPECT_CALL(ftrace, ClearFile(MatchesRegex("/root/per_cpu/cpu[0-9]/trace")));
863   ASSERT_TRUE(model.RemoveConfig(id));
864 }
865 
TEST_F(FtraceConfigMuxerTest,CompactSchedConfig)866 TEST_F(FtraceConfigMuxerTest, CompactSchedConfig) {
867   // Set scheduling event format as validated. The pre-parsed format itself
868   // doesn't need to be sensible, as the tests won't use it.
869   auto valid_compact_format =
870       CompactSchedEventFormat{/*format_valid=*/true, CompactSchedSwitchFormat{},
871                               CompactSchedWakingFormat{}};
872 
873   NiceMock<MockFtraceProcfs> ftrace;
874   table_ = CreateFakeTable(valid_compact_format);
875   FtraceConfigMuxer model(&ftrace, table_.get(), {});
876 
877   // First data source - request compact encoding.
878   FtraceConfig config_enabled = CreateFtraceConfig({"sched/sched_switch"});
879   config_enabled.mutable_compact_sched()->set_enabled(true);
880 
881   // Second data source - no compact encoding (default).
882   FtraceConfig config_disabled = CreateFtraceConfig({"sched/sched_switch"});
883 
884   {
885     FtraceConfigId id = model.SetupConfig(config_enabled);
886     ASSERT_TRUE(id);
887     const FtraceDataSourceConfig* ds_config = model.GetDataSourceConfig(id);
888     ASSERT_TRUE(ds_config);
889     EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
890                 Contains(FtraceConfigMuxerTest::kFakeSchedSwitchEventId));
891     EXPECT_TRUE(ds_config->compact_sched.enabled);
892   }
893   {
894     FtraceConfigId id = model.SetupConfig(config_disabled);
895     ASSERT_TRUE(id);
896     const FtraceDataSourceConfig* ds_config = model.GetDataSourceConfig(id);
897     ASSERT_TRUE(ds_config);
898     EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
899                 Contains(FtraceConfigMuxerTest::kFakeSchedSwitchEventId));
900     EXPECT_FALSE(ds_config->compact_sched.enabled);
901   }
902 }
903 
TEST_F(FtraceConfigMuxerTest,CompactSchedConfigWithInvalidFormat)904 TEST_F(FtraceConfigMuxerTest, CompactSchedConfigWithInvalidFormat) {
905   NiceMock<MockFtraceProcfs> ftrace;
906   FtraceConfigMuxer model(&ftrace, table_.get(), {});
907 
908   // Request compact encoding.
909   FtraceConfig config = CreateFtraceConfig({"sched/sched_switch"});
910   config.mutable_compact_sched()->set_enabled(true);
911 
912   FtraceConfigId id = model.SetupConfig(config);
913   ASSERT_TRUE(id);
914 
915   // The translation table says that the scheduling events' format didn't match
916   // compile-time assumptions, so we won't enable compact events even if
917   // requested.
918   const FtraceDataSourceConfig* ds_config = model.GetDataSourceConfig(id);
919   ASSERT_TRUE(ds_config);
920   EXPECT_THAT(ds_config->event_filter.GetEnabledEvents(),
921               Contains(FtraceConfigMuxerTest::kFakeSchedSwitchEventId));
922   EXPECT_FALSE(ds_config->compact_sched.enabled);
923 }
924 
925 }  // namespace
926 }  // namespace perfetto
927