summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGeorges Khalil <georgesak@chromium.org>2015-07-13 10:37:14 -0400
committerGeorges Khalil <georgesak@chromium.org>2015-07-13 14:38:16 +0000
commit293593a63221936fb2c82f6bbc8f2b5b6181f000 (patch)
treec29eb4c1462f79a54ae0af14e819ee8e1a842d97
parent5fc3f81f087fd190e6d7ebdc56be47fbf98d9721 (diff)
downloadchromium_src-293593a63221936fb2c82f6bbc8f2b5b6181f000.zip
chromium_src-293593a63221936fb2c82f6bbc8f2b5b6181f000.tar.gz
chromium_src-293593a63221936fb2c82f6bbc8f2b5b6181f000.tar.bz2
[Memory] Refactor OOM priority manager and seperate CrOS code into delegate.
This CL splits OomPriorityManager into OomPriorityManager and OomPriorityManagerDelegate, the former being the shared functionality on all platforms and the latter being OS specific (only CrOS for now). It is still only enabled on CrOS, other platforms will be enabled behind flags once everything is in place (still missing chrome://discards). Note: This is CL #7 towards expanding Chromeos tab killing to other platforms. BUG=463597 R=jamescook@chromium.org TBR=jam@chromium.org Review URL: https://codereview.chromium.org/1221883005 . Cr-Commit-Position: refs/heads/master@{#338504}
-rw-r--r--chrome/browser/browser_process_impl.h2
-rw-r--r--chrome/browser/memory/oom_priority_manager.cc (renamed from chrome/browser/memory/oom_priority_manager_chromeos.cc)281
-rw-r--r--chrome/browser/memory/oom_priority_manager.h118
-rw-r--r--chrome/browser/memory/oom_priority_manager_browsertest.cc (renamed from chrome/browser/memory/oom_priority_manager_browsertest_chromeos.cc)12
-rw-r--r--chrome/browser/memory/oom_priority_manager_chromeos_unittest.cc218
-rw-r--r--chrome/browser/memory/oom_priority_manager_delegate_chromeos.cc216
-rw-r--r--chrome/browser/memory/oom_priority_manager_delegate_chromeos.h82
-rw-r--r--chrome/browser/memory/oom_priority_manager_delegate_chromeos_unittest.cc79
-rw-r--r--chrome/browser/memory/oom_priority_manager_unittest.cc148
-rw-r--r--chrome/browser/memory/tab_stats.cc24
-rw-r--r--chrome/browser/memory/tab_stats.h36
-rw-r--r--chrome/chrome_browser.gypi6
-rw-r--r--chrome/chrome_tests.gypi2
-rw-r--r--chrome/chrome_tests_unit.gypi3
14 files changed, 709 insertions, 518 deletions
diff --git a/chrome/browser/browser_process_impl.h b/chrome/browser/browser_process_impl.h
index 6c1595c..c84f1c4 100644
--- a/chrome/browser/browser_process_impl.h
+++ b/chrome/browser/browser_process_impl.h
@@ -310,6 +310,8 @@ class BrowserProcessImpl : public BrowserProcess,
#endif
#if defined(OS_CHROMEOS)
+ // Any change to this #ifdef must be reflected as well in
+ // chrome/browser/memory/oom_priority_manager_browsertest.cc
scoped_ptr<memory::OomPriorityManager> oom_priority_manager_;
#endif
diff --git a/chrome/browser/memory/oom_priority_manager_chromeos.cc b/chrome/browser/memory/oom_priority_manager.cc
index 72f759c..ae06da0 100644
--- a/chrome/browser/memory/oom_priority_manager_chromeos.cc
+++ b/chrome/browser/memory/oom_priority_manager.cc
@@ -22,7 +22,6 @@
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
-#include "base/synchronization/lock.h"
#include "base/threading/thread.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
@@ -37,14 +36,13 @@
#include "chrome/browser/ui/tabs/tab_utils.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/url_constants.h"
-#include "chromeos/chromeos_switches.h"
#include "content/public/browser/browser_thread.h"
-#include "content/public/browser/notification_service.h"
-#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_process_host.h"
-#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/web_contents.h"
-#include "content/public/browser/zygote_host_linux.h"
+
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/memory/oom_priority_manager_delegate_chromeos.h"
+#endif
using base::TimeDelta;
using base::TimeTicks;
@@ -67,13 +65,7 @@ const int kRecentTabDiscardIntervalSeconds = 60;
// machine was suspended and correct our timing statistics.
const int kSuspendThresholdSeconds = kAdjustmentIntervalSeconds * 4;
-// When switching to a new tab the tab's renderer's OOM score needs to be
-// updated to reflect its front-most status and protect it from discard.
-// However, doing this immediately might slow down tab switch time, so wait
-// a little while before doing the adjustment.
-const int kFocusedTabScoreAdjustIntervalMs = 500;
-
-// Returns a unique ID for a WebContents. Do not cast back to a pointer, as
+// Returns a unique ID for a WebContents. Do not cast back to a pointer, as
// the WebContents could be deleted if the user closed the tab.
int64 IdFromWebContents(WebContents* web_contents) {
return reinterpret_cast<int64>(web_contents);
@@ -84,30 +76,11 @@ int64 IdFromWebContents(WebContents* web_contents) {
////////////////////////////////////////////////////////////////////////////////
// OomPriorityManager
-OomPriorityManager::TabStats::TabStats()
- : is_app(false),
- is_reloadable_ui(false),
- is_playing_audio(false),
- is_pinned(false),
- is_selected(false),
- is_discarded(false),
- renderer_handle(0),
- tab_contents_id(0) {
-}
-
-OomPriorityManager::TabStats::~TabStats() {
-}
-
OomPriorityManager::OomPriorityManager()
- : focused_tab_process_info_(std::make_pair(0, 0)),
- discard_count_(0),
- recent_tab_discard_(false) {
- registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
- content::NotificationService::AllBrowserContextsAndSources());
- registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_TERMINATED,
- content::NotificationService::AllBrowserContextsAndSources());
- registrar_.Add(this, content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
- content::NotificationService::AllBrowserContextsAndSources());
+ : discard_count_(0), recent_tab_discard_(false) {
+#if defined(OS_CHROMEOS)
+ delegate_.reset(new OomPriorityManagerDelegate);
+#endif
}
OomPriorityManager::~OomPriorityManager() {
@@ -115,9 +88,10 @@ OomPriorityManager::~OomPriorityManager() {
}
void OomPriorityManager::Start() {
- if (!timer_.IsRunning()) {
- timer_.Start(FROM_HERE, TimeDelta::FromSeconds(kAdjustmentIntervalSeconds),
- this, &OomPriorityManager::AdjustOomPriorities);
+ if (!update_timer_.IsRunning()) {
+ update_timer_.Start(FROM_HERE,
+ TimeDelta::FromSeconds(kAdjustmentIntervalSeconds),
+ this, &OomPriorityManager::UpdateTimerCallback);
}
if (!recent_tab_discard_timer_.IsRunning()) {
recent_tab_discard_timer_.Start(
@@ -139,26 +113,27 @@ void OomPriorityManager::Start() {
}
void OomPriorityManager::Stop() {
- timer_.Stop();
+ update_timer_.Stop();
recent_tab_discard_timer_.Stop();
memory_pressure_listener_.reset();
}
std::vector<base::string16> OomPriorityManager::GetTabTitles() {
TabStatsList stats = GetTabStatsOnUIThread();
- base::AutoLock oom_score_autolock(oom_score_lock_);
std::vector<base::string16> titles;
titles.reserve(stats.size());
TabStatsList::iterator it = stats.begin();
for (; it != stats.end(); ++it) {
base::string16 str;
str.reserve(4096);
- int score = oom_score_map_[it->child_process_host_id];
+#if defined(OS_CHROMEOS)
+ int score = delegate_->GetOomScore(it->child_process_host_id);
str += base::IntToString16(score);
str += base::ASCIIToUTF16(" - ");
+#endif
str += it->title;
str += base::ASCIIToUTF16(it->is_app ? " app" : "");
- str += base::ASCIIToUTF16(it->is_reloadable_ui ? " reloadable_ui" : "");
+ str += base::ASCIIToUTF16(it->is_internal_page ? " internal_page" : "");
str += base::ASCIIToUTF16(it->is_playing_audio ? " playing_audio" : "");
str += base::ASCIIToUTF16(it->is_pinned ? " pinned" : "");
str += base::ASCIIToUTF16(it->is_discarded ? " discarded" : "");
@@ -187,7 +162,7 @@ bool OomPriorityManager::DiscardTab() {
void OomPriorityManager::LogMemoryAndDiscardTab() {
LogMemory("Tab Discards Memory details",
- base::Bind(&OomPriorityManager::PurgeMemoryAndDiscardTabs));
+ base::Bind(&OomPriorityManager::PurgeMemoryAndDiscardTab));
}
void OomPriorityManager::LogMemory(const std::string& title,
@@ -200,7 +175,7 @@ void OomPriorityManager::LogMemory(const std::string& title,
// OomPriorityManager, private:
// static
-void OomPriorityManager::PurgeMemoryAndDiscardTabs() {
+void OomPriorityManager::PurgeMemoryAndDiscardTab() {
if (g_browser_process && g_browser_process->GetOomPriorityManager()) {
OomPriorityManager* manager = g_browser_process->GetOomPriorityManager();
manager->PurgeBrowserMemory();
@@ -209,10 +184,10 @@ void OomPriorityManager::PurgeMemoryAndDiscardTabs() {
}
// static
-bool OomPriorityManager::IsReloadableUI(const GURL& url) {
+bool OomPriorityManager::IsInternalPage(const GURL& url) {
// There are many chrome:// UI URLs, but only look for the ones that users
// are likely to have open. Most of the benefit is the from NTP URL.
- const char* const kReloadableUrlPrefixes[] = {
+ const char* const kInternalPagePrefixes[] = {
chrome::kChromeUIDownloadsURL,
chrome::kChromeUIHistoryURL,
chrome::kChromeUINewTabURL,
@@ -220,9 +195,9 @@ bool OomPriorityManager::IsReloadableUI(const GURL& url) {
};
// Prefix-match against the table above. Use strncmp to avoid allocating
// memory to convert the URL prefix constants into std::strings.
- for (size_t i = 0; i < arraysize(kReloadableUrlPrefixes); ++i) {
- if (!strncmp(url.spec().c_str(), kReloadableUrlPrefixes[i],
- strlen(kReloadableUrlPrefixes[i])))
+ for (size_t i = 0; i < arraysize(kInternalPagePrefixes); ++i) {
+ if (!strncmp(url.spec().c_str(), kInternalPagePrefixes[i],
+ strlen(kInternalPagePrefixes[i])))
return true;
}
return false;
@@ -262,12 +237,13 @@ void OomPriorityManager::RecordDiscardStatistics() {
// TODO(jamescook): Maybe incorporate extension count?
UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.Discard.TabCount", GetTabCount(), 1, 100,
50);
+#if defined(OS_CHROMEOS)
// Record the discarded tab in relation to the amount of simultaneously
// logged in users.
ash::MultiProfileUMA::RecordDiscardedTab(ash::Shell::GetInstance()
->session_state_delegate()
->NumberOfLoggedInUsers());
-
+#endif
// TODO(jamescook): If the time stats prove too noisy, then divide up users
// based on how heavily they use Chrome using tab count as a proxy.
// Bin into <= 1, <= 2, <= 4, <= 8, etc.
@@ -287,10 +263,12 @@ void OomPriorityManager::RecordDiscardStatistics() {
UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.Discard.IntervalTime2", interval_ms, 100,
100000 * 1000, 50);
}
- // Record chromeos's concept of system memory usage at the time of the
- // discard.
+// TODO(georgesak): Remove this #if when RecordMemoryStats is implemented for
+// all platforms.
+#if defined(OS_WIN) || defined(OS_CHROMEOS)
+ // Record system memory usage at the time of the discard.
RecordMemoryStats(RECORD_MEMORY_STATS_TAB_DISCARDED);
-
+#endif
// Set up to record the next interval.
last_discard_time_ = TimeTicks::Now();
}
@@ -335,8 +313,8 @@ bool OomPriorityManager::CompareTabStats(TabStats first, TabStats second) {
// Tab with internal web UI like NTP or Settings are good choices to discard,
// so protect non-Web UI and let the other conditionals finish the sort.
- if (first.is_reloadable_ui != second.is_reloadable_ui)
- return !first.is_reloadable_ui;
+ if (first.is_internal_page != second.is_internal_page)
+ return !first.is_internal_page;
// Being pinned is important to protect.
if (first.is_pinned != second.is_pinned)
@@ -362,97 +340,17 @@ bool OomPriorityManager::CompareTabStats(TabStats first, TabStats second) {
return first.last_active > second.last_active;
}
-void OomPriorityManager::AdjustFocusedTabScoreOnFileThread() {
- DCHECK_CURRENTLY_ON(BrowserThread::FILE);
- base::AutoLock oom_score_autolock(oom_score_lock_);
- base::ProcessHandle pid = focused_tab_process_info_.second;
- content::ZygoteHost::GetInstance()->AdjustRendererOOMScore(
- pid, chrome::kLowestRendererOomScore);
- oom_score_map_[focused_tab_process_info_.first] =
- chrome::kLowestRendererOomScore;
-}
-
-void OomPriorityManager::OnFocusTabScoreAdjustmentTimeout() {
- BrowserThread::PostTask(
- BrowserThread::FILE, FROM_HERE,
- base::Bind(&OomPriorityManager::AdjustFocusedTabScoreOnFileThread,
- base::Unretained(this)));
-}
-
-void OomPriorityManager::Observe(int type,
- const content::NotificationSource& source,
- const content::NotificationDetails& details) {
- base::AutoLock oom_score_autolock(oom_score_lock_);
- switch (type) {
- case content::NOTIFICATION_RENDERER_PROCESS_CLOSED:
- case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED: {
- content::RenderProcessHost* host =
- content::Source<content::RenderProcessHost>(source).ptr();
- oom_score_map_.erase(host->GetID());
- // Coming here we know that a renderer was just killed and memory should
- // come back into the pool. However - the memory pressure observer did
- // not yet update its status and therefore we ask it to redo the
- // measurement, calling us again if we have to release more.
- // Note: We do not only accelerate the discarding speed by doing another
- // check in short succession - we also accelerate it because the timer
- // driven MemoryPressureMonitor will continue to produce timed events
- // on top. So the longer the cleanup phase takes, the more tabs will
- // get discarded in parallel.
- base::chromeos::MemoryPressureMonitor* monitor =
- base::chromeos::MemoryPressureMonitor::Get();
- if (monitor)
- monitor->ScheduleEarlyCheck();
- break;
- }
- case content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED: {
- bool visible = *content::Details<bool>(details).ptr();
- if (visible) {
- content::RenderProcessHost* render_host =
- content::Source<content::RenderWidgetHost>(source)
- .ptr()
- ->GetProcess();
- focused_tab_process_info_ =
- std::make_pair(render_host->GetID(), render_host->GetHandle());
-
- // If the currently focused tab already has a lower score, do not
- // set it. This can happen in case the newly focused tab is script
- // connected to the previous tab.
- ProcessScoreMap::iterator it;
- it = oom_score_map_.find(focused_tab_process_info_.first);
- if (it == oom_score_map_.end() ||
- it->second != chrome::kLowestRendererOomScore) {
- // By starting a timer we guarantee that the tab is focused for
- // certain amount of time. Secondly, it also does not add overhead
- // to the tab switching time.
- if (focus_tab_score_adjust_timer_.IsRunning())
- focus_tab_score_adjust_timer_.Reset();
- else
- focus_tab_score_adjust_timer_.Start(
- FROM_HERE,
- TimeDelta::FromMilliseconds(kFocusedTabScoreAdjustIntervalMs),
- this, &OomPriorityManager::OnFocusTabScoreAdjustmentTimeout);
- }
- }
- break;
- }
- default:
- NOTREACHED() << L"Received unexpected notification";
- break;
- }
-}
-
-// Here we collect most of the information we need to sort the
-// existing renderers in priority order, and hand out oom_score_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
-// 3) is the tab currently selected
-void OomPriorityManager::AdjustOomPriorities() {
+// This function is called when |update_timer_| fires. It will adjust the clock
+// if needed (if we detect that the machine was asleep) and will fire the stats
+// updating on ChromeOS via the delegate.
+void OomPriorityManager::UpdateTimerCallback() {
+#if defined(USE_ASH) || defined(OS_CHROMEOS)
if (BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH)->empty())
return;
+#else
+ if (BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_NATIVE)->empty())
+ return;
+#endif
// Check for a discontinuity in time caused by the machine being suspended.
if (!last_adjust_time_.is_null()) {
@@ -467,23 +365,33 @@ void OomPriorityManager::AdjustOomPriorities() {
}
last_adjust_time_ = TimeTicks::Now();
+#if defined(OS_CHROMEOS)
TabStatsList stats_list = GetTabStatsOnUIThread();
- BrowserThread::PostTask(
- BrowserThread::FILE, FROM_HERE,
- base::Bind(&OomPriorityManager::AdjustOomPrioritiesOnFileThread,
- base::Unretained(this), stats_list));
+ // This starts the CrOS specific OOM adjustments in /proc/<pid>/oom_score_adj.
+ delegate_->AdjustOomPriorities(stats_list);
+#endif
}
-OomPriorityManager::TabStatsList OomPriorityManager::GetTabStatsOnUIThread() {
+// 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
+// 3) is the tab currently selected
+TabStatsList OomPriorityManager::GetTabStatsOnUIThread() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
TabStatsList stats_list;
stats_list.reserve(32); // 99% of users have < 30 tabs open
bool browser_active = true;
- const BrowserList* ash_browser_list =
+#if defined(USE_ASH) || defined(OS_CHROMEOS)
+ const BrowserList* browser_list =
BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH);
+#else
+ const BrowserList* browser_list =
+ BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_NATIVE);
+#endif
for (BrowserList::const_reverse_iterator browser_iterator =
- ash_browser_list->begin_last_active();
- browser_iterator != ash_browser_list->end_last_active();
+ browser_list->begin_last_active();
+ browser_iterator != browser_list->end_last_active();
++browser_iterator) {
Browser* browser = *browser_iterator;
bool is_browser_for_app = browser->is_app();
@@ -493,8 +401,8 @@ OomPriorityManager::TabStatsList OomPriorityManager::GetTabStatsOnUIThread() {
if (!contents->IsCrashed()) {
TabStats stats;
stats.is_app = is_browser_for_app;
- stats.is_reloadable_ui =
- IsReloadableUI(contents->GetLastCommittedURL());
+ stats.is_internal_page =
+ IsInternalPage(contents->GetLastCommittedURL());
stats.is_playing_audio = chrome::IsPlayingAudio(contents);
stats.is_pinned = model->IsTabPinned(i);
stats.is_selected = browser_active && model->IsTabSelected(i);
@@ -516,69 +424,6 @@ OomPriorityManager::TabStatsList OomPriorityManager::GetTabStatsOnUIThread() {
return stats_list;
}
-// static
-std::vector<OomPriorityManager::ProcessInfo>
-OomPriorityManager::GetChildProcessInfos(const TabStatsList& stats_list) {
- std::vector<ProcessInfo> process_infos;
- std::set<base::ProcessHandle> already_seen;
- for (TabStatsList::const_iterator iterator = stats_list.begin();
- iterator != stats_list.end(); ++iterator) {
- // stats_list contains entries for already-discarded tabs. If the PID
- // (renderer_handle) is zero, we don't need to adjust the oom_score.
- if (iterator->renderer_handle == 0)
- continue;
-
- bool inserted = already_seen.insert(iterator->renderer_handle).second;
- if (!inserted) {
- // We've already seen this process handle.
- continue;
- }
-
- process_infos.push_back(std::make_pair(iterator->child_process_host_id,
- iterator->renderer_handle));
- }
- return process_infos;
-}
-
-void OomPriorityManager::AdjustOomPrioritiesOnFileThread(
- TabStatsList stats_list) {
- DCHECK_CURRENTLY_ON(BrowserThread::FILE);
- base::AutoLock oom_score_autolock(oom_score_lock_);
-
- // Remove any duplicate PIDs. Order of the list is maintained, so each
- // renderer process will take on the oom_score_adj of the most important
- // (least likely to be killed) tab.
- std::vector<ProcessInfo> process_infos = GetChildProcessInfos(stats_list);
-
- // Now we assign priorities based on the sorted list. We're
- // assigning priorities in the range of kLowestRendererOomScore to
- // kHighestRendererOomScore (defined in chrome_constants.h).
- // oom_score_adj takes values from -1000 to 1000. Negative values
- // are reserved for system processes, and we want to give some room
- // below the range we're using to allow for things that want to be
- // above the renderers in priority, so the defined range 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.
- float priority = chrome::kLowestRendererOomScore;
- const int kPriorityRange =
- chrome::kHighestRendererOomScore - chrome::kLowestRendererOomScore;
- float priority_increment =
- static_cast<float>(kPriorityRange) / process_infos.size();
- for (const auto& process_info : process_infos) {
- int score = static_cast<int>(priority + 0.5f);
- ProcessScoreMap::iterator it = oom_score_map_.find(process_info.first);
- // If a process has the same score as the newly calculated value,
- // do not set it.
- if (it == oom_score_map_.end() || it->second != score) {
- content::ZygoteHost::GetInstance()->AdjustRendererOOMScore(
- process_info.second, score);
- oom_score_map_[process_info.first] = score;
- }
- priority += priority_increment;
- }
-}
-
void OomPriorityManager::OnMemoryPressure(
base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
// For the moment we only do something when we reach a critical state.
diff --git a/chrome/browser/memory/oom_priority_manager.h b/chrome/browser/memory/oom_priority_manager.h
index a320c9d..6f63a7b 100644
--- a/chrome/browser/memory/oom_priority_manager.h
+++ b/chrome/browser/memory/oom_priority_manager.h
@@ -9,35 +9,42 @@
#include <vector>
#include "base/compiler_specific.h"
-#include "base/containers/hash_tables.h"
#include "base/gtest_prod_util.h"
#include "base/memory/memory_pressure_listener.h"
#include "base/memory/scoped_ptr.h"
-#include "base/process/process.h"
#include "base/strings/string16.h"
-#include "base/synchronization/lock.h"
-#include "base/time/time.h"
#include "base/timer/timer.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
+#include "chrome/browser/memory/tab_stats.h"
class GURL;
namespace memory {
-// The OomPriorityManager periodically checks (see
-// ADJUSTMENT_INTERVAL_SECONDS in the source) the status of renderers
-// and adjusts the out of memory (OOM) adjustment value (in
-// /proc/<pid>/oom_score_adj) of the renderers so that they match the
-// algorithm embedded here for priority in being killed upon OOM
-// conditions.
+#if defined(OS_CHROMEOS)
+class OomPriorityManagerDelegate;
+#endif
+
+// The OomPriorityManager periodically updates (see
+// |kAdjustmentIntervalSeconds| in the source) the status of renderers
+// which are then used by the algorithm embedded here for priority in being
+// killed upon OOM conditions.
//
// The algorithm used favors killing tabs that are not selected, not pinned,
// and have been idle for longest, in that order of priority.
-class OomPriorityManager : public content::NotificationObserver {
+//
+// On Chrome OS (via the delegate), the kernel (via /proc/<pid>/oom_score_adj)
+// will be informed of each renderer's score, which is based on the status, so
+// in case Chrome is not able to relieve the pressure quickly enough and the
+// kernel is forced to kill processes, it will be able to do so using the same
+// algorithm as the one used here.
+//
+// Note that the browser tests are only active for platforms that use
+// OomPriorityManager (CrOS only for now) and need to be adjusted accordingly if
+// support for new platforms is added.
+class OomPriorityManager {
public:
OomPriorityManager();
- ~OomPriorityManager() override;
+ ~OomPriorityManager();
// Number of discard events since Chrome started.
int discard_count() const { return discard_count_; }
@@ -67,33 +74,15 @@ class OomPriorityManager : public content::NotificationObserver {
private:
FRIEND_TEST_ALL_PREFIXES(OomPriorityManagerTest, Comparator);
- FRIEND_TEST_ALL_PREFIXES(OomPriorityManagerTest, IsReloadableUI);
- FRIEND_TEST_ALL_PREFIXES(OomPriorityManagerTest, GetProcessHandles);
-
- struct TabStats {
- TabStats();
- ~TabStats();
- bool is_app; // browser window is an app
- bool is_reloadable_ui; // Reloadable web UI page, like NTP or Settings.
- bool is_playing_audio;
- bool is_pinned;
- bool is_selected; // selected in the currently active browser window
- bool is_discarded;
- base::TimeTicks last_active;
- base::ProcessHandle renderer_handle;
- int child_process_host_id;
- base::string16 title;
- int64 tab_contents_id; // unique ID per WebContents
- };
- typedef std::vector<TabStats> TabStatsList;
-
- static void PurgeMemoryAndDiscardTabs();
+ FRIEND_TEST_ALL_PREFIXES(OomPriorityManagerTest, IsInternalPage);
+
+ static void PurgeMemoryAndDiscardTab();
// Returns true if the |url| represents an internal Chrome web UI page that
// can be easily reloaded and hence makes a good choice to discard.
- static bool IsReloadableUI(const GURL& url);
+ static bool IsInternalPage(const GURL& url);
- // Discards a tab with the given unique ID. Returns true if discard occurred.
+ // Discards a tab with the given unique ID. Returns true if discard occurred.
bool DiscardTabById(int64 target_web_contents_id);
// Records UMA histogram statistics for a tab discard. We record statistics
@@ -111,56 +100,27 @@ class OomPriorityManager : public content::NotificationObserver {
// Returns the number of tabs open in all browser instances.
int GetTabCount() const;
+ // Returns the list of the stats for all renderers.
TabStatsList GetTabStatsOnUIThread();
- // Called when the timer fires, sets oom_adjust_score for all renderers.
- void AdjustOomPriorities();
-
- // Pair to hold child process host id and ProcessHandle
- typedef std::pair<int, base::ProcessHandle> ProcessInfo;
-
- // Returns a list of child process host ids and ProcessHandles from
- // |stats_list| with unique pids. If multiple tabs use the same process,
- // returns the first child process host id and corresponding pid. This implies
- // that the processes are selected based on their "most important" tab.
- static std::vector<ProcessInfo> GetChildProcessInfos(
- const TabStatsList& stats_list);
-
- // Called by AdjustOomPriorities.
- void AdjustOomPrioritiesOnFileThread(TabStatsList stats_list);
-
- // Posts AdjustFocusedTabScore task to the file thread.
- void OnFocusTabScoreAdjustmentTimeout();
-
- // Sets the score of the focused tab to the least value.
- void AdjustFocusedTabScoreOnFileThread();
+ // Callback for when |update_timer_| fires. Takes care of executing the tasks
+ // that need to be run periodically (see comment in implementation).
+ void UpdateTimerCallback();
static bool CompareTabStats(TabStats first, TabStats second);
- void Observe(int type,
- const content::NotificationSource& source,
- const content::NotificationDetails& details) override;
-
// Called by the memory pressure listener when the memory pressure rises.
void OnMemoryPressure(
base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level);
- base::RepeatingTimer<OomPriorityManager> timer_;
- base::OneShotTimer<OomPriorityManager> focus_tab_score_adjust_timer_;
+ // Timer to periodically update the stats of the renderers.
+ base::RepeatingTimer<OomPriorityManager> update_timer_;
+
+ // Timer to periodically report whether a tab has been discarded since the
+ // last time the timer has fired.
base::RepeatingTimer<OomPriorityManager> recent_tab_discard_timer_;
- content::NotificationRegistrar registrar_;
-
- // This lock is for |oom_score_map_| and |focused_tab_process_info_|.
- base::Lock oom_score_lock_;
- // Map maintaining the child process host id - oom_score mapping.
- typedef base::hash_map<int, int> ProcessScoreMap;
- ProcessScoreMap oom_score_map_;
- // Holds the focused tab's child process host id.
- ProcessInfo focused_tab_process_info_;
-
- // A listener to global memory pressure events. This will be used if the
- // memory pressure system was instantiated - otherwise the LowMemoryObserver
- // will be used.
+
+ // A listener to global memory pressure events.
scoped_ptr<base::MemoryPressureListener> memory_pressure_listener_;
// Wall-clock time when the priority manager started running.
@@ -181,6 +141,10 @@ class OomPriorityManager : public content::NotificationObserver {
// used for statistics normalized by usage.
bool recent_tab_discard_;
+#if defined(OS_CHROMEOS)
+ scoped_ptr<OomPriorityManagerDelegate> delegate_;
+#endif
+
DISALLOW_COPY_AND_ASSIGN(OomPriorityManager);
};
diff --git a/chrome/browser/memory/oom_priority_manager_browsertest_chromeos.cc b/chrome/browser/memory/oom_priority_manager_browsertest.cc
index 6ebc413..ceae384 100644
--- a/chrome/browser/memory/oom_priority_manager_browsertest_chromeos.cc
+++ b/chrome/browser/memory/oom_priority_manager_browsertest.cc
@@ -19,6 +19,9 @@
using content::OpenURLParams;
+// OomPriorityManager is only active on Chrome OS.
+#if defined(OS_CHROMEOS)
+
namespace memory {
namespace {
@@ -26,9 +29,9 @@ typedef InProcessBrowserTest OomPriorityManagerTest;
IN_PROC_BROWSER_TEST_F(OomPriorityManagerTest, OomPriorityManagerBasics) {
using content::WindowedNotificationObserver;
-
OomPriorityManager* oom_priority_manager =
g_browser_process->GetOomPriorityManager();
+ ASSERT_TRUE(oom_priority_manager);
EXPECT_FALSE(oom_priority_manager->recent_tab_discard());
// Get three tabs open.
@@ -162,6 +165,8 @@ IN_PROC_BROWSER_TEST_F(OomPriorityManagerTest, OomPriorityManagerBasics) {
IN_PROC_BROWSER_TEST_F(OomPriorityManagerTest, OomPressureListener) {
OomPriorityManager* oom_priority_manager =
g_browser_process->GetOomPriorityManager();
+ ASSERT_TRUE(oom_priority_manager);
+
// Get three tabs open.
content::WindowedNotificationObserver load1(
content::NOTIFICATION_NAV_ENTRY_COMMITTED,
@@ -194,7 +199,8 @@ IN_PROC_BROWSER_TEST_F(OomPriorityManagerTest, OomPressureListener) {
const int kIntervalTimeInMS = 5;
int timeout = kTimeoutTimeInMS / kIntervalTimeInMS;
while (--timeout) {
- usleep(kIntervalTimeInMS * 1000);
+ base::PlatformThread::Sleep(
+ base::TimeDelta::FromMilliseconds(kIntervalTimeInMS));
base::RunLoop().RunUntilIdle();
if (oom_priority_manager->recent_tab_discard())
break;
@@ -204,3 +210,5 @@ IN_PROC_BROWSER_TEST_F(OomPriorityManagerTest, OomPressureListener) {
} // namespace
} // namespace memory
+
+#endif // defined(OS_CHROMEOS)
diff --git a/chrome/browser/memory/oom_priority_manager_chromeos_unittest.cc b/chrome/browser/memory/oom_priority_manager_chromeos_unittest.cc
deleted file mode 100644
index 58a48bd..0000000
--- a/chrome/browser/memory/oom_priority_manager_chromeos_unittest.cc
+++ /dev/null
@@ -1,218 +0,0 @@
-// Copyright (c) 2012 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 <algorithm>
-#include <vector>
-
-#include "base/logging.h"
-#include "base/strings/string16.h"
-#include "base/time/time.h"
-#include "chrome/browser/memory/oom_priority_manager.h"
-#include "chrome/common/url_constants.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "url/gurl.h"
-
-namespace memory {
-
-typedef testing::Test OomPriorityManagerTest;
-
-namespace {
-enum TestIndicies {
- kSelected,
- kPinned,
- kApp,
- kPlayingAudio,
- kRecent,
- kOld,
- kReallyOld,
- kOldButPinned,
- kReloadableUI,
-};
-} // namespace
-
-// Tests the sorting comparator so that we know it's producing the
-// desired order.
-TEST_F(OomPriorityManagerTest, Comparator) {
- OomPriorityManager::TabStatsList test_list;
- const base::TimeTicks now = base::TimeTicks::Now();
-
- // Add kSelected last to verify we are sorting the array.
-
- {
- OomPriorityManager::TabStats stats;
- stats.is_pinned = true;
- stats.renderer_handle = kPinned;
- stats.child_process_host_id = kPinned;
- test_list.push_back(stats);
- }
-
- {
- OomPriorityManager::TabStats stats;
- stats.is_app = true;
- stats.renderer_handle = kApp;
- stats.child_process_host_id = kApp;
- test_list.push_back(stats);
- }
-
- {
- OomPriorityManager::TabStats stats;
- stats.is_playing_audio = true;
- stats.renderer_handle = kPlayingAudio;
- stats.child_process_host_id = kPlayingAudio;
- test_list.push_back(stats);
- }
-
- {
- OomPriorityManager::TabStats stats;
- stats.last_active = now - base::TimeDelta::FromSeconds(10);
- stats.renderer_handle = kRecent;
- stats.child_process_host_id = kRecent;
- test_list.push_back(stats);
- }
-
- {
- OomPriorityManager::TabStats stats;
- stats.last_active = now - base::TimeDelta::FromMinutes(15);
- stats.renderer_handle = kOld;
- stats.child_process_host_id = kOld;
- test_list.push_back(stats);
- }
-
- {
- OomPriorityManager::TabStats stats;
- stats.last_active = now - base::TimeDelta::FromDays(365);
- stats.renderer_handle = kReallyOld;
- stats.child_process_host_id = kReallyOld;
- test_list.push_back(stats);
- }
-
- {
- OomPriorityManager::TabStats stats;
- stats.is_pinned = true;
- stats.last_active = now - base::TimeDelta::FromDays(365);
- stats.renderer_handle = kOldButPinned;
- stats.child_process_host_id = kOldButPinned;
- test_list.push_back(stats);
- }
-
- {
- OomPriorityManager::TabStats stats;
- stats.is_reloadable_ui = true;
- stats.renderer_handle = kReloadableUI;
- stats.child_process_host_id = kReloadableUI;
- test_list.push_back(stats);
- }
-
- // This entry sorts to the front, so by adding it last we verify that
- // we are actually sorting the array.
- {
- OomPriorityManager::TabStats stats;
- stats.is_selected = true;
- stats.renderer_handle = kSelected;
- stats.child_process_host_id = kSelected;
- test_list.push_back(stats);
- }
-
- std::sort(test_list.begin(), test_list.end(),
- OomPriorityManager::CompareTabStats);
-
- int index = 0;
- EXPECT_EQ(kSelected, test_list[index++].renderer_handle);
- EXPECT_EQ(kPinned, test_list[index++].renderer_handle);
- EXPECT_EQ(kOldButPinned, test_list[index++].renderer_handle);
- EXPECT_EQ(kApp, test_list[index++].renderer_handle);
- EXPECT_EQ(kPlayingAudio, test_list[index++].renderer_handle);
- EXPECT_EQ(kRecent, test_list[index++].renderer_handle);
- EXPECT_EQ(kOld, test_list[index++].renderer_handle);
- EXPECT_EQ(kReallyOld, test_list[index++].renderer_handle);
- EXPECT_EQ(kReloadableUI, test_list[index++].renderer_handle);
-
- index = 0;
- EXPECT_EQ(kSelected, test_list[index++].child_process_host_id);
- EXPECT_EQ(kPinned, test_list[index++].child_process_host_id);
- EXPECT_EQ(kOldButPinned, test_list[index++].child_process_host_id);
- EXPECT_EQ(kApp, test_list[index++].child_process_host_id);
- EXPECT_EQ(kPlayingAudio, test_list[index++].child_process_host_id);
- EXPECT_EQ(kRecent, test_list[index++].child_process_host_id);
- EXPECT_EQ(kOld, test_list[index++].child_process_host_id);
- EXPECT_EQ(kReallyOld, test_list[index++].child_process_host_id);
- EXPECT_EQ(kReloadableUI, test_list[index++].child_process_host_id);
-}
-
-TEST_F(OomPriorityManagerTest, IsReloadableUI) {
- EXPECT_TRUE(
- OomPriorityManager::IsReloadableUI(GURL(chrome::kChromeUIDownloadsURL)));
- EXPECT_TRUE(
- OomPriorityManager::IsReloadableUI(GURL(chrome::kChromeUIHistoryURL)));
- EXPECT_TRUE(
- OomPriorityManager::IsReloadableUI(GURL(chrome::kChromeUINewTabURL)));
- EXPECT_TRUE(
- OomPriorityManager::IsReloadableUI(GURL(chrome::kChromeUISettingsURL)));
-
- // Debugging URLs are not included.
- EXPECT_FALSE(
- OomPriorityManager::IsReloadableUI(GURL(chrome::kChromeUIDiscardsURL)));
- EXPECT_FALSE(OomPriorityManager::IsReloadableUI(
- GURL(chrome::kChromeUINetInternalsURL)));
-
- // Prefix matches are included.
- EXPECT_TRUE(OomPriorityManager::IsReloadableUI(
- GURL("chrome://settings/fakeSetting")));
-}
-
-TEST_F(OomPriorityManagerTest, GetProcessHandles) {
- OomPriorityManager::TabStats stats;
- std::vector<OomPriorityManager::ProcessInfo> process_id_pairs;
-
- // Empty stats list gives empty |process_id_pairs| list.
- OomPriorityManager::TabStatsList empty_list;
- process_id_pairs = OomPriorityManager::GetChildProcessInfos(empty_list);
- EXPECT_EQ(0u, process_id_pairs.size());
-
- // Two tabs in two different processes generates two
- // |child_process_host_id| out.
- OomPriorityManager::TabStatsList two_list;
- stats.child_process_host_id = 100;
- stats.renderer_handle = 101;
- two_list.push_back(stats);
- stats.child_process_host_id = 200;
- stats.renderer_handle = 201;
- two_list.push_back(stats);
- process_id_pairs = OomPriorityManager::GetChildProcessInfos(two_list);
- EXPECT_EQ(2u, process_id_pairs.size());
- EXPECT_EQ(100, process_id_pairs[0].first);
- EXPECT_EQ(101, process_id_pairs[0].second);
- EXPECT_EQ(200, process_id_pairs[1].first);
- EXPECT_EQ(201, process_id_pairs[1].second);
-
- // Zero handles are removed.
- OomPriorityManager::TabStatsList zero_handle_list;
- stats.child_process_host_id = 100;
- stats.renderer_handle = 0;
- zero_handle_list.push_back(stats);
- process_id_pairs = OomPriorityManager::GetChildProcessInfos(zero_handle_list);
- EXPECT_EQ(0u, process_id_pairs.size());
-
- // Two tabs in the same process generates one handle out. When a duplicate
- // occurs the later instance is dropped.
- OomPriorityManager::TabStatsList same_process_list;
- stats.child_process_host_id = 100;
- stats.renderer_handle = 101;
- same_process_list.push_back(stats);
- stats.child_process_host_id = 200;
- stats.renderer_handle = 201;
- same_process_list.push_back(stats);
- stats.child_process_host_id = 300;
- stats.renderer_handle = 101; // Duplicate.
- same_process_list.push_back(stats);
- process_id_pairs =
- OomPriorityManager::GetChildProcessInfos(same_process_list);
- EXPECT_EQ(2u, process_id_pairs.size());
- EXPECT_EQ(100, process_id_pairs[0].first);
- EXPECT_EQ(101, process_id_pairs[0].second);
- EXPECT_EQ(200, process_id_pairs[1].first);
- EXPECT_EQ(201, process_id_pairs[1].second);
-}
-
-} // namespace memory
diff --git a/chrome/browser/memory/oom_priority_manager_delegate_chromeos.cc b/chrome/browser/memory/oom_priority_manager_delegate_chromeos.cc
new file mode 100644
index 0000000..e7f5c1a
--- /dev/null
+++ b/chrome/browser/memory/oom_priority_manager_delegate_chromeos.cc
@@ -0,0 +1,216 @@
+// Copyright 2015 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/memory/oom_priority_manager_delegate_chromeos.h"
+
+#include "base/memory/memory_pressure_monitor_chromeos.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/synchronization/lock.h"
+#include "chrome/browser/memory/tab_stats.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/common/chrome_constants.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_widget_host.h"
+#include "content/public/browser/zygote_host_linux.h"
+
+using base::TimeDelta;
+using content::BrowserThread;
+
+namespace memory {
+namespace {
+
+// When switching to a new tab the tab's renderer's OOM score needs to be
+// updated to reflect its front-most status and protect it from discard.
+// However, doing this immediately might slow down tab switch time, so wait
+// a little while before doing the adjustment.
+const int kFocusedTabScoreAdjustIntervalMs = 500;
+
+} // namespace
+
+OomPriorityManagerDelegate::OomPriorityManagerDelegate()
+ : focused_tab_process_info_(std::make_pair(0, 0)) {
+ registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
+ content::NotificationService::AllBrowserContextsAndSources());
+ registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_TERMINATED,
+ content::NotificationService::AllBrowserContextsAndSources());
+ registrar_.Add(this, content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
+ content::NotificationService::AllBrowserContextsAndSources());
+}
+
+OomPriorityManagerDelegate::~OomPriorityManagerDelegate() {
+}
+
+int OomPriorityManagerDelegate::GetOomScore(int child_process_host_id) {
+ base::AutoLock oom_score_autolock(oom_score_lock_);
+ int score = oom_score_map_[child_process_host_id];
+ return score;
+}
+
+void OomPriorityManagerDelegate::AdjustFocusedTabScoreOnFileThread() {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+ base::AutoLock oom_score_autolock(oom_score_lock_);
+ base::ProcessHandle pid = focused_tab_process_info_.second;
+ content::ZygoteHost::GetInstance()->AdjustRendererOOMScore(
+ pid, chrome::kLowestRendererOomScore);
+ oom_score_map_[focused_tab_process_info_.first] =
+ chrome::kLowestRendererOomScore;
+}
+
+void OomPriorityManagerDelegate::OnFocusTabScoreAdjustmentTimeout() {
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&OomPriorityManagerDelegate::AdjustFocusedTabScoreOnFileThread,
+ base::Unretained(this)));
+}
+void OomPriorityManagerDelegate::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ base::AutoLock oom_score_autolock(oom_score_lock_);
+ switch (type) {
+ case content::NOTIFICATION_RENDERER_PROCESS_CLOSED:
+ case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED: {
+ content::RenderProcessHost* host =
+ content::Source<content::RenderProcessHost>(source).ptr();
+ oom_score_map_.erase(host->GetID());
+ // Coming here we know that a renderer was just killed and memory should
+ // come back into the pool. However - the memory pressure observer did
+ // not yet update its status and therefore we ask it to redo the
+ // measurement, calling us again if we have to release more.
+ // Note: We do not only accelerate the discarding speed by doing another
+ // check in short succession - we also accelerate it because the timer
+ // driven MemoryPressureMonitor will continue to produce timed events
+ // on top. So the longer the cleanup phase takes, the more tabs will
+ // get discarded in parallel.
+ base::chromeos::MemoryPressureMonitor* monitor =
+ base::chromeos::MemoryPressureMonitor::Get();
+ if (monitor)
+ monitor->ScheduleEarlyCheck();
+ break;
+ }
+ case content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED: {
+ bool visible = *content::Details<bool>(details).ptr();
+ if (visible) {
+ content::RenderProcessHost* render_host =
+ content::Source<content::RenderWidgetHost>(source)
+ .ptr()
+ ->GetProcess();
+ focused_tab_process_info_ =
+ std::make_pair(render_host->GetID(), render_host->GetHandle());
+
+ // If the currently focused tab already has a lower score, do not
+ // set it. This can happen in case the newly focused tab is script
+ // connected to the previous tab.
+ ProcessScoreMap::iterator it;
+ it = oom_score_map_.find(focused_tab_process_info_.first);
+ if (it == oom_score_map_.end() ||
+ it->second != chrome::kLowestRendererOomScore) {
+ // By starting a timer we guarantee that the tab is focused for
+ // certain amount of time. Secondly, it also does not add overhead
+ // to the tab switching time.
+ if (focus_tab_score_adjust_timer_.IsRunning())
+ focus_tab_score_adjust_timer_.Reset();
+ else
+ focus_tab_score_adjust_timer_.Start(
+ FROM_HERE,
+ TimeDelta::FromMilliseconds(kFocusedTabScoreAdjustIntervalMs),
+ this,
+ &OomPriorityManagerDelegate::OnFocusTabScoreAdjustmentTimeout);
+ }
+ }
+ break;
+ }
+ default:
+ NOTREACHED() << "Received unexpected notification";
+ break;
+ }
+}
+
+// Here we collect most of the information we need to sort the existing
+// renderers in priority order, and hand out oom_score_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
+// 3) is the tab currently selected
+void OomPriorityManagerDelegate::AdjustOomPriorities(
+ const TabStatsList& stats_list) {
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&OomPriorityManagerDelegate::AdjustOomPrioritiesOnFileThread,
+ base::Unretained(this), stats_list));
+}
+
+// static
+std::vector<OomPriorityManagerDelegate::ProcessInfo>
+OomPriorityManagerDelegate::GetChildProcessInfos(
+ const TabStatsList& stats_list) {
+ std::vector<ProcessInfo> process_infos;
+ std::set<base::ProcessHandle> already_seen;
+ for (TabStatsList::const_iterator iterator = stats_list.begin();
+ iterator != stats_list.end(); ++iterator) {
+ // stats_list contains entries for already-discarded tabs. If the PID
+ // (renderer_handle) is zero, we don't need to adjust the oom_score.
+ if (iterator->renderer_handle == 0)
+ continue;
+
+ bool inserted = already_seen.insert(iterator->renderer_handle).second;
+ if (!inserted) {
+ // We've already seen this process handle.
+ continue;
+ }
+
+ process_infos.push_back(std::make_pair(iterator->child_process_host_id,
+ iterator->renderer_handle));
+ }
+ return process_infos;
+}
+
+void OomPriorityManagerDelegate::AdjustOomPrioritiesOnFileThread(
+ TabStatsList stats_list) {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+ base::AutoLock oom_score_autolock(oom_score_lock_);
+
+ // Remove any duplicate PIDs. Order of the list is maintained, so each
+ // renderer process will take on the oom_score_adj of the most important
+ // (least likely to be killed) tab.
+ std::vector<ProcessInfo> process_infos = GetChildProcessInfos(stats_list);
+
+ // Now we assign priorities based on the sorted list. We're assigning
+ // priorities in the range of kLowestRendererOomScore to
+ // kHighestRendererOomScore (defined in chrome_constants.h). oom_score_adj
+ // takes values from -1000 to 1000. Negative values are reserved for system
+ // processes, and we want to give some room below the range we're using to
+ // allow for things that want to be above the renderers in priority, so the
+ // defined range 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.
+ float priority = chrome::kLowestRendererOomScore;
+ const int kPriorityRange =
+ chrome::kHighestRendererOomScore - chrome::kLowestRendererOomScore;
+ float priority_increment =
+ static_cast<float>(kPriorityRange) / process_infos.size();
+ for (const auto& process_info : process_infos) {
+ int score = static_cast<int>(priority + 0.5f);
+ ProcessScoreMap::iterator it = oom_score_map_.find(process_info.first);
+ // If a process has the same score as the newly calculated value,
+ // do not set it.
+ if (it == oom_score_map_.end() || it->second != score) {
+ content::ZygoteHost::GetInstance()->AdjustRendererOOMScore(
+ process_info.second, score);
+ oom_score_map_[process_info.first] = score;
+ }
+ priority += priority_increment;
+ }
+}
+
+} // namespace memory
diff --git a/chrome/browser/memory/oom_priority_manager_delegate_chromeos.h b/chrome/browser/memory/oom_priority_manager_delegate_chromeos.h
new file mode 100644
index 0000000..a9824b4
--- /dev/null
+++ b/chrome/browser/memory/oom_priority_manager_delegate_chromeos.h
@@ -0,0 +1,82 @@
+// Copyright 2015 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.
+
+#ifndef CHROME_BROWSER_MEMORY_OOM_PRIORITY_MANAGER_DELEGATE_CHROMEOS_H_
+#define CHROME_BROWSER_MEMORY_OOM_PRIORITY_MANAGER_DELEGATE_CHROMEOS_H_
+
+#include <utility>
+#include <vector>
+
+#include "base/containers/hash_tables.h"
+#include "base/process/process.h"
+#include "base/timer/timer.h"
+#include "chrome/browser/memory/oom_priority_manager.h"
+#include "chrome/browser/memory/tab_stats.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+
+namespace memory {
+
+// The Chrome OS OomPriorityManagerDelegate is responsible for keeping the
+// renderers' scores up to date in /proc/<pid>/oom_score_adj.
+//
+// Note that AdjustOomPriorities will be called on the UI thread by
+// OomPriorityManager, but the actual work will take place on the file thread
+// (see implementation of AdjustOomPriorities).
+class OomPriorityManagerDelegate : public content::NotificationObserver {
+ public:
+ OomPriorityManagerDelegate();
+ ~OomPriorityManagerDelegate() override;
+
+ // Return the score of a process.
+ int GetOomScore(int child_process_host_id);
+
+ // Called when the timer fires, sets oom_adjust_score for all renderers.
+ void AdjustOomPriorities(const TabStatsList& stats_list);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(OomPriorityManagerDelegateTest, GetProcessHandles);
+
+ // content::NotificationObserver:
+ void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) override;
+
+ // Pair to hold child process host id and ProcessHandle.
+ typedef std::pair<int, base::ProcessHandle> ProcessInfo;
+
+ // Returns a list of child process host ids and ProcessHandles from
+ // |stats_list| with unique pids. If multiple tabs use the same process,
+ // returns the first child process host id and corresponding pid. This implies
+ // that the processes are selected based on their "most important" tab.
+ static std::vector<ProcessInfo> GetChildProcessInfos(
+ const TabStatsList& stats_list);
+
+ // Called by AdjustOomPriorities.
+ void AdjustOomPrioritiesOnFileThread(TabStatsList stats_list);
+
+ // Posts AdjustFocusedTabScore task to the file thread.
+ void OnFocusTabScoreAdjustmentTimeout();
+
+ // Sets the score of the focused tab to the least value.
+ void AdjustFocusedTabScoreOnFileThread();
+
+ // Registrar to receive renderer notifications.
+ content::NotificationRegistrar registrar_;
+ // Timer to guarantee that the tab is focused for a certain amount of time.
+ base::OneShotTimer<OomPriorityManagerDelegate> focus_tab_score_adjust_timer_;
+ // This lock is for |oom_score_map_| and |focused_tab_process_info_|.
+ base::Lock oom_score_lock_;
+ // Map maintaining the child process host id - oom_score mapping.
+ typedef base::hash_map<int, int> ProcessScoreMap;
+ ProcessScoreMap oom_score_map_;
+ // Holds the focused tab's child process host id.
+ ProcessInfo focused_tab_process_info_;
+
+ DISALLOW_COPY_AND_ASSIGN(OomPriorityManagerDelegate);
+};
+
+} // namespace memory
+
+#endif // CHROME_BROWSER_MEMORY_OOM_PRIORITY_MANAGER_DELEGATE_CHROMEOS_H_
diff --git a/chrome/browser/memory/oom_priority_manager_delegate_chromeos_unittest.cc b/chrome/browser/memory/oom_priority_manager_delegate_chromeos_unittest.cc
new file mode 100644
index 0000000..4b73b6e
--- /dev/null
+++ b/chrome/browser/memory/oom_priority_manager_delegate_chromeos_unittest.cc
@@ -0,0 +1,79 @@
+// Copyright (c) 2012 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/memory/oom_priority_manager.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "chrome/browser/memory/oom_priority_manager_delegate_chromeos.h"
+#include "chrome/browser/memory/tab_stats.h"
+#include "chrome/common/url_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace memory {
+
+typedef testing::Test OomPriorityManagerDelegateTest;
+
+TEST_F(OomPriorityManagerDelegateTest, GetProcessHandles) {
+ TabStats stats;
+ std::vector<OomPriorityManagerDelegate::ProcessInfo> process_id_pairs;
+
+ // Empty stats list gives empty |process_id_pairs| list.
+ TabStatsList empty_list;
+ process_id_pairs =
+ OomPriorityManagerDelegate::GetChildProcessInfos(empty_list);
+ EXPECT_EQ(0u, process_id_pairs.size());
+
+ // Two tabs in two different processes generates two
+ // |child_process_host_id| out.
+ TabStatsList two_list;
+ stats.child_process_host_id = 100;
+ stats.renderer_handle = 101;
+ two_list.push_back(stats);
+ stats.child_process_host_id = 200;
+ stats.renderer_handle = 201;
+ two_list.push_back(stats);
+ process_id_pairs = OomPriorityManagerDelegate::GetChildProcessInfos(two_list);
+ EXPECT_EQ(2u, process_id_pairs.size());
+ EXPECT_EQ(100, process_id_pairs[0].first);
+ EXPECT_EQ(101, process_id_pairs[0].second);
+ EXPECT_EQ(200, process_id_pairs[1].first);
+ EXPECT_EQ(201, process_id_pairs[1].second);
+
+ // Zero handles are removed.
+ TabStatsList zero_handle_list;
+ stats.child_process_host_id = 100;
+ stats.renderer_handle = 0;
+ zero_handle_list.push_back(stats);
+ process_id_pairs =
+ OomPriorityManagerDelegate::GetChildProcessInfos(zero_handle_list);
+ EXPECT_EQ(0u, process_id_pairs.size());
+
+ // Two tabs in the same process generates one handle out. When a duplicate
+ // occurs the later instance is dropped.
+ TabStatsList same_process_list;
+ stats.child_process_host_id = 100;
+ stats.renderer_handle = 101;
+ same_process_list.push_back(stats);
+ stats.child_process_host_id = 200;
+ stats.renderer_handle = 201;
+ same_process_list.push_back(stats);
+ stats.child_process_host_id = 300;
+ stats.renderer_handle = 101; // Duplicate.
+ same_process_list.push_back(stats);
+ process_id_pairs =
+ OomPriorityManagerDelegate::GetChildProcessInfos(same_process_list);
+ EXPECT_EQ(2u, process_id_pairs.size());
+ EXPECT_EQ(100, process_id_pairs[0].first);
+ EXPECT_EQ(101, process_id_pairs[0].second);
+ EXPECT_EQ(200, process_id_pairs[1].first);
+ EXPECT_EQ(201, process_id_pairs[1].second);
+}
+
+} // namespace memory
diff --git a/chrome/browser/memory/oom_priority_manager_unittest.cc b/chrome/browser/memory/oom_priority_manager_unittest.cc
new file mode 100644
index 0000000..f3af781
--- /dev/null
+++ b/chrome/browser/memory/oom_priority_manager_unittest.cc
@@ -0,0 +1,148 @@
+// Copyright (c) 2012 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/memory/oom_priority_manager.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "chrome/browser/memory/tab_stats.h"
+#include "chrome/common/url_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace memory {
+
+typedef testing::Test OomPriorityManagerTest;
+
+namespace {
+enum TestIndicies {
+ kSelected,
+ kPinned,
+ kApp,
+ kPlayingAudio,
+ kRecent,
+ kOld,
+ kReallyOld,
+ kOldButPinned,
+ kInternalPage,
+};
+} // namespace
+
+// Tests the sorting comparator so that we know it's producing the
+// desired order.
+TEST_F(OomPriorityManagerTest, Comparator) {
+ TabStatsList test_list;
+ const base::TimeTicks now = base::TimeTicks::Now();
+
+ // Add kSelected last to verify we are sorting the array.
+
+ {
+ TabStats stats;
+ stats.is_pinned = true;
+ stats.child_process_host_id = kPinned;
+ test_list.push_back(stats);
+ }
+
+ {
+ TabStats stats;
+ stats.is_app = true;
+ stats.child_process_host_id = kApp;
+ test_list.push_back(stats);
+ }
+
+ {
+ TabStats stats;
+ stats.is_playing_audio = true;
+ stats.child_process_host_id = kPlayingAudio;
+ test_list.push_back(stats);
+ }
+
+ {
+ TabStats stats;
+ stats.last_active = now - base::TimeDelta::FromSeconds(10);
+ stats.child_process_host_id = kRecent;
+ test_list.push_back(stats);
+ }
+
+ {
+ TabStats stats;
+ stats.last_active = now - base::TimeDelta::FromMinutes(15);
+ stats.child_process_host_id = kOld;
+ test_list.push_back(stats);
+ }
+
+ {
+ TabStats stats;
+ stats.last_active = now - base::TimeDelta::FromDays(365);
+ stats.child_process_host_id = kReallyOld;
+ test_list.push_back(stats);
+ }
+
+ {
+ TabStats stats;
+ stats.is_pinned = true;
+ stats.last_active = now - base::TimeDelta::FromDays(365);
+ stats.child_process_host_id = kOldButPinned;
+ test_list.push_back(stats);
+ }
+
+ {
+ TabStats stats;
+ stats.is_internal_page = true;
+ stats.child_process_host_id = kInternalPage;
+ test_list.push_back(stats);
+ }
+
+ // This entry sorts to the front, so by adding it last we verify that
+ // we are actually sorting the array.
+ {
+ TabStats stats;
+ stats.is_selected = true;
+ stats.child_process_host_id = kSelected;
+ test_list.push_back(stats);
+ }
+
+ std::sort(test_list.begin(), test_list.end(),
+ OomPriorityManager::CompareTabStats);
+
+ int index = 0;
+ EXPECT_EQ(kSelected, test_list[index++].child_process_host_id);
+ EXPECT_EQ(kPinned, test_list[index++].child_process_host_id);
+ EXPECT_EQ(kOldButPinned, test_list[index++].child_process_host_id);
+ EXPECT_EQ(kApp, test_list[index++].child_process_host_id);
+ EXPECT_EQ(kPlayingAudio, test_list[index++].child_process_host_id);
+ EXPECT_EQ(kRecent, test_list[index++].child_process_host_id);
+ EXPECT_EQ(kOld, test_list[index++].child_process_host_id);
+ EXPECT_EQ(kReallyOld, test_list[index++].child_process_host_id);
+ EXPECT_EQ(kInternalPage, test_list[index++].child_process_host_id);
+}
+
+TEST_F(OomPriorityManagerTest, IsInternalPage) {
+ EXPECT_TRUE(
+ OomPriorityManager::IsInternalPage(GURL(chrome::kChromeUIDownloadsURL)));
+ EXPECT_TRUE(
+ OomPriorityManager::IsInternalPage(GURL(chrome::kChromeUIHistoryURL)));
+ EXPECT_TRUE(
+ OomPriorityManager::IsInternalPage(GURL(chrome::kChromeUINewTabURL)));
+ EXPECT_TRUE(
+ OomPriorityManager::IsInternalPage(GURL(chrome::kChromeUISettingsURL)));
+
+// Debugging URLs are not included.
+#if defined(OS_CHROMEOS)
+ EXPECT_FALSE(
+ OomPriorityManager::IsInternalPage(GURL(chrome::kChromeUIDiscardsURL)));
+#endif
+ EXPECT_FALSE(OomPriorityManager::IsInternalPage(
+ GURL(chrome::kChromeUINetInternalsURL)));
+
+ // Prefix matches are included.
+ EXPECT_TRUE(OomPriorityManager::IsInternalPage(
+ GURL("chrome://settings/fakeSetting")));
+}
+
+} // namespace memory
diff --git a/chrome/browser/memory/tab_stats.cc b/chrome/browser/memory/tab_stats.cc
new file mode 100644
index 0000000..a7badb9
--- /dev/null
+++ b/chrome/browser/memory/tab_stats.cc
@@ -0,0 +1,24 @@
+// Copyright 2015 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/memory/tab_stats.h"
+
+namespace memory {
+
+TabStats::TabStats()
+ : is_app(false),
+ is_internal_page(false),
+ is_playing_audio(false),
+ is_pinned(false),
+ is_selected(false),
+ is_discarded(false),
+ renderer_handle(0),
+ child_process_host_id(0),
+ tab_contents_id(0) {
+}
+
+TabStats::~TabStats() {
+}
+
+} // namespace memory
diff --git a/chrome/browser/memory/tab_stats.h b/chrome/browser/memory/tab_stats.h
new file mode 100644
index 0000000..690f6a8
--- /dev/null
+++ b/chrome/browser/memory/tab_stats.h
@@ -0,0 +1,36 @@
+// Copyright 2015 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.
+
+#ifndef CHROME_BROWSER_MEMORY_TAB_STATS_H_
+#define CHROME_BROWSER_MEMORY_TAB_STATS_H_
+
+#include <vector>
+
+#include "base/process/process.h"
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+
+namespace memory {
+
+struct TabStats {
+ TabStats();
+ ~TabStats();
+ bool is_app; // Browser window is an app.
+ bool is_internal_page; // Internal page, such as NTP or Settings.
+ bool is_playing_audio;
+ bool is_pinned;
+ bool is_selected; // Selected in the currently active browser window.
+ bool is_discarded;
+ base::TimeTicks last_active;
+ base::ProcessHandle renderer_handle;
+ int child_process_host_id;
+ base::string16 title;
+ int64 tab_contents_id; // Unique ID per WebContents.
+};
+
+typedef std::vector<TabStats> TabStatsList;
+
+} // namespace memory
+
+#endif // CHROME_BROWSER_MEMORY_TAB_STATS_H_
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 6718aad..4ab2be3 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -623,11 +623,15 @@
'browser/media/webrtc_log_list.h',
'browser/memory/oom_memory_details.cc',
'browser/memory/oom_memory_details.h',
+ 'browser/memory/oom_priority_manager.cc',
'browser/memory/oom_priority_manager.h',
- 'browser/memory/oom_priority_manager_chromeos.cc',
+ 'browser/memory/oom_priority_manager_delegate_chromeos.cc',
+ 'browser/memory/oom_priority_manager_delegate_chromeos.h',
'browser/memory/system_memory_stats_recorder.h',
'browser/memory/system_memory_stats_recorder_linux.cc',
'browser/memory/system_memory_stats_recorder_win.cc',
+ 'browser/memory/tab_stats.cc',
+ 'browser/memory/tab_stats.h',
'browser/memory_details.cc',
'browser/memory_details.h',
'browser/memory_details_android.cc',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 00e1932..b324ac9 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -340,7 +340,7 @@
'browser/media_galleries/fileapi/media_file_validator_browsertest.cc',
'browser/media_galleries/media_galleries_dialog_controller_mock.cc',
'browser/media_galleries/media_galleries_dialog_controller_mock.h',
- 'browser/memory/oom_priority_manager_browsertest_chromeos.cc',
+ 'browser/memory/oom_priority_manager_browsertest.cc',
'browser/metrics/metrics_memory_details_browsertest.cc',
'browser/metrics/metrics_service_browsertest.cc',
'browser/net/cookie_policy_browsertest.cc',
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index 8db95cf..5dc6a29 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -126,7 +126,8 @@
'browser/manifest/manifest_icon_selector_unittest.cc',
'browser/media/midi_permission_context_unittest.cc',
'browser/media/native_desktop_media_list_unittest.cc',
- 'browser/memory/oom_priority_manager_chromeos_unittest.cc',
+ 'browser/memory/oom_priority_manager_delegate_chromeos_unittest.cc',
+ 'browser/memory/oom_priority_manager_unittest.cc',
'browser/metrics/chrome_browser_main_extra_parts_metrics_unittest.cc',
'browser/metrics/chrome_metrics_service_accessor_unittest.cc',
'browser/metrics/cloned_install_detector_unittest.cc',