Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "content/browser/memory/memory_coordinator_impl.h" | 5 #include "content/browser/memory/memory_coordinator_impl.h" |
| 6 | 6 |
| 7 #include "base/memory/memory_coordinator_client_registry.h" | 7 #include "base/memory/memory_coordinator_client_registry.h" |
| 8 #include "base/memory/ptr_util.h" | 8 #include "base/memory/ptr_util.h" |
| 9 #include "base/metrics/histogram_macros.h" | 9 #include "base/metrics/histogram_macros.h" |
| 10 #include "base/process/process_handle.h" | 10 #include "base/process/process_handle.h" |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 21 #include "content/public/common/content_features.h" | 21 #include "content/public/common/content_features.h" |
| 22 #include "mojo/public/cpp/bindings/binding.h" | 22 #include "mojo/public/cpp/bindings/binding.h" |
| 23 | 23 |
| 24 namespace content { | 24 namespace content { |
| 25 | 25 |
| 26 using MemoryState = base::MemoryState; | 26 using MemoryState = base::MemoryState; |
| 27 | 27 |
| 28 namespace { | 28 namespace { |
| 29 | 29 |
| 30 const int kDefaultMinimumTransitionPeriodSeconds = 30; | 30 const int kDefaultMinimumTransitionPeriodSeconds = 30; |
| 31 const int kDefaultBackgroundChildPurgeCandidatePeriodSeconds = 30; | |
| 31 | 32 |
| 32 mojom::MemoryState ToMojomMemoryState(MemoryState state) { | 33 mojom::MemoryState ToMojomMemoryState(MemoryState state) { |
| 33 switch (state) { | 34 switch (state) { |
| 34 case MemoryState::UNKNOWN: | 35 case MemoryState::UNKNOWN: |
| 35 return mojom::MemoryState::UNKNOWN; | 36 return mojom::MemoryState::UNKNOWN; |
| 36 case MemoryState::NORMAL: | 37 case MemoryState::NORMAL: |
| 37 return mojom::MemoryState::NORMAL; | 38 return mojom::MemoryState::NORMAL; |
| 38 case MemoryState::THROTTLED: | 39 case MemoryState::THROTTLED: |
| 39 return mojom::MemoryState::THROTTLED; | 40 return mojom::MemoryState::THROTTLED; |
| 40 case MemoryState::SUSPENDED: | 41 case MemoryState::SUSPENDED: |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 70 return MemoryState::NORMAL; | 71 return MemoryState::NORMAL; |
| 71 case MemoryCondition::WARNING: | 72 case MemoryCondition::WARNING: |
| 72 return is_visible ? MemoryState::NORMAL : MemoryState::THROTTLED; | 73 return is_visible ? MemoryState::NORMAL : MemoryState::THROTTLED; |
| 73 case MemoryCondition::CRITICAL: | 74 case MemoryCondition::CRITICAL: |
| 74 return MemoryState::THROTTLED; | 75 return MemoryState::THROTTLED; |
| 75 } | 76 } |
| 76 NOTREACHED(); | 77 NOTREACHED(); |
| 77 return MemoryState::NORMAL; | 78 return MemoryState::NORMAL; |
| 78 } | 79 } |
| 79 | 80 |
| 81 void RecordBrowserPurge(size_t before) { | |
| 82 auto metrics = base::ProcessMetrics::CreateCurrentProcessMetrics(); | |
| 83 size_t after = metrics->GetWorkingSetSize(); | |
| 84 int64_t bytes = static_cast<int64_t>(before) - static_cast<int64_t>(after); | |
| 85 if (bytes < 0) | |
|
haraken
2017/03/24 11:55:39
Maybe we want to record this case too. See my comm
bashi
2017/03/28 03:15:31
Acknowledged.
| |
| 86 return; | |
| 87 UMA_HISTOGRAM_MEMORY_LARGE_MB("Memory.Experimental.Browser.PurgedMemory", | |
| 88 bytes / 1024 / 1024); | |
| 89 } | |
| 90 | |
| 80 } // namespace | 91 } // namespace |
| 81 | 92 |
| 82 // The implementation of MemoryCoordinatorHandle. See memory_coordinator.mojom | 93 // The implementation of MemoryCoordinatorHandle. See memory_coordinator.mojom |
| 83 // for the role of this class. | 94 // for the role of this class. |
| 84 class MemoryCoordinatorHandleImpl : public mojom::MemoryCoordinatorHandle { | 95 class MemoryCoordinatorHandleImpl : public mojom::MemoryCoordinatorHandle { |
| 85 public: | 96 public: |
| 86 MemoryCoordinatorHandleImpl(mojom::MemoryCoordinatorHandleRequest request, | 97 MemoryCoordinatorHandleImpl(mojom::MemoryCoordinatorHandleRequest request, |
| 87 MemoryCoordinatorImpl* coordinator, | 98 MemoryCoordinatorImpl* coordinator, |
| 88 int render_process_id); | 99 int render_process_id); |
| 89 ~MemoryCoordinatorHandleImpl() override; | 100 ~MemoryCoordinatorHandleImpl() override; |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 147 | 158 |
| 148 MemoryCoordinatorImpl::MemoryCoordinatorImpl( | 159 MemoryCoordinatorImpl::MemoryCoordinatorImpl( |
| 149 scoped_refptr<base::SingleThreadTaskRunner> task_runner, | 160 scoped_refptr<base::SingleThreadTaskRunner> task_runner, |
| 150 std::unique_ptr<MemoryMonitor> memory_monitor) | 161 std::unique_ptr<MemoryMonitor> memory_monitor) |
| 151 : task_runner_(task_runner), | 162 : task_runner_(task_runner), |
| 152 delegate_(GetContentClient()->browser()->GetMemoryCoordinatorDelegate()), | 163 delegate_(GetContentClient()->browser()->GetMemoryCoordinatorDelegate()), |
| 153 memory_monitor_(std::move(memory_monitor)), | 164 memory_monitor_(std::move(memory_monitor)), |
| 154 condition_observer_( | 165 condition_observer_( |
| 155 base::MakeUnique<MemoryConditionObserver>(this, task_runner)), | 166 base::MakeUnique<MemoryConditionObserver>(this, task_runner)), |
| 156 tick_clock_(base::MakeUnique<base::DefaultTickClock>()), | 167 tick_clock_(base::MakeUnique<base::DefaultTickClock>()), |
| 157 minimum_state_transition_period_(base::TimeDelta::FromSeconds( | 168 minimum_state_transition_period_( |
| 158 kDefaultMinimumTransitionPeriodSeconds)) { | 169 base::TimeDelta::FromSeconds(kDefaultMinimumTransitionPeriodSeconds)), |
| 170 background_child_purge_candidate_period_(base::TimeDelta::FromSeconds( | |
| 171 kDefaultBackgroundChildPurgeCandidatePeriodSeconds)) { | |
| 159 DCHECK(memory_monitor_.get()); | 172 DCHECK(memory_monitor_.get()); |
| 160 base::MemoryCoordinatorProxy::SetMemoryCoordinator(this); | 173 base::MemoryCoordinatorProxy::SetMemoryCoordinator(this); |
| 161 | 174 |
| 162 // Force the "memory_coordinator" category to show up in the trace viewer. | 175 // Force the "memory_coordinator" category to show up in the trace viewer. |
| 163 base::trace_event::TraceLog::GetCategoryGroupEnabled( | 176 base::trace_event::TraceLog::GetCategoryGroupEnabled( |
| 164 TRACE_DISABLED_BY_DEFAULT("memory_coordinator")); | 177 TRACE_DISABLED_BY_DEFAULT("memory_coordinator")); |
| 165 } | 178 } |
| 166 | 179 |
| 167 MemoryCoordinatorImpl::~MemoryCoordinatorImpl() { | 180 MemoryCoordinatorImpl::~MemoryCoordinatorImpl() { |
| 168 base::MemoryCoordinatorProxy::SetMemoryCoordinator(nullptr); | 181 base::MemoryCoordinatorProxy::SetMemoryCoordinator(nullptr); |
| (...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 278 if (render_process_host && render_process_host->GetHandle() == handle) | 291 if (render_process_host && render_process_host->GetHandle() == handle) |
| 279 return iter.second.memory_state; | 292 return iter.second.memory_state; |
| 280 } | 293 } |
| 281 return MemoryState::UNKNOWN; | 294 return MemoryState::UNKNOWN; |
| 282 } | 295 } |
| 283 | 296 |
| 284 void MemoryCoordinatorImpl::UpdateConditionIfNeeded( | 297 void MemoryCoordinatorImpl::UpdateConditionIfNeeded( |
| 285 MemoryCondition next_condition) { | 298 MemoryCondition next_condition) { |
| 286 DCHECK(CalledOnValidThread()); | 299 DCHECK(CalledOnValidThread()); |
| 287 | 300 |
| 288 // Discard one tab when the system is under high memory pressure. | 301 if (next_condition == MemoryCondition::WARNING) |
| 302 OnWarningCondition(); | |
| 303 | |
| 289 if (next_condition == MemoryCondition::CRITICAL) | 304 if (next_condition == MemoryCondition::CRITICAL) |
|
haraken
2017/03/24 11:55:39
else if
bashi
2017/03/28 03:15:31
Done.
| |
| 290 DiscardTab(); | 305 OnCriticalCondition(); |
| 291 | 306 |
| 292 if (memory_condition_ == next_condition) | 307 if (memory_condition_ == next_condition) |
| 293 return; | 308 return; |
| 294 | 309 |
| 295 MemoryCondition prev_condition = memory_condition_; | 310 MemoryCondition prev_condition = memory_condition_; |
| 296 memory_condition_ = next_condition; | 311 memory_condition_ = next_condition; |
| 297 | 312 |
| 298 TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("memory_coordinator"), | 313 TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("memory_coordinator"), |
| 299 "MemoryCoordinatorImpl::UpdateConditionIfNeeded", "prev", | 314 "MemoryCoordinatorImpl::UpdateConditionIfNeeded", "prev", |
| 300 MemoryConditionToString(prev_condition), "next", | 315 MemoryConditionToString(prev_condition), "next", |
| 301 MemoryConditionToString(next_condition)); | 316 MemoryConditionToString(next_condition)); |
| 302 | 317 |
| 303 // TODO(bashi): Following actions are tentative. We might want to prioritize | 318 // TODO(bashi): Following actions are tentative. We might want to prioritize |
| 304 // processes and handle them one-by-one. | 319 // processes and handle them one-by-one. |
| 305 | 320 |
| 306 if (next_condition == MemoryCondition::NORMAL) { | 321 if (next_condition == MemoryCondition::NORMAL) { |
| 307 // Set NORMAL state to all clients/processes. | 322 // Set NORMAL state to all clients/processes. |
| 308 UpdateBrowserStateAndNotifyStateToClients(MemoryState::NORMAL); | 323 UpdateBrowserStateAndNotifyStateToClients(MemoryState::NORMAL); |
| 309 NotifyStateToChildren(MemoryState::NORMAL); | 324 NotifyStateToChildren(MemoryState::NORMAL); |
| 310 } else if (next_condition == MemoryCondition::WARNING) { | 325 } else if (next_condition == MemoryCondition::WARNING) { |
| 311 // Set NORMAL state to foreground proceses and clients in the browser | 326 // Set NORMAL state to foreground proceses and clients in the browser |
| 312 // process. Set THROTTLED state to background processes. | 327 // process. Set THROTTLED state to background processes. |
| 313 UpdateBrowserStateAndNotifyStateToClients(MemoryState::NORMAL); | 328 UpdateBrowserStateAndNotifyStateToClients(MemoryState::NORMAL); |
| 314 for (auto& iter : children()) { | 329 for (auto& iter : children()) { |
| 315 auto state = | 330 auto state = |
| 316 iter.second.is_visible ? MemoryState::NORMAL : MemoryState::THROTTLED; | 331 iter.second.is_visible ? MemoryState::NORMAL : MemoryState::THROTTLED; |
| 317 SetChildMemoryState(iter.first, state); | 332 SetChildMemoryState(iter.first, state); |
| 318 } | 333 } |
| 319 // Idea: Purge memory from background processes. | |
| 320 } else if (next_condition == MemoryCondition::CRITICAL) { | 334 } else if (next_condition == MemoryCondition::CRITICAL) { |
| 321 // Set THROTTLED state to all clients/processes. | 335 // Set THROTTLED state to all clients/processes. |
| 322 UpdateBrowserStateAndNotifyStateToClients(MemoryState::THROTTLED); | 336 UpdateBrowserStateAndNotifyStateToClients(MemoryState::THROTTLED); |
| 323 NotifyStateToChildren(MemoryState::THROTTLED); | 337 NotifyStateToChildren(MemoryState::THROTTLED); |
| 324 } | 338 } |
| 325 } | 339 } |
| 326 | 340 |
| 327 void MemoryCoordinatorImpl::DiscardTab() { | 341 bool MemoryCoordinatorImpl::DiscardTab() { |
| 328 if (delegate_) | 342 if (delegate_) |
| 329 delegate_->DiscardTab(); | 343 return delegate_->DiscardTab(); |
| 344 return false; | |
| 330 } | 345 } |
| 331 | 346 |
| 332 RenderProcessHost* MemoryCoordinatorImpl::GetRenderProcessHost( | 347 RenderProcessHost* MemoryCoordinatorImpl::GetRenderProcessHost( |
| 333 int render_process_id) { | 348 int render_process_id) { |
| 334 return RenderProcessHost::FromID(render_process_id); | 349 return RenderProcessHost::FromID(render_process_id); |
| 335 } | 350 } |
| 336 | 351 |
| 337 void MemoryCoordinatorImpl::SetDelegateForTesting( | 352 void MemoryCoordinatorImpl::SetDelegateForTesting( |
| 338 std::unique_ptr<MemoryCoordinatorDelegate> delegate) { | 353 std::unique_ptr<MemoryCoordinatorDelegate> delegate) { |
| 339 CHECK(!delegate_); | 354 CHECK(!delegate_); |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 386 !render_process_host->IsProcessBackgrounded()); | 401 !render_process_host->IsProcessBackgrounded()); |
| 387 } | 402 } |
| 388 | 403 |
| 389 void MemoryCoordinatorImpl::OnChildVisibilityChanged(int render_process_id, | 404 void MemoryCoordinatorImpl::OnChildVisibilityChanged(int render_process_id, |
| 390 bool is_visible) { | 405 bool is_visible) { |
| 391 auto iter = children().find(render_process_id); | 406 auto iter = children().find(render_process_id); |
| 392 if (iter == children().end()) | 407 if (iter == children().end()) |
| 393 return; | 408 return; |
| 394 | 409 |
| 395 iter->second.is_visible = is_visible; | 410 iter->second.is_visible = is_visible; |
| 411 if (!is_visible) { | |
| 412 // A backgrounded process becomes a candidate for purging memory when | |
| 413 // the process remains backgrounded for a certian period of time. | |
| 414 iter->second.can_purge_after = | |
| 415 tick_clock_->NowTicks() + background_child_purge_candidate_period_; | |
|
haraken
2017/03/24 11:55:39
Maybe you can inline base::TimeDelta::FromSeconds(
bashi
2017/03/28 03:15:31
Done.
| |
| 416 } | |
| 396 MemoryState new_state = | 417 MemoryState new_state = |
| 397 CalculateMemoryStateForProcess(GetMemoryCondition(), is_visible); | 418 CalculateMemoryStateForProcess(GetMemoryCondition(), is_visible); |
| 398 SetChildMemoryState(render_process_id, new_state); | 419 SetChildMemoryState(render_process_id, new_state); |
| 399 } | 420 } |
| 400 | 421 |
| 401 MemoryState MemoryCoordinatorImpl::OverrideState(MemoryState memory_state, | 422 MemoryState MemoryCoordinatorImpl::OverrideState(MemoryState memory_state, |
| 402 const ChildInfo& child) { | 423 const ChildInfo& child) { |
| 403 // We don't suspend foreground renderers. Throttle them instead. | 424 // We don't suspend foreground renderers. Throttle them instead. |
| 404 if (child.is_visible && memory_state == MemoryState::SUSPENDED) | 425 if (child.is_visible && memory_state == MemoryState::SUSPENDED) |
| 405 return MemoryState::THROTTLED; | 426 return MemoryState::THROTTLED; |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 458 base::MemoryCoordinatorClientRegistry::GetInstance()->Notify(state); | 479 base::MemoryCoordinatorClientRegistry::GetInstance()->Notify(state); |
| 459 } | 480 } |
| 460 | 481 |
| 461 void MemoryCoordinatorImpl::NotifyStateToChildren(MemoryState state) { | 482 void MemoryCoordinatorImpl::NotifyStateToChildren(MemoryState state) { |
| 462 // It's OK to call SetChildMemoryState() unconditionally because it checks | 483 // It's OK to call SetChildMemoryState() unconditionally because it checks |
| 463 // whether this state transition is valid. | 484 // whether this state transition is valid. |
| 464 for (auto& iter : children()) | 485 for (auto& iter : children()) |
| 465 SetChildMemoryState(iter.first, state); | 486 SetChildMemoryState(iter.first, state); |
| 466 } | 487 } |
| 467 | 488 |
| 489 void MemoryCoordinatorImpl::OnWarningCondition() { | |
| 490 TryPurgeMemoryFromChildren(PurgeTarget::BACKGROUNDED); | |
| 491 } | |
| 492 | |
| 493 void MemoryCoordinatorImpl::OnCriticalCondition() { | |
| 494 // Prefer to discard a tab rather than purging memory on critical condition | |
| 495 // to mitigate a risk to cause thrashing. | |
| 496 if (DiscardTab()) | |
| 497 return; | |
| 498 | |
| 499 if (TryPurgeMemoryFromChildren(PurgeTarget::ALL)) | |
|
haraken
2017/03/24 11:55:39
Add:
// Prefer purging memory from child proces
bashi
2017/03/28 03:15:31
Done.
| |
| 500 return; | |
| 501 | |
| 502 TryPurgeMemoryFromBrowser(); | |
| 503 } | |
| 504 | |
| 505 bool MemoryCoordinatorImpl::TryPurgeMemoryFromChildren(PurgeTarget target) { | |
| 506 base::TimeTicks now = tick_clock_->NowTicks(); | |
| 507 // TODO(bashi): Better to sort child processes based on their priorities. | |
| 508 for (auto& iter : children()) { | |
| 509 if (iter.second.is_visible && target == PurgeTarget::BACKGROUNDED) | |
| 510 continue; | |
| 511 if (!iter.second.can_purge_after.is_null() && | |
|
haraken
2017/03/24 11:55:38
I'd prefer initializing can_purge_after to base::T
bashi
2017/03/28 03:15:31
Using Max() as the initial value is slightly diffe
| |
| 512 iter.second.can_purge_after > now) | |
| 513 continue; | |
| 514 | |
| 515 // Set |can_purge_after| to the maximum value to suppress another purge | |
| 516 // request until the child process goes foreground and then goes background | |
| 517 // again. | |
| 518 iter.second.can_purge_after = base::TimeTicks::Max(); | |
| 519 iter.second.handle->child()->PurgeMemory(); | |
| 520 return true; | |
| 521 } | |
| 522 return false; | |
| 523 } | |
| 524 | |
| 525 bool MemoryCoordinatorImpl::TryPurgeMemoryFromBrowser() { | |
| 526 base::TimeTicks now = tick_clock_->NowTicks(); | |
| 527 if (!can_purge_after_.is_null() && can_purge_after_ > now) | |
|
haraken
2017/03/24 11:55:38
Remove the null check.
bashi
2017/03/28 03:15:31
Done.
| |
| 528 return false; | |
| 529 | |
| 530 auto metrics = base::ProcessMetrics::CreateCurrentProcessMetrics(); | |
| 531 size_t before = metrics->GetWorkingSetSize(); | |
| 532 task_runner_->PostDelayedTask(FROM_HERE, | |
| 533 base::Bind(&RecordBrowserPurge, before), | |
| 534 base::TimeDelta::FromSeconds(2)); | |
| 535 | |
| 536 // Suppress purging in the browser process until a certain period of time is | |
| 537 // passed. | |
| 538 can_purge_after_ = now + base::TimeDelta::FromMinutes(2); | |
| 539 base::MemoryCoordinatorClientRegistry::GetInstance()->PurgeMemory(); | |
| 540 return true; | |
| 541 } | |
| 542 | |
| 468 MemoryCoordinatorImpl::ChildInfo::ChildInfo() {} | 543 MemoryCoordinatorImpl::ChildInfo::ChildInfo() {} |
| 469 | 544 |
| 470 MemoryCoordinatorImpl::ChildInfo::ChildInfo(const ChildInfo& rhs) { | 545 MemoryCoordinatorImpl::ChildInfo::ChildInfo(const ChildInfo& rhs) { |
| 471 // This is a nop, but exists for compatibility with STL containers. | 546 // This is a nop, but exists for compatibility with STL containers. |
| 472 } | 547 } |
| 473 | 548 |
| 474 MemoryCoordinatorImpl::ChildInfo::~ChildInfo() {} | 549 MemoryCoordinatorImpl::ChildInfo::~ChildInfo() {} |
| 475 | 550 |
| 476 } // namespace content | 551 } // namespace content |
| OLD | NEW |