Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(389)

Side by Side Diff: chrome/browser/memory/tab_manager_delegate_chromeos.cc

Issue 2898033002: [TabManager] Move TabManager into chrome/browser/resource_coordinator. (Closed)
Patch Set: rebase Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/memory/tab_manager_delegate_chromeos.h"
6
7 #include <math.h>
8 #include <stdint.h>
9
10 #include <algorithm>
11 #include <map>
12 #include <vector>
13
14 #include "ash/shell.h"
15 #include "base/bind.h"
16 #include "base/command_line.h"
17 #include "base/files/file_path.h"
18 #include "base/files/file_util.h"
19 #include "base/memory/memory_pressure_monitor_chromeos.h"
20 #include "base/metrics/histogram_macros.h"
21 #include "base/process/process_handle.h" // kNullProcessHandle.
22 #include "base/process/process_metrics.h"
23 #include "base/strings/string16.h"
24 #include "base/strings/string_number_conversions.h"
25 #include "base/strings/string_util.h"
26 #include "base/strings/utf_string_conversions.h"
27 #include "base/time/time.h"
28 #include "chrome/browser/chromeos/arc/process/arc_process.h"
29 #include "chrome/browser/chromeos/arc/process/arc_process_service.h"
30 #include "chrome/browser/memory/memory_kills_monitor.h"
31 #include "chrome/browser/memory/tab_stats.h"
32 #include "chrome/browser/ui/browser.h"
33 #include "chrome/browser/ui/browser_list.h"
34 #include "chrome/browser/ui/tabs/tab_strip_model.h"
35 #include "chrome/common/chrome_constants.h"
36 #include "chrome/common/chrome_features.h"
37 #include "chromeos/dbus/dbus_thread_manager.h"
38 #include "components/arc/arc_bridge_service.h"
39 #include "components/arc/arc_service_manager.h"
40 #include "components/arc/arc_util.h"
41 #include "components/device_event_log/device_event_log.h"
42 #include "content/public/browser/browser_thread.h"
43 #include "content/public/browser/notification_service.h"
44 #include "content/public/browser/notification_types.h"
45 #include "content/public/browser/render_process_host.h"
46 #include "content/public/browser/render_widget_host.h"
47 #include "content/public/browser/zygote_host_linux.h"
48 #include "ui/wm/public/activation_client.h"
49
50 using base::ProcessHandle;
51 using base::TimeDelta;
52 using base::TimeTicks;
53 using content::BrowserThread;
54
55 namespace memory {
56 namespace {
57
58 // When switching to a new tab the tab's renderer's OOM score needs to be
59 // updated to reflect its front-most status and protect it from discard.
60 // However, doing this immediately might slow down tab switch time, so wait
61 // a little while before doing the adjustment.
62 const int kFocusedProcessScoreAdjustIntervalMs = 500;
63
64 wm::ActivationClient* GetActivationClient() {
65 if (!ash::Shell::HasInstance())
66 return nullptr;
67 return wm::GetActivationClient(ash::Shell::GetPrimaryRootWindow());
68 }
69
70 bool IsArcMemoryManagementEnabled() {
71 return base::FeatureList::IsEnabled(features::kArcMemoryManagement);
72 }
73
74 void OnSetOomScoreAdj(bool success, const std::string& output) {
75 VLOG(2) << "OnSetOomScoreAdj " << success << " " << output;
76 if (!success)
77 LOG(ERROR) << "Set OOM score error: " << output;
78 else if (!output.empty())
79 LOG(WARNING) << "Set OOM score: " << output;
80 }
81
82 } // namespace
83
84 // static
85 const int TabManagerDelegate::kLowestOomScore = -1000;
86
87 std::ostream& operator<<(std::ostream& os, const ProcessType& type) {
88 switch (type) {
89 case ProcessType::FOCUSED_TAB:
90 return os << "FOCUSED_TAB";
91 case ProcessType::FOCUSED_APP:
92 return os << "FOCUSED_APP";
93 case ProcessType::IMPORTANT_APP:
94 return os << "IMPORTANT_APP";
95 case ProcessType::BACKGROUND_TAB:
96 return os << "BACKGROUND_TAB";
97 case ProcessType::BACKGROUND_APP:
98 return os << "BACKGROUND_APP";
99 case ProcessType::UNKNOWN_TYPE:
100 return os << "UNKNOWN_TYPE";
101 default:
102 return os << "NOT_IMPLEMENTED_ERROR";
103 }
104 return os;
105 }
106
107 // TabManagerDelegate::Candidate implementation.
108 std::ostream& operator<<(
109 std::ostream& out, const TabManagerDelegate::Candidate& candidate) {
110 if (candidate.app()) {
111 out << "app " << *candidate.app();
112 } else if (candidate.tab()) {
113 const TabStats* const& tab = candidate.tab();
114 out << "tab " << tab->title << ", renderer_handle: " << tab->renderer_handle
115 << ", oom_score: " << tab->oom_score
116 << ", is_discarded: " << tab->is_discarded
117 << ", discard_count: " << tab->discard_count
118 << ", last_active: " << tab->last_active;
119 }
120 out << ", process_type " << candidate.process_type();
121 return out;
122 }
123
124 TabManagerDelegate::Candidate& TabManagerDelegate::Candidate::operator=(
125 TabManagerDelegate::Candidate&& other) {
126 tab_ = other.tab_;
127 app_ = other.app_;
128 process_type_ = other.process_type_;
129 return *this;
130 }
131
132 bool TabManagerDelegate::Candidate::operator<(
133 const TabManagerDelegate::Candidate& rhs) const {
134 if (process_type() != rhs.process_type())
135 return process_type() < rhs.process_type();
136 if (app() && rhs.app())
137 return *app() < *rhs.app();
138 if (tab() && rhs.tab())
139 return TabManager::CompareTabStats(*tab(), *rhs.tab());
140 // Impossible case. If app and tab are mixed in one process type, favor
141 // apps.
142 NOTREACHED() << "Undefined comparison between apps and tabs: process_type="
143 << process_type();
144 return app();
145 }
146
147 ProcessType TabManagerDelegate::Candidate::GetProcessTypeInternal() const {
148 if (app()) {
149 if (app()->is_focused())
150 return ProcessType::FOCUSED_APP;
151 if (app()->IsImportant())
152 return ProcessType::IMPORTANT_APP;
153 return ProcessType::BACKGROUND_APP;
154 }
155 if (tab()) {
156 if (tab()->is_selected)
157 return ProcessType::FOCUSED_TAB;
158 return ProcessType::BACKGROUND_TAB;
159 }
160 NOTREACHED() << "Unexpected process type";
161 return ProcessType::UNKNOWN_TYPE;
162 }
163
164 // Holds the info of a newly focused tab or app window. The focused process is
165 // set to highest priority (lowest OOM score), but not immediately. To avoid
166 // redundant settings the OOM score adjusting only happens after a timeout. If
167 // the process loses focus before the timeout, the adjustment is canceled.
168 class TabManagerDelegate::FocusedProcess {
169 public:
170 static const int kInvalidArcAppNspid = 0;
171
172 FocusedProcess() { Reset(); }
173
174 void SetTabPid(const base::ProcessHandle pid) {
175 pid_ = pid;
176 nspid_ = kInvalidArcAppNspid;
177 }
178
179 void SetArcAppNspid(const int nspid) {
180 pid_ = base::kNullProcessHandle;
181 nspid_ = nspid;
182 }
183
184 base::ProcessHandle GetTabPid() const { return pid_; }
185
186 int GetArcAppNspid() const { return nspid_; }
187
188 // Checks whether the containing instance is an ARC app. If so it resets the
189 // data and returns true. Useful when canceling an ongoing OOM score setting
190 // for a focused ARC app because the focus has been shifted away shortly.
191 bool ResetIfIsArcApp() {
192 if (nspid_ != kInvalidArcAppNspid) {
193 Reset();
194 return true;
195 }
196 return false;
197 }
198
199 private:
200 void Reset() {
201 pid_ = base::kNullProcessHandle;
202 nspid_ = kInvalidArcAppNspid;
203 }
204
205 // The focused app could be a Chrome tab or an Android app, but not both.
206 // At most one of them contains a valid value at any time.
207
208 // If a chrome tab.
209 base::ProcessHandle pid_;
210 // If an Android app.
211 int nspid_;
212 };
213
214 // TabManagerDelegate::MemoryStat implementation.
215
216 // static
217 int TabManagerDelegate::MemoryStat::ReadIntFromFile(
218 const char* file_name, const int default_val) {
219 std::string file_string;
220 if (!base::ReadFileToString(base::FilePath(file_name), &file_string)) {
221 LOG(WARNING) << "Unable to read file" << file_name;
222 return default_val;
223 }
224 int val = default_val;
225 if (!base::StringToInt(
226 base::TrimWhitespaceASCII(file_string, base::TRIM_TRAILING),
227 &val)) {
228 LOG(WARNING) << "Unable to parse string" << file_string;
229 return default_val;
230 }
231 return val;
232 }
233
234 // static
235 int TabManagerDelegate::MemoryStat::LowMemoryMarginKB() {
236 static const int kDefaultLowMemoryMarginMb = 50;
237 static const char kLowMemoryMarginConfig[] =
238 "/sys/kernel/mm/chromeos-low_mem/margin";
239 return ReadIntFromFile(
240 kLowMemoryMarginConfig, kDefaultLowMemoryMarginMb) * 1024;
241 }
242
243 // The logic of available memory calculation is copied from
244 // _is_low_mem_situation() in kernel file include/linux/low-mem-notify.h.
245 // Maybe we should let kernel report the number directly.
246 int TabManagerDelegate::MemoryStat::TargetMemoryToFreeKB() {
247 static const int kRamVsSwapWeight = 4;
248 static const char kMinFilelistConfig[] = "/proc/sys/vm/min_filelist_kbytes";
249 static const char kMinFreeKbytes[] = "/proc/sys/vm/min_free_kbytes";
250
251 base::SystemMemoryInfoKB system_mem;
252 base::GetSystemMemoryInfo(&system_mem);
253 const int file_mem_kb = system_mem.active_file + system_mem.inactive_file;
254 const int min_filelist_kb = ReadIntFromFile(kMinFilelistConfig, 0);
255 const int min_free_kb = ReadIntFromFile(kMinFreeKbytes, 0);
256 // Calculate current available memory in system.
257 // File-backed memory should be easy to reclaim, unless they're dirty.
258 // TODO(cylee): On ChromeOS, kernel reports low memory condition when
259 // available memory is low. The following formula duplicates the logic in
260 // kernel to calculate how much memory should be released. In the future,
261 // kernel should try to report the amount of memory to release directly to
262 // eliminate the duplication here.
263 const int available_mem_kb = system_mem.free +
264 file_mem_kb - system_mem.dirty - min_filelist_kb +
265 system_mem.swap_free / kRamVsSwapWeight -
266 min_free_kb;
267
268 return LowMemoryMarginKB() - available_mem_kb;
269 }
270
271 int TabManagerDelegate::MemoryStat::EstimatedMemoryFreedKB(
272 base::ProcessHandle pid) {
273 std::unique_ptr<base::ProcessMetrics> process_metrics(
274 base::ProcessMetrics::CreateProcessMetrics(pid));
275 base::WorkingSetKBytes mem_usage;
276 process_metrics->GetWorkingSetKBytes(&mem_usage);
277 return mem_usage.priv;
278 }
279
280 TabManagerDelegate::TabManagerDelegate(
281 const base::WeakPtr<TabManager>& tab_manager)
282 : TabManagerDelegate(tab_manager, new MemoryStat()) {
283 }
284
285 TabManagerDelegate::TabManagerDelegate(
286 const base::WeakPtr<TabManager>& tab_manager,
287 TabManagerDelegate::MemoryStat* mem_stat)
288 : tab_manager_(tab_manager),
289 focused_process_(new FocusedProcess()),
290 mem_stat_(mem_stat),
291 weak_ptr_factory_(this) {
292 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
293 content::NotificationService::AllBrowserContextsAndSources());
294 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_TERMINATED,
295 content::NotificationService::AllBrowserContextsAndSources());
296 registrar_.Add(this, content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
297 content::NotificationService::AllBrowserContextsAndSources());
298 auto* activation_client = GetActivationClient();
299 if (activation_client)
300 activation_client->AddObserver(this);
301 BrowserList::GetInstance()->AddObserver(this);
302 }
303
304 TabManagerDelegate::~TabManagerDelegate() {
305 BrowserList::GetInstance()->RemoveObserver(this);
306 auto* activation_client = GetActivationClient();
307 if (activation_client)
308 activation_client->RemoveObserver(this);
309 }
310
311 void TabManagerDelegate::OnBrowserSetLastActive(Browser* browser) {
312 // Set OOM score to the selected tab when a browser window is activated.
313 // content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED didn't catch the
314 // case (like when switching focus between 2 browser windows) so we need to
315 // handle it here.
316 TabStripModel* tab_strip_model = browser->tab_strip_model();
317 int selected_index = tab_strip_model->active_index();
318 content::WebContents* contents =
319 tab_strip_model->GetWebContentsAt(selected_index);
320 if (!contents)
321 return;
322
323 base::ProcessHandle pid = contents->GetRenderProcessHost()->GetHandle();
324 AdjustFocusedTabScore(pid);
325 }
326
327 void TabManagerDelegate::OnWindowActivated(
328 wm::ActivationChangeObserver::ActivationReason reason,
329 aura::Window* gained_active,
330 aura::Window* lost_active) {
331 if (arc::IsArcAppWindow(gained_active)) {
332 // Currently there is no way to know which app is displayed in the ARC
333 // window, so schedule an early adjustment for all processes to reflect
334 // the change.
335 // Put a dummy FocusedProcess with nspid = kInvalidArcAppNspid for now to
336 // indicate the focused process is an arc app.
337 // TODO(cylee): Fix it when we have nspid info in ARC windows.
338 focused_process_->SetArcAppNspid(FocusedProcess::kInvalidArcAppNspid);
339 // If the timer is already running (possibly for a tab), it'll be reset
340 // here.
341 focus_process_score_adjust_timer_.Start(
342 FROM_HERE,
343 TimeDelta::FromMilliseconds(kFocusedProcessScoreAdjustIntervalMs),
344 this, &TabManagerDelegate::ScheduleEarlyOomPrioritiesAdjustment);
345 }
346 if (arc::IsArcAppWindow(lost_active)) {
347 // Do not bother adjusting OOM score if the ARC window is deactivated
348 // shortly.
349 if (focused_process_->ResetIfIsArcApp() &&
350 focus_process_score_adjust_timer_.IsRunning())
351 focus_process_score_adjust_timer_.Stop();
352 }
353 }
354
355 void TabManagerDelegate::ScheduleEarlyOomPrioritiesAdjustment() {
356 DCHECK_CURRENTLY_ON(BrowserThread::UI);
357 if (tab_manager_) {
358 AdjustOomPriorities(tab_manager_->GetUnsortedTabStats());
359 }
360 }
361
362 // If able to get the list of ARC procsses, prioritize tabs and apps as a whole.
363 // Otherwise try to kill tabs only.
364 void TabManagerDelegate::LowMemoryKill(
365 const TabStatsList& tab_list) {
366 arc::ArcProcessService* arc_process_service = arc::ArcProcessService::Get();
367 if (arc_process_service &&
368 arc_process_service->RequestAppProcessList(
369 base::Bind(&TabManagerDelegate::LowMemoryKillImpl,
370 weak_ptr_factory_.GetWeakPtr(), tab_list))) {
371 // LowMemoryKillImpl will be called asynchronously so nothing left to do.
372 return;
373 }
374 // If the list of ARC processes is not available, call LowMemoryKillImpl
375 // synchronously with an empty list of apps.
376 std::vector<arc::ArcProcess> dummy_apps;
377 LowMemoryKillImpl(tab_list, dummy_apps);
378 }
379
380 int TabManagerDelegate::GetCachedOomScore(ProcessHandle process_handle) {
381 DCHECK_CURRENTLY_ON(BrowserThread::UI);
382 auto it = oom_score_map_.find(process_handle);
383 if (it != oom_score_map_.end()) {
384 return it->second;
385 }
386 // An impossible value for oom_score_adj.
387 return -1001;
388 }
389
390 void TabManagerDelegate::OnFocusTabScoreAdjustmentTimeout() {
391 DCHECK_CURRENTLY_ON(BrowserThread::UI);
392 base::ProcessHandle pid = focused_process_->GetTabPid();
393 // The focused process doesn't render a tab. Could happen when the focus
394 // just switched to an ARC app before the timeout. We can not avoid the race.
395 if (pid == base::kNullProcessHandle)
396 return;
397
398 // Update the OOM score cache.
399 oom_score_map_[pid] = chrome::kLowestRendererOomScore;
400
401 // Sets OOM score.
402 VLOG(3) << "Set OOM score " << chrome::kLowestRendererOomScore
403 << " for focused tab " << pid;
404 std::map<int, int> dict;
405 dict[pid] = chrome::kLowestRendererOomScore;
406 GetDebugDaemonClient()->SetOomScoreAdj(dict, base::Bind(&OnSetOomScoreAdj));
407 }
408
409 void TabManagerDelegate::AdjustFocusedTabScore(base::ProcessHandle pid) {
410 DCHECK_CURRENTLY_ON(BrowserThread::UI);
411
412 // Clear running timer if one was set for a previous focused tab/app.
413 if (focus_process_score_adjust_timer_.IsRunning())
414 focus_process_score_adjust_timer_.Stop();
415 focused_process_->SetTabPid(pid);
416
417 // If the currently focused tab already has a lower score, do not
418 // set it. This can happen in case the newly focused tab is script
419 // connected to the previous tab.
420 ProcessScoreMap::iterator it = oom_score_map_.find(pid);
421 const bool not_lowest_score = (it == oom_score_map_.end() ||
422 it->second != chrome::kLowestRendererOomScore);
423
424 if (not_lowest_score) {
425 // By starting a timer we guarantee that the tab is focused for
426 // certain amount of time. Secondly, it also does not add overhead
427 // to the tab switching time.
428 // If there's an existing running timer (could be for ARC app), it
429 // would be replaced by a new task.
430 focus_process_score_adjust_timer_.Start(
431 FROM_HERE,
432 TimeDelta::FromMilliseconds(kFocusedProcessScoreAdjustIntervalMs),
433 this, &TabManagerDelegate::OnFocusTabScoreAdjustmentTimeout);
434 }
435 }
436
437 void TabManagerDelegate::Observe(int type,
438 const content::NotificationSource& source,
439 const content::NotificationDetails& details) {
440 switch (type) {
441 case content::NOTIFICATION_RENDERER_PROCESS_CLOSED:
442 case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED: {
443 content::RenderProcessHost* host =
444 content::Source<content::RenderProcessHost>(source).ptr();
445 oom_score_map_.erase(host->GetHandle());
446 // Coming here we know that a renderer was just killed and memory should
447 // come back into the pool. However - the memory pressure observer did
448 // not yet update its status and therefore we ask it to redo the
449 // measurement, calling us again if we have to release more.
450 // Note: We do not only accelerate the discarding speed by doing another
451 // check in short succession - we also accelerate it because the timer
452 // driven MemoryPressureMonitor will continue to produce timed events
453 // on top. So the longer the cleanup phase takes, the more tabs will
454 // get discarded in parallel.
455 base::chromeos::MemoryPressureMonitor* monitor =
456 base::chromeos::MemoryPressureMonitor::Get();
457 if (monitor)
458 monitor->ScheduleEarlyCheck();
459 break;
460 }
461 case content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED: {
462 bool visible = *content::Details<bool>(details).ptr();
463 if (visible) {
464 content::RenderProcessHost* render_host =
465 content::Source<content::RenderWidgetHost>(source)
466 .ptr()
467 ->GetProcess();
468 AdjustFocusedTabScore(render_host->GetHandle());
469 }
470 // Do not handle the "else" case when it changes to invisible because
471 // 1. The behavior is a bit awkward in that when switching from tab A to
472 // tab B, the event "invisible of B" comes after "visible of A". It can
473 // cause problems when the 2 tabs have the same content (e.g., New Tab
474 // Page). To be more clear, if we try to cancel the timer when losing
475 // focus it may cancel the timer for the same renderer process.
476 // 2. When another window is launched on top of an existing browser
477 // window, the selected tab in the existing browser didn't receive this
478 // event, so an attempt to cancel timer in this case doesn't work.
479 break;
480 }
481 default:
482 NOTREACHED() << "Received unexpected notification";
483 break;
484 }
485 }
486
487 // Here we collect most of the information we need to sort the existing
488 // renderers in priority order, and hand out oom_score_adj scores based on that
489 // sort order.
490 //
491 // Things we need to collect on the browser thread (because
492 // TabStripModel isn't thread safe):
493 // 1) whether or not a tab is pinned
494 // 2) last time a tab was selected
495 // 3) is the tab currently selected
496 void TabManagerDelegate::AdjustOomPriorities(const TabStatsList& tab_list) {
497 if (IsArcMemoryManagementEnabled()) {
498 arc::ArcProcessService* arc_process_service = arc::ArcProcessService::Get();
499 if (arc_process_service &&
500 arc_process_service->RequestAppProcessList(
501 base::Bind(&TabManagerDelegate::AdjustOomPrioritiesImpl,
502 weak_ptr_factory_.GetWeakPtr(), tab_list))) {
503 return;
504 }
505 }
506 // Pass in a dummy list if unable to get ARC processes.
507 AdjustOomPrioritiesImpl(tab_list, std::vector<arc::ArcProcess>());
508 }
509
510 // Excludes persistent ARC apps, but still preserves active chrome tabs and
511 // focused ARC apps. The latter ones should not be killed by TabManager here,
512 // but we want to adjust their oom_score_adj.
513 // static
514 std::vector<TabManagerDelegate::Candidate>
515 TabManagerDelegate::GetSortedCandidates(
516 const TabStatsList& tab_list,
517 const std::vector<arc::ArcProcess>& arc_processes) {
518 std::vector<Candidate> candidates;
519 candidates.reserve(tab_list.size() + arc_processes.size());
520
521 for (const auto& tab : tab_list) {
522 candidates.emplace_back(&tab);
523 }
524
525 for (const auto& app : arc_processes) {
526 candidates.emplace_back(&app);
527 }
528
529 // Sort candidates according to priority.
530 std::sort(candidates.begin(), candidates.end());
531
532 return candidates;
533 }
534
535 bool TabManagerDelegate::IsRecentlyKilledArcProcess(
536 const std::string& process_name,
537 const TimeTicks& now) {
538 const auto it = recently_killed_arc_processes_.find(process_name);
539 if (it == recently_killed_arc_processes_.end())
540 return false;
541 return (now - it->second) <= GetArcRespawnKillDelay();
542 }
543
544 bool TabManagerDelegate::KillArcProcess(const int nspid) {
545 auto* arc_service_manager = arc::ArcServiceManager::Get();
546 if (!arc_service_manager)
547 return false;
548
549 auto* arc_process_instance = ARC_GET_INSTANCE_FOR_METHOD(
550 arc_service_manager->arc_bridge_service()->process(), KillProcess);
551 if (!arc_process_instance)
552 return false;
553
554 arc_process_instance->KillProcess(nspid, "LowMemoryKill");
555 return true;
556 }
557
558 bool TabManagerDelegate::KillTab(int64_t tab_id) {
559 // Check |tab_manager_| is alive before taking tabs into consideration.
560 return tab_manager_ &&
561 tab_manager_->CanDiscardTab(tab_id) &&
562 tab_manager_->DiscardTabById(tab_id);
563 }
564
565
566 chromeos::DebugDaemonClient* TabManagerDelegate::GetDebugDaemonClient() {
567 return chromeos::DBusThreadManager::Get()->GetDebugDaemonClient();
568 }
569
570 void TabManagerDelegate::LowMemoryKillImpl(
571 const TabStatsList& tab_list,
572 const std::vector<arc::ArcProcess>& arc_processes) {
573 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
574 VLOG(2) << "LowMemoryKillImpl";
575
576 const std::vector<TabManagerDelegate::Candidate> candidates =
577 GetSortedCandidates(tab_list, arc_processes);
578
579 int target_memory_to_free_kb = mem_stat_->TargetMemoryToFreeKB();
580 const TimeTicks now = TimeTicks::Now();
581
582 MEMORY_LOG(ERROR) << "List of low memory kill candidates "
583 "(sorted from low priority to high priority):";
584 for (auto it = candidates.rbegin(); it != candidates.rend(); ++it) {
585 MEMORY_LOG(ERROR) << *it;
586 }
587 // Kill processes until the estimated amount of freed memory is sufficient to
588 // bring the system memory back to a normal level.
589 // The list is sorted by descending importance, so we go through the list
590 // backwards.
591 for (auto it = candidates.rbegin(); it != candidates.rend(); ++it) {
592 MEMORY_LOG(ERROR) << "Target memory to free: " << target_memory_to_free_kb
593 << " KB";
594 if (target_memory_to_free_kb <= 0)
595 break;
596 // Never kill selected tab, foreground app, and important apps regardless of
597 // whether they're in the active window. Since the user experience would be
598 // bad.
599 ProcessType process_type = it->process_type();
600 if (process_type <= ProcessType::IMPORTANT_APP) {
601 MEMORY_LOG(ERROR) << "Skipped killing " << it->app()->process_name();
602 continue;
603 }
604 if (it->app()) {
605 if (IsRecentlyKilledArcProcess(it->app()->process_name(), now)) {
606 MEMORY_LOG(ERROR) << "Avoided killing " << it->app()->process_name()
607 << " too often";
608 continue;
609 }
610 int estimated_memory_freed_kb =
611 mem_stat_->EstimatedMemoryFreedKB(it->app()->pid());
612 if (KillArcProcess(it->app()->nspid())) {
613 recently_killed_arc_processes_[it->app()->process_name()] = now;
614 target_memory_to_free_kb -= estimated_memory_freed_kb;
615 MemoryKillsMonitor::LogLowMemoryKill("APP", estimated_memory_freed_kb);
616 MEMORY_LOG(ERROR) << "Killed app " << it->app()->process_name() << " ("
617 << it->app()->pid() << ")"
618 << ", estimated " << estimated_memory_freed_kb
619 << " KB freed";
620 } else {
621 MEMORY_LOG(ERROR) << "Failed to kill " << it->app()->process_name();
622 }
623 } else {
624 int64_t tab_id = it->tab()->tab_contents_id;
625 // The estimation is problematic since multiple tabs may share the same
626 // process, while the calculation counts memory used by the whole process.
627 // So |estimated_memory_freed_kb| is an over-estimation.
628 int estimated_memory_freed_kb =
629 mem_stat_->EstimatedMemoryFreedKB(it->tab()->renderer_handle);
630 if (KillTab(tab_id)) {
631 target_memory_to_free_kb -= estimated_memory_freed_kb;
632 MemoryKillsMonitor::LogLowMemoryKill("TAB", estimated_memory_freed_kb);
633 MEMORY_LOG(ERROR) << "Killed tab " << it->tab()->title << " ("
634 << it->tab()->renderer_handle << "), estimated "
635 << estimated_memory_freed_kb << " KB freed";
636 }
637 }
638 }
639 if (target_memory_to_free_kb > 0) {
640 MEMORY_LOG(ERROR)
641 << "Unable to kill enough candidates to meet target_memory_to_free_kb ";
642 }
643 }
644
645 void TabManagerDelegate::AdjustOomPrioritiesImpl(
646 const TabStatsList& tab_list,
647 const std::vector<arc::ArcProcess>& arc_processes) {
648 std::vector<TabManagerDelegate::Candidate> candidates;
649 std::vector<TabManagerDelegate::Candidate> apps_non_killable;
650
651 // Least important first.
652 auto all_candidates = GetSortedCandidates(tab_list, arc_processes);
653 for (auto& candidate : all_candidates) {
654 // TODO(cylee|yusukes): Consider using IsImportant() instead of
655 // IsKernelKillable() for simplicity.
656 // TODO(cylee): Also consider protecting FOCUSED_TAB from the kernel OOM
657 // killer so that Chrome and the kernel do the same regarding OOM handling.
658 if (!candidate.app() || candidate.app()->IsKernelKillable()) {
659 // Add tabs and killable apps to |candidates|.
660 candidates.emplace_back(std::move(candidate));
661 } else {
662 // Add non-killable apps to |apps_non_killable|.
663 apps_non_killable.emplace_back(std::move(candidate));
664 }
665 }
666
667 // Now we assign priorities based on the sorted list. We're assigning
668 // priorities in the range of kLowestRendererOomScore to
669 // kHighestRendererOomScore (defined in chrome_constants.h). oom_score_adj
670 // takes values from -1000 to 1000. Negative values are reserved for system
671 // processes, and we want to give some room below the range we're using to
672 // allow for things that want to be above the renderers in priority, so the
673 // defined range gives us some variation in priority without taking up the
674 // whole range. In the end, however, it's a pretty arbitrary range to use.
675 // Higher values are more likely to be killed by the OOM killer.
676
677 // Break the processes into 2 parts. This is to help lower the chance of
678 // altering OOM score for many processes on any small change.
679 int range_middle =
680 (chrome::kLowestRendererOomScore + chrome::kHighestRendererOomScore) / 2;
681
682 // Find some pivot point. For now processes with priority >= CHROME_INTERNAL
683 // are prone to be affected by LRU change. Taking them as "high priority"
684 // processes.
685 auto lower_priority_part = candidates.end();
686 for (auto it = candidates.begin(); it != candidates.end(); ++it) {
687 if (it->process_type() >= ProcessType::BACKGROUND_APP) {
688 lower_priority_part = it;
689 break;
690 }
691 }
692
693 ProcessScoreMap new_map;
694
695 // Make the apps non-killable.
696 if (!apps_non_killable.empty())
697 SetOomScore(apps_non_killable, kLowestOomScore, &new_map);
698
699 // Higher priority part.
700 DistributeOomScoreInRange(candidates.begin(), lower_priority_part,
701 chrome::kLowestRendererOomScore, range_middle,
702 &new_map);
703 // Lower priority part.
704 DistributeOomScoreInRange(lower_priority_part, candidates.end(), range_middle,
705 chrome::kHighestRendererOomScore, &new_map);
706
707 oom_score_map_.swap(new_map);
708 }
709
710 void TabManagerDelegate::SetOomScore(
711 const std::vector<TabManagerDelegate::Candidate>& candidates,
712 int score,
713 ProcessScoreMap* new_map) {
714 DistributeOomScoreInRange(candidates.begin(), candidates.end(), score, score,
715 new_map);
716 }
717
718 void TabManagerDelegate::DistributeOomScoreInRange(
719 std::vector<TabManagerDelegate::Candidate>::const_iterator begin,
720 std::vector<TabManagerDelegate::Candidate>::const_iterator end,
721 int range_begin,
722 int range_end,
723 ProcessScoreMap* new_map) {
724 // Processes whose OOM scores should be updated. Ignore duplicated pids but
725 // the last occurrence.
726 std::map<base::ProcessHandle, int32_t> oom_scores_to_change;
727
728 // Though there might be duplicate process handles, it doesn't matter to
729 // overestimate the number of processes here since the we don't need to
730 // use up the full range.
731 int num = (end - begin);
732 const float priority_increment =
733 static_cast<float>(range_end - range_begin) / num;
734
735 float priority = range_begin;
736 for (auto cur = begin; cur != end; ++cur) {
737 const int score = round(priority);
738
739 base::ProcessHandle pid = base::kNullProcessHandle;
740 if (cur->app()) {
741 pid = cur->app()->pid();
742 } else {
743 pid = cur->tab()->renderer_handle;
744 // 1. tab_list contains entries for already-discarded tabs. If the PID
745 // (renderer_handle) is zero, we don't need to adjust the oom_score.
746 // 2. Only add unseen process handle so if there's multiple tab maps to
747 // the same process, the process is set to an OOM score based on its "most
748 // important" tab.
749 if (pid == base::kNullProcessHandle ||
750 new_map->find(pid) != new_map->end())
751 continue;
752 }
753
754 if (pid == base::kNullProcessHandle)
755 continue;
756
757 // Update the to-be-cached OOM score map. Use pid as map keys so it's
758 // globally unique.
759 (*new_map)[pid] = score;
760
761 // Need to update OOM score if the calculated score is different from
762 // current cached score.
763 if (oom_score_map_[pid] != score) {
764 VLOG(3) << "Update OOM score " << score << " for " << *cur;
765 oom_scores_to_change[pid] = static_cast<int32_t>(score);
766 }
767 priority += priority_increment;
768 }
769
770 if (oom_scores_to_change.size()) {
771 GetDebugDaemonClient()->SetOomScoreAdj(
772 oom_scores_to_change, base::Bind(&OnSetOomScoreAdj));
773 }
774 }
775
776 } // namespace memory
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698