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 |