blob: 53c7d9250dab441711846adb0585bc2c0ed72819 [file] [log] [blame]
// 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 "base/bind_helpers.h"
#include "base/memory/scoped_vector.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/thread_task_runner_handle.h"
#include "base/threading/thread.h"
#include "base/trace_event/memory_dump_provider.h"
#include "base/trace_event/process_memory_dump.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::Between;
using testing::Invoke;
using testing::Return;
namespace base {
namespace trace_event {
// Testing MemoryDumpManagerDelegate which short-circuits dump requests locally
// instead of performing IPC dances.
class MemoryDumpManagerDelegateForTesting : public MemoryDumpManagerDelegate {
public:
void RequestGlobalMemoryDump(const MemoryDumpRequestArgs& args,
const MemoryDumpCallback& callback) override {
CreateProcessDump(args, callback);
}
bool IsCoordinatorProcess() const override { return false; }
};
class MemoryDumpManagerTest : public testing::Test {
public:
void SetUp() override {
message_loop_.reset(new MessageLoop());
mdm_.reset(new MemoryDumpManager());
MemoryDumpManager::SetInstanceForTesting(mdm_.get());
ASSERT_EQ(mdm_, MemoryDumpManager::GetInstance());
MemoryDumpManager::GetInstance()->Initialize();
MemoryDumpManager::GetInstance()->SetDelegate(&delegate_);
}
void TearDown() override {
MemoryDumpManager::SetInstanceForTesting(nullptr);
mdm_.reset();
message_loop_.reset();
TraceLog::DeleteForTesting();
}
void DumpCallbackAdapter(scoped_refptr<SingleThreadTaskRunner> task_runner,
Closure closure,
uint64 dump_guid,
bool success) {
task_runner->PostTask(FROM_HERE, closure);
}
protected:
const char* kTraceCategory = MemoryDumpManager::kTraceCategoryForTesting;
void EnableTracing(const char* category) {
TraceLog::GetInstance()->SetEnabled(
TraceConfig(category, ""), TraceLog::RECORDING_MODE);
}
void DisableTracing() { TraceLog::GetInstance()->SetDisabled(); }
scoped_ptr<MemoryDumpManager> mdm_;
private:
scoped_ptr<MessageLoop> message_loop_;
MemoryDumpManagerDelegateForTesting delegate_;
// We want our singleton torn down after each test.
ShadowingAtExitManager at_exit_manager_;
};
class MockDumpProvider : public MemoryDumpProvider {
public:
MockDumpProvider()
: dump_provider_to_register_or_unregister(nullptr),
last_session_state_(nullptr) {}
// Ctor used by the RespectTaskRunnerAffinity test.
explicit MockDumpProvider(
const scoped_refptr<SingleThreadTaskRunner>& task_runner)
: last_session_state_(nullptr), task_runner_(task_runner) {}
virtual ~MockDumpProvider() {}
MOCK_METHOD1(OnMemoryDump, bool(ProcessMemoryDump* pmd));
// OnMemoryDump() override for the RespectTaskRunnerAffinity test.
bool OnMemoryDump_CheckTaskRunner(ProcessMemoryDump* pmd) {
EXPECT_TRUE(task_runner_->RunsTasksOnCurrentThread());
return true;
}
// OnMemoryDump() override for the SharedSessionState test.
bool OnMemoryDump_CheckSessionState(ProcessMemoryDump* pmd) {
MemoryDumpSessionState* cur_session_state = pmd->session_state().get();
if (last_session_state_)
EXPECT_EQ(last_session_state_, cur_session_state);
last_session_state_ = cur_session_state;
return true;
}
// OnMemoryDump() override for the RegisterDumperWhileDumping test.
bool OnMemoryDump_RegisterExtraDumpProvider(ProcessMemoryDump* pmd) {
MemoryDumpManager::GetInstance()->RegisterDumpProvider(
dump_provider_to_register_or_unregister);
return true;
}
// OnMemoryDump() override for the UnegisterDumperWhileDumping test.
bool OnMemoryDump_UnregisterDumpProvider(ProcessMemoryDump* pmd) {
MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
dump_provider_to_register_or_unregister);
return true;
}
// Used by OnMemoryDump_(Un)RegisterExtraDumpProvider.
MemoryDumpProvider* dump_provider_to_register_or_unregister;
private:
MemoryDumpSessionState* last_session_state_;
scoped_refptr<SingleThreadTaskRunner> task_runner_;
};
TEST_F(MemoryDumpManagerTest, SingleDumper) {
MockDumpProvider mdp;
mdm_->RegisterDumpProvider(&mdp);
// Check that the dumper is not called if the memory category is not enabled.
EnableTracing("foo-and-bar-but-not-memory");
EXPECT_CALL(mdp, OnMemoryDump(_)).Times(0);
mdm_->RequestGlobalDump(MemoryDumpType::EXPLICITLY_TRIGGERED);
DisableTracing();
// Now repeat enabling the memory category and check that the dumper is
// invoked this time.
EnableTracing(kTraceCategory);
EXPECT_CALL(mdp, OnMemoryDump(_)).Times(3).WillRepeatedly(Return(true));
for (int i = 0; i < 3; ++i)
mdm_->RequestGlobalDump(MemoryDumpType::EXPLICITLY_TRIGGERED);
DisableTracing();
mdm_->UnregisterDumpProvider(&mdp);
// Finally check the unregister logic (no calls to the mdp after unregister).
EnableTracing(kTraceCategory);
EXPECT_CALL(mdp, OnMemoryDump(_)).Times(0);
mdm_->RequestGlobalDump(MemoryDumpType::EXPLICITLY_TRIGGERED);
TraceLog::GetInstance()->SetDisabled();
}
TEST_F(MemoryDumpManagerTest, SharedSessionState) {
MockDumpProvider mdp1;
MockDumpProvider mdp2;
mdm_->RegisterDumpProvider(&mdp1);
mdm_->RegisterDumpProvider(&mdp2);
EnableTracing(kTraceCategory);
EXPECT_CALL(mdp1, OnMemoryDump(_))
.Times(2)
.WillRepeatedly(
Invoke(&mdp1, &MockDumpProvider::OnMemoryDump_CheckSessionState));
EXPECT_CALL(mdp2, OnMemoryDump(_))
.Times(2)
.WillRepeatedly(
Invoke(&mdp2, &MockDumpProvider::OnMemoryDump_CheckSessionState));
for (int i = 0; i < 2; ++i)
mdm_->RequestGlobalDump(MemoryDumpType::EXPLICITLY_TRIGGERED);
DisableTracing();
}
TEST_F(MemoryDumpManagerTest, MultipleDumpers) {
MockDumpProvider mdp1;
MockDumpProvider mdp2;
// Enable only mdp1.
mdm_->RegisterDumpProvider(&mdp1);
EnableTracing(kTraceCategory);
EXPECT_CALL(mdp1, OnMemoryDump(_)).Times(1).WillRepeatedly(Return(true));
EXPECT_CALL(mdp2, OnMemoryDump(_)).Times(0);
mdm_->RequestGlobalDump(MemoryDumpType::EXPLICITLY_TRIGGERED);
DisableTracing();
// Invert: enable mdp1 and disable mdp2.
mdm_->UnregisterDumpProvider(&mdp1);
mdm_->RegisterDumpProvider(&mdp2);
EnableTracing(kTraceCategory);
EXPECT_CALL(mdp1, OnMemoryDump(_)).Times(0);
EXPECT_CALL(mdp2, OnMemoryDump(_)).Times(1).WillRepeatedly(Return(true));
mdm_->RequestGlobalDump(MemoryDumpType::EXPLICITLY_TRIGGERED);
DisableTracing();
// Enable both mdp1 and mdp2.
mdm_->RegisterDumpProvider(&mdp1);
EnableTracing(kTraceCategory);
EXPECT_CALL(mdp1, OnMemoryDump(_)).Times(1).WillRepeatedly(Return(true));
EXPECT_CALL(mdp2, OnMemoryDump(_)).Times(1).WillRepeatedly(Return(true));
mdm_->RequestGlobalDump(MemoryDumpType::EXPLICITLY_TRIGGERED);
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 kNumInitialThreads = 8;
ScopedVector<Thread> threads;
ScopedVector<MockDumpProvider> 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 i = kNumInitialThreads; i > 0; --i) {
threads.push_back(new Thread("test thread"));
threads.back()->Start();
mdps.push_back(new MockDumpProvider(threads.back()->task_runner()));
MockDumpProvider* mdp = mdps.back();
mdm_->RegisterDumpProvider(mdp, threads.back()->task_runner());
EXPECT_CALL(*mdp, OnMemoryDump(_))
.Times(i)
.WillRepeatedly(
Invoke(mdp, &MockDumpProvider::OnMemoryDump_CheckTaskRunner));
}
EnableTracing(kTraceCategory);
while (!threads.empty()) {
{
RunLoop run_loop;
MemoryDumpCallback callback =
Bind(&MemoryDumpManagerTest::DumpCallbackAdapter, Unretained(this),
MessageLoop::current()->task_runner(), run_loop.QuitClosure());
mdm_->RequestGlobalDump(MemoryDumpType::EXPLICITLY_TRIGGERED, callback);
// This nested message loop (|run_loop|) will be quit if and only if
// the RequestGlobalDump callback is invoked.
run_loop.Run();
}
// 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()));
threads.back()->task_runner()->PostTaskAndReply(FROM_HERE, unregistration,
run_loop.QuitClosure());
run_loop.Run();
}
mdps.pop_back();
threads.back()->Stop();
threads.pop_back();
}
DisableTracing();
}
// Enable both dump providers, make sure that mdp gets disabled after 3 failures
// and not disabled after 1.
TEST_F(MemoryDumpManagerTest, DisableFailingDumpers) {
MockDumpProvider mdp1;
MockDumpProvider mdp2;
mdm_->RegisterDumpProvider(&mdp1);
mdm_->RegisterDumpProvider(&mdp2);
EnableTracing(kTraceCategory);
EXPECT_CALL(mdp1, OnMemoryDump(_))
.Times(MemoryDumpManager::kMaxConsecutiveFailuresCount)
.WillRepeatedly(Return(false));
EXPECT_CALL(mdp2, OnMemoryDump(_))
.Times(1 + MemoryDumpManager::kMaxConsecutiveFailuresCount)
.WillOnce(Return(false))
.WillRepeatedly(Return(true));
for (int i = 0; i < 1 + MemoryDumpManager::kMaxConsecutiveFailuresCount;
i++) {
mdm_->RequestGlobalDump(MemoryDumpType::EXPLICITLY_TRIGGERED);
}
DisableTracing();
}
// Sneakily register 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) {
MockDumpProvider mdp1;
MockDumpProvider mdp2;
mdp1.dump_provider_to_register_or_unregister = &mdp2;
mdm_->RegisterDumpProvider(&mdp1);
EnableTracing(kTraceCategory);
EXPECT_CALL(mdp1, OnMemoryDump(_))
.Times(4)
.WillOnce(Return(true))
.WillOnce(Invoke(
&mdp1, &MockDumpProvider::OnMemoryDump_RegisterExtraDumpProvider))
.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++) {
mdm_->RequestGlobalDump(MemoryDumpType::EXPLICITLY_TRIGGERED);
}
DisableTracing();
}
// Like the above, but suddenly unregister the dump provider.
TEST_F(MemoryDumpManagerTest, UnregisterDumperWhileDumping) {
MockDumpProvider mdp1;
MockDumpProvider mdp2;
mdm_->RegisterDumpProvider(&mdp1, ThreadTaskRunnerHandle::Get());
mdm_->RegisterDumpProvider(&mdp2, ThreadTaskRunnerHandle::Get());
mdp1.dump_provider_to_register_or_unregister = &mdp2;
EnableTracing(kTraceCategory);
EXPECT_CALL(mdp1, OnMemoryDump(_))
.Times(4)
.WillOnce(Return(true))
.WillOnce(Invoke(&mdp1,
&MockDumpProvider::OnMemoryDump_UnregisterDumpProvider))
.WillRepeatedly(Return(true));
// Depending on the insertion order (before or after mdp1), mdp2 might have
// been already called when OnMemoryDump_UnregisterDumpProvider happens.
EXPECT_CALL(mdp2, OnMemoryDump(_))
.Times(Between(1, 2))
.WillRepeatedly(Return(true));
for (int i = 0; i < 4; i++) {
mdm_->RequestGlobalDump(MemoryDumpType::EXPLICITLY_TRIGGERED);
}
DisableTracing();
}
} // namespace trace_event
} // namespace base