| // 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 <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/allocator/buildflags.h" |
| #include "base/base_switches.h" |
| #include "base/callback.h" |
| #include "base/command_line.h" |
| #include "base/debug/thread_heap_usage_tracker.h" |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/task/post_task.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "base/test/test_io_thread.h" |
| #include "base/threading/platform_thread.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #include "base/threading/thread.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/trace_event/memory_dump_manager_test_utils.h" |
| #include "base/trace_event/memory_dump_provider.h" |
| #include "base/trace_event/memory_dump_request_args.h" |
| #include "base/trace_event/memory_dump_scheduler.h" |
| #include "base/trace_event/memory_infra_background_whitelist.h" |
| #include "base/trace_event/process_memory_dump.h" |
| #include "build/build_config.h" |
| #include "starboard/types.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using testing::_; |
| 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 { |
| |
| const char* kMDPName = "TestDumpProvider"; |
| const char* kWhitelistedMDPName = "WhitelistedTestDumpProvider"; |
| const char* const kTestMDPWhitelist[] = {kWhitelistedMDPName, nullptr}; |
| |
| void RegisterDumpProvider( |
| MemoryDumpProvider* mdp, |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner, |
| const MemoryDumpProvider::Options& options, |
| const char* name = kMDPName) { |
| MemoryDumpManager* mdm = MemoryDumpManager::GetInstance(); |
| mdm->set_dumper_registrations_ignored_for_testing(false); |
| mdm->RegisterDumpProvider(mdp, name, std::move(task_runner), options); |
| mdm->set_dumper_registrations_ignored_for_testing(true); |
| } |
| |
| void RegisterDumpProvider( |
| MemoryDumpProvider* mdp, |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner) { |
| RegisterDumpProvider(mdp, task_runner, MemoryDumpProvider::Options()); |
| } |
| |
| void RegisterDumpProviderWithSequencedTaskRunner( |
| MemoryDumpProvider* mdp, |
| scoped_refptr<base::SequencedTaskRunner> task_runner, |
| const MemoryDumpProvider::Options& options) { |
| MemoryDumpManager* mdm = MemoryDumpManager::GetInstance(); |
| mdm->set_dumper_registrations_ignored_for_testing(false); |
| mdm->RegisterDumpProviderWithSequencedTaskRunner(mdp, kMDPName, task_runner, |
| options); |
| mdm->set_dumper_registrations_ignored_for_testing(true); |
| } |
| |
| // Posts |task| to |task_runner| and blocks until it is executed. |
| void PostTaskAndWait(const Location& from_here, |
| SequencedTaskRunner* task_runner, |
| base::OnceClosure task) { |
| base::WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL, |
| WaitableEvent::InitialState::NOT_SIGNALED); |
| task_runner->PostTask(from_here, std::move(task)); |
| task_runner->PostTask(FROM_HERE, base::BindOnce(&WaitableEvent::Signal, |
| base::Unretained(&event))); |
| // The SequencedTaskRunner guarantees that |event| will only be signaled after |
| // |task| is executed. |
| event.Wait(); |
| } |
| |
| class MockMemoryDumpProvider : public MemoryDumpProvider { |
| public: |
| MOCK_METHOD0(Destructor, void()); |
| MOCK_METHOD2(OnMemoryDump, |
| bool(const MemoryDumpArgs& args, ProcessMemoryDump* pmd)); |
| |
| MockMemoryDumpProvider() : enable_mock_destructor(false) { |
| ON_CALL(*this, OnMemoryDump(_, _)) |
| .WillByDefault( |
| Invoke([](const MemoryDumpArgs&, ProcessMemoryDump* pmd) -> bool { |
| return true; |
| })); |
| } |
| ~MockMemoryDumpProvider() override { |
| if (enable_mock_destructor) |
| Destructor(); |
| } |
| |
| bool enable_mock_destructor; |
| }; |
| |
| class TestSequencedTaskRunner : public SequencedTaskRunner { |
| public: |
| TestSequencedTaskRunner() = default; |
| |
| void set_enabled(bool value) { enabled_ = value; } |
| unsigned no_of_post_tasks() const { return num_of_post_tasks_; } |
| |
| bool PostNonNestableDelayedTask(const Location& from_here, |
| OnceClosure task, |
| TimeDelta delay) override { |
| NOTREACHED(); |
| return false; |
| } |
| |
| bool PostDelayedTask(const Location& from_here, |
| OnceClosure task, |
| TimeDelta delay) override { |
| num_of_post_tasks_++; |
| if (enabled_) { |
| return task_runner_->PostDelayedTask(from_here, std::move(task), delay); |
| } |
| return false; |
| } |
| |
| bool RunsTasksInCurrentSequence() const override { |
| return task_runner_->RunsTasksInCurrentSequence(); |
| } |
| |
| private: |
| ~TestSequencedTaskRunner() override = default; |
| |
| const scoped_refptr<SequencedTaskRunner> task_runner_ = |
| CreateSequencedTaskRunnerWithTraits({}); |
| bool enabled_ = true; |
| unsigned num_of_post_tasks_ = 0; |
| }; |
| |
| class TestingThreadHeapUsageTracker : public debug::ThreadHeapUsageTracker { |
| public: |
| using ThreadHeapUsageTracker::DisableHeapTrackingForTesting; |
| }; |
| |
| } // namespace |
| |
| class MemoryDumpManagerTest : public testing::Test { |
| public: |
| MemoryDumpManagerTest(bool is_coordinator = false) |
| : is_coordinator_(is_coordinator) {} |
| |
| void SetUp() override { |
| // Bring up and initialize MemoryDumpManager while single-threaded (before |
| // instantiating ScopedTaskEnvironment) to avoid data races if worker |
| // threads use tracing globals early. |
| mdm_ = MemoryDumpManager::CreateInstanceForTesting(); |
| ASSERT_EQ(mdm_.get(), MemoryDumpManager::GetInstance()); |
| |
| InitializeMemoryDumpManagerForInProcessTesting(is_coordinator_); |
| |
| scoped_task_environment_ = std::make_unique<test::ScopedTaskEnvironment>(); |
| } |
| |
| void TearDown() override { |
| scoped_task_environment_.reset(); |
| |
| // Tear down the MemoryDumpManager while single-threaded to mirror logic in |
| // SetUp(). |
| mdm_.reset(); |
| TraceLog::ResetForTesting(); |
| } |
| |
| protected: |
| // Blocks the current thread (spinning a nested message loop) until the |
| // memory dump is complete. Returns: |
| // - return value: the |success| from the CreateProcessDump() callback. |
| bool RequestProcessDumpAndWait(MemoryDumpType dump_type, |
| MemoryDumpLevelOfDetail level_of_detail) { |
| RunLoop run_loop; |
| bool success = false; |
| static uint64_t test_guid = 1; |
| test_guid++; |
| MemoryDumpRequestArgs request_args{test_guid, dump_type, level_of_detail}; |
| |
| // The signature of the callback delivered by MemoryDumpManager is: |
| // void ProcessMemoryDumpCallback( |
| // uint64_t dump_guid, |
| // bool success, |
| // std::unique_ptr<ProcessMemoryDump> pmd) |
| // The extra arguments prepended to the |callback| below (the ones with the |
| // "curried_" prefix) are just passed from the Bind(). This is just to get |
| // around the limitation of Bind() in supporting only capture-less lambdas. |
| ProcessMemoryDumpCallback callback = Bind( |
| [](bool* curried_success, Closure curried_quit_closure, |
| uint64_t curried_expected_guid, bool success, uint64_t dump_guid, |
| std::unique_ptr<ProcessMemoryDump> pmd) { |
| *curried_success = success; |
| EXPECT_EQ(curried_expected_guid, dump_guid); |
| ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, |
| curried_quit_closure); |
| }, |
| Unretained(&success), run_loop.QuitClosure(), test_guid); |
| |
| mdm_->CreateProcessDump(request_args, callback); |
| run_loop.Run(); |
| return success; |
| } |
| |
| void EnableForTracing() { |
| mdm_->SetupForTracing(TraceConfig::MemoryDumpConfig()); |
| } |
| |
| void EnableForTracingWithTraceConfig(const std::string trace_config_string) { |
| TraceConfig trace_config(trace_config_string); |
| mdm_->SetupForTracing(trace_config.memory_dump_config()); |
| } |
| |
| void DisableTracing() { mdm_->TeardownForTracing(); } |
| |
| int GetMaxConsecutiveFailuresCount() const { |
| return MemoryDumpManager::kMaxConsecutiveFailuresCount; |
| } |
| |
| const MemoryDumpProvider::Options kDefaultOptions; |
| std::unique_ptr<MemoryDumpManager> mdm_; |
| |
| private: |
| // To tear down the singleton instance after each test. |
| ShadowingAtExitManager at_exit_manager_; |
| |
| std::unique_ptr<test::ScopedTaskEnvironment> scoped_task_environment_; |
| |
| // Whether the test MemoryDumpManager should be initialized as the |
| // coordinator. |
| const bool is_coordinator_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MemoryDumpManagerTest); |
| }; |
| |
| class MemoryDumpManagerTestAsCoordinator : public MemoryDumpManagerTest { |
| public: |
| MemoryDumpManagerTestAsCoordinator() : MemoryDumpManagerTest(true) {} |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MemoryDumpManagerTestAsCoordinator); |
| }; |
| |
| // Basic sanity checks. Registers a memory dump provider and checks that it is |
| // called. |
| TEST_F(MemoryDumpManagerTest, SingleDumper) { |
| MockMemoryDumpProvider mdp; |
| RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get()); |
| |
| // Now repeat enabling the memory category and check that the dumper is |
| // invoked this time. |
| EnableForTracing(); |
| EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(3); |
| for (int i = 0; i < 3; ++i) { |
| EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, |
| MemoryDumpLevelOfDetail::DETAILED)); |
| } |
| DisableTracing(); |
| |
| mdm_->UnregisterDumpProvider(&mdp); |
| |
| // Finally check the unregister logic: the global dump handler will be invoked |
| // but not the dump provider, as it has been unregistered. |
| EnableForTracing(); |
| EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(0); |
| for (int i = 0; i < 3; ++i) { |
| EXPECT_TRUE(RequestProcessDumpAndWait(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) { |
| MockMemoryDumpProvider mdp; |
| |
| RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get()); |
| EnableForTracing(); |
| EXPECT_CALL(mdp, OnMemoryDump(IsDetailedDump(), _)); |
| EXPECT_TRUE(RequestProcessDumpAndWait(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, ThreadTaskRunnerHandle::Get()); |
| EnableForTracing(); |
| EXPECT_CALL(mdp, OnMemoryDump(IsLightDump(), _)); |
| EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, |
| MemoryDumpLevelOfDetail::LIGHT)); |
| DisableTracing(); |
| mdm_->UnregisterDumpProvider(&mdp); |
| } |
| |
| // Checks that the (Un)RegisterDumpProvider logic behaves sanely. |
| TEST_F(MemoryDumpManagerTest, MultipleDumpers) { |
| MockMemoryDumpProvider mdp1; |
| MockMemoryDumpProvider mdp2; |
| |
| // Enable only mdp1. |
| RegisterDumpProvider(&mdp1, ThreadTaskRunnerHandle::Get()); |
| EnableForTracing(); |
| EXPECT_CALL(mdp1, OnMemoryDump(_, _)); |
| EXPECT_CALL(mdp2, OnMemoryDump(_, _)).Times(0); |
| EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, |
| MemoryDumpLevelOfDetail::DETAILED)); |
| DisableTracing(); |
| |
| // Invert: enable mdp2 and disable mdp1. |
| mdm_->UnregisterDumpProvider(&mdp1); |
| RegisterDumpProvider(&mdp2, nullptr); |
| EnableForTracing(); |
| EXPECT_CALL(mdp1, OnMemoryDump(_, _)).Times(0); |
| EXPECT_CALL(mdp2, OnMemoryDump(_, _)); |
| EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, |
| MemoryDumpLevelOfDetail::DETAILED)); |
| DisableTracing(); |
| |
| // Enable both mdp1 and mdp2. |
| RegisterDumpProvider(&mdp1, nullptr); |
| EnableForTracing(); |
| EXPECT_CALL(mdp1, OnMemoryDump(_, _)); |
| EXPECT_CALL(mdp2, OnMemoryDump(_, _)); |
| EXPECT_TRUE(RequestProcessDumpAndWait(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. |
| // Flaky on iOS, see crbug.com/706874 |
| #if defined(OS_IOS) |
| #define MAYBE_RegistrationConsistency DISABLED_RegistrationConsistency |
| #else |
| #define MAYBE_RegistrationConsistency RegistrationConsistency |
| #endif |
| TEST_F(MemoryDumpManagerTest, MAYBE_RegistrationConsistency) { |
| MockMemoryDumpProvider mdp; |
| |
| RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get()); |
| |
| { |
| EXPECT_CALL(mdp, OnMemoryDump(_, _)); |
| EnableForTracing(); |
| EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, |
| MemoryDumpLevelOfDetail::DETAILED)); |
| DisableTracing(); |
| } |
| |
| mdm_->UnregisterDumpProvider(&mdp); |
| |
| { |
| EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(0); |
| EnableForTracing(); |
| EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, |
| MemoryDumpLevelOfDetail::DETAILED)); |
| DisableTracing(); |
| } |
| |
| RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get()); |
| mdm_->UnregisterDumpProvider(&mdp); |
| |
| { |
| EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(0); |
| EnableForTracing(); |
| EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, |
| MemoryDumpLevelOfDetail::DETAILED)); |
| DisableTracing(); |
| } |
| |
| RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get()); |
| mdm_->UnregisterDumpProvider(&mdp); |
| RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get()); |
| |
| { |
| EXPECT_CALL(mdp, OnMemoryDump(_, _)); |
| EnableForTracing(); |
| EXPECT_TRUE(RequestProcessDumpAndWait(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) { |
| const uint32_t kNumInitialThreads = 8; |
| |
| std::vector<std::unique_ptr<Thread>> threads; |
| std::vector<std::unique_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(WrapUnique(new Thread("test thread"))); |
| auto* thread = threads.back().get(); |
| thread->Start(); |
| scoped_refptr<SingleThreadTaskRunner> task_runner = thread->task_runner(); |
| mdps.push_back(WrapUnique(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->RunsTasksInCurrentSequence()); |
| return true; |
| })); |
| } |
| EnableForTracing(); |
| |
| while (!threads.empty()) { |
| EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, |
| MemoryDumpLevelOfDetail::DETAILED)); |
| |
| // 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(); |
| } |
| |
| // Check that the memory dump calls are always posted on task runner for |
| // SequencedTaskRunner case and that the dump provider gets disabled when |
| // PostTask fails, but the dump still succeeds. |
| TEST_F(MemoryDumpManagerTest, PostTaskForSequencedTaskRunner) { |
| std::vector<MockMemoryDumpProvider> mdps(3); |
| scoped_refptr<TestSequencedTaskRunner> task_runner1( |
| MakeRefCounted<TestSequencedTaskRunner>()); |
| scoped_refptr<TestSequencedTaskRunner> task_runner2( |
| MakeRefCounted<TestSequencedTaskRunner>()); |
| RegisterDumpProviderWithSequencedTaskRunner(&mdps[0], task_runner1, |
| kDefaultOptions); |
| RegisterDumpProviderWithSequencedTaskRunner(&mdps[1], task_runner2, |
| kDefaultOptions); |
| RegisterDumpProviderWithSequencedTaskRunner(&mdps[2], task_runner2, |
| kDefaultOptions); |
| // |mdps[0]| should be disabled permanently after first dump. |
| EXPECT_CALL(mdps[0], OnMemoryDump(_, _)).Times(0); |
| EXPECT_CALL(mdps[1], OnMemoryDump(_, _)).Times(2); |
| EXPECT_CALL(mdps[2], OnMemoryDump(_, _)).Times(2); |
| |
| EnableForTracing(); |
| |
| task_runner1->set_enabled(false); |
| EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, |
| MemoryDumpLevelOfDetail::DETAILED)); |
| EXPECT_EQ(1u, task_runner1->no_of_post_tasks()); |
| EXPECT_EQ(1u, task_runner2->no_of_post_tasks()); |
| |
| task_runner1->set_enabled(true); |
| EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, |
| MemoryDumpLevelOfDetail::DETAILED)); |
| EXPECT_EQ(2u, task_runner1->no_of_post_tasks()); |
| EXPECT_EQ(2u, task_runner2->no_of_post_tasks()); |
| DisableTracing(); |
| } |
| |
| // Checks that providers get disabled after 3 consecutive failures, but not |
| // otherwise (e.g., if interleaved). |
| TEST_F(MemoryDumpManagerTest, DisableFailingDumpers) { |
| MockMemoryDumpProvider mdp1; |
| MockMemoryDumpProvider mdp2; |
| |
| RegisterDumpProvider(&mdp1, nullptr); |
| RegisterDumpProvider(&mdp2, nullptr); |
| EnableForTracing(); |
| |
| 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)); |
| |
| const int kNumDumps = 2 * GetMaxConsecutiveFailuresCount(); |
| for (int i = 0; i < kNumDumps; i++) { |
| EXPECT_TRUE(RequestProcessDumpAndWait(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) { |
| MockMemoryDumpProvider mdp1; |
| MockMemoryDumpProvider mdp2; |
| |
| RegisterDumpProvider(&mdp1, nullptr); |
| EnableForTracing(); |
| |
| EXPECT_CALL(mdp1, OnMemoryDump(_, _)) |
| .Times(4) |
| .WillOnce(Return(true)) |
| .WillOnce( |
| Invoke([&mdp2](const MemoryDumpArgs&, ProcessMemoryDump*) -> bool { |
| RegisterDumpProvider(&mdp2, nullptr); |
| 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)); |
| |
| for (int i = 0; i < 4; i++) { |
| EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, |
| MemoryDumpLevelOfDetail::DETAILED)); |
| } |
| |
| DisableTracing(); |
| } |
| |
| // Like RegisterDumperWhileDumping, but unregister the dump provider instead. |
| TEST_F(MemoryDumpManagerTest, UnregisterDumperWhileDumping) { |
| MockMemoryDumpProvider mdp1; |
| MockMemoryDumpProvider mdp2; |
| |
| RegisterDumpProvider(&mdp1, ThreadTaskRunnerHandle::Get(), kDefaultOptions); |
| RegisterDumpProvider(&mdp2, ThreadTaskRunnerHandle::Get(), kDefaultOptions); |
| EnableForTracing(); |
| |
| 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)); |
| |
| for (int i = 0; i < 4; i++) { |
| EXPECT_TRUE(RequestProcessDumpAndWait(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) { |
| std::vector<std::unique_ptr<TestIOThread>> threads; |
| std::vector<std::unique_ptr<MockMemoryDumpProvider>> mdps; |
| |
| for (int i = 0; i < 2; i++) { |
| threads.push_back( |
| WrapUnique(new TestIOThread(TestIOThread::kAutoStart))); |
| mdps.push_back(WrapUnique(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 std::unique_ptr<MockMemoryDumpProvider>& mdp : mdps) { |
| int other_idx = (mdps.front() == mdp); |
| // TestIOThread's task runner must be obtained from the main thread but can |
| // then be used from other threads. |
| scoped_refptr<SingleThreadTaskRunner> other_runner = |
| threads[other_idx]->task_runner(); |
| MockMemoryDumpProvider* other_mdp = mdps[other_idx].get(); |
| auto on_dump = [this, other_runner, other_mdp, &on_memory_dump_call_count]( |
| const MemoryDumpArgs& args, ProcessMemoryDump* pmd) { |
| PostTaskAndWait(FROM_HERE, other_runner.get(), |
| base::BindOnce(&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)); |
| } |
| |
| EnableForTracing(); |
| EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, |
| MemoryDumpLevelOfDetail::DETAILED)); |
| ASSERT_EQ(1, on_memory_dump_call_count); |
| |
| 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) { |
| std::vector<std::unique_ptr<TestIOThread>> threads; |
| std::vector<std::unique_ptr<MockMemoryDumpProvider>> mdps; |
| |
| for (int i = 0; i < 2; i++) { |
| threads.push_back( |
| WrapUnique(new TestIOThread(TestIOThread::kAutoStart))); |
| mdps.push_back(WrapUnique(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 std::unique_ptr<MockMemoryDumpProvider>& mdp : mdps) { |
| int other_idx = (mdps.front() == mdp); |
| TestIOThread* other_thread = threads[other_idx].get(); |
| // TestIOThread isn't thread-safe and must be stopped on the |main_runner|. |
| scoped_refptr<SequencedTaskRunner> main_runner = |
| SequencedTaskRunnerHandle::Get(); |
| auto on_dump = [other_thread, main_runner, &on_memory_dump_call_count]( |
| const MemoryDumpArgs& args, ProcessMemoryDump* pmd) { |
| PostTaskAndWait( |
| FROM_HERE, main_runner.get(), |
| base::BindOnce(&TestIOThread::Stop, base::Unretained(other_thread))); |
| 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)); |
| } |
| |
| EnableForTracing(); |
| EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, |
| MemoryDumpLevelOfDetail::DETAILED)); |
| ASSERT_EQ(1, on_memory_dump_call_count); |
| |
| DisableTracing(); |
| } |
| |
| // Checks that the callback is invoked if CreateProcessDump() is called when |
| // tracing is not enabled. |
| TEST_F(MemoryDumpManagerTest, TriggerDumpWithoutTracing) { |
| MockMemoryDumpProvider mdp; |
| RegisterDumpProvider(&mdp, nullptr); |
| EXPECT_CALL(mdp, OnMemoryDump(_, _)); |
| EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, |
| MemoryDumpLevelOfDetail::DETAILED)); |
| } |
| |
| TEST_F(MemoryDumpManagerTest, BackgroundWhitelisting) { |
| SetDumpProviderWhitelistForTesting(kTestMDPWhitelist); |
| |
| // Standard provider with default options (create dump for current process). |
| MockMemoryDumpProvider backgroundMdp; |
| RegisterDumpProvider(&backgroundMdp, nullptr, kDefaultOptions, |
| kWhitelistedMDPName); |
| |
| EnableForTracing(); |
| |
| EXPECT_CALL(backgroundMdp, OnMemoryDump(_, _)).Times(1); |
| EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::SUMMARY_ONLY, |
| MemoryDumpLevelOfDetail::BACKGROUND)); |
| DisableTracing(); |
| } |
| |
| // Tests the basics of the UnregisterAndDeleteDumpProviderSoon(): the |
| // unregistration should actually delete the providers and not leak them. |
| TEST_F(MemoryDumpManagerTest, UnregisterAndDeleteDumpProviderSoon) { |
| static const int kNumProviders = 3; |
| int dtor_count = 0; |
| std::vector<std::unique_ptr<MemoryDumpProvider>> mdps; |
| for (int i = 0; i < kNumProviders; ++i) { |
| std::unique_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) { |
| std::unique_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); |
| PostTaskAndWait( |
| FROM_HERE, thread_for_unregistration.task_runner().get(), |
| base::BindOnce(&MemoryDumpManager::UnregisterAndDeleteDumpProviderSoon, |
| base::Unretained(MemoryDumpManager::GetInstance()), |
| 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()); |
| })); |
| |
| EnableForTracing(); |
| for (int i = 0; i < 2; ++i) { |
| EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, |
| MemoryDumpLevelOfDetail::DETAILED)); |
| } |
| DisableTracing(); |
| } |
| |
| // Mock MDP class that tests if the number of OnMemoryDump() calls are expected. |
| // It is implemented without gmocks since EXPECT_CALL implementation is slow |
| // when there are 1000s of instances, as required in |
| // NoStackOverflowWithTooManyMDPs test. |
| class SimpleMockMemoryDumpProvider : public MemoryDumpProvider { |
| public: |
| SimpleMockMemoryDumpProvider(int expected_num_dump_calls) |
| : expected_num_dump_calls_(expected_num_dump_calls), num_dump_calls_(0) {} |
| |
| ~SimpleMockMemoryDumpProvider() override { |
| EXPECT_EQ(expected_num_dump_calls_, num_dump_calls_); |
| } |
| |
| bool OnMemoryDump(const MemoryDumpArgs& args, |
| ProcessMemoryDump* pmd) override { |
| ++num_dump_calls_; |
| return true; |
| } |
| |
| private: |
| int expected_num_dump_calls_; |
| int num_dump_calls_; |
| }; |
| |
| TEST_F(MemoryDumpManagerTest, NoStackOverflowWithTooManyMDPs) { |
| SetDumpProviderWhitelistForTesting(kTestMDPWhitelist); |
| |
| int kMDPCount = 1000; |
| std::vector<std::unique_ptr<SimpleMockMemoryDumpProvider>> mdps; |
| for (int i = 0; i < kMDPCount; ++i) { |
| mdps.push_back(std::make_unique<SimpleMockMemoryDumpProvider>(1)); |
| RegisterDumpProvider(mdps.back().get(), nullptr); |
| } |
| for (int i = 0; i < kMDPCount; ++i) { |
| mdps.push_back(std::make_unique<SimpleMockMemoryDumpProvider>(3)); |
| RegisterDumpProvider(mdps.back().get(), nullptr, kDefaultOptions, |
| kWhitelistedMDPName); |
| } |
| std::unique_ptr<Thread> stopped_thread(new Thread("test thread")); |
| stopped_thread->Start(); |
| for (int i = 0; i < kMDPCount; ++i) { |
| mdps.push_back(std::make_unique<SimpleMockMemoryDumpProvider>(0)); |
| RegisterDumpProvider(mdps.back().get(), stopped_thread->task_runner(), |
| kDefaultOptions, kWhitelistedMDPName); |
| } |
| stopped_thread->Stop(); |
| |
| EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, |
| MemoryDumpLevelOfDetail::DETAILED)); |
| EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, |
| MemoryDumpLevelOfDetail::BACKGROUND)); |
| EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::SUMMARY_ONLY, |
| MemoryDumpLevelOfDetail::BACKGROUND)); |
| } |
| |
| } // namespace trace_event |
| } // namespace base |