// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/oom_priority_manager.h" #include <list> #include "base/process.h" #include "base/process_util.h" #include "base/threading/thread.h" #include "build/build_config.h" #include "chrome/browser/tabs/tab_strip_model.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" #include "content/browser/browser_thread.h" #include "content/browser/renderer_host/render_process_host.h" #include "content/browser/tab_contents/tab_contents.h" #include "content/browser/zygote_host_linux.h" #if !defined(OS_CHROMEOS) #error This file only meant to be compiled on ChromeOS #endif using base::TimeDelta; using base::TimeTicks; using base::ProcessHandle; using base::ProcessMetrics; namespace browser { // The default interval in seconds after which to adjust the oom_adj // value. #define ADJUSTMENT_INTERVAL_SECONDS 10 // The default interval in minutes for considering activation times // "equal". #define BUCKET_INTERVAL_MINUTES 10 OomPriorityManager::OomPriorityManager() { StartTimer(); } OomPriorityManager::~OomPriorityManager() { StopTimer(); } void OomPriorityManager::StartTimer() { if (!timer_.IsRunning()) { timer_.Start(TimeDelta::FromSeconds(ADJUSTMENT_INTERVAL_SECONDS), this, &OomPriorityManager::AdjustOomPriorities); } } void OomPriorityManager::StopTimer() { timer_.Stop(); } // Returns true if |first| is considered less desirable to be killed // than |second|. bool OomPriorityManager::CompareRendererStats(RendererStats first, RendererStats second) { // The size of the slop in comparing activation times. [This is // allocated here to avoid static initialization at startup time.] static const int64 kTimeBucketInterval = TimeDelta::FromMinutes(BUCKET_INTERVAL_MINUTES).ToInternalValue(); // Being pinned is most important. if (first.is_pinned != second.is_pinned) return first.is_pinned == true; // We want to be a little "fuzzy" when we compare these, because // it's not really possible for the times to be identical, but if // the user selected two tabs at about the same time, we still want // to take the one that uses more memory. if (abs((first.last_selected - second.last_selected).ToInternalValue()) < kTimeBucketInterval) return first.last_selected < second.last_selected; return first.memory_used < second.memory_used; } // Here we collect most of the information we need to sort the // existing renderers in priority order, and hand out oom_adj scores // based on that sort order. // // Things we need to collect on the browser thread (because // TabStripModel isn't thread safe): // 1) whether or not a tab is pinned // 2) last time a tab was selected // // We also need to collect: // 3) size in memory of a tab // But we do that in DoAdjustOomPriorities on the FILE thread so that // we avoid jank, because it accesses /proc. void OomPriorityManager::AdjustOomPriorities() { if (BrowserList::size() == 0) return; StatsList renderer_stats; for (BrowserList::const_iterator browser_iterator = BrowserList::begin(); browser_iterator != BrowserList::end(); ++browser_iterator) { Browser* browser = *browser_iterator; const TabStripModel* model = browser->tabstrip_model(); for (int i = 0; i < model->count(); i++) { TabContents* contents = model->GetTabContentsAt(i)->tab_contents(); RendererStats stats; stats.last_selected = contents->last_selected_time(); stats.renderer_handle = contents->GetRenderProcessHost()->GetHandle(); stats.is_pinned = model->IsTabPinned(i); stats.memory_used = 0; // This gets calculated in DoAdjustOomPriorities. renderer_stats.push_back(stats); } } BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, NewRunnableMethod(this, &OomPriorityManager::DoAdjustOomPriorities, renderer_stats)); } void OomPriorityManager::DoAdjustOomPriorities(StatsList renderer_stats) { for (StatsList::iterator stats_iter = renderer_stats.begin(); stats_iter != renderer_stats.end(); ++stats_iter) { scoped_ptr<ProcessMetrics> metrics(ProcessMetrics::CreateProcessMetrics( stats_iter->renderer_handle)); base::WorkingSetKBytes working_set_kbytes; if (metrics->GetWorkingSetKBytes(&working_set_kbytes)) { // We use the proportional set size (PSS) to calculate memory // usage "badness" on Linux. stats_iter->memory_used = working_set_kbytes.shared * 1024; } else { // and if for some reason we can't get PSS, we revert to using // resident set size (RSS). This will be zero if the process // has already gone away, but we can live with that, since the // process is gone anyhow. stats_iter->memory_used = metrics->GetWorkingSetSize(); } } // Now we sort the data we collected so that least desirable to be // killed is first, most desirable is last. renderer_stats.sort(OomPriorityManager::CompareRendererStats); // Now we assign priorities based on the sorted list. We're // assigning priorities in the range of 5 to 10. oom_adj takes // values from -17 to 15. Negative values are reserved for system // processes, and we want to give some room on either side of the // range we're using to allow for things that want to be above or // below the renderers in priority, so 5 to 10 gives us some // variation in priority without taking up the whole range. In the // end, however, it's a pretty arbitrary range to use. Higher // values are more likely to be killed by the OOM killer. We also // remove any duplicate PIDs, leaving the most important of the // duplicates. const int kMinPriority = 5; const int kMaxPriority = 10; const int kPriorityRange = kMaxPriority - kMinPriority; float priority_increment = static_cast<float>(kPriorityRange) / renderer_stats.size(); float priority = kMinPriority; std::set<base::ProcessHandle> already_seen; for (StatsList::iterator iterator = renderer_stats.begin(); iterator != renderer_stats.end(); ++iterator) { if (already_seen.find(iterator->renderer_handle) == already_seen.end()) { already_seen.insert(iterator->renderer_handle); ZygoteHost::GetInstance()->AdjustRendererOOMScore( iterator->renderer_handle, static_cast<int>(priority + 0.5f)); priority += priority_increment; } } } } // namespace browser