blob: 4b0d96e8e6765feff470014049d633e0b9150736 [file] [log] [blame]
// 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_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
TabManagerDelegate::TabManagerDelegate()
: 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());
}
TabManagerDelegate::~TabManagerDelegate() {}
int TabManagerDelegate::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 TabManagerDelegate::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 TabManagerDelegate::OnFocusTabScoreAdjustmentTimeout() {
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&TabManagerDelegate::AdjustFocusedTabScoreOnFileThread,
base::Unretained(this)));
}
void TabManagerDelegate::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, &TabManagerDelegate::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 TabManagerDelegate::AdjustOomPriorities(const TabStatsList& stats_list) {
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&TabManagerDelegate::AdjustOomPrioritiesOnFileThread,
base::Unretained(this), stats_list));
}
// static
std::vector<TabManagerDelegate::ProcessInfo>
TabManagerDelegate::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 TabManagerDelegate::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