// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/trace_event/memory_dump_manager.h" #include <stdint.h> #include <vector> #include "base/bind_helpers.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "base/strings/stringprintf.h" #include "base/synchronization/waitable_event.h" #include "base/test/test_io_thread.h" #include "base/test/trace_event_analyzer.h" #include "base/thread_task_runner_handle.h" #include "base/threading/platform_thread.h" #include "base/threading/thread.h" #include "base/trace_event/memory_dump_provider.h" #include "base/trace_event/process_memory_dump.h" #include "base/trace_event/trace_buffer.h" #include "base/trace_event/trace_config_memory_test_util.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using testing::_; using testing::AnyNumber; using testing::AtMost; using testing::Between; using testing::Invoke; using testing::Return; namespace base { namespace trace_event { // GTest matchers for MemoryDumpRequestArgs arguments. MATCHER(IsDetailedDump, "") { return arg.level_of_detail == MemoryDumpLevelOfDetail::DETAILED; } MATCHER(IsLightDump, "") { return arg.level_of_detail == MemoryDumpLevelOfDetail::LIGHT; } namespace { void RegisterDumpProvider( MemoryDumpProvider* mdp, const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, const MemoryDumpProvider::Options& options) { MemoryDumpManager* mdm = MemoryDumpManager::GetInstance(); mdm->set_dumper_registrations_ignored_for_testing(false); mdm->RegisterDumpProvider(mdp, "TestDumpProvider", task_runner, options); mdm->set_dumper_registrations_ignored_for_testing(true); } void RegisterDumpProvider(MemoryDumpProvider* mdp) { RegisterDumpProvider(mdp, nullptr, MemoryDumpProvider::Options()); } void OnTraceDataCollected(Closure quit_closure, trace_event::TraceResultBuffer* buffer, const scoped_refptr<RefCountedString>& json, bool has_more_events) { buffer->AddFragment(json->data()); if (!has_more_events) quit_closure.Run(); } } // namespace // Testing MemoryDumpManagerDelegate which, by default, short-circuits dump // requests locally to the MemoryDumpManager instead of performing IPC dances. class MemoryDumpManagerDelegateForTesting : public MemoryDumpManagerDelegate { public: MemoryDumpManagerDelegateForTesting() { ON_CALL(*this, RequestGlobalMemoryDump(_, _)) .WillByDefault(Invoke( this, &MemoryDumpManagerDelegateForTesting::CreateProcessDump)); } MOCK_METHOD2(RequestGlobalMemoryDump, void(const MemoryDumpRequestArgs& args, const MemoryDumpCallback& callback)); uint64_t GetTracingProcessId() const override { NOTREACHED(); return MemoryDumpManager::kInvalidTracingProcessId; } }; class MockMemoryDumpProvider : public MemoryDumpProvider { public: MOCK_METHOD0(Destructor, void()); MOCK_METHOD2(OnMemoryDump, bool(const MemoryDumpArgs& args, ProcessMemoryDump* pmd)); MockMemoryDumpProvider() : enable_mock_destructor(false) {} ~MockMemoryDumpProvider() override { if (enable_mock_destructor) Destructor(); } bool enable_mock_destructor; }; class MemoryDumpManagerTest : public testing::Test { public: MemoryDumpManagerTest() : testing::Test(), kDefaultOptions() {} void SetUp() override { last_callback_success_ = false; message_loop_.reset(new MessageLoop()); mdm_.reset(new MemoryDumpManager()); MemoryDumpManager::SetInstanceForTesting(mdm_.get()); ASSERT_EQ(mdm_.get(), MemoryDumpManager::GetInstance()); delegate_.reset(new MemoryDumpManagerDelegateForTesting); } void TearDown() override { MemoryDumpManager::SetInstanceForTesting(nullptr); mdm_.reset(); delegate_.reset(); message_loop_.reset(); TraceLog::DeleteForTesting(); } // Turns a Closure into a MemoryDumpCallback, keeping track of the callback // result and taking care of posting the closure on the correct task runner. void DumpCallbackAdapter(scoped_refptr<SingleThreadTaskRunner> task_runner, Closure closure, uint64_t dump_guid, bool success) { last_callback_success_ = success; task_runner->PostTask(FROM_HERE, closure); } protected: void InitializeMemoryDumpManager(bool is_coordinator) { mdm_->set_dumper_registrations_ignored_for_testing(true); mdm_->Initialize(delegate_.get(), is_coordinator); } void RequestGlobalDumpAndWait(MemoryDumpType dump_type, MemoryDumpLevelOfDetail level_of_detail) { RunLoop run_loop; MemoryDumpCallback callback = Bind(&MemoryDumpManagerTest::DumpCallbackAdapter, Unretained(this), MessageLoop::current()->task_runner(), run_loop.QuitClosure()); mdm_->RequestGlobalDump(dump_type, level_of_detail, callback); run_loop.Run(); } void EnableTracingWithLegacyCategories(const char* category) { TraceLog::GetInstance()->SetEnabled(TraceConfig(category, ""), TraceLog::RECORDING_MODE); } void EnableTracingWithTraceConfig(const std::string& trace_config) { TraceLog::GetInstance()->SetEnabled(TraceConfig(trace_config), TraceLog::RECORDING_MODE); } void DisableTracing() { TraceLog::GetInstance()->SetDisabled(); } bool IsPeriodicDumpingEnabled() const { return mdm_->periodic_dump_timer_.IsRunning(); } int GetMaxConsecutiveFailuresCount() const { return MemoryDumpManager::kMaxConsecutiveFailuresCount; } const MemoryDumpProvider::Options kDefaultOptions; scoped_ptr<MemoryDumpManager> mdm_; scoped_ptr<MemoryDumpManagerDelegateForTesting> delegate_; bool last_callback_success_; private: scoped_ptr<MessageLoop> message_loop_; // We want our singleton torn down after each test. ShadowingAtExitManager at_exit_manager_; }; // Basic sanity checks. Registers a memory dump provider and checks that it is // called, but only when memory-infra is enabled. TEST_F(MemoryDumpManagerTest, SingleDumper) { InitializeMemoryDumpManager(false /* is_coordinator */); MockMemoryDumpProvider mdp; RegisterDumpProvider(&mdp); // Check that the dumper is not called if the memory category is not enabled. EnableTracingWithLegacyCategories("foobar-but-not-memory"); EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(0); EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(0); RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::DETAILED); DisableTracing(); // Now repeat enabling the memory category and check that the dumper is // invoked this time. EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(3); EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(3).WillRepeatedly(Return(true)); for (int i = 0; i < 3; ++i) RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::DETAILED); DisableTracing(); mdm_->UnregisterDumpProvider(&mdp); // Finally check the unregister logic: the delegate will be invoked but not // the dump provider, as it has been unregistered. EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(3); EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(0); for (int i = 0; i < 3; ++i) { RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::DETAILED); } DisableTracing(); } // Checks that requesting dumps with high level of detail actually propagates // the level of the detail properly to OnMemoryDump() call on dump providers. TEST_F(MemoryDumpManagerTest, CheckMemoryDumpArgs) { InitializeMemoryDumpManager(false /* is_coordinator */); MockMemoryDumpProvider mdp; RegisterDumpProvider(&mdp); EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); EXPECT_CALL(mdp, OnMemoryDump(IsDetailedDump(), _)).WillOnce(Return(true)); RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::DETAILED); DisableTracing(); mdm_->UnregisterDumpProvider(&mdp); // Check that requesting dumps with low level of detail actually propagates to // OnMemoryDump() call on dump providers. RegisterDumpProvider(&mdp); EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); EXPECT_CALL(mdp, OnMemoryDump(IsLightDump(), _)).WillOnce(Return(true)); RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::LIGHT); DisableTracing(); mdm_->UnregisterDumpProvider(&mdp); } // Checks that the SharedSessionState object is acqually shared over time. TEST_F(MemoryDumpManagerTest, SharedSessionState) { InitializeMemoryDumpManager(false /* is_coordinator */); MockMemoryDumpProvider mdp1; MockMemoryDumpProvider mdp2; RegisterDumpProvider(&mdp1); RegisterDumpProvider(&mdp2); EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); const MemoryDumpSessionState* session_state = mdm_->session_state().get(); EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(2); EXPECT_CALL(mdp1, OnMemoryDump(_, _)) .Times(2) .WillRepeatedly(Invoke([session_state](const MemoryDumpArgs&, ProcessMemoryDump* pmd) -> bool { EXPECT_EQ(session_state, pmd->session_state().get()); return true; })); EXPECT_CALL(mdp2, OnMemoryDump(_, _)) .Times(2) .WillRepeatedly(Invoke([session_state](const MemoryDumpArgs&, ProcessMemoryDump* pmd) -> bool { EXPECT_EQ(session_state, pmd->session_state().get()); return true; })); for (int i = 0; i < 2; ++i) { RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::DETAILED); } DisableTracing(); } // Checks that the (Un)RegisterDumpProvider logic behaves sanely. TEST_F(MemoryDumpManagerTest, MultipleDumpers) { InitializeMemoryDumpManager(false /* is_coordinator */); MockMemoryDumpProvider mdp1; MockMemoryDumpProvider mdp2; // Enable only mdp1. RegisterDumpProvider(&mdp1); EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); EXPECT_CALL(mdp1, OnMemoryDump(_, _)).WillOnce(Return(true)); EXPECT_CALL(mdp2, OnMemoryDump(_, _)).Times(0); RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::DETAILED); DisableTracing(); // Invert: enable mdp1 and disable mdp2. mdm_->UnregisterDumpProvider(&mdp1); RegisterDumpProvider(&mdp2); EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); EXPECT_CALL(mdp1, OnMemoryDump(_, _)).Times(0); EXPECT_CALL(mdp2, OnMemoryDump(_, _)).WillOnce(Return(true)); RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::DETAILED); DisableTracing(); // Enable both mdp1 and mdp2. RegisterDumpProvider(&mdp1); EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); EXPECT_CALL(mdp1, OnMemoryDump(_, _)).WillOnce(Return(true)); EXPECT_CALL(mdp2, OnMemoryDump(_, _)).WillOnce(Return(true)); RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::DETAILED); DisableTracing(); } // Checks that the dump provider invocations depend only on the current // registration state and not on previous registrations and dumps. TEST_F(MemoryDumpManagerTest, RegistrationConsistency) { InitializeMemoryDumpManager(false /* is_coordinator */); MockMemoryDumpProvider mdp; RegisterDumpProvider(&mdp); { EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); EXPECT_CALL(mdp, OnMemoryDump(_, _)).WillOnce(Return(true)); EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::DETAILED); DisableTracing(); } mdm_->UnregisterDumpProvider(&mdp); { EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(0); EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::DETAILED); DisableTracing(); } RegisterDumpProvider(&mdp); mdm_->UnregisterDumpProvider(&mdp); { EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(0); EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::DETAILED); DisableTracing(); } RegisterDumpProvider(&mdp); mdm_->UnregisterDumpProvider(&mdp); RegisterDumpProvider(&mdp); { EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); EXPECT_CALL(mdp, OnMemoryDump(_, _)).WillOnce(Return(true)); EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::DETAILED); DisableTracing(); } } // Checks that the MemoryDumpManager respects the thread affinity when a // MemoryDumpProvider specifies a task_runner(). The test starts creating 8 // threads and registering a MemoryDumpProvider on each of them. At each // iteration, one thread is removed, to check the live unregistration logic. TEST_F(MemoryDumpManagerTest, RespectTaskRunnerAffinity) { InitializeMemoryDumpManager(false /* is_coordinator */); const uint32_t kNumInitialThreads = 8; std::vector<scoped_ptr<Thread>> threads; std::vector<scoped_ptr<MockMemoryDumpProvider>> mdps; // Create the threads and setup the expectations. Given that at each iteration // we will pop out one thread/MemoryDumpProvider, each MDP is supposed to be // invoked a number of times equal to its index. for (uint32_t i = kNumInitialThreads; i > 0; --i) { threads.push_back(make_scoped_ptr(new Thread("test thread"))); auto thread = threads.back().get(); thread->Start(); scoped_refptr<SingleThreadTaskRunner> task_runner = thread->task_runner(); mdps.push_back(make_scoped_ptr(new MockMemoryDumpProvider())); auto mdp = mdps.back().get(); RegisterDumpProvider(mdp, task_runner, kDefaultOptions); EXPECT_CALL(*mdp, OnMemoryDump(_, _)) .Times(i) .WillRepeatedly(Invoke( [task_runner](const MemoryDumpArgs&, ProcessMemoryDump*) -> bool { EXPECT_TRUE(task_runner->RunsTasksOnCurrentThread()); return true; })); } EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); while (!threads.empty()) { last_callback_success_ = false; EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::DETAILED); EXPECT_TRUE(last_callback_success_); // Unregister a MDP and destroy one thread at each iteration to check the // live unregistration logic. The unregistration needs to happen on the same // thread the MDP belongs to. { RunLoop run_loop; Closure unregistration = Bind(&MemoryDumpManager::UnregisterDumpProvider, Unretained(mdm_.get()), Unretained(mdps.back().get())); threads.back()->task_runner()->PostTaskAndReply(FROM_HERE, unregistration, run_loop.QuitClosure()); run_loop.Run(); } mdps.pop_back(); threads.back()->Stop(); threads.pop_back(); } DisableTracing(); } // Checks that providers get disabled after 3 consecutive failures, but not // otherwise (e.g., if interleaved). TEST_F(MemoryDumpManagerTest, DisableFailingDumpers) { InitializeMemoryDumpManager(false /* is_coordinator */); MockMemoryDumpProvider mdp1; MockMemoryDumpProvider mdp2; RegisterDumpProvider(&mdp1); RegisterDumpProvider(&mdp2); EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); const int kNumDumps = 2 * GetMaxConsecutiveFailuresCount(); EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(kNumDumps); EXPECT_CALL(mdp1, OnMemoryDump(_, _)) .Times(GetMaxConsecutiveFailuresCount()) .WillRepeatedly(Return(false)); EXPECT_CALL(mdp2, OnMemoryDump(_, _)) .WillOnce(Return(false)) .WillOnce(Return(true)) .WillOnce(Return(false)) .WillOnce(Return(false)) .WillOnce(Return(true)) .WillOnce(Return(false)); for (int i = 0; i < kNumDumps; i++) { RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::DETAILED); } DisableTracing(); } // Sneakily registers an extra memory dump provider while an existing one is // dumping and expect it to take part in the already active tracing session. TEST_F(MemoryDumpManagerTest, RegisterDumperWhileDumping) { InitializeMemoryDumpManager(false /* is_coordinator */); MockMemoryDumpProvider mdp1; MockMemoryDumpProvider mdp2; RegisterDumpProvider(&mdp1); EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(4); EXPECT_CALL(mdp1, OnMemoryDump(_, _)) .Times(4) .WillOnce(Return(true)) .WillOnce( Invoke([&mdp2](const MemoryDumpArgs&, ProcessMemoryDump*) -> bool { RegisterDumpProvider(&mdp2); return true; })) .WillRepeatedly(Return(true)); // Depending on the insertion order (before or after mdp1), mdp2 might be // called also immediately after it gets registered. EXPECT_CALL(mdp2, OnMemoryDump(_, _)) .Times(Between(2, 3)) .WillRepeatedly(Return(true)); for (int i = 0; i < 4; i++) { RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::DETAILED); } DisableTracing(); } // Like RegisterDumperWhileDumping, but unregister the dump provider instead. TEST_F(MemoryDumpManagerTest, UnregisterDumperWhileDumping) { InitializeMemoryDumpManager(false /* is_coordinator */); MockMemoryDumpProvider mdp1; MockMemoryDumpProvider mdp2; RegisterDumpProvider(&mdp1, ThreadTaskRunnerHandle::Get(), kDefaultOptions); RegisterDumpProvider(&mdp2, ThreadTaskRunnerHandle::Get(), kDefaultOptions); EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(4); EXPECT_CALL(mdp1, OnMemoryDump(_, _)) .Times(4) .WillOnce(Return(true)) .WillOnce( Invoke([&mdp2](const MemoryDumpArgs&, ProcessMemoryDump*) -> bool { MemoryDumpManager::GetInstance()->UnregisterDumpProvider(&mdp2); return true; })) .WillRepeatedly(Return(true)); // Depending on the insertion order (before or after mdp1), mdp2 might have // been already called when UnregisterDumpProvider happens. EXPECT_CALL(mdp2, OnMemoryDump(_, _)) .Times(Between(1, 2)) .WillRepeatedly(Return(true)); for (int i = 0; i < 4; i++) { RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::DETAILED); } DisableTracing(); } // Checks that the dump does not abort when unregistering a provider while // dumping from a different thread than the dumping thread. TEST_F(MemoryDumpManagerTest, UnregisterDumperFromThreadWhileDumping) { InitializeMemoryDumpManager(false /* is_coordinator */); std::vector<scoped_ptr<TestIOThread>> threads; std::vector<scoped_ptr<MockMemoryDumpProvider>> mdps; for (int i = 0; i < 2; i++) { threads.push_back( make_scoped_ptr(new TestIOThread(TestIOThread::kAutoStart))); mdps.push_back(make_scoped_ptr(new MockMemoryDumpProvider())); RegisterDumpProvider(mdps.back().get(), threads.back()->task_runner(), kDefaultOptions); } int on_memory_dump_call_count = 0; // When OnMemoryDump is called on either of the dump providers, it will // unregister the other one. for (const scoped_ptr<MockMemoryDumpProvider>& mdp : mdps) { int other_idx = (mdps.front() == mdp); TestIOThread* other_thread = threads[other_idx].get(); MockMemoryDumpProvider* other_mdp = mdps[other_idx].get(); auto on_dump = [this, other_thread, other_mdp, &on_memory_dump_call_count]( const MemoryDumpArgs& args, ProcessMemoryDump* pmd) { other_thread->PostTaskAndWait( FROM_HERE, base::Bind(&MemoryDumpManager::UnregisterDumpProvider, base::Unretained(&*mdm_), other_mdp)); on_memory_dump_call_count++; return true; }; // OnMemoryDump is called once for the provider that dumps first, and zero // times for the other provider. EXPECT_CALL(*mdp, OnMemoryDump(_, _)) .Times(AtMost(1)) .WillOnce(Invoke(on_dump)); } last_callback_success_ = false; EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::DETAILED); ASSERT_EQ(1, on_memory_dump_call_count); ASSERT_TRUE(last_callback_success_); DisableTracing(); } // If a thread (with a dump provider living on it) is torn down during a dump // its dump provider should be skipped but the dump itself should succeed. TEST_F(MemoryDumpManagerTest, TearDownThreadWhileDumping) { InitializeMemoryDumpManager(false /* is_coordinator */); std::vector<scoped_ptr<TestIOThread>> threads; std::vector<scoped_ptr<MockMemoryDumpProvider>> mdps; for (int i = 0; i < 2; i++) { threads.push_back( make_scoped_ptr(new TestIOThread(TestIOThread::kAutoStart))); mdps.push_back(make_scoped_ptr(new MockMemoryDumpProvider())); RegisterDumpProvider(mdps.back().get(), threads.back()->task_runner(), kDefaultOptions); } int on_memory_dump_call_count = 0; // When OnMemoryDump is called on either of the dump providers, it will // tear down the thread of the other one. for (const scoped_ptr<MockMemoryDumpProvider>& mdp : mdps) { int other_idx = (mdps.front() == mdp); TestIOThread* other_thread = threads[other_idx].get(); auto on_dump = [other_thread, &on_memory_dump_call_count]( const MemoryDumpArgs& args, ProcessMemoryDump* pmd) { other_thread->Stop(); on_memory_dump_call_count++; return true; }; // OnMemoryDump is called once for the provider that dumps first, and zero // times for the other provider. EXPECT_CALL(*mdp, OnMemoryDump(_, _)) .Times(AtMost(1)) .WillOnce(Invoke(on_dump)); } last_callback_success_ = false; EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::DETAILED); ASSERT_EQ(1, on_memory_dump_call_count); ASSERT_TRUE(last_callback_success_); DisableTracing(); } // Checks that a NACK callback is invoked if RequestGlobalDump() is called when // tracing is not enabled. TEST_F(MemoryDumpManagerTest, CallbackCalledOnFailure) { InitializeMemoryDumpManager(false /* is_coordinator */); MockMemoryDumpProvider mdp1; RegisterDumpProvider(&mdp1); EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(0); EXPECT_CALL(mdp1, OnMemoryDump(_, _)).Times(0); last_callback_success_ = true; RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::DETAILED); EXPECT_FALSE(last_callback_success_); } // Checks that is the MemoryDumpManager is initialized after tracing already // began, it will still late-join the party (real use case: startup tracing). TEST_F(MemoryDumpManagerTest, InitializedAfterStartOfTracing) { MockMemoryDumpProvider mdp; RegisterDumpProvider(&mdp); EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); // First check that a RequestGlobalDump() issued before the MemoryDumpManager // initialization gets NACK-ed cleanly. { EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(0); EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(0); RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::DETAILED); EXPECT_FALSE(last_callback_success_); } // Now late-initialize the MemoryDumpManager and check that the // RequestGlobalDump completes successfully. { EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(1); EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); InitializeMemoryDumpManager(false /* is_coordinator */); RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::DETAILED); EXPECT_TRUE(last_callback_success_); } DisableTracing(); } // This test (and the MemoryDumpManagerTestCoordinator below) crystallizes the // expectations of the chrome://tracing UI and chrome telemetry w.r.t. periodic // dumps in memory-infra, handling gracefully the transition between the legacy // and the new-style (JSON-based) TraceConfig. TEST_F(MemoryDumpManagerTest, TraceConfigExpectations) { InitializeMemoryDumpManager(false /* is_coordinator */); MemoryDumpManagerDelegateForTesting& delegate = *delegate_; // Don't trigger the default behavior of the mock delegate in this test, // which would short-circuit the dump request to the actual // CreateProcessDump(). // We don't want to create any dump in this test, only check whether the dumps // are requested or not. ON_CALL(delegate, RequestGlobalMemoryDump(_, _)).WillByDefault(Return()); // Enabling memory-infra in a non-coordinator process should not trigger any // periodic dumps. EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); EXPECT_FALSE(IsPeriodicDumpingEnabled()); DisableTracing(); // Enabling memory-infra with the new (JSON) TraceConfig in a non-coordinator // process with a fully defined trigger config should NOT enable any periodic // dumps. EnableTracingWithTraceConfig( TraceConfigMemoryTestUtil::GetTraceConfig_PeriodicTriggers(1, 5)); EXPECT_FALSE(IsPeriodicDumpingEnabled()); DisableTracing(); } TEST_F(MemoryDumpManagerTest, TraceConfigExpectationsWhenIsCoordinator) { InitializeMemoryDumpManager(true /* is_coordinator */); MemoryDumpManagerDelegateForTesting& delegate = *delegate_; ON_CALL(delegate, RequestGlobalMemoryDump(_, _)).WillByDefault(Return()); // Enabling memory-infra with the legacy TraceConfig (category filter) in // a coordinator process should enable periodic dumps. EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); EXPECT_TRUE(IsPeriodicDumpingEnabled()); DisableTracing(); // Enabling memory-infra with the new (JSON) TraceConfig in a coordinator // process without specifying any "memory_dump_config" section should enable // periodic dumps. This is to preserve the behavior chrome://tracing UI, that // is: ticking memory-infra should dump periodically with the default config. EnableTracingWithTraceConfig( TraceConfigMemoryTestUtil::GetTraceConfig_NoTriggers()); EXPECT_TRUE(IsPeriodicDumpingEnabled()); DisableTracing(); // Enabling memory-infra with the new (JSON) TraceConfig in a coordinator // process with an empty "memory_dump_config" should NOT enable periodic // dumps. This is the way telemetry is supposed to use memory-infra with // only explicitly triggered dumps. EnableTracingWithTraceConfig( TraceConfigMemoryTestUtil::GetTraceConfig_EmptyTriggers()); EXPECT_FALSE(IsPeriodicDumpingEnabled()); DisableTracing(); // Enabling memory-infra with the new (JSON) TraceConfig in a coordinator // process with a fully defined trigger config should cause periodic dumps to // be performed in the correct order. RunLoop run_loop; auto quit_closure = run_loop.QuitClosure(); const int kHeavyDumpRate = 5; const int kLightDumpPeriodMs = 1; const int kHeavyDumpPeriodMs = kHeavyDumpRate * kLightDumpPeriodMs; // The expected sequence with light=1ms, heavy=5ms is H,L,L,L,L,H,... testing::InSequence sequence; EXPECT_CALL(delegate, RequestGlobalMemoryDump(IsDetailedDump(), _)); EXPECT_CALL(delegate, RequestGlobalMemoryDump(IsLightDump(), _)) .Times(kHeavyDumpRate - 1); EXPECT_CALL(delegate, RequestGlobalMemoryDump(IsDetailedDump(), _)); EXPECT_CALL(delegate, RequestGlobalMemoryDump(IsLightDump(), _)) .Times(kHeavyDumpRate - 2); EXPECT_CALL(delegate, RequestGlobalMemoryDump(IsLightDump(), _)) .WillOnce(Invoke([quit_closure](const MemoryDumpRequestArgs& args, const MemoryDumpCallback& callback) { ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, quit_closure); })); // Swallow all the final spurious calls until tracing gets disabled. EXPECT_CALL(delegate, RequestGlobalMemoryDump(_, _)).Times(AnyNumber()); EnableTracingWithTraceConfig( TraceConfigMemoryTestUtil::GetTraceConfig_PeriodicTriggers( kLightDumpPeriodMs, kHeavyDumpPeriodMs)); run_loop.Run(); DisableTracing(); } // Tests against race conditions that might arise when disabling tracing in the // middle of a global memory dump. TEST_F(MemoryDumpManagerTest, DisableTracingWhileDumping) { base::WaitableEvent tracing_disabled_event(false, false); InitializeMemoryDumpManager(false /* is_coordinator */); // Register a bound dump provider. scoped_ptr<Thread> mdp_thread(new Thread("test thread")); mdp_thread->Start(); MockMemoryDumpProvider mdp_with_affinity; RegisterDumpProvider(&mdp_with_affinity, mdp_thread->task_runner(), kDefaultOptions); // Register also an unbound dump provider. Unbound dump providers are always // invoked after bound ones. MockMemoryDumpProvider unbound_mdp; RegisterDumpProvider(&unbound_mdp, nullptr, kDefaultOptions); EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); EXPECT_CALL(mdp_with_affinity, OnMemoryDump(_, _)) .Times(1) .WillOnce( Invoke([&tracing_disabled_event](const MemoryDumpArgs&, ProcessMemoryDump* pmd) -> bool { tracing_disabled_event.Wait(); // At this point tracing has been disabled and the // MemoryDumpManager.dump_thread_ has been shut down. return true; })); // |unbound_mdp| should never be invoked because the thread for unbound dump // providers has been shutdown in the meanwhile. EXPECT_CALL(unbound_mdp, OnMemoryDump(_, _)).Times(0); last_callback_success_ = true; RunLoop run_loop; MemoryDumpCallback callback = Bind(&MemoryDumpManagerTest::DumpCallbackAdapter, Unretained(this), MessageLoop::current()->task_runner(), run_loop.QuitClosure()); mdm_->RequestGlobalDump(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::DETAILED, callback); DisableTracing(); tracing_disabled_event.Signal(); run_loop.Run(); // RequestGlobalMemoryDump() should still suceed even if some threads were // torn down during the dump. EXPECT_TRUE(last_callback_success_); } TEST_F(MemoryDumpManagerTest, DumpOnBehalfOfOtherProcess) { using trace_analyzer::Query; InitializeMemoryDumpManager(false /* is_coordinator */); // Standard provider with default options (create dump for current process). MemoryDumpProvider::Options options; MockMemoryDumpProvider mdp1; RegisterDumpProvider(&mdp1, nullptr, options); // Provider with out-of-process dumping. MockMemoryDumpProvider mdp2; options.target_pid = 123; RegisterDumpProvider(&mdp2, nullptr, options); // Another provider with out-of-process dumping. MockMemoryDumpProvider mdp3; options.target_pid = 456; RegisterDumpProvider(&mdp3, nullptr, options); EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); EXPECT_CALL(mdp1, OnMemoryDump(_, _)).Times(1).WillRepeatedly(Return(true)); EXPECT_CALL(mdp2, OnMemoryDump(_, _)).Times(1).WillRepeatedly(Return(true)); EXPECT_CALL(mdp3, OnMemoryDump(_, _)).Times(1).WillRepeatedly(Return(true)); RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::DETAILED); DisableTracing(); // Flush the trace into JSON. trace_event::TraceResultBuffer buffer; TraceResultBuffer::SimpleOutput trace_output; buffer.SetOutputCallback(trace_output.GetCallback()); RunLoop run_loop; buffer.Start(); trace_event::TraceLog::GetInstance()->Flush( Bind(&OnTraceDataCollected, run_loop.QuitClosure(), Unretained(&buffer))); run_loop.Run(); buffer.Finish(); // Analyze the JSON. scoped_ptr<trace_analyzer::TraceAnalyzer> analyzer = make_scoped_ptr( trace_analyzer::TraceAnalyzer::Create(trace_output.json_output)); trace_analyzer::TraceEventVector events; analyzer->FindEvents(Query::EventPhaseIs(TRACE_EVENT_PHASE_MEMORY_DUMP), &events); ASSERT_EQ(3u, events.size()); ASSERT_EQ(1u, trace_analyzer::CountMatches(events, Query::EventPidIs(123))); ASSERT_EQ(1u, trace_analyzer::CountMatches(events, Query::EventPidIs(456))); ASSERT_EQ(1u, trace_analyzer::CountMatches( events, Query::EventPidIs(GetCurrentProcId()))); ASSERT_EQ(events[0]->id, events[1]->id); ASSERT_EQ(events[0]->id, events[2]->id); } // Tests the basics of the UnregisterAndDeleteDumpProviderSoon(): the // unregistration should actually delete the providers and not leak them. TEST_F(MemoryDumpManagerTest, UnregisterAndDeleteDumpProviderSoon) { InitializeMemoryDumpManager(false /* is_coordinator */); static const int kNumProviders = 3; int dtor_count = 0; std::vector<scoped_ptr<MemoryDumpProvider>> mdps; for (int i = 0; i < kNumProviders; ++i) { scoped_ptr<MockMemoryDumpProvider> mdp(new MockMemoryDumpProvider); mdp->enable_mock_destructor = true; EXPECT_CALL(*mdp, Destructor()) .WillOnce(Invoke([&dtor_count]() { dtor_count++; })); RegisterDumpProvider(mdp.get(), nullptr, kDefaultOptions); mdps.push_back(std::move(mdp)); } while (!mdps.empty()) { mdm_->UnregisterAndDeleteDumpProviderSoon(std::move(mdps.back())); mdps.pop_back(); } ASSERT_EQ(kNumProviders, dtor_count); } // This test checks against races when unregistering an unbound dump provider // from another thread while dumping. It registers one MDP and, when // OnMemoryDump() is called, it invokes UnregisterAndDeleteDumpProviderSoon() // from another thread. The OnMemoryDump() and the dtor call are expected to // happen on the same thread (the MemoryDumpManager utility thread). TEST_F(MemoryDumpManagerTest, UnregisterAndDeleteDumpProviderSoonDuringDump) { InitializeMemoryDumpManager(false /* is_coordinator */); scoped_ptr<MockMemoryDumpProvider> mdp(new MockMemoryDumpProvider); mdp->enable_mock_destructor = true; RegisterDumpProvider(mdp.get(), nullptr, kDefaultOptions); base::PlatformThreadRef thread_ref; auto self_unregister_from_another_thread = [&mdp, &thread_ref]( const MemoryDumpArgs&, ProcessMemoryDump*) -> bool { thread_ref = PlatformThread::CurrentRef(); TestIOThread thread_for_unregistration(TestIOThread::kAutoStart); thread_for_unregistration.PostTaskAndWait( FROM_HERE, base::Bind( &MemoryDumpManager::UnregisterAndDeleteDumpProviderSoon, base::Unretained(MemoryDumpManager::GetInstance()), base::Passed(scoped_ptr<MemoryDumpProvider>(std::move(mdp))))); thread_for_unregistration.Stop(); return true; }; EXPECT_CALL(*mdp, OnMemoryDump(_, _)) .Times(1) .WillOnce(Invoke(self_unregister_from_another_thread)); EXPECT_CALL(*mdp, Destructor()) .Times(1) .WillOnce(Invoke([&thread_ref]() { EXPECT_EQ(thread_ref, PlatformThread::CurrentRef()); })); EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(2); for (int i = 0; i < 2; ++i) { RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, MemoryDumpLevelOfDetail::DETAILED); } DisableTracing(); } } // namespace trace_event } // namespace base