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