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