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) |
| 86 bytes = 0; |
| 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 188 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
278 if (render_process_host && render_process_host->GetHandle() == handle) | 289 if (render_process_host && render_process_host->GetHandle() == handle) |
279 return iter.second.memory_state; | 290 return iter.second.memory_state; |
280 } | 291 } |
281 return MemoryState::UNKNOWN; | 292 return MemoryState::UNKNOWN; |
282 } | 293 } |
283 | 294 |
284 void MemoryCoordinatorImpl::UpdateConditionIfNeeded( | 295 void MemoryCoordinatorImpl::UpdateConditionIfNeeded( |
285 MemoryCondition next_condition) { | 296 MemoryCondition next_condition) { |
286 DCHECK(CalledOnValidThread()); | 297 DCHECK(CalledOnValidThread()); |
287 | 298 |
288 // Discard one tab when the system is under high memory pressure. | 299 if (next_condition == MemoryCondition::WARNING) |
289 if (next_condition == MemoryCondition::CRITICAL) | 300 OnWarningCondition(); |
290 DiscardTab(); | 301 else if (next_condition == MemoryCondition::CRITICAL) |
| 302 OnCriticalCondition(); |
291 | 303 |
292 if (memory_condition_ == next_condition) | 304 if (memory_condition_ == next_condition) |
293 return; | 305 return; |
294 | 306 |
295 MemoryCondition prev_condition = memory_condition_; | 307 MemoryCondition prev_condition = memory_condition_; |
296 memory_condition_ = next_condition; | 308 memory_condition_ = next_condition; |
297 | 309 |
298 TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("memory_coordinator"), | 310 TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("memory_coordinator"), |
299 "MemoryCoordinatorImpl::UpdateConditionIfNeeded", "prev", | 311 "MemoryCoordinatorImpl::UpdateConditionIfNeeded", "prev", |
300 MemoryConditionToString(prev_condition), "next", | 312 MemoryConditionToString(prev_condition), "next", |
301 MemoryConditionToString(next_condition)); | 313 MemoryConditionToString(next_condition)); |
302 | 314 |
303 // TODO(bashi): Following actions are tentative. We might want to prioritize | 315 // TODO(bashi): Following actions are tentative. We might want to prioritize |
304 // processes and handle them one-by-one. | 316 // processes and handle them one-by-one. |
305 | 317 |
306 if (next_condition == MemoryCondition::NORMAL) { | 318 if (next_condition == MemoryCondition::NORMAL) { |
307 // Set NORMAL state to all clients/processes. | 319 // Set NORMAL state to all clients/processes. |
308 UpdateBrowserStateAndNotifyStateToClients(MemoryState::NORMAL); | 320 UpdateBrowserStateAndNotifyStateToClients(MemoryState::NORMAL); |
309 NotifyStateToChildren(MemoryState::NORMAL); | 321 NotifyStateToChildren(MemoryState::NORMAL); |
310 } else if (next_condition == MemoryCondition::WARNING) { | 322 } else if (next_condition == MemoryCondition::WARNING) { |
311 // Set NORMAL state to foreground proceses and clients in the browser | 323 // Set NORMAL state to foreground proceses and clients in the browser |
312 // process. Set THROTTLED state to background processes. | 324 // process. Set THROTTLED state to background processes. |
313 UpdateBrowserStateAndNotifyStateToClients(MemoryState::NORMAL); | 325 UpdateBrowserStateAndNotifyStateToClients(MemoryState::NORMAL); |
314 for (auto& iter : children()) { | 326 for (auto& iter : children()) { |
315 auto state = | 327 auto state = |
316 iter.second.is_visible ? MemoryState::NORMAL : MemoryState::THROTTLED; | 328 iter.second.is_visible ? MemoryState::NORMAL : MemoryState::THROTTLED; |
317 SetChildMemoryState(iter.first, state); | 329 SetChildMemoryState(iter.first, state); |
318 } | 330 } |
319 // Idea: Purge memory from background processes. | |
320 } else if (next_condition == MemoryCondition::CRITICAL) { | 331 } else if (next_condition == MemoryCondition::CRITICAL) { |
321 // Set THROTTLED state to all clients/processes. | 332 // Set THROTTLED state to all clients/processes. |
322 UpdateBrowserStateAndNotifyStateToClients(MemoryState::THROTTLED); | 333 UpdateBrowserStateAndNotifyStateToClients(MemoryState::THROTTLED); |
323 NotifyStateToChildren(MemoryState::THROTTLED); | 334 NotifyStateToChildren(MemoryState::THROTTLED); |
324 } | 335 } |
325 } | 336 } |
326 | 337 |
327 void MemoryCoordinatorImpl::DiscardTab() { | 338 void MemoryCoordinatorImpl::DiscardTab() { |
328 if (delegate_) | 339 if (delegate_) |
329 delegate_->DiscardTab(); | 340 delegate_->DiscardTab(); |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
386 !render_process_host->IsProcessBackgrounded()); | 397 !render_process_host->IsProcessBackgrounded()); |
387 } | 398 } |
388 | 399 |
389 void MemoryCoordinatorImpl::OnChildVisibilityChanged(int render_process_id, | 400 void MemoryCoordinatorImpl::OnChildVisibilityChanged(int render_process_id, |
390 bool is_visible) { | 401 bool is_visible) { |
391 auto iter = children().find(render_process_id); | 402 auto iter = children().find(render_process_id); |
392 if (iter == children().end()) | 403 if (iter == children().end()) |
393 return; | 404 return; |
394 | 405 |
395 iter->second.is_visible = is_visible; | 406 iter->second.is_visible = is_visible; |
| 407 if (!is_visible) { |
| 408 // A backgrounded process becomes a candidate for purging memory when |
| 409 // the process remains backgrounded for a certian period of time. |
| 410 iter->second.can_purge_after = |
| 411 tick_clock_->NowTicks() + |
| 412 base::TimeDelta::FromSeconds( |
| 413 kDefaultBackgroundChildPurgeCandidatePeriodSeconds); |
| 414 } |
396 MemoryState new_state = | 415 MemoryState new_state = |
397 CalculateMemoryStateForProcess(GetMemoryCondition(), is_visible); | 416 CalculateMemoryStateForProcess(GetMemoryCondition(), is_visible); |
398 SetChildMemoryState(render_process_id, new_state); | 417 SetChildMemoryState(render_process_id, new_state); |
399 } | 418 } |
400 | 419 |
401 MemoryState MemoryCoordinatorImpl::OverrideState(MemoryState memory_state, | 420 MemoryState MemoryCoordinatorImpl::OverrideState(MemoryState memory_state, |
402 const ChildInfo& child) { | 421 const ChildInfo& child) { |
403 // We don't suspend foreground renderers. Throttle them instead. | 422 // We don't suspend foreground renderers. Throttle them instead. |
404 if (child.is_visible && memory_state == MemoryState::SUSPENDED) | 423 if (child.is_visible && memory_state == MemoryState::SUSPENDED) |
405 return MemoryState::THROTTLED; | 424 return MemoryState::THROTTLED; |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
458 base::MemoryCoordinatorClientRegistry::GetInstance()->Notify(state); | 477 base::MemoryCoordinatorClientRegistry::GetInstance()->Notify(state); |
459 } | 478 } |
460 | 479 |
461 void MemoryCoordinatorImpl::NotifyStateToChildren(MemoryState state) { | 480 void MemoryCoordinatorImpl::NotifyStateToChildren(MemoryState state) { |
462 // It's OK to call SetChildMemoryState() unconditionally because it checks | 481 // It's OK to call SetChildMemoryState() unconditionally because it checks |
463 // whether this state transition is valid. | 482 // whether this state transition is valid. |
464 for (auto& iter : children()) | 483 for (auto& iter : children()) |
465 SetChildMemoryState(iter.first, state); | 484 SetChildMemoryState(iter.first, state); |
466 } | 485 } |
467 | 486 |
| 487 void MemoryCoordinatorImpl::OnWarningCondition() { |
| 488 TryToPurgeMemoryFromChildren(PurgeTarget::BACKGROUNDED); |
| 489 } |
| 490 |
| 491 void MemoryCoordinatorImpl::OnCriticalCondition() { |
| 492 DiscardTab(); |
| 493 |
| 494 // Prefer to purge memory from child processes than browser process because |
| 495 // we prioritize the brower process. |
| 496 if (TryToPurgeMemoryFromChildren(PurgeTarget::ALL)) |
| 497 return; |
| 498 |
| 499 TryToPurgeMemoryFromBrowser(); |
| 500 } |
| 501 |
| 502 bool MemoryCoordinatorImpl::TryToPurgeMemoryFromChildren(PurgeTarget target) { |
| 503 base::TimeTicks now = tick_clock_->NowTicks(); |
| 504 // TODO(bashi): Better to sort child processes based on their priorities. |
| 505 for (auto& iter : children()) { |
| 506 if (iter.second.is_visible && target == PurgeTarget::BACKGROUNDED) |
| 507 continue; |
| 508 if (!iter.second.can_purge_after.is_null() && |
| 509 iter.second.can_purge_after > now) |
| 510 continue; |
| 511 |
| 512 // Set |can_purge_after| to the maximum value to suppress another purge |
| 513 // request until the child process goes foreground and then goes background |
| 514 // again. |
| 515 iter.second.can_purge_after = base::TimeTicks::Max(); |
| 516 iter.second.handle->child()->PurgeMemory(); |
| 517 return true; |
| 518 } |
| 519 return false; |
| 520 } |
| 521 |
| 522 bool MemoryCoordinatorImpl::TryToPurgeMemoryFromBrowser() { |
| 523 base::TimeTicks now = tick_clock_->NowTicks(); |
| 524 if (can_purge_after_ > now) |
| 525 return false; |
| 526 |
| 527 auto metrics = base::ProcessMetrics::CreateCurrentProcessMetrics(); |
| 528 size_t before = metrics->GetWorkingSetSize(); |
| 529 task_runner_->PostDelayedTask(FROM_HERE, |
| 530 base::Bind(&RecordBrowserPurge, before), |
| 531 base::TimeDelta::FromSeconds(2)); |
| 532 |
| 533 // Suppress purging in the browser process until a certain period of time is |
| 534 // passed. |
| 535 can_purge_after_ = now + base::TimeDelta::FromMinutes(2); |
| 536 base::MemoryCoordinatorClientRegistry::GetInstance()->PurgeMemory(); |
| 537 return true; |
| 538 } |
| 539 |
468 MemoryCoordinatorImpl::ChildInfo::ChildInfo() {} | 540 MemoryCoordinatorImpl::ChildInfo::ChildInfo() {} |
469 | 541 |
470 MemoryCoordinatorImpl::ChildInfo::ChildInfo(const ChildInfo& rhs) { | 542 MemoryCoordinatorImpl::ChildInfo::ChildInfo(const ChildInfo& rhs) { |
471 // This is a nop, but exists for compatibility with STL containers. | 543 // This is a nop, but exists for compatibility with STL containers. |
472 } | 544 } |
473 | 545 |
474 MemoryCoordinatorImpl::ChildInfo::~ChildInfo() {} | 546 MemoryCoordinatorImpl::ChildInfo::~ChildInfo() {} |
475 | 547 |
476 } // namespace content | 548 } // namespace content |
OLD | NEW |