| 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 |