// 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