blob: 8c916774c9dca88d33a517a1930ae140386904b3 [file] [log] [blame]
// Copyright 2016 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 "content/browser/memory/memory_coordinator_impl.h"
#include "base/memory/memory_coordinator_client_registry.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/process/process_handle.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/default_tick_clock.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/memory/memory_condition_observer.h"
#include "content/browser/memory/memory_coordinator_default_policy.h"
#include "content/browser/memory/memory_monitor.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/common/content_features.h"
#include "mojo/public/cpp/bindings/binding.h"
namespace content {
namespace {
const int kDefaultMinimumTransitionPeriodSeconds = 30;
mojom::MemoryState ToMojomMemoryState(MemoryState state) {
switch (state) {
case MemoryState::UNKNOWN:
return mojom::MemoryState::UNKNOWN;
case MemoryState::NORMAL:
return mojom::MemoryState::NORMAL;
case MemoryState::THROTTLED:
return mojom::MemoryState::THROTTLED;
case MemoryState::SUSPENDED:
return mojom::MemoryState::SUSPENDED;
default:
NOTREACHED();
return mojom::MemoryState::UNKNOWN;
}
}
const char* MemoryConditionToString(MemoryCondition condition) {
switch (condition) {
case MemoryCondition::NORMAL:
return "normal";
case MemoryCondition::CRITICAL:
return "critical";
}
NOTREACHED();
return "N/A";
}
void RecordBrowserPurge(size_t before) {
auto metrics = base::ProcessMetrics::CreateCurrentProcessMetrics();
size_t after = metrics->GetWorkingSetSize();
int64_t bytes = static_cast<int64_t>(before) - static_cast<int64_t>(after);
if (bytes < 0)
bytes = 0;
UMA_HISTOGRAM_MEMORY_LARGE_MB("Memory.Experimental.Browser.PurgedMemory",
bytes / 1024 / 1024);
}
} // namespace
// The implementation of MemoryCoordinatorHandle. See memory_coordinator.mojom
// for the role of this class.
class MemoryCoordinatorHandleImpl : public mojom::MemoryCoordinatorHandle {
public:
MemoryCoordinatorHandleImpl(mojom::MemoryCoordinatorHandleRequest request,
MemoryCoordinatorImpl* coordinator,
int render_process_id);
~MemoryCoordinatorHandleImpl() override;
// mojom::MemoryCoordinatorHandle:
void AddChild(mojom::ChildMemoryCoordinatorPtr child) override;
mojom::ChildMemoryCoordinatorPtr& child() { return child_; }
mojo::Binding<mojom::MemoryCoordinatorHandle>& binding() { return binding_; }
private:
MemoryCoordinatorImpl* coordinator_;
int render_process_id_;
mojom::ChildMemoryCoordinatorPtr child_;
mojo::Binding<mojom::MemoryCoordinatorHandle> binding_;
DISALLOW_COPY_AND_ASSIGN(MemoryCoordinatorHandleImpl);
};
MemoryCoordinatorHandleImpl::MemoryCoordinatorHandleImpl(
mojom::MemoryCoordinatorHandleRequest request,
MemoryCoordinatorImpl* coordinator,
int render_process_id)
: coordinator_(coordinator),
render_process_id_(render_process_id),
binding_(this, std::move(request)) {
DCHECK(coordinator_);
}
MemoryCoordinatorHandleImpl::~MemoryCoordinatorHandleImpl() {}
void MemoryCoordinatorHandleImpl::AddChild(
mojom::ChildMemoryCoordinatorPtr child) {
DCHECK(!child_.is_bound());
child_ = std::move(child);
coordinator_->OnChildAdded(render_process_id_);
}
// static
MemoryCoordinator* MemoryCoordinator::GetInstance() {
return MemoryCoordinatorImpl::GetInstance();
}
// static
MemoryCoordinatorImpl* MemoryCoordinatorImpl::GetInstance() {
if (!base::FeatureList::IsEnabled(features::kMemoryCoordinator))
return nullptr;
static MemoryCoordinatorImpl* instance = new MemoryCoordinatorImpl(
base::ThreadTaskRunnerHandle::Get(), CreateMemoryMonitor());
return instance;
}
MemoryCoordinatorImpl::MemoryCoordinatorImpl(
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
std::unique_ptr<MemoryMonitor> memory_monitor)
: task_runner_(task_runner),
policy_(std::make_unique<MemoryCoordinatorDefaultPolicy>(this)),
delegate_(GetContentClient()->browser()->GetMemoryCoordinatorDelegate()),
memory_monitor_(std::move(memory_monitor)),
condition_observer_(
std::make_unique<MemoryConditionObserver>(this, task_runner)),
tick_clock_(base::DefaultTickClock::GetInstance()),
minimum_state_transition_period_(base::TimeDelta::FromSeconds(
kDefaultMinimumTransitionPeriodSeconds)) {
DCHECK(memory_monitor_.get());
base::MemoryCoordinatorProxy::SetMemoryCoordinator(this);
// Force the "memory_coordinator" category to show up in the trace viewer.
base::trace_event::TraceLog::GetCategoryGroupEnabled(
TRACE_DISABLED_BY_DEFAULT("memory_coordinator"));
}
MemoryCoordinatorImpl::~MemoryCoordinatorImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::MemoryCoordinatorProxy::SetMemoryCoordinator(nullptr);
}
void MemoryCoordinatorImpl::Start() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(last_state_change_.is_null());
notification_registrar_.Add(
this, NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
NotificationService::AllBrowserContextsAndSources());
condition_observer_->ScheduleUpdateCondition(base::TimeDelta());
}
void MemoryCoordinatorImpl::OnForegrounded() {
condition_observer_->OnForegrounded();
}
void MemoryCoordinatorImpl::OnBackgrounded() {
condition_observer_->OnBackgrounded();
}
void MemoryCoordinatorImpl::CreateHandle(
int render_process_id,
mojom::MemoryCoordinatorHandleRequest request) {
std::unique_ptr<MemoryCoordinatorHandleImpl> handle(
new MemoryCoordinatorHandleImpl(std::move(request), this,
render_process_id));
handle->binding().set_connection_error_handler(
base::BindOnce(&MemoryCoordinatorImpl::OnConnectionError,
base::Unretained(this), render_process_id));
CreateChildInfoMapEntry(render_process_id, std::move(handle));
}
void MemoryCoordinatorImpl::SetBrowserMemoryState(MemoryState memory_state) {
if (memory_state == browser_memory_state_)
return;
base::TimeTicks now = tick_clock_->NowTicks();
base::TimeDelta elapsed = now - last_state_change_;
if (!last_state_change_.is_null() &&
(elapsed < minimum_state_transition_period_)) {
base::TimeDelta delay = minimum_state_transition_period_ - elapsed +
base::TimeDelta::FromSeconds(1);
delayed_browser_memory_state_setter_.Reset(
base::Bind(&MemoryCoordinatorImpl::SetBrowserMemoryState,
base::Unretained(this), memory_state));
task_runner_->PostDelayedTask(
FROM_HERE, delayed_browser_memory_state_setter_.callback(), delay);
return;
}
if (!delayed_browser_memory_state_setter_.IsCancelled())
delayed_browser_memory_state_setter_.Cancel();
last_state_change_ = now;
browser_memory_state_ = memory_state;
NotifyStateToClients(memory_state);
}
bool MemoryCoordinatorImpl::SetChildMemoryState(int render_process_id,
MemoryState memory_state) {
// Can't set an invalid memory state.
if (memory_state == MemoryState::UNKNOWN)
return false;
// SUSPENDED state isn't supported yet.
if (memory_state == MemoryState::SUSPENDED)
return false;
// Can't send a message to a child that doesn't exist.
auto iter = children_.find(render_process_id);
if (iter == children_.end())
return false;
// Can't send a message to a child that isn't bound.
if (!iter->second.handle->child().is_bound())
return false;
memory_state = OverrideState(memory_state, iter->second);
// A nop doesn't need to be sent, but is considered successful.
if (iter->second.memory_state == memory_state)
return true;
// Update the internal state and send the message.
iter->second.memory_state = memory_state;
iter->second.handle->child()->OnStateChange(ToMojomMemoryState(memory_state));
return true;
}
bool MemoryCoordinatorImpl::TryToPurgeMemoryFromBrowser() {
base::TimeTicks now = tick_clock_->NowTicks();
if (can_purge_after_ > now)
return false;
auto metrics = base::ProcessMetrics::CreateCurrentProcessMetrics();
size_t before = metrics->GetWorkingSetSize();
task_runner_->PostDelayedTask(FROM_HERE,
base::BindOnce(&RecordBrowserPurge, before),
base::TimeDelta::FromSeconds(2));
// Suppress purging in the browser process until a certain period of time is
// passed.
can_purge_after_ = now + base::TimeDelta::FromMinutes(2);
base::MemoryCoordinatorClientRegistry::GetInstance()->PurgeMemory();
return true;
}
bool MemoryCoordinatorImpl::TryToPurgeMemoryFromChild(int render_process_id) {
auto iter = children().find(render_process_id);
if (iter == children().end())
return false;
MemoryCoordinatorHandleImpl* handle = iter->second.handle.get();
if (!handle || !handle->child() || !handle->child().is_bound())
return false;
if (!iter->second.can_purge_after.is_null() &&
iter->second.can_purge_after > tick_clock_->NowTicks())
return false;
// Set |can_purge_after| to the maximum value to suppress another purge
// request until the child process goes foreground and then goes background
// again.
iter->second.can_purge_after = base::TimeTicks::Max();
handle->child()->PurgeMemory();
return true;
}
MemoryState MemoryCoordinatorImpl::GetCurrentMemoryState() const {
return browser_memory_state_;
}
void MemoryCoordinatorImpl::ForceSetMemoryCondition(MemoryCondition condition,
base::TimeDelta duration) {
UpdateConditionIfNeeded(condition);
suppress_condition_change_until_ = tick_clock_->NowTicks() + duration;
}
void MemoryCoordinatorImpl::Observe(int type,
const NotificationSource& source,
const NotificationDetails& details) {
DCHECK(type == NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED);
RenderWidgetHost* render_widget_host = Source<RenderWidgetHost>(source).ptr();
RenderProcessHost* process = render_widget_host->GetProcess();
if (!process)
return;
bool is_visible = *Details<bool>(details).ptr();
policy_->OnChildVisibilityChanged(process->GetID(), is_visible);
}
MemoryState MemoryCoordinatorImpl::GetStateForProcess(
base::ProcessHandle handle) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (handle == base::kNullProcessHandle)
return MemoryState::UNKNOWN;
if (handle == base::GetCurrentProcessHandle())
return browser_memory_state_;
for (auto& iter : children()) {
auto* render_process_host = GetRenderProcessHost(iter.first);
if (render_process_host && render_process_host->GetHandle() == handle)
return iter.second.memory_state;
}
return MemoryState::UNKNOWN;
}
void MemoryCoordinatorImpl::UpdateConditionIfNeeded(
MemoryCondition next_condition) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (next_condition == MemoryCondition::CRITICAL)
policy_->OnCriticalCondition();
if (suppress_condition_change_until_ > tick_clock_->NowTicks() ||
memory_condition_ == next_condition)
return;
TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("memory_coordinator"),
"MemoryCoordinatorImpl::UpdateConditionIfNeeded", "prev",
MemoryConditionToString(memory_condition_), "next",
MemoryConditionToString(next_condition));
policy_->OnConditionChanged(memory_condition_, next_condition);
memory_condition_ = next_condition;
}
void MemoryCoordinatorImpl::DiscardTab(bool skip_unload_handlers) {
if (delegate_)
delegate_->DiscardTab(skip_unload_handlers);
}
RenderProcessHost* MemoryCoordinatorImpl::GetRenderProcessHost(
int render_process_id) {
return RenderProcessHost::FromID(render_process_id);
}
void MemoryCoordinatorImpl::SetDelegateForTesting(
std::unique_ptr<MemoryCoordinatorDelegate> delegate) {
CHECK(!delegate_);
delegate_ = std::move(delegate);
}
void MemoryCoordinatorImpl::SetPolicyForTesting(
std::unique_ptr<Policy> policy) {
policy_ = std::move(policy);
}
void MemoryCoordinatorImpl::AddChildForTesting(
int dummy_render_process_id, mojom::ChildMemoryCoordinatorPtr child) {
mojom::MemoryCoordinatorHandlePtr mch;
auto request = mojo::MakeRequest(&mch);
std::unique_ptr<MemoryCoordinatorHandleImpl> handle(
new MemoryCoordinatorHandleImpl(std::move(request), this,
dummy_render_process_id));
handle->AddChild(std::move(child));
CreateChildInfoMapEntry(dummy_render_process_id, std::move(handle));
}
void MemoryCoordinatorImpl::SetTickClockForTesting(
base::TickClock* tick_clock) {
tick_clock_ = tick_clock;
}
void MemoryCoordinatorImpl::OnConnectionError(int render_process_id) {
children_.erase(render_process_id);
}
void MemoryCoordinatorImpl::OnChildAdded(int render_process_id) {
RenderProcessHost* render_process_host =
GetRenderProcessHost(render_process_id);
if (!render_process_host)
return;
// Populate an initial state of a newly created process.
// TODO(bashi): IsProcessBackgrounded() may return true even when tabs in the
// renderer process are invisible (e.g. restoring tabs all at once).
// Figure out a better way to set visibility.
policy_->OnChildVisibilityChanged(
render_process_id, !render_process_host->IsProcessBackgrounded());
}
MemoryState MemoryCoordinatorImpl::OverrideState(MemoryState memory_state,
const ChildInfo& child) {
// We don't suspend foreground renderers. Throttle them instead.
if (child.is_visible && memory_state == MemoryState::SUSPENDED)
return MemoryState::THROTTLED;
#if defined(OS_ANDROID)
// On Android, we throttle background renderers immediately.
// TODO(bashi): Create a specialized class of MemoryCoordinator for Android
// and move this ifdef to the class.
if (!child.is_visible && memory_state == MemoryState::NORMAL)
return MemoryState::THROTTLED;
// TODO(bashi): Suspend background renderers after a certain period of time.
#endif // defined(OS_ANDROID)
return memory_state;
}
void MemoryCoordinatorImpl::CreateChildInfoMapEntry(
int render_process_id,
std::unique_ptr<MemoryCoordinatorHandleImpl> handle) {
auto& child_info = children_[render_process_id];
// Process always start with normal memory state.
// We'll set renderer's memory state to the current global state when the
// corresponding renderer process is ready to communicate. Renderer processes
// call AddChild() when they are ready.
child_info.memory_state = MemoryState::NORMAL;
child_info.is_visible = true;
child_info.handle = std::move(handle);
}
void MemoryCoordinatorImpl::NotifyStateToClients(MemoryState state) {
base::MemoryCoordinatorClientRegistry::GetInstance()->Notify(state);
}
MemoryCoordinatorImpl::ChildInfo::ChildInfo() {}
MemoryCoordinatorImpl::ChildInfo::ChildInfo(const ChildInfo& rhs) {
// This is a nop, but exists for compatibility with STL containers.
}
MemoryCoordinatorImpl::ChildInfo::~ChildInfo() {}
} // namespace content