/* * Copyright (C) 2018 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/ftrace_reader/ftrace_config_muxer.h" #include <memory> #include "gmock/gmock.h" #include "gtest/gtest.h" #include "src/ftrace_reader/atrace_wrapper.h" #include "src/ftrace_reader/ftrace_procfs.h" #include "src/ftrace_reader/proto_translation_table.h" using testing::_; using testing::AnyNumber; using testing::Contains; using testing::ElementsAreArray; using testing::Eq; using testing::IsEmpty; using testing::NiceMock; using testing::Not; using testing::Return; using testing::UnorderedElementsAre; namespace perfetto { namespace { class MockFtraceProcfs : public FtraceProcfs { public: MockFtraceProcfs() : FtraceProcfs("/root/") { ON_CALL(*this, NumberOfCpus()).WillByDefault(Return(1)); ON_CALL(*this, WriteToFile(_, _)).WillByDefault(Return(true)); ON_CALL(*this, ClearFile(_)).WillByDefault(Return(true)); EXPECT_CALL(*this, NumberOfCpus()).Times(AnyNumber()); } MOCK_METHOD2(WriteToFile, bool(const std::string& path, const std::string& str)); MOCK_METHOD1(ReadOneCharFromFile, char(const std::string& path)); MOCK_METHOD1(ClearFile, bool(const std::string& path)); MOCK_CONST_METHOD1(ReadFileIntoString, std::string(const std::string& path)); MOCK_CONST_METHOD0(NumberOfCpus, size_t()); }; struct MockRunAtrace { MockRunAtrace() { static MockRunAtrace* instance; instance = this; SetRunAtraceForTesting([](const std::vector<std::string>& args) { return instance->RunAtrace(args); }); } ~MockRunAtrace() { SetRunAtraceForTesting(nullptr); } MOCK_METHOD1(RunAtrace, bool(const std::vector<std::string>&)); }; std::unique_ptr<ProtoTranslationTable> CreateFakeTable() { std::vector<Field> common_fields; std::vector<Event> events; { Event event; event.name = "sched_switch"; event.group = "sched"; event.ftrace_event_id = 1; events.push_back(event); } { Event event; event.name = "sched_wakeup"; event.group = "sched"; event.ftrace_event_id = 10; events.push_back(event); } { Event event; event.name = "sched_new"; event.group = "sched"; event.ftrace_event_id = 11; events.push_back(event); } { Event event; event.name = "cgroup_mkdir"; event.group = "cgroup"; event.ftrace_event_id = 12; events.push_back(event); } { Event event; event.name = "mm_vmscan_direct_reclaim_begin"; event.group = "vmscan"; event.ftrace_event_id = 13; events.push_back(event); } { Event event; event.name = "lowmemory_kill"; event.group = "lowmemorykiller"; event.ftrace_event_id = 14; events.push_back(event); } { Event event; event.name = "print"; event.group = "ftrace"; event.ftrace_event_id = 20; events.push_back(event); } return std::unique_ptr<ProtoTranslationTable>( new ProtoTranslationTable(events, std::move(common_fields))); } TEST(FtraceConfigMuxerTest, ComputeCpuBufferSizeInPages) { static constexpr size_t kMaxBufSizeInPages = 16 * 1024u; // No buffer size given: good default (128 pages = 512kb). EXPECT_EQ(ComputeCpuBufferSizeInPages(0), 128u); // Buffer size given way too big: good default. EXPECT_EQ(ComputeCpuBufferSizeInPages(10 * 1024 * 1024), kMaxBufSizeInPages); // The limit is 64mb per CPU, 512mb is too much. EXPECT_EQ(ComputeCpuBufferSizeInPages(512 * 1024), kMaxBufSizeInPages); // Your size ends up with less than 1 page per cpu -> 1 page. EXPECT_EQ(ComputeCpuBufferSizeInPages(3), 1u); // You picked a good size -> your size rounded to nearest page. EXPECT_EQ(ComputeCpuBufferSizeInPages(42), 10u); } TEST(FtraceConfigMuxerTest, TurnFtraceOnOff) { std::unique_ptr<ProtoTranslationTable> table = CreateFakeTable(); MockFtraceProcfs ftrace; FtraceConfig config = CreateFtraceConfig({"sched_switch", "foo"}); FtraceConfigMuxer model(&ftrace, table.get()); ON_CALL(ftrace, ReadFileIntoString("/root/trace_clock")) .WillByDefault(Return("[local] global boot")); EXPECT_CALL(ftrace, ReadFileIntoString("/root/trace_clock")) .Times(AnyNumber()); EXPECT_CALL(ftrace, ReadOneCharFromFile("/root/tracing_on")) .WillOnce(Return('0')); EXPECT_CALL(ftrace, WriteToFile("/root/buffer_size_kb", "512")); EXPECT_CALL(ftrace, WriteToFile("/root/trace_clock", "boot")); EXPECT_CALL(ftrace, WriteToFile("/root/tracing_on", "1")); EXPECT_CALL(ftrace, WriteToFile("/root/events/sched/sched_switch/enable", "1")); FtraceConfigId id = model.RequestConfig(config); ASSERT_TRUE(id); const FtraceConfig* actual_config = model.GetConfig(id); EXPECT_TRUE(actual_config); EXPECT_THAT(actual_config->ftrace_events(), Contains("sched_switch")); EXPECT_THAT(actual_config->ftrace_events(), Not(Contains("foo"))); EXPECT_CALL(ftrace, WriteToFile("/root/tracing_on", "0")); EXPECT_CALL(ftrace, WriteToFile("/root/buffer_size_kb", "0")); EXPECT_CALL(ftrace, WriteToFile("/root/events/enable", "0")); EXPECT_CALL(ftrace, WriteToFile("/root/events/sched/sched_switch/enable", "0")); EXPECT_CALL(ftrace, ClearFile("/root/trace")); ASSERT_TRUE(model.RemoveConfig(id)); } TEST(FtraceConfigMuxerTest, FtraceIsAlreadyOn) { std::unique_ptr<ProtoTranslationTable> table = CreateFakeTable(); MockFtraceProcfs ftrace; FtraceConfig config = CreateFtraceConfig({"sched_switch"}); FtraceConfigMuxer model(&ftrace, table.get()); // If someone is using ftrace already don't stomp on what they are doing. EXPECT_CALL(ftrace, ReadOneCharFromFile("/root/tracing_on")) .WillOnce(Return('1')); FtraceConfigId id = model.RequestConfig(config); ASSERT_FALSE(id); } TEST(FtraceConfigMuxerTest, Atrace) { std::unique_ptr<ProtoTranslationTable> table = CreateFakeTable(); NiceMock<MockFtraceProcfs> ftrace; MockRunAtrace atrace; FtraceConfig config = CreateFtraceConfig({"sched_switch"}); *config.add_atrace_categories() = "sched"; FtraceConfigMuxer model(&ftrace, table.get()); EXPECT_CALL(ftrace, ReadOneCharFromFile("/root/tracing_on")) .WillOnce(Return('0')); EXPECT_CALL(atrace, RunAtrace(ElementsAreArray( {"atrace", "--async_start", "--only_userspace", "sched"}))) .WillOnce(Return(true)); FtraceConfigId id = model.RequestConfig(config); ASSERT_TRUE(id); const FtraceConfig* actual_config = model.GetConfig(id); EXPECT_TRUE(actual_config); EXPECT_THAT(actual_config->ftrace_events(), Contains("sched_switch")); EXPECT_THAT(actual_config->ftrace_events(), Contains("print")); EXPECT_CALL(atrace, RunAtrace(ElementsAreArray( {"atrace", "--async_stop", "--only_userspace"}))) .WillOnce(Return(true)); ASSERT_TRUE(model.RemoveConfig(id)); } TEST(FtraceConfigMuxerTest, SetupClockForTesting) { std::unique_ptr<ProtoTranslationTable> table = CreateFakeTable(); MockFtraceProcfs ftrace; FtraceConfig config; FtraceConfigMuxer model(&ftrace, table.get()); EXPECT_CALL(ftrace, ReadFileIntoString("/root/trace_clock")) .Times(AnyNumber()); ON_CALL(ftrace, ReadFileIntoString("/root/trace_clock")) .WillByDefault(Return("[local] global boot")); EXPECT_CALL(ftrace, WriteToFile("/root/trace_clock", "boot")); model.SetupClockForTesting(config); ON_CALL(ftrace, ReadFileIntoString("/root/trace_clock")) .WillByDefault(Return("[local] global")); EXPECT_CALL(ftrace, WriteToFile("/root/trace_clock", "global")); model.SetupClockForTesting(config); ON_CALL(ftrace, ReadFileIntoString("/root/trace_clock")) .WillByDefault(Return("")); model.SetupClockForTesting(config); ON_CALL(ftrace, ReadFileIntoString("/root/trace_clock")) .WillByDefault(Return("local [global]")); model.SetupClockForTesting(config); } TEST(FtraceConfigMuxerTest, GetFtraceEvents) { std::unique_ptr<ProtoTranslationTable> table = CreateFakeTable(); FtraceConfig config = CreateFtraceConfig({"sched_switch"}); std::set<std::string> events = GetFtraceEvents(config, table.get()); EXPECT_THAT(events, Contains("sched_switch")); EXPECT_THAT(events, Not(Contains("print"))); } TEST(FtraceConfigMuxerTest, GetFtraceEventsAtrace) { std::unique_ptr<ProtoTranslationTable> table = CreateFakeTable(); FtraceConfig config = CreateFtraceConfig({}); *config.add_atrace_categories() = "sched"; std::set<std::string> events = GetFtraceEvents(config, table.get()); EXPECT_THAT(events, Contains("sched_switch")); EXPECT_THAT(events, Contains("sched_cpu_hotplug")); EXPECT_THAT(events, Contains("print")); } TEST(FtraceConfigMuxerTest, GetFtraceEventsAtraceCategories) { std::unique_ptr<ProtoTranslationTable> table = CreateFakeTable(); FtraceConfig config = CreateFtraceConfig({}); *config.add_atrace_categories() = "sched"; *config.add_atrace_categories() = "memreclaim"; std::set<std::string> events = GetFtraceEvents(config, table.get()); EXPECT_THAT(events, Contains("sched_switch")); EXPECT_THAT(events, Contains("sched_cpu_hotplug")); EXPECT_THAT(events, Contains("cgroup_mkdir")); EXPECT_THAT(events, Contains("mm_vmscan_direct_reclaim_begin")); EXPECT_THAT(events, Contains("lowmemory_kill")); EXPECT_THAT(events, Contains("print")); } } // namespace } // namespace perfetto