Skip to content

Commit

Permalink
[memory-infra] Add support for polling memory totals in MemoryDumpMan…
Browse files Browse the repository at this point in the history
…ager

This adds new method in MemoryDumpProvider interface PollFastMemoryTotal
which is used for polling memory totals in process. The dump provider
has to enable this feature while registration.
For Simplicity of polling logic the dump providers supporting polling
must not have task runner affinity. Polling will be done on dump thread.

BUG=607533

Review-Url: https://codereview.chromium.org/2537363003
Cr-Commit-Position: refs/heads/master@{#439390}
  • Loading branch information
ssiddhartha authored and Commit bot committed Dec 19, 2016
1 parent 10f963b commit 4460b9b
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 3 deletions.
64 changes: 64 additions & 0 deletions base/trace_event/memory_dump_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,15 @@ void MemoryDumpManager::RegisterDumpProviderInternal(
// path for RenderThreadImpl::Init().
if (already_registered)
return;

// The list of polling MDPs is populated OnTraceLogEnabled(). This code
// deals with the case of a MDP capable of fast polling that is registered
// after the OnTraceLogEnabled()
if (options.is_fast_polling_supported && dump_thread_) {
dump_thread_->task_runner()->PostTask(
FROM_HERE, Bind(&MemoryDumpManager::RegisterPollingMDPOnDumpThread,
Unretained(this), mdpinfo));
}
}

if (heap_profiling_enabled_)
Expand Down Expand Up @@ -322,6 +331,7 @@ void MemoryDumpManager::UnregisterDumpProviderInternal(
// - At the end of this function, if no dump is in progress.
// - Either in SetupNextMemoryDump() or InvokeOnMemoryDump() when MDPInfo is
// removed from |pending_dump_providers|.
// - When the provider is removed from |dump_providers_for_polling_|.
DCHECK(!(*mdp_iter)->owned_dump_provider);
(*mdp_iter)->owned_dump_provider = std::move(owned_mdp);
} else if (subtle::NoBarrier_Load(&memory_tracing_enabled_)) {
Expand All @@ -340,6 +350,16 @@ void MemoryDumpManager::UnregisterDumpProviderInternal(
<< "unregister itself in a racy way. Please file a crbug.";
}

if ((*mdp_iter)->options.is_fast_polling_supported && dump_thread_) {
DCHECK(take_mdp_ownership_and_delete_async)
<< "MemoryDumpProviders capable of fast polling must NOT be thread "
"bound and hence must be destroyed using "
"UnregisterAndDeleteDumpProviderSoon()";
dump_thread_->task_runner()->PostTask(
FROM_HERE, Bind(&MemoryDumpManager::UnregisterPollingMDPOnDumpThread,
Unretained(this), *mdp_iter));
}

// The MDPInfo instance can still be referenced by the
// |ProcessMemoryDumpAsyncState.pending_dump_providers|. For this reason
// the MDPInfo is flagged as disabled. It will cause InvokeOnMemoryDump()
Expand All @@ -349,6 +369,21 @@ void MemoryDumpManager::UnregisterDumpProviderInternal(
dump_providers_.erase(mdp_iter);
}

void MemoryDumpManager::RegisterPollingMDPOnDumpThread(
scoped_refptr<MemoryDumpManager::MemoryDumpProviderInfo> mdpinfo) {
DCHECK(!mdpinfo->task_runner);
AutoLock lock(lock_);
dump_providers_for_polling_.insert(mdpinfo);
}

void MemoryDumpManager::UnregisterPollingMDPOnDumpThread(
scoped_refptr<MemoryDumpManager::MemoryDumpProviderInfo> mdpinfo) {
mdpinfo->dump_provider->SuspendFastMemoryPolling();

AutoLock lock(lock_);
dump_providers_for_polling_.erase(mdpinfo);
}

void MemoryDumpManager::RequestGlobalDump(
MemoryDumpType dump_type,
MemoryDumpLevelOfDetail level_of_detail,
Expand Down Expand Up @@ -602,6 +637,18 @@ void MemoryDumpManager::InvokeOnMemoryDump(
SetupNextMemoryDump(std::move(pmd_async_state));
}

void MemoryDumpManager::PollFastMemoryTotal(uint64_t* memory_total) {
*memory_total = 0;
// Note that we call PollFastMemoryTotal() even if the dump provider is
// disabled (unregistered). This is to avoid taking lock while polling.
for (const auto& mdpinfo : dump_providers_for_polling_) {
uint64_t value = 0;
mdpinfo->dump_provider->PollFastMemoryTotal(&value);
*memory_total += value;
}
return;
}

// static
void MemoryDumpManager::FinalizeDumpAndAddToTrace(
std::unique_ptr<ProcessMemoryDumpAsyncState> pmd_async_state) {
Expand Down Expand Up @@ -715,6 +762,12 @@ void MemoryDumpManager::OnTraceLogEnabled() {
DCHECK(!dump_thread_);
dump_thread_ = std::move(dump_thread);

dump_providers_for_polling_.clear();
for (const auto& mdpinfo : dump_providers_) {
if (mdpinfo->options.is_fast_polling_supported)
dump_providers_for_polling_.insert(mdpinfo);
}

subtle::NoBarrier_Store(&memory_tracing_enabled_, 1);

// TODO(primiano): This is a temporary hack to disable periodic memory dumps
Expand All @@ -735,6 +788,8 @@ void MemoryDumpManager::OnTraceLogDisabled() {
// There might be a memory dump in progress while this happens. Therefore,
// ensure that the MDM state which depends on the tracing enabled / disabled
// state is always accessed by the dumping methods holding the |lock_|.
if (!subtle::NoBarrier_Load(&memory_tracing_enabled_))
return;
subtle::NoBarrier_Store(&memory_tracing_enabled_, 0);
std::unique_ptr<Thread> dump_thread;
{
Expand All @@ -748,6 +803,15 @@ void MemoryDumpManager::OnTraceLogDisabled() {
periodic_dump_timer_.Stop();
if (dump_thread)
dump_thread->Stop();

// |dump_providers_for_polling_| must be cleared only after the dump thread is
// stopped (polling tasks are done).
{
AutoLock lock(lock_);
for (const auto& mdpinfo : dump_providers_for_polling_)
mdpinfo->dump_provider->SuspendFastMemoryPolling();
dump_providers_for_polling_.clear();
}
}

bool MemoryDumpManager::IsDumpModeAllowed(MemoryDumpLevelOfDetail dump_mode) {
Expand Down
21 changes: 20 additions & 1 deletion base/trace_event/memory_dump_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ class BASE_EXPORT MemoryDumpManager : public TraceLog::EnabledStateObserver {
// This method takes ownership of the dump provider and guarantees that:
// - The |mdp| will be deleted at some point in the near future.
// - Its deletion will not happen concurrently with the OnMemoryDump() call.
// Note that OnMemoryDump() calls can still happen after this method returns.
// Note that OnMemoryDump() and PollFastMemoryTotal() calls can still happen
// after this method returns.
void UnregisterAndDeleteDumpProviderSoon(
std::unique_ptr<MemoryDumpProvider> mdp);

Expand Down Expand Up @@ -329,6 +330,13 @@ class BASE_EXPORT MemoryDumpManager : public TraceLog::EnabledStateObserver {
// runner.
void InvokeOnMemoryDump(ProcessMemoryDumpAsyncState* owned_pmd_async_state);

// Records a quick total memory usage in |memory_total|. This is used to track
// and detect peaks in the memory usage of the process without having to
// record all data from dump providers. This value is approximate to trade-off
// speed, and not consistent with the rest of the memory-infra metrics. Must
// be called on the dump thread.
void PollFastMemoryTotal(uint64_t* memory_total);

// Helper for RegierDumpProvider* functions.
void RegisterDumpProviderInternal(
MemoryDumpProvider* mdp,
Expand All @@ -340,10 +348,21 @@ class BASE_EXPORT MemoryDumpManager : public TraceLog::EnabledStateObserver {
void UnregisterDumpProviderInternal(MemoryDumpProvider* mdp,
bool take_mdp_ownership_and_delete_async);

// Adds / removes provider that supports polling to
// |dump_providers_for_polling_|.
void RegisterPollingMDPOnDumpThread(
scoped_refptr<MemoryDumpProviderInfo> mdpinfo);
void UnregisterPollingMDPOnDumpThread(
scoped_refptr<MemoryDumpProviderInfo> mdpinfo);

// An ordererd set of registered MemoryDumpProviderInfo(s), sorted by task
// runner affinity (MDPs belonging to the same task runners are adjacent).
MemoryDumpProviderInfo::OrderedSet dump_providers_;

// A copy of mdpinfo list that support polling. It must be accessed only on
// the dump thread if dump thread exists.
MemoryDumpProviderInfo::OrderedSet dump_providers_for_polling_;

// Shared among all the PMDs to keep state scoped to the tracing session.
scoped_refptr<MemoryDumpSessionState> session_state_;

Expand Down
72 changes: 72 additions & 0 deletions base/trace_event/memory_dump_manager_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ class MockMemoryDumpProvider : public MemoryDumpProvider {
MOCK_METHOD0(Destructor, void());
MOCK_METHOD2(OnMemoryDump,
bool(const MemoryDumpArgs& args, ProcessMemoryDump* pmd));
MOCK_METHOD1(PollFastMemoryTotal, void(uint64_t* memory_total));
MOCK_METHOD0(SuspendFastMemoryPolling, void());

MockMemoryDumpProvider() : enable_mock_destructor(false) {
ON_CALL(*this, OnMemoryDump(_, _))
Expand All @@ -151,6 +153,10 @@ class MockMemoryDumpProvider : public MemoryDumpProvider {
EXPECT_TRUE(pmd->session_state().get() != nullptr);
return true;
}));

ON_CALL(*this, PollFastMemoryTotal(_))
.WillByDefault(
Invoke([](uint64_t* memory_total) -> void { NOTREACHED(); }));
}
~MockMemoryDumpProvider() override {
if (enable_mock_destructor)
Expand Down Expand Up @@ -232,6 +238,10 @@ class MemoryDumpManagerTest : public testing::Test {
task_runner->PostTask(FROM_HERE, closure);
}

void PollFastMemoryTotal(uint64_t* memory_total) {
mdm_->PollFastMemoryTotal(memory_total);
}

protected:
void InitializeMemoryDumpManager(bool is_coordinator) {
mdm_->set_dumper_registrations_ignored_for_testing(true);
Expand Down Expand Up @@ -268,6 +278,10 @@ class MemoryDumpManagerTest : public testing::Test {
return MemoryDumpManager::kMaxConsecutiveFailuresCount;
}

scoped_refptr<SequencedTaskRunner> GetPollingTaskRunnerUnsafe() {
return mdm_->dump_thread_->task_runner();
}

const MemoryDumpProvider::Options kDefaultOptions;
std::unique_ptr<MemoryDumpManager> mdm_;
std::unique_ptr<MemoryDumpManagerDelegateForTesting> delegate_;
Expand Down Expand Up @@ -736,6 +750,64 @@ TEST_F(MemoryDumpManagerTest, UnregisterDumperFromThreadWhileDumping) {
DisableTracing();
}

TEST_F(MemoryDumpManagerTest, TestPollingOnDumpThread) {
InitializeMemoryDumpManager(false /* is_coordinator */);
std::unique_ptr<MockMemoryDumpProvider> mdp1(new MockMemoryDumpProvider());
std::unique_ptr<MockMemoryDumpProvider> mdp2(new MockMemoryDumpProvider());
mdp1->enable_mock_destructor = true;
mdp2->enable_mock_destructor = true;

EXPECT_CALL(*mdp1, SuspendFastMemoryPolling()).Times(1);
EXPECT_CALL(*mdp2, SuspendFastMemoryPolling()).Times(1);
EXPECT_CALL(*mdp1, Destructor());
EXPECT_CALL(*mdp2, Destructor());

RunLoop run_loop;
scoped_refptr<SingleThreadTaskRunner> test_task_runner =
ThreadTaskRunnerHandle::Get();
auto quit_closure = run_loop.QuitClosure();

int call_count = 0;
EXPECT_CALL(*mdp1, PollFastMemoryTotal(_))
.Times(4)
.WillRepeatedly(Invoke([&call_count, &test_task_runner,
quit_closure](uint64_t* total) -> void {
++call_count;
if (call_count == 4)
test_task_runner->PostTask(FROM_HERE, quit_closure);
}));

// Depending on the order of PostTask calls the mdp2 might be registered after
// all polls or in between polls.
EXPECT_CALL(*mdp2, PollFastMemoryTotal(_))
.Times(Between(0, 4))
.WillRepeatedly(Return());

MemoryDumpProvider::Options options;
options.is_fast_polling_supported = true;
RegisterDumpProvider(mdp1.get(), nullptr, options);
EnableTracingWithTraceConfig(
TraceConfigMemoryTestUtil::GetTraceConfig_PeakDetectionTrigger(1));
scoped_refptr<SequencedTaskRunner> polling_task_runner =
GetPollingTaskRunnerUnsafe().get();
ASSERT_TRUE(polling_task_runner);

uint64_t value = 0;
for (int i = 0; i < 4; i++) {
if (i == 0)
RegisterDumpProvider(mdp2.get(), nullptr, options);
if (i == 2)
mdm_->UnregisterAndDeleteDumpProviderSoon(std::move(mdp2));
polling_task_runner->PostTask(
FROM_HERE, Bind(&MemoryDumpManagerTest::PollFastMemoryTotal,
Unretained(this), &value));
}

run_loop.Run();
DisableTracing();
mdm_->UnregisterAndDeleteDumpProviderSoon(std::move(mdp1));
}

// 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) {
Expand Down
20 changes: 19 additions & 1 deletion base/trace_event/memory_dump_provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ class BASE_EXPORT MemoryDumpProvider {
struct Options {
Options()
: target_pid(kNullProcessId),
dumps_on_single_thread_task_runner(false) {}
dumps_on_single_thread_task_runner(false),
is_fast_polling_supported(false) {}

// If the dump provider generates dumps on behalf of another process,
// |target_pid| contains the pid of that process.
Expand All @@ -34,6 +35,11 @@ class BASE_EXPORT MemoryDumpProvider {
// a SingleThreadTaskRunner, which is usually the case. It is faster to run
// all providers that run on the same thread together without thread hops.
bool dumps_on_single_thread_task_runner;

// Set to true if the dump provider implementation supports high frequency
// polling. Only providers running without task runner affinity are
// supported.
bool is_fast_polling_supported;
};

virtual ~MemoryDumpProvider() {}
Expand All @@ -52,6 +58,18 @@ class BASE_EXPORT MemoryDumpProvider {
// collecting extensive allocation data, if supported.
virtual void OnHeapProfilingEnabled(bool enabled) {}

// Quickly record the total memory usage in |memory_total|. This method will
// be called only when the dump provider registration has
// |is_fast_polling_supported| set to true. This method is used for polling at
// high frequency for detecting peaks. See comment on
// |is_fast_polling_supported| option if you need to override this method.
virtual void PollFastMemoryTotal(uint64_t* memory_total) {}

// Indicates that fast memory polling is not going to be used in the near
// future and the MDP can tear down any resource kept around for fast memory
// polling.
virtual void SuspendFastMemoryPolling() {}

protected:
MemoryDumpProvider() {}

Expand Down
6 changes: 5 additions & 1 deletion base/trace_event/memory_dump_session_state.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
namespace base {
namespace trace_event {

MemoryDumpSessionState::MemoryDumpSessionState() {}
MemoryDumpSessionState::MemoryDumpSessionState() : is_polling_enabled_(false) {}

MemoryDumpSessionState::~MemoryDumpSessionState() {}

Expand All @@ -26,6 +26,10 @@ void MemoryDumpSessionState::SetTypeNameDeduplicator(
void MemoryDumpSessionState::SetMemoryDumpConfig(
const TraceConfig::MemoryDumpConfig& config) {
memory_dump_config_ = config;
for (const auto& trigger : config.triggers) {
if (trigger.trigger_type == MemoryDumpType::PEAK_MEMORY_USAGE)
is_polling_enabled_ = true;
}
}

} // namespace trace_event
Expand Down
5 changes: 5 additions & 0 deletions base/trace_event/memory_dump_session_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ class BASE_EXPORT MemoryDumpSessionState

void SetMemoryDumpConfig(const TraceConfig::MemoryDumpConfig& config);

bool is_polling_enabled() { return is_polling_enabled_; }

private:
friend class RefCountedThreadSafe<MemoryDumpSessionState>;
~MemoryDumpSessionState();
Expand All @@ -61,6 +63,9 @@ class BASE_EXPORT MemoryDumpSessionState
// The memory dump config, copied at the time when the tracing session was
// started.
TraceConfig::MemoryDumpConfig memory_dump_config_;

// True if memory polling is enabled by the config in the tracing session.
bool is_polling_enabled_;
};

} // namespace trace_event
Expand Down

0 comments on commit 4460b9b

Please sign in to comment.