1 // Copyright (c) 2012 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/chromeos/memory/oom_priority_manager.h" 6 7 #include <algorithm> 8 #include <set> 9 #include <vector> 10 11 #include "base/bind.h" 12 #include "base/bind_helpers.h" 13 #include "base/command_line.h" 14 #include "base/metrics/field_trial.h" 15 #include "base/metrics/histogram.h" 16 #include "base/process/process.h" 17 #include "base/strings/string16.h" 18 #include "base/strings/string_number_conversions.h" 19 #include "base/strings/string_util.h" 20 #include "base/strings/utf_string_conversions.h" 21 #include "base/synchronization/lock.h" 22 #include "base/threading/thread.h" 23 #include "base/time/time.h" 24 #include "build/build_config.h" 25 #include "chrome/browser/browser_process.h" 26 #include "chrome/browser/browser_process_platform_part_chromeos.h" 27 #include "chrome/browser/memory_details.h" 28 #include "chrome/browser/ui/browser.h" 29 #include "chrome/browser/ui/browser_iterator.h" 30 #include "chrome/browser/ui/browser_list.h" 31 #include "chrome/browser/ui/host_desktop.h" 32 #include "chrome/browser/ui/tab_contents/tab_contents_iterator.h" 33 #include "chrome/browser/ui/tabs/tab_strip_model.h" 34 #include "chrome/browser/ui/tabs/tab_utils.h" 35 #include "chrome/common/chrome_constants.h" 36 #include "chrome/common/url_constants.h" 37 #include "chromeos/chromeos_switches.h" 38 #include "chromeos/memory/low_memory_listener.h" 39 #include "content/public/browser/browser_thread.h" 40 #include "content/public/browser/notification_service.h" 41 #include "content/public/browser/notification_types.h" 42 #include "content/public/browser/render_process_host.h" 43 #include "content/public/browser/render_widget_host.h" 44 #include "content/public/browser/web_contents.h" 45 #include "content/public/browser/zygote_host_linux.h" 46 #include "ui/base/text/bytes_formatting.h" 47 48 using base::TimeDelta; 49 using base::TimeTicks; 50 using content::BrowserThread; 51 using content::WebContents; 52 53 namespace chromeos { 54 55 namespace { 56 57 // Record a size in megabytes, over a potential interval up to 32 GB. 58 #define HISTOGRAM_MEGABYTES(name, sample) \ 59 UMA_HISTOGRAM_CUSTOM_COUNTS(name, sample, 1, 32768, 50) 60 61 // The default interval in seconds after which to adjust the oom_score_adj 62 // value. 63 const int kAdjustmentIntervalSeconds = 10; 64 65 // For each period of this length we record a statistic to indicate whether 66 // or not the user experienced a low memory event. If you change this interval 67 // you must replace Tabs.Discard.DiscardInLastMinute with a new statistic. 68 const int kRecentTabDiscardIntervalSeconds = 60; 69 70 // If there has been no priority adjustment in this interval, we assume the 71 // machine was suspended and correct our timing statistics. 72 const int kSuspendThresholdSeconds = kAdjustmentIntervalSeconds * 4; 73 74 // When switching to a new tab the tab's renderer's OOM score needs to be 75 // updated to reflect its front-most status and protect it from discard. 76 // However, doing this immediately might slow down tab switch time, so wait 77 // a little while before doing the adjustment. 78 const int kFocusedTabScoreAdjustIntervalMs = 500; 79 80 // Returns a unique ID for a WebContents. Do not cast back to a pointer, as 81 // the WebContents could be deleted if the user closed the tab. 82 int64 IdFromWebContents(WebContents* web_contents) { 83 return reinterpret_cast<int64>(web_contents); 84 } 85 86 // Records a statistics |sample| for UMA histogram |name| using a linear 87 // distribution of buckets. 88 void RecordLinearHistogram(const std::string& name, 89 int sample, 90 int maximum, 91 size_t bucket_count) { 92 // Do not use the UMA_HISTOGRAM_... macros here. They cache the Histogram 93 // instance and thus only work if |name| is constant. 94 base::HistogramBase* counter = base::LinearHistogram::FactoryGet( 95 name, 96 1, // Minimum. The 0 bin for underflow is automatically added. 97 maximum + 1, // Ensure bucket size of |maximum| / |bucket_count|. 98 bucket_count + 2, // Account for the underflow and overflow bins. 99 base::Histogram::kUmaTargetedHistogramFlag); 100 counter->Add(sample); 101 } 102 103 } // namespace 104 105 //////////////////////////////////////////////////////////////////////////////// 106 // OomMemoryDetails logs details about all Chrome processes during an out-of- 107 // memory event in an attempt to identify the culprit, then discards a tab and 108 // deletes itself. 109 class OomMemoryDetails : public MemoryDetails { 110 public: 111 OomMemoryDetails(); 112 113 // MemoryDetails overrides: 114 virtual void OnDetailsAvailable() OVERRIDE; 115 116 private: 117 virtual ~OomMemoryDetails() {} 118 119 TimeTicks start_time_; 120 121 DISALLOW_COPY_AND_ASSIGN(OomMemoryDetails); 122 }; 123 124 OomMemoryDetails::OomMemoryDetails() { 125 AddRef(); // Released in OnDetailsAvailable(). 126 start_time_ = TimeTicks::Now(); 127 } 128 129 void OomMemoryDetails::OnDetailsAvailable() { 130 TimeDelta delta = TimeTicks::Now() - start_time_; 131 // These logs are collected by user feedback reports. We want them to help 132 // diagnose user-reported problems with frequently discarded tabs. 133 std::string log_string = ToLogString(); 134 base::SystemMemoryInfoKB memory; 135 if (base::GetSystemMemoryInfo(&memory) && memory.gem_size != -1) { 136 log_string += "Graphics "; 137 log_string += UTF16ToASCII(ui::FormatBytes(memory.gem_size)); 138 } 139 LOG(WARNING) << "OOM details (" << delta.InMilliseconds() << " ms):\n" 140 << log_string; 141 if (g_browser_process && 142 g_browser_process->platform_part()->oom_priority_manager()) { 143 OomPriorityManager* manager = 144 g_browser_process->platform_part()->oom_priority_manager(); 145 manager->PurgeBrowserMemory(); 146 manager->DiscardTab(); 147 } 148 // Delete ourselves so we don't have to worry about OomPriorityManager 149 // deleting us when we're still working. 150 Release(); 151 } 152 153 //////////////////////////////////////////////////////////////////////////////// 154 // OomPriorityManager 155 156 OomPriorityManager::TabStats::TabStats() 157 : is_app(false), 158 is_reloadable_ui(false), 159 is_playing_audio(false), 160 is_pinned(false), 161 is_selected(false), 162 is_discarded(false), 163 renderer_handle(0), 164 tab_contents_id(0) { 165 } 166 167 OomPriorityManager::TabStats::~TabStats() { 168 } 169 170 OomPriorityManager::OomPriorityManager() 171 : focused_tab_pid_(0), 172 discard_count_(0), 173 recent_tab_discard_(false) { 174 // We only need the low memory observer if we want to discard tabs. 175 if (!CommandLine::ForCurrentProcess()->HasSwitch( 176 chromeos::switches::kNoDiscardTabs)) 177 low_memory_listener_.reset(new LowMemoryListener(this)); 178 179 registrar_.Add(this, 180 content::NOTIFICATION_RENDERER_PROCESS_CLOSED, 181 content::NotificationService::AllBrowserContextsAndSources()); 182 registrar_.Add(this, 183 content::NOTIFICATION_RENDERER_PROCESS_TERMINATED, 184 content::NotificationService::AllBrowserContextsAndSources()); 185 registrar_.Add(this, 186 content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED, 187 content::NotificationService::AllBrowserContextsAndSources()); 188 } 189 190 OomPriorityManager::~OomPriorityManager() { 191 Stop(); 192 } 193 194 void OomPriorityManager::Start() { 195 if (!timer_.IsRunning()) { 196 timer_.Start(FROM_HERE, 197 TimeDelta::FromSeconds(kAdjustmentIntervalSeconds), 198 this, 199 &OomPriorityManager::AdjustOomPriorities); 200 } 201 if (!recent_tab_discard_timer_.IsRunning()) { 202 recent_tab_discard_timer_.Start( 203 FROM_HERE, 204 TimeDelta::FromSeconds(kRecentTabDiscardIntervalSeconds), 205 this, 206 &OomPriorityManager::RecordRecentTabDiscard); 207 } 208 if (low_memory_listener_.get()) 209 low_memory_listener_->Start(); 210 start_time_ = TimeTicks::Now(); 211 } 212 213 void OomPriorityManager::Stop() { 214 timer_.Stop(); 215 recent_tab_discard_timer_.Stop(); 216 if (low_memory_listener_.get()) 217 low_memory_listener_->Stop(); 218 } 219 220 std::vector<string16> OomPriorityManager::GetTabTitles() { 221 TabStatsList stats = GetTabStatsOnUIThread(); 222 base::AutoLock pid_to_oom_score_autolock(pid_to_oom_score_lock_); 223 std::vector<string16> titles; 224 titles.reserve(stats.size()); 225 TabStatsList::iterator it = stats.begin(); 226 for ( ; it != stats.end(); ++it) { 227 string16 str; 228 str.reserve(4096); 229 int score = pid_to_oom_score_[it->renderer_handle]; 230 str += base::IntToString16(score); 231 str += ASCIIToUTF16(" - "); 232 str += it->title; 233 str += ASCIIToUTF16(it->is_app ? " app" : ""); 234 str += ASCIIToUTF16(it->is_reloadable_ui ? " reloadable_ui" : ""); 235 str += ASCIIToUTF16(it->is_playing_audio ? " playing_audio" : ""); 236 str += ASCIIToUTF16(it->is_pinned ? " pinned" : ""); 237 str += ASCIIToUTF16(it->is_discarded ? " discarded" : ""); 238 titles.push_back(str); 239 } 240 return titles; 241 } 242 243 // TODO(jamescook): This should consider tabs with references to other tabs, 244 // such as tabs created with JavaScript window.open(). We might want to 245 // discard the entire set together, or use that in the priority computation. 246 bool OomPriorityManager::DiscardTab() { 247 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 248 TabStatsList stats = GetTabStatsOnUIThread(); 249 if (stats.empty()) 250 return false; 251 // Loop until we find a non-discarded tab to kill. 252 for (TabStatsList::const_reverse_iterator stats_rit = stats.rbegin(); 253 stats_rit != stats.rend(); 254 ++stats_rit) { 255 int64 least_important_tab_id = stats_rit->tab_contents_id; 256 if (DiscardTabById(least_important_tab_id)) 257 return true; 258 } 259 return false; 260 } 261 262 void OomPriorityManager::LogMemoryAndDiscardTab() { 263 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 264 // Deletes itself upon completion. 265 OomMemoryDetails* details = new OomMemoryDetails(); 266 details->StartFetch(MemoryDetails::SKIP_USER_METRICS); 267 } 268 269 /////////////////////////////////////////////////////////////////////////////// 270 // OomPriorityManager, private: 271 272 // static 273 bool OomPriorityManager::IsReloadableUI(const GURL& url) { 274 // There are many chrome:// UI URLs, but only look for the ones that users 275 // are likely to have open. Most of the benefit is the from NTP URL. 276 const char* kReloadableUrlPrefixes[] = { 277 chrome::kChromeUIDownloadsURL, 278 chrome::kChromeUIHistoryURL, 279 chrome::kChromeUINewTabURL, 280 chrome::kChromeUISettingsURL, 281 }; 282 // Prefix-match against the table above. Use strncmp to avoid allocating 283 // memory to convert the URL prefix constants into std::strings. 284 for (size_t i = 0; i < arraysize(kReloadableUrlPrefixes); ++i) { 285 if (!strncmp(url.spec().c_str(), 286 kReloadableUrlPrefixes[i], 287 strlen(kReloadableUrlPrefixes[i]))) 288 return true; 289 } 290 return false; 291 } 292 293 bool OomPriorityManager::DiscardTabById(int64 target_web_contents_id) { 294 for (chrome::BrowserIterator it; !it.done(); it.Next()) { 295 Browser* browser = *it; 296 TabStripModel* model = browser->tab_strip_model(); 297 for (int idx = 0; idx < model->count(); idx++) { 298 // Can't discard tabs that are already discarded or active. 299 if (model->IsTabDiscarded(idx) || (model->active_index() == idx)) 300 continue; 301 WebContents* web_contents = model->GetWebContentsAt(idx); 302 int64 web_contents_id = IdFromWebContents(web_contents); 303 if (web_contents_id == target_web_contents_id) { 304 LOG(WARNING) << "Discarding tab " << idx 305 << " id " << target_web_contents_id; 306 // Record statistics before discarding because we want to capture the 307 // memory state that lead to the discard. 308 RecordDiscardStatistics(); 309 model->DiscardWebContentsAt(idx); 310 recent_tab_discard_ = true; 311 return true; 312 } 313 } 314 } 315 return false; 316 } 317 318 void OomPriorityManager::RecordDiscardStatistics() { 319 // Record a raw count so we can compare to discard reloads. 320 discard_count_++; 321 UMA_HISTOGRAM_CUSTOM_COUNTS( 322 "Tabs.Discard.DiscardCount", discard_count_, 1, 1000, 50); 323 324 // TODO(jamescook): Maybe incorporate extension count? 325 UMA_HISTOGRAM_CUSTOM_COUNTS( 326 "Tabs.Discard.TabCount", GetTabCount(), 1, 100, 50); 327 328 // TODO(jamescook): If the time stats prove too noisy, then divide up users 329 // based on how heavily they use Chrome using tab count as a proxy. 330 // Bin into <= 1, <= 2, <= 4, <= 8, etc. 331 if (last_discard_time_.is_null()) { 332 // This is the first discard this session. 333 TimeDelta interval = TimeTicks::Now() - start_time_; 334 int interval_seconds = static_cast<int>(interval.InSeconds()); 335 // Record time in seconds over an interval of approximately 1 day. 336 UMA_HISTOGRAM_CUSTOM_COUNTS( 337 "Tabs.Discard.InitialTime2", interval_seconds, 1, 100000, 50); 338 } else { 339 // Not the first discard, so compute time since last discard. 340 TimeDelta interval = TimeTicks::Now() - last_discard_time_; 341 int interval_ms = static_cast<int>(interval.InMilliseconds()); 342 // Record time in milliseconds over an interval of approximately 1 day. 343 // Start at 100 ms to get extra resolution in the target 750 ms range. 344 UMA_HISTOGRAM_CUSTOM_COUNTS( 345 "Tabs.Discard.IntervalTime2", interval_ms, 100, 100000 * 1000, 50); 346 } 347 // Record Chrome's concept of system memory usage at the time of the discard. 348 base::SystemMemoryInfoKB memory; 349 if (base::GetSystemMemoryInfo(&memory)) { 350 // TODO(jamescook): Remove this after R25 is deployed to stable. It does 351 // not have sufficient resolution in the 2-4 GB range and does not properly 352 // account for graphics memory on ARM. Replace with MemAllocatedMB below. 353 int mem_anonymous_mb = (memory.active_anon + memory.inactive_anon) / 1024; 354 HISTOGRAM_MEGABYTES("Tabs.Discard.MemAnonymousMB", mem_anonymous_mb); 355 356 // Record graphics GEM object size in a histogram with 50 MB buckets. 357 int mem_graphics_gem_mb = 0; 358 if (memory.gem_size != -1) 359 mem_graphics_gem_mb = memory.gem_size / 1024 / 1024; 360 RecordLinearHistogram( 361 "Tabs.Discard.MemGraphicsMB", mem_graphics_gem_mb, 2500, 50); 362 363 // Record shared memory (used by renderer/GPU buffers). 364 int mem_shmem_mb = memory.shmem / 1024; 365 RecordLinearHistogram("Tabs.Discard.MemShmemMB", mem_shmem_mb, 2500, 50); 366 367 // On Intel, graphics objects are in anonymous pages, but on ARM they are 368 // not. For a total "allocated count" add in graphics pages on ARM. 369 int mem_allocated_mb = mem_anonymous_mb; 370 #if defined(ARCH_CPU_ARM_FAMILY) 371 mem_allocated_mb += mem_graphics_gem_mb; 372 #endif 373 UMA_HISTOGRAM_CUSTOM_COUNTS( 374 "Tabs.Discard.MemAllocatedMB", mem_allocated_mb, 256, 32768, 50); 375 376 int mem_available_mb = 377 (memory.active_file + memory.inactive_file + memory.free) / 1024; 378 HISTOGRAM_MEGABYTES("Tabs.Discard.MemAvailableMB", mem_available_mb); 379 } 380 // Set up to record the next interval. 381 last_discard_time_ = TimeTicks::Now(); 382 } 383 384 void OomPriorityManager::RecordRecentTabDiscard() { 385 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 386 // If we change the interval we need to change the histogram name. 387 UMA_HISTOGRAM_BOOLEAN("Tabs.Discard.DiscardInLastMinute", 388 recent_tab_discard_); 389 // Reset for the next interval. 390 recent_tab_discard_ = false; 391 } 392 393 void OomPriorityManager::PurgeBrowserMemory() { 394 // Based on experimental evidence, attempts to free memory from renderers 395 // have been too slow to use in OOM situations (V8 garbage collection) or 396 // do not lead to persistent decreased usage (image/bitmap caches). This 397 // function therefore only targets large blocks of memory in the browser. 398 for (TabContentsIterator it; !it.done(); it.Next()) { 399 WebContents* web_contents = *it; 400 // Screenshots can consume ~5 MB per web contents for platforms that do 401 // touch back/forward. 402 web_contents->GetController().ClearAllScreenshots(); 403 } 404 // TODO(jamescook): Are there other things we could flush? Drive metadata? 405 } 406 407 int OomPriorityManager::GetTabCount() const { 408 int tab_count = 0; 409 for (chrome::BrowserIterator it; !it.done(); it.Next()) 410 tab_count += it->tab_strip_model()->count(); 411 return tab_count; 412 } 413 414 // Returns true if |first| is considered less desirable to be killed 415 // than |second|. 416 bool OomPriorityManager::CompareTabStats(TabStats first, 417 TabStats second) { 418 // Being currently selected is most important to protect. 419 if (first.is_selected != second.is_selected) 420 return first.is_selected; 421 422 // Tab with internal web UI like NTP or Settings are good choices to discard, 423 // so protect non-Web UI and let the other conditionals finish the sort. 424 if (first.is_reloadable_ui != second.is_reloadable_ui) 425 return !first.is_reloadable_ui; 426 427 // Being pinned is important to protect. 428 if (first.is_pinned != second.is_pinned) 429 return first.is_pinned; 430 431 // Being an app is important too, as you're the only visible surface in the 432 // window and we don't want to discard that. 433 if (first.is_app != second.is_app) 434 return first.is_app; 435 436 // Protect streaming audio and video conferencing tabs. 437 if (first.is_playing_audio != second.is_playing_audio) 438 return first.is_playing_audio; 439 440 // TODO(jamescook): Incorporate sudden_termination_allowed into the sort 441 // order. We don't do this now because pages with unload handlers set 442 // sudden_termination_allowed false, and that covers too many common pages 443 // with ad networks and statistics scripts. Ideally we would like to check 444 // for beforeUnload handlers, which are likely to present a dialog asking 445 // if the user wants to discard state. crbug.com/123049 446 447 // Being more recently selected is more important. 448 return first.last_selected > second.last_selected; 449 } 450 451 void OomPriorityManager::AdjustFocusedTabScoreOnFileThread() { 452 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 453 base::AutoLock pid_to_oom_score_autolock(pid_to_oom_score_lock_); 454 content::ZygoteHost::GetInstance()->AdjustRendererOOMScore( 455 focused_tab_pid_, chrome::kLowestRendererOomScore); 456 pid_to_oom_score_[focused_tab_pid_] = chrome::kLowestRendererOomScore; 457 } 458 459 void OomPriorityManager::OnFocusTabScoreAdjustmentTimeout() { 460 BrowserThread::PostTask( 461 BrowserThread::FILE, FROM_HERE, 462 base::Bind(&OomPriorityManager::AdjustFocusedTabScoreOnFileThread, 463 base::Unretained(this))); 464 } 465 466 void OomPriorityManager::Observe(int type, 467 const content::NotificationSource& source, 468 const content::NotificationDetails& details) { 469 base::ProcessHandle handle = 0; 470 base::AutoLock pid_to_oom_score_autolock(pid_to_oom_score_lock_); 471 switch (type) { 472 case content::NOTIFICATION_RENDERER_PROCESS_CLOSED: { 473 handle = 474 content::Details<content::RenderProcessHost::RendererClosedDetails>( 475 details)->handle; 476 pid_to_oom_score_.erase(handle); 477 break; 478 } 479 case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED: { 480 handle = content::Source<content::RenderProcessHost>(source)-> 481 GetHandle(); 482 pid_to_oom_score_.erase(handle); 483 break; 484 } 485 case content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED: { 486 bool visible = *content::Details<bool>(details).ptr(); 487 if (visible) { 488 focused_tab_pid_ = 489 content::Source<content::RenderWidgetHost>(source).ptr()-> 490 GetProcess()->GetHandle(); 491 492 // If the currently focused tab already has a lower score, do not 493 // set it. This can happen in case the newly focused tab is script 494 // connected to the previous tab. 495 ProcessScoreMap::iterator it; 496 it = pid_to_oom_score_.find(focused_tab_pid_); 497 if (it == pid_to_oom_score_.end() 498 || it->second != chrome::kLowestRendererOomScore) { 499 // By starting a timer we guarantee that the tab is focused for 500 // certain amount of time. Secondly, it also does not add overhead 501 // to the tab switching time. 502 if (focus_tab_score_adjust_timer_.IsRunning()) 503 focus_tab_score_adjust_timer_.Reset(); 504 else 505 focus_tab_score_adjust_timer_.Start(FROM_HERE, 506 TimeDelta::FromMilliseconds(kFocusedTabScoreAdjustIntervalMs), 507 this, &OomPriorityManager::OnFocusTabScoreAdjustmentTimeout); 508 } 509 } 510 break; 511 } 512 default: 513 NOTREACHED() << L"Received unexpected notification"; 514 break; 515 } 516 } 517 518 // Here we collect most of the information we need to sort the 519 // existing renderers in priority order, and hand out oom_score_adj 520 // scores based on that sort order. 521 // 522 // Things we need to collect on the browser thread (because 523 // TabStripModel isn't thread safe): 524 // 1) whether or not a tab is pinned 525 // 2) last time a tab was selected 526 // 3) is the tab currently selected 527 void OomPriorityManager::AdjustOomPriorities() { 528 if (BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH)->empty()) 529 return; 530 531 // Check for a discontinuity in time caused by the machine being suspended. 532 if (!last_adjust_time_.is_null()) { 533 TimeDelta suspend_time = TimeTicks::Now() - last_adjust_time_; 534 if (suspend_time.InSeconds() > kSuspendThresholdSeconds) { 535 // We were probably suspended, move our event timers forward in time so 536 // when we subtract them out later we are counting "uptime". 537 start_time_ += suspend_time; 538 if (!last_discard_time_.is_null()) 539 last_discard_time_ += suspend_time; 540 } 541 } 542 last_adjust_time_ = TimeTicks::Now(); 543 544 TabStatsList stats_list = GetTabStatsOnUIThread(); 545 BrowserThread::PostTask( 546 BrowserThread::FILE, FROM_HERE, 547 base::Bind(&OomPriorityManager::AdjustOomPrioritiesOnFileThread, 548 base::Unretained(this), stats_list)); 549 } 550 551 OomPriorityManager::TabStatsList OomPriorityManager::GetTabStatsOnUIThread() { 552 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 553 TabStatsList stats_list; 554 stats_list.reserve(32); // 99% of users have < 30 tabs open 555 bool browser_active = true; 556 const BrowserList* ash_browser_list = 557 BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH); 558 for (BrowserList::const_reverse_iterator browser_iterator = 559 ash_browser_list->begin_last_active(); 560 browser_iterator != ash_browser_list->end_last_active(); 561 ++browser_iterator) { 562 Browser* browser = *browser_iterator; 563 bool is_browser_for_app = browser->is_app(); 564 const TabStripModel* model = browser->tab_strip_model(); 565 for (int i = 0; i < model->count(); i++) { 566 WebContents* contents = model->GetWebContentsAt(i); 567 if (!contents->IsCrashed()) { 568 TabStats stats; 569 stats.is_app = is_browser_for_app; 570 stats.is_reloadable_ui = 571 IsReloadableUI(contents->GetLastCommittedURL()); 572 stats.is_playing_audio = chrome::IsPlayingAudio(contents); 573 stats.is_pinned = model->IsTabPinned(i); 574 stats.is_selected = browser_active && model->IsTabSelected(i); 575 stats.is_discarded = model->IsTabDiscarded(i); 576 stats.last_selected = contents->GetLastSelectedTime(); 577 stats.renderer_handle = contents->GetRenderProcessHost()->GetHandle(); 578 stats.title = contents->GetTitle(); 579 stats.tab_contents_id = IdFromWebContents(contents); 580 stats_list.push_back(stats); 581 } 582 } 583 // We process the active browser window in the first iteration. 584 browser_active = false; 585 } 586 // Sort the data we collected so that least desirable to be 587 // killed is first, most desirable is last. 588 std::sort(stats_list.begin(), stats_list.end(), CompareTabStats); 589 return stats_list; 590 } 591 592 void OomPriorityManager::AdjustOomPrioritiesOnFileThread( 593 TabStatsList stats_list) { 594 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 595 base::AutoLock pid_to_oom_score_autolock(pid_to_oom_score_lock_); 596 597 // Now we assign priorities based on the sorted list. We're 598 // assigning priorities in the range of kLowestRendererOomScore to 599 // kHighestRendererOomScore (defined in chrome_constants.h). 600 // oom_score_adj takes values from -1000 to 1000. Negative values 601 // are reserved for system processes, and we want to give some room 602 // below the range we're using to allow for things that want to be 603 // above the renderers in priority, so the defined range gives us 604 // some variation in priority without taking up the whole range. In 605 // the end, however, it's a pretty arbitrary range to use. Higher 606 // values are more likely to be killed by the OOM killer. 607 // 608 // We also remove any duplicate PIDs, leaving the most important 609 // (least likely to be killed) of the duplicates, so that a 610 // particular renderer process takes on the oom_score_adj of the 611 // least likely tab to be killed. 612 const int kPriorityRange = chrome::kHighestRendererOomScore - 613 chrome::kLowestRendererOomScore; 614 float priority_increment = 615 static_cast<float>(kPriorityRange) / stats_list.size(); 616 float priority = chrome::kLowestRendererOomScore; 617 std::set<base::ProcessHandle> already_seen; 618 int score = 0; 619 ProcessScoreMap::iterator it; 620 for (TabStatsList::iterator iterator = stats_list.begin(); 621 iterator != stats_list.end(); ++iterator) { 622 // stats_list also contains discarded tab stat. If renderer_handler is zero, 623 // we don't need to adjust oom_score. 624 if (iterator->renderer_handle == 0) 625 continue; 626 if (already_seen.find(iterator->renderer_handle) == already_seen.end()) { 627 already_seen.insert(iterator->renderer_handle); 628 // If a process has the same score as the newly calculated value, 629 // do not set it. 630 score = static_cast<int>(priority + 0.5f); 631 it = pid_to_oom_score_.find(iterator->renderer_handle); 632 if (it == pid_to_oom_score_.end() || it->second != score) { 633 content::ZygoteHost::GetInstance()->AdjustRendererOOMScore( 634 iterator->renderer_handle, score); 635 pid_to_oom_score_[iterator->renderer_handle] = score; 636 } 637 priority += priority_increment; 638 } 639 } 640 } 641 642 void OomPriorityManager::OnMemoryLow() { 643 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 644 LogMemoryAndDiscardTab(); 645 } 646 647 } // namespace chromeos 648