OLD | NEW |
| (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 | |
OLD | NEW |