diff options
Diffstat (limited to 'src/traced/probes/ftrace/ftrace_controller_unittest.cc')
-rw-r--r-- | src/traced/probes/ftrace/ftrace_controller_unittest.cc | 223 |
1 files changed, 154 insertions, 69 deletions
diff --git a/src/traced/probes/ftrace/ftrace_controller_unittest.cc b/src/traced/probes/ftrace/ftrace_controller_unittest.cc index 6622ee235..e6bdc12a2 100644 --- a/src/traced/probes/ftrace/ftrace_controller_unittest.cc +++ b/src/traced/probes/ftrace/ftrace_controller_unittest.cc @@ -20,32 +20,32 @@ #include <sys/stat.h> #include <sys/types.h> -#include "src/traced/probes/ftrace/compact_sched.h" #include "src/traced/probes/ftrace/cpu_reader.h" +#include "src/traced/probes/ftrace/ftrace_config.h" #include "src/traced/probes/ftrace/ftrace_config_muxer.h" -#include "src/traced/probes/ftrace/ftrace_config_utils.h" #include "src/traced/probes/ftrace/ftrace_data_source.h" #include "src/traced/probes/ftrace/ftrace_procfs.h" #include "src/traced/probes/ftrace/proto_translation_table.h" #include "src/tracing/core/trace_writer_for_testing.h" -#include "test/gtest_and_gmock.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" -#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" -#include "protos/perfetto/trace/ftrace/ftrace_stats.pbzero.h" -#include "protos/perfetto/trace/trace_packet.pb.h" -#include "protos/perfetto/trace/trace_packet.pbzero.h" +#include "perfetto/trace/trace_packet.pb.h" + +#include "perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" +#include "perfetto/trace/ftrace/ftrace_stats.pbzero.h" +#include "perfetto/trace/trace_packet.pbzero.h" using testing::_; using testing::AnyNumber; using testing::ByMove; -using testing::ElementsAre; using testing::Invoke; -using testing::IsEmpty; -using testing::MatchesRegex; -using testing::Mock; using testing::NiceMock; -using testing::Pair; +using testing::MatchesRegex; using testing::Return; +using testing::IsEmpty; +using testing::ElementsAre; +using testing::Pair; using Table = perfetto::ProtoTranslationTable; using FtraceEventBundle = perfetto::protos::pbzero::FtraceEventBundle; @@ -59,35 +59,72 @@ constexpr char kBarEnablePath[] = "/root/events/group/bar/enable"; class MockTaskRunner : public base::TaskRunner { public: + MockTaskRunner() { + ON_CALL(*this, PostTask(_)) + .WillByDefault(Invoke(this, &MockTaskRunner::OnPostTask)); + ON_CALL(*this, PostDelayedTask(_, _)) + .WillByDefault(Invoke(this, &MockTaskRunner::OnPostDelayedTask)); + } + + void OnPostTask(std::function<void()> task) { + std::unique_lock<std::mutex> lock(lock_); + EXPECT_FALSE(task_); + task_ = std::move(task); + } + + void OnPostDelayedTask(std::function<void()> task, int /*delay*/) { + std::unique_lock<std::mutex> lock(lock_); + EXPECT_FALSE(task_); + task_ = std::move(task); + } + + void RunLastTask() { + auto task = TakeTask(); + if (task) + task(); + } + + std::function<void()> TakeTask() { + std::unique_lock<std::mutex> lock(lock_); + auto task(std::move(task_)); + task_ = std::function<void()>(); + return task; + } + MOCK_METHOD1(PostTask, void(std::function<void()>)); MOCK_METHOD2(PostDelayedTask, void(std::function<void()>, uint32_t delay_ms)); MOCK_METHOD2(AddFileDescriptorWatch, void(int fd, std::function<void()>)); MOCK_METHOD1(RemoveFileDescriptorWatch, void(int fd)); MOCK_CONST_METHOD0(RunsTasksOnCurrentThread, bool()); + + private: + std::mutex lock_; + std::function<void()> task_; }; std::unique_ptr<Table> FakeTable(FtraceProcfs* ftrace) { std::vector<Field> common_fields; std::vector<Event> events; + { - events.push_back(Event{}); - auto& event = events.back(); + Event event; event.name = "foo"; event.group = "group"; event.ftrace_event_id = 1; + events.push_back(event); } + { - events.push_back(Event{}); - auto& event = events.back(); + Event event; event.name = "bar"; event.group = "group"; event.ftrace_event_id = 10; + events.push_back(event); } return std::unique_ptr<Table>( new Table(ftrace, events, std::move(common_fields), - ProtoTranslationTable::DefaultPageHeaderSpecForTesting(), - InvalidCompactSchedEventFormatForTesting())); + ProtoTranslationTable::DefaultPageHeaderSpecForTesting())); } std::unique_ptr<FtraceConfigMuxer> FakeModel(FtraceProcfs* ftrace, @@ -178,11 +215,34 @@ class TestFtraceController : public FtraceController, runner_(std::move(runner)), procfs_(raw_procfs) {} + MOCK_METHOD1(OnDrainCpuForTesting, void(size_t cpu)); + MockTaskRunner* runner() { return runner_.get(); } MockFtraceProcfs* procfs() { return procfs_; } + uint64_t NowMs() const override { return now_ms; } + uint32_t drain_period_ms() { return GetDrainPeriodMs(); } + std::function<void()> GetDataAvailableCallback(size_t cpu) { + int generation = generation_; + auto* thread_sync = &thread_sync_; + return [cpu, generation, thread_sync] { + FtraceController::OnCpuReaderRead(cpu, generation, thread_sync); + }; + } + + void WaitForData(size_t cpu) { + for (;;) { + { + std::unique_lock<std::mutex> lock(thread_sync_.mutex); + if (thread_sync_.cpus_to_drain[cpu]) + return; + } + usleep(5000); + } + } + std::unique_ptr<FtraceDataSource> AddFakeDataSource(const FtraceConfig& cfg) { std::unique_ptr<FtraceDataSource> data_source(new FtraceDataSource( GetWeakPtr(), 0 /* session id */, cfg, nullptr /* trace_writer */)); @@ -206,10 +266,15 @@ class TestFtraceController : public FtraceController, namespace { std::unique_ptr<TestFtraceController> CreateTestController( + bool runner_is_nice_mock, bool procfs_is_nice_mock, size_t cpu_count = 1) { - std::unique_ptr<MockTaskRunner> runner = - std::unique_ptr<MockTaskRunner>(new NiceMock<MockTaskRunner>()); + std::unique_ptr<MockTaskRunner> runner; + if (runner_is_nice_mock) { + runner = std::unique_ptr<MockTaskRunner>(new NiceMock<MockTaskRunner>()); + } else { + runner = std::unique_ptr<MockTaskRunner>(new MockTaskRunner()); + } std::unique_ptr<MockFtraceProcfs> ftrace_procfs; if (procfs_is_nice_mock) { @@ -233,14 +298,16 @@ std::unique_ptr<TestFtraceController> CreateTestController( } // namespace TEST(FtraceControllerTest, NonExistentEventsDontCrash) { - auto controller = CreateTestController(true /* nice procfs */); + auto controller = + CreateTestController(true /* nice runner */, true /* nice procfs */); FtraceConfig config = CreateFtraceConfig({"not_an_event"}); EXPECT_TRUE(controller->AddFakeDataSource(config)); } TEST(FtraceControllerTest, RejectsBadEventNames) { - auto controller = CreateTestController(true /* nice procfs */); + auto controller = + CreateTestController(true /* nice runner */, true /* nice procfs */); FtraceConfig config = CreateFtraceConfig({"../try/to/escape"}); EXPECT_FALSE(controller->AddFakeDataSource(config)); @@ -251,10 +318,8 @@ TEST(FtraceControllerTest, RejectsBadEventNames) { } TEST(FtraceControllerTest, OneSink) { - auto controller = CreateTestController(false /* nice procfs */); - - // No read tasks posted as part of adding the data source. - EXPECT_CALL(*controller->runner(), PostDelayedTask(_, _)).Times(0); + auto controller = + CreateTestController(true /* nice runner */, false /* nice procfs */); FtraceConfig config = CreateFtraceConfig({"group/foo"}); @@ -263,20 +328,10 @@ TEST(FtraceControllerTest, OneSink) { auto data_source = controller->AddFakeDataSource(config); ASSERT_TRUE(data_source); - // Verify that no read tasks have been posted. And set up expectation that - // a single recurring read task will be posted as part of starting the data - // source. - Mock::VerifyAndClearExpectations(controller->runner()); - EXPECT_CALL(*controller->runner(), PostDelayedTask(_, _)).Times(1); - EXPECT_CALL(*controller->procfs(), WriteToFile("/root/tracing_on", "1")); ASSERT_TRUE(controller->StartDataSource(data_source.get())); - // Verify single posted read task. - Mock::VerifyAndClearExpectations(controller->runner()); - - // State clearing on tracing teardown. - EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_size_kb", "4")); + EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_size_kb", "0")); EXPECT_CALL(*controller->procfs(), ClearFile("/root/trace")) .WillOnce(Return(true)); EXPECT_CALL(*controller->procfs(), @@ -292,39 +347,27 @@ TEST(FtraceControllerTest, OneSink) { } TEST(FtraceControllerTest, MultipleSinks) { - auto controller = CreateTestController(false /* nice procfs */); + auto controller = + CreateTestController(false /* nice runner */, false /* nice procfs */); FtraceConfig configA = CreateFtraceConfig({"group/foo"}); FtraceConfig configB = CreateFtraceConfig({"group/foo", "group/bar"}); - // No read tasks posted as part of adding the data sources. - EXPECT_CALL(*controller->runner(), PostDelayedTask(_, _)).Times(0); - EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_size_kb", _)); EXPECT_CALL(*controller->procfs(), WriteToFile(kFooEnablePath, "1")); auto data_sourceA = controller->AddFakeDataSource(configA); EXPECT_CALL(*controller->procfs(), WriteToFile(kBarEnablePath, "1")); auto data_sourceB = controller->AddFakeDataSource(configB); - // Verify that no read tasks have been posted. And set up expectation that - // a single recurring read task will be posted as part of starting the data - // sources. - Mock::VerifyAndClearExpectations(controller->runner()); - EXPECT_CALL(*controller->runner(), PostDelayedTask(_, _)).Times(1); - EXPECT_CALL(*controller->procfs(), WriteToFile("/root/tracing_on", "1")); ASSERT_TRUE(controller->StartDataSource(data_sourceA.get())); ASSERT_TRUE(controller->StartDataSource(data_sourceB.get())); - // Verify single posted read task. - Mock::VerifyAndClearExpectations(controller->runner()); - data_sourceA.reset(); - // State clearing on tracing teardown. EXPECT_CALL(*controller->procfs(), WriteToFile(kFooEnablePath, "0")); EXPECT_CALL(*controller->procfs(), WriteToFile(kBarEnablePath, "0")); - EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_size_kb", "4")); + EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_size_kb", "0")); EXPECT_CALL(*controller->procfs(), WriteToFile("/root/tracing_on", "0")); EXPECT_CALL(*controller->procfs(), WriteToFile("/root/events/enable", "0")); EXPECT_CALL(*controller->procfs(), ClearFile("/root/trace")); @@ -334,7 +377,8 @@ TEST(FtraceControllerTest, MultipleSinks) { } TEST(FtraceControllerTest, ControllerMayDieFirst) { - auto controller = CreateTestController(false /* nice procfs */); + auto controller = + CreateTestController(false /* nice runner */, false /* nice procfs */); FtraceConfig config = CreateFtraceConfig({"group/foo"}); @@ -345,7 +389,6 @@ TEST(FtraceControllerTest, ControllerMayDieFirst) { EXPECT_CALL(*controller->procfs(), WriteToFile("/root/tracing_on", "1")); ASSERT_TRUE(controller->StartDataSource(data_source.get())); - // State clearing on tracing teardown. EXPECT_CALL(*controller->procfs(), WriteToFile(kFooEnablePath, "0")); EXPECT_CALL(*controller->procfs(), ClearFile("/root/trace")) .WillOnce(Return(true)); @@ -353,24 +396,62 @@ TEST(FtraceControllerTest, ControllerMayDieFirst) { ClearFile(MatchesRegex("/root/per_cpu/cpu[0-9]/trace"))) .WillRepeatedly(Return(true)); EXPECT_CALL(*controller->procfs(), WriteToFile("/root/tracing_on", "0")); - EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_size_kb", "4")); + EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_size_kb", "0")); EXPECT_CALL(*controller->procfs(), WriteToFile("/root/events/enable", "0")); controller.reset(); data_source.reset(); } +TEST(FtraceControllerTest, BackToBackEnableDisable) { + auto controller = + CreateTestController(false /* nice runner */, false /* nice procfs */); + + // For this test we don't care about calls to WriteToFile/ClearFile. + EXPECT_CALL(*controller->procfs(), WriteToFile(_, _)).Times(AnyNumber()); + EXPECT_CALL(*controller->procfs(), ClearFile(_)).Times(AnyNumber()); + EXPECT_CALL(*controller->procfs(), ReadOneCharFromFile("/root/tracing_on")) + .Times(AnyNumber()); + + EXPECT_CALL(*controller->runner(), PostTask(_)).Times(2); + EXPECT_CALL(*controller->runner(), PostDelayedTask(_, 100)).Times(2); + FtraceConfig config = CreateFtraceConfig({"group/foo"}); + auto data_source = controller->AddFakeDataSource(config); + ASSERT_TRUE(controller->StartDataSource(data_source.get())); + + auto on_data_available = controller->GetDataAvailableCallback(0u); + std::thread worker([on_data_available] { on_data_available(); }); + controller->WaitForData(0u); + + // Disable the first data source and run the delayed task that it generated. + // It should be a no-op. + data_source.reset(); + controller->runner()->RunLastTask(); + controller->runner()->RunLastTask(); + worker.join(); + + // Register another data source and wait for it to generate data. + data_source = controller->AddFakeDataSource(config); + ASSERT_TRUE(controller->StartDataSource(data_source.get())); + + on_data_available = controller->GetDataAvailableCallback(0u); + std::thread worker2([on_data_available] { on_data_available(); }); + controller->WaitForData(0u); + + // This drain should also be a no-op after the data source is unregistered. + data_source.reset(); + controller->runner()->RunLastTask(); + controller->runner()->RunLastTask(); + worker2.join(); +} + TEST(FtraceControllerTest, BufferSize) { - auto controller = CreateTestController(false /* nice procfs */); + auto controller = + CreateTestController(true /* nice runner */, false /* nice procfs */); // For this test we don't care about most calls to WriteToFile/ClearFile. EXPECT_CALL(*controller->procfs(), WriteToFile(_, _)).Times(AnyNumber()); EXPECT_CALL(*controller->procfs(), ClearFile(_)).Times(AnyNumber()); - // Every time a fake data source is destroyed, the controller will reset the - // buffer size to a single page. - EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_size_kb", "4")) - .Times(AnyNumber()); - { // No buffer size -> good default. EXPECT_CALL(*controller->procfs(), @@ -402,8 +483,9 @@ TEST(FtraceControllerTest, BufferSize) { } { - // Your size ends up with less than 1 page per cpu -> 1 page (gmock already - // covered by the cleanup expectation above). + // Your size ends up with less than 1 page per cpu -> 1 page. + EXPECT_CALL(*controller->procfs(), + WriteToFile("/root/buffer_size_kb", "4")); FtraceConfig config = CreateFtraceConfig({"group/foo"}); config.set_buffer_size_kb(1); auto data_source = controller->AddFakeDataSource(config); @@ -433,7 +515,8 @@ TEST(FtraceControllerTest, BufferSize) { } TEST(FtraceControllerTest, PeriodicDrainConfig) { - auto controller = CreateTestController(false /* nice procfs */); + auto controller = + CreateTestController(true /* nice runner */, false /* nice procfs */); // For this test we don't care about calls to WriteToFile/ClearFile. EXPECT_CALL(*controller->procfs(), WriteToFile(_, _)).Times(AnyNumber()); @@ -475,10 +558,12 @@ TEST(FtraceMetadataTest, Clear) { FtraceMetadata metadata; metadata.inode_and_device.push_back(std::make_pair(1, 1)); metadata.pids.push_back(2); + metadata.overwrite_count = 3; metadata.last_seen_device_id = 100; metadata.Clear(); EXPECT_THAT(metadata.inode_and_device, IsEmpty()); EXPECT_THAT(metadata.pids, IsEmpty()); + EXPECT_EQ(0u, metadata.overwrite_count); EXPECT_EQ(BlockDeviceID(0), metadata.last_seen_device_id); } @@ -534,11 +619,11 @@ TEST(FtraceStatsTest, Write) { stats.Write(out); } - protos::TracePacket result_packet = writer->GetOnlyTracePacket(); - auto result = result_packet.ftrace_stats().cpu_stats(0); - EXPECT_EQ(result.cpu(), 0u); - EXPECT_EQ(result.entries(), 1u); - EXPECT_EQ(result.overrun(), 2u); + std::unique_ptr<protos::TracePacket> result_packet = writer->ParseProto(); + auto result = result_packet->ftrace_stats().cpu_stats(0); + EXPECT_EQ(result.cpu(), 0); + EXPECT_EQ(result.entries(), 1); + EXPECT_EQ(result.overrun(), 2); } } // namespace perfetto |