diff options
-rw-r--r-- | chrome/browser/browser_process_impl.h | 2 | ||||
-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.h | 118 | ||||
-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.cc | 218 | ||||
-rw-r--r-- | chrome/browser/memory/oom_priority_manager_delegate_chromeos.cc | 216 | ||||
-rw-r--r-- | chrome/browser/memory/oom_priority_manager_delegate_chromeos.h | 82 | ||||
-rw-r--r-- | chrome/browser/memory/oom_priority_manager_delegate_chromeos_unittest.cc | 79 | ||||
-rw-r--r-- | chrome/browser/memory/oom_priority_manager_unittest.cc | 148 | ||||
-rw-r--r-- | chrome/browser/memory/tab_stats.cc | 24 | ||||
-rw-r--r-- | chrome/browser/memory/tab_stats.h | 36 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 6 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 2 | ||||
-rw-r--r-- | chrome/chrome_tests_unit.gypi | 3 |
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', |