blob: 93453227bb7baf76bc7c10c3ceee620738b929b1 [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 <algorithm>
#include <set>
#include <string>
#include <vector>
#include "ash/shell.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/memory/memory_pressure_monitor_chromeos.h"
#include "base/metrics/histogram_macros.h"
#include "base/process/process_handle.h" // kNullProcessHandle.
#include "base/process/process_metrics.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 "base/time/time.h"
#include "chrome/browser/chromeos/arc/arc_process.h"
#include "chrome/browser/chromeos/arc/arc_process_service.h"
#include "chrome/browser/memory/tab_stats.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_features.h"
#include "components/arc/arc_bridge_service.h"
#include "components/arc/common/process.mojom.h"
#include "components/arc/metrics/oom_kills_histogram.h"
#include "components/exo/shell_surface.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"
#include "ui/aura/window.h"
#include "ui/wm/public/activation_client.h"
using base::ProcessHandle;
using base::TimeDelta;
using content::BrowserThread;
namespace memory {
namespace {
const char kExoShellSurfaceWindowName[] = "ExoShellSurface";
const char kArcProcessNamePrefix[] = "org.chromium.arc.";
// 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 kFocusedProcessScoreAdjustIntervalMs = 500;
aura::client::ActivationClient* GetActivationClient() {
if (!ash::Shell::HasInstance())
return nullptr;
return aura::client::GetActivationClient(ash::Shell::GetPrimaryRootWindow());
}
// Checks if a window renders ARC apps.
bool IsArcWindow(aura::Window* window) {
if (!window || window->name() != kExoShellSurfaceWindowName)
return false;
std::string application_id = exo::ShellSurface::GetApplicationId(window);
return application_id.find(kArcProcessNamePrefix) == 0;
}
bool IsArcWindowInForeground() {
auto activation_client = GetActivationClient();
return activation_client && IsArcWindow(activation_client->GetActiveWindow());
}
int AppStateToPriority(
const arc::mojom::ProcessState& process_state) {
// Logic copied from Android:
// frameworks/base/core/java/android/app/ActivityManager.java
// Note that ProcessState enumerates from most important (lower value) to
// least important (higher value), while ProcessPriority enumerates the
// opposite.
if (process_state >= arc::mojom::ProcessState::HOME) {
return ProcessPriority::ANDROID_BACKGROUND;
} else if (process_state >= arc::mojom::ProcessState::SERVICE) {
return ProcessPriority::ANDROID_SERVICE;
} else if (process_state >= arc::mojom::ProcessState::HEAVY_WEIGHT) {
return ProcessPriority::ANDROID_CANT_SAVE_STATE;
} else if (process_state >= arc::mojom::ProcessState::IMPORTANT_BACKGROUND) {
return ProcessPriority::ANDROID_PERCEPTIBLE;
} else if (process_state >= arc::mojom::ProcessState::IMPORTANT_FOREGROUND) {
return ProcessPriority::ANDROID_VISIBLE;
} else if (process_state >= arc::mojom::ProcessState::TOP_SLEEPING) {
return ProcessPriority::ANDROID_TOP_SLEEPING;
} else if (process_state >= arc::mojom::ProcessState::FOREGROUND_SERVICE) {
return ProcessPriority::ANDROID_FOREGROUND_SERVICE;
} else if (process_state >= arc::mojom::ProcessState::TOP) {
return IsArcWindowInForeground() ?
ProcessPriority::ANDROID_TOP :
ProcessPriority::ANDROID_TOP_INACTIVE;
} else if (process_state >= arc::mojom::ProcessState::PERSISTENT) {
return ProcessPriority::ANDROID_PERSISTENT;
}
return ProcessPriority::ANDROID_NON_EXISTS;
}
int TabStatsToPriority(const TabStats& tab) {
if (tab.is_selected)
return ProcessPriority::CHROME_SELECTED;
int priority = 0;
if (tab.is_app) {
priority = ProcessPriority::CHROME_APP;
} else if (tab.is_internal_page) {
priority = ProcessPriority::CHROME_INTERNAL;
} else {
priority = ProcessPriority::CHROME_NORMAL;
}
if (tab.is_pinned)
priority |= ProcessPriority::CHROME_PINNED;
if (tab.is_media)
priority |= ProcessPriority::CHROME_MEDIA;
if (tab.has_form_entry)
priority |= ProcessPriority::CHROME_CANT_SAVE_STATE;
return priority;
}
bool IsArcMemoryManagementEnabled() {
return base::FeatureList::IsEnabled(features::kArcMemoryManagement);
}
} // namespace
std::ostream& operator<<(
std::ostream& out, const TabManagerDelegate::Candidate& candidate) {
if (candidate.is_arc_app) {
out << "app " << candidate.app->pid()
<< " (" << candidate.app->process_name() << ")";
} else {
out << "tab " << candidate.tab->renderer_handle;
}
out << " with priority " << candidate.priority;
return out;
}
// Holds the info of a newly focused tab or app window. The focused process is
// set to highest priority (lowest OOM score), but not immediately. To avoid
// redundant settings the OOM score adjusting only happens after a timeout. If
// the process loses focus before the timeout, the adjustment is canceled.
//
// This information might be set on UI thread and looked up on FILE thread. So a
// lock is needed to avoid racing.
class TabManagerDelegate::FocusedProcess {
public:
static const int kInvalidArcAppNspid = 0;
struct Data {
union {
// If a chrome tqab.
base::ProcessHandle pid;
// If an ARC app.
int nspid;
};
bool is_arc_app;
};
void SetTabPid(base::ProcessHandle pid) {
Data* data = new Data();
data->is_arc_app = false;
data->pid = pid;
base::AutoLock lock(lock_);
data_.reset(data);
}
void SetArcAppNspid(int nspid) {
Data* data = new Data();
data->is_arc_app = true;
data->nspid = nspid;
base::AutoLock lock(lock_);
data_.reset(data);
}
// Getter. Returns kNullProcessHandle if the process is not a tab.
base::ProcessHandle GetTabPid() {
base::AutoLock lock(lock_);
if (data_ && !data_->is_arc_app)
return data_->pid;
return base::kNullProcessHandle;
}
// Getter. Returns kInvalidArcAppNspid if the process is not an arc app.
int GetArcAppNspid() {
base::AutoLock lock(lock_);
if (data_ && data_->is_arc_app)
return data_->nspid;
return kInvalidArcAppNspid;
}
// An atomic operation which checks whether the containing instance is an ARC
// app. If so it resets the data and returns true. Useful when canceling an
// ongoing OOM score setting for a focused ARC app because the focus has been
// shifted away shortly.
bool ResetIfIsArcApp() {
base::AutoLock lock(lock_);
if (data_ && data_->is_arc_app) {
data_.reset();
return true;
}
return false;
}
private:
std::unique_ptr<Data> data_;
// Protects rw access to data_;
base::Lock lock_;
};
// TabManagerDelegate::MemoryStat implementation.
// static
int TabManagerDelegate::MemoryStat::ReadIntFromFile(
const char* file_name, const int default_val) {
std::string file_string;
if (!base::ReadFileToString(base::FilePath(file_name), &file_string)) {
LOG(WARNING) << "Unable to read file" << file_name;
return default_val;
}
int val = default_val;
if (!base::StringToInt(
base::TrimWhitespaceASCII(file_string, base::TRIM_TRAILING),
&val)) {
LOG(WARNING) << "Unable to parse string" << file_string;
return default_val;
}
return val;
}
// static
int TabManagerDelegate::MemoryStat::LowMemoryMarginKB() {
static const int kDefaultLowMemoryMarginMb = 50;
static const char kLowMemoryMarginConfig[] =
"/sys/kernel/mm/chromeos-low_mem/margin";
return ReadIntFromFile(
kLowMemoryMarginConfig, kDefaultLowMemoryMarginMb) * 1024;
}
// The logic of available memory calculation is copied from
// _is_low_mem_situation() in kernel file include/linux/low-mem-notify.h.
// Maybe we should let kernel report the number directly.
int TabManagerDelegate::MemoryStat::TargetMemoryToFreeKB() {
static const int kRamVsSwapWeight = 4;
static const char kMinFilelistConfig[] = "/proc/sys/vm/min_filelist_kbytes";
base::SystemMemoryInfoKB system_mem;
base::GetSystemMemoryInfo(&system_mem);
const int file_mem_kb = system_mem.active_file + system_mem.inactive_file;
const int min_filelist_kb = ReadIntFromFile(kMinFilelistConfig, 0);
// Calculate current available memory in system.
// File-backed memory should be easy to reclaim, unless they're dirty.
const int available_mem_kb = system_mem.free +
file_mem_kb - system_mem.dirty - min_filelist_kb +
system_mem.swap_free / kRamVsSwapWeight;
return LowMemoryMarginKB() - available_mem_kb;
}
int TabManagerDelegate::MemoryStat::EstimatedMemoryFreedKB(
base::ProcessHandle pid) {
std::unique_ptr<base::ProcessMetrics> process_metrics(
base::ProcessMetrics::CreateProcessMetrics(pid));
base::WorkingSetKBytes mem_usage;
process_metrics->GetWorkingSetKBytes(&mem_usage);
return mem_usage.priv;
}
class TabManagerDelegate::UmaReporter {
public:
UmaReporter() : last_kill_time_(), total_kills_(0) {}
~UmaReporter() {}
void ReportKill(const int memory_freed);
private:
base::Time last_kill_time_;
int total_kills_;
};
void TabManagerDelegate::UmaReporter::ReportKill(const int memory_freed) {
base::Time now = base::Time::Now();
const TimeDelta time_delta =
last_kill_time_.is_null() ?
TimeDelta::FromSeconds(arc::kMaxOomMemoryKillTimeDeltaSecs) :
(now - last_kill_time_);
UMA_HISTOGRAM_OOM_KILL_TIME_INTERVAL(
"Arc.LowMemoryKiller.TimeDelta", time_delta);
last_kill_time_ = now;
++total_kills_;
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Arc.LowMemoryKiller.Count", total_kills_, 1, 1000, 1001);
UMA_HISTOGRAM_MEMORY_KB("Arc.LowMemoryKiller.FreedSize",
memory_freed);
}
TabManagerDelegate::TabManagerDelegate(
const base::WeakPtr<TabManager>& tab_manager)
: TabManagerDelegate(tab_manager, new MemoryStat()) {
}
TabManagerDelegate::TabManagerDelegate(
const base::WeakPtr<TabManager>& tab_manager,
TabManagerDelegate::MemoryStat* mem_stat)
: tab_manager_(tab_manager),
focused_process_(new FocusedProcess()),
mem_stat_(mem_stat),
arc_process_instance_(nullptr),
arc_process_instance_version_(0),
uma_(new UmaReporter()),
weak_ptr_factory_(this) {
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());
auto arc_bridge_service = arc::ArcBridgeService::Get();
if (arc_bridge_service)
arc_bridge_service->process()->AddObserver(this);
auto activation_client = GetActivationClient();
if (activation_client)
activation_client->AddObserver(this);
BrowserList::GetInstance()->AddObserver(this);
}
TabManagerDelegate::~TabManagerDelegate() {
BrowserList::GetInstance()->RemoveObserver(this);
auto activation_client = GetActivationClient();
if (activation_client)
activation_client->RemoveObserver(this);
auto arc_bridge_service = arc::ArcBridgeService::Get();
if (arc_bridge_service)
arc_bridge_service->process()->RemoveObserver(this);
}
void TabManagerDelegate::OnBrowserSetLastActive(Browser* browser) {
// Set OOM score to the selected tab when a browser window is activated.
// content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED didn't catch the
// case (like when switching focus between 2 browser windows) so we need to
// handle it here.
TabStripModel* tab_strip_model = browser->tab_strip_model();
int selected_index = tab_strip_model->active_index();
content::WebContents* contents =
tab_strip_model->GetWebContentsAt(selected_index);
if (!contents)
return;
base::ProcessHandle pid = contents->GetRenderProcessHost()->GetHandle();
AdjustFocusedTabScore(pid);
}
void TabManagerDelegate::OnInstanceReady() {
auto arc_bridge_service = arc::ArcBridgeService::Get();
DCHECK(arc_bridge_service);
arc_process_instance_ = arc_bridge_service->process()->instance();
arc_process_instance_version_ = arc_bridge_service->process()->version();
DCHECK(arc_process_instance_);
if (!IsArcMemoryManagementEnabled())
return;
if (arc_process_instance_version_ < 2) {
VLOG(1) << "ProcessInstance version < 2 does not "
"support DisableBuiltinOomAdjustment() yet.";
return;
}
// Stop Android system-wide oom_adj adjustment since this class will
// take over oom_score_adj settings.
arc_process_instance_->DisableBuiltinOomAdjustment();
if (arc_process_instance_version_ < 3) {
VLOG(1) << "arc::ProcessInstance version < 3 does not "
"support DisableLowMemoryKiller() yet.";
return;
}
VLOG(2) << "Disable LowMemoryKiller";
arc_process_instance_->DisableLowMemoryKiller();
}
void TabManagerDelegate::OnInstanceClosed() {
arc_process_instance_ = nullptr;
arc_process_instance_version_ = 0;
}
void TabManagerDelegate::OnWindowActivated(
aura::client::ActivationChangeObserver::ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) {
if (IsArcWindow(gained_active)) {
// Currently there is no way to know which app is displayed in the ARC
// window, so schedule an early adjustment for all processes to reflect
// the change.
// Put a dummy FocusedProcess with nspid = kInvalidArcAppNspid for now to
// indicate the focused process is an arc app.
// TODO(cylee): Fix it when we have nspid info in ARC windows.
focused_process_->SetArcAppNspid(FocusedProcess::kInvalidArcAppNspid);
// If the timer is already running (possibly for a tab), it'll be reset
// here.
focus_process_score_adjust_timer_.Start(
FROM_HERE,
TimeDelta::FromMilliseconds(kFocusedProcessScoreAdjustIntervalMs),
this, &TabManagerDelegate::ScheduleEarlyOomPrioritiesAdjustment);
}
if (IsArcWindow(lost_active)) {
// Do not bother adjusting OOM score if the ARC window is deactivated
// shortly.
if (focused_process_->ResetIfIsArcApp() &&
focus_process_score_adjust_timer_.IsRunning())
focus_process_score_adjust_timer_.Stop();
}
}
void TabManagerDelegate::ScheduleEarlyOomPrioritiesAdjustment() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (tab_manager_) {
AdjustOomPriorities(tab_manager_->GetUnsortedTabStats());
}
}
// If able to get the list of ARC procsses, prioritize tabs and apps as a whole.
// Otherwise try to kill tabs only.
void TabManagerDelegate::LowMemoryKill(
const TabStatsList& tab_list) {
arc::ArcProcessService* arc_process_service = arc::ArcProcessService::Get();
if (arc_process_service &&
arc_process_service->RequestProcessList(
base::Bind(&TabManagerDelegate::LowMemoryKillImpl,
weak_ptr_factory_.GetWeakPtr(), tab_list))) {
// LowMemoryKillImpl will be called asynchronously so nothing left to do.
return;
}
// If the list of ARC processes is not available, call LowMemoryKillImpl
// synchronously with an empty list of apps.
std::vector<arc::ArcProcess> dummy_apps;
LowMemoryKillImpl(tab_list, dummy_apps);
}
int TabManagerDelegate::GetCachedOomScore(ProcessHandle process_handle) {
base::AutoLock oom_score_autolock(oom_score_lock_);
auto it = oom_score_map_.find(process_handle);
if (it != oom_score_map_.end()) {
return it->second;
}
// An impossible value for oom_score_adj.
return -1001;
}
void TabManagerDelegate::AdjustFocusedTabScoreOnFileThread() {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
base::ProcessHandle pid = focused_process_->GetTabPid();
// The focused process doesn't render a tab. Could happen when the focus
// just switched to an ARC app. We can not avoid the race.
if (pid == base::kNullProcessHandle)
return;
{
base::AutoLock oom_score_autolock(oom_score_lock_);
oom_score_map_[pid] = chrome::kLowestRendererOomScore;
}
VLOG(3) << "Set OOM score " << chrome::kLowestRendererOomScore
<< " for focused tab " << pid;
content::ZygoteHost::GetInstance()->AdjustRendererOOMScore(
pid, chrome::kLowestRendererOomScore);
}
void TabManagerDelegate::OnFocusTabScoreAdjustmentTimeout() {
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&TabManagerDelegate::AdjustFocusedTabScoreOnFileThread,
base::Unretained(this)));
}
void TabManagerDelegate::AdjustFocusedTabScore(base::ProcessHandle pid) {
// Clear running timer if one was set for a previous focused tab/app.
if (focus_process_score_adjust_timer_.IsRunning())
focus_process_score_adjust_timer_.Stop();
focused_process_->SetTabPid(pid);
bool not_lowest_score = false;
{
base::AutoLock oom_score_autolock(oom_score_lock_);
// 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 = oom_score_map_.find(pid);
not_lowest_score = (it == oom_score_map_.end() ||
it->second != chrome::kLowestRendererOomScore);
}
if (not_lowest_score) {
// 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 there's an existing running timer (could be for ARC app), it
// would be replaced by a new task.
focus_process_score_adjust_timer_.Start(
FROM_HERE,
TimeDelta::FromMilliseconds(kFocusedProcessScoreAdjustIntervalMs),
this, &TabManagerDelegate::OnFocusTabScoreAdjustmentTimeout);
}
}
void TabManagerDelegate::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
switch (type) {
case content::NOTIFICATION_RENDERER_PROCESS_CLOSED:
case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED: {
content::RenderProcessHost* host =
content::Source<content::RenderProcessHost>(source).ptr();
{
base::AutoLock oom_score_autolock(oom_score_lock_);
oom_score_map_.erase(host->GetHandle());
}
// 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();
AdjustFocusedTabScore(render_host->GetHandle());
}
// Do not handle the "else" case when it changes to invisible because
// 1. The behavior is a bit awkward in that when switching from tab A to
// tab B, the event "invisible of B" comes after "visible of A". It can
// cause problems when the 2 tabs have the same content (e.g., New Tab
// Page). To be more clear, if we try to cancel the timer when losing
// focus it may cancel the timer for the same renderer process.
// 2. When another window is launched on top of an existing browser
// window, the selected tab in the existing browser didn't receive this
// event, so an attempt to cancel timer in this case doesn't work.
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& tab_list) {
if (IsArcMemoryManagementEnabled()) {
arc::ArcProcessService* arc_process_service = arc::ArcProcessService::Get();
if (arc_process_service &&
arc_process_service->RequestProcessList(
base::Bind(&TabManagerDelegate::AdjustOomPrioritiesImpl,
weak_ptr_factory_.GetWeakPtr(), tab_list))) {
return;
}
}
// Pass in a dummy list if unable to get ARC processes.
AdjustOomPrioritiesImpl(tab_list, std::vector<arc::ArcProcess>());
}
// Excludes persistent ARC apps, but still preserves active chrome tabs and
// top ARC apps. The latter ones should not be killed by TabManager since
// we still want to adjust their oom_score_adj.
// static
std::vector<TabManagerDelegate::Candidate>
TabManagerDelegate::GetSortedCandidates(
const TabStatsList& tab_list,
const std::vector<arc::ArcProcess>& arc_processes) {
std::vector<Candidate> candidates;
candidates.reserve(tab_list.size() + arc_processes.size());
for (const auto& tab : tab_list) {
candidates.push_back(Candidate(&tab, TabStatsToPriority(tab)));
}
for (const auto& app : arc_processes) {
Candidate candidate(&app, AppStateToPriority(app.process_state()));
// Skip persistent android processes since we should never kill them.
// Also don't ajust OOM score so their score remains min oom_score_adj.
if (candidate.priority >= ProcessPriority::ANDROID_PERSISTENT)
continue;
candidates.push_back(candidate);
}
// Sort candidates according to priority.
// TODO(cylee): Missing LRU property. Fix it when apps has the information.
std::sort(candidates.begin(), candidates.end());
return candidates;
}
bool TabManagerDelegate::KillArcProcess(const int nspid) {
if (!arc_process_instance_)
return false;
arc_process_instance_->KillProcess(nspid, "LowMemoryKill");
return true;
}
bool TabManagerDelegate::KillTab(int64_t tab_id) {
// Check |tab_manager_| is alive before taking tabs into consideration.
return tab_manager_ &&
tab_manager_->CanDiscardTab(tab_id) &&
tab_manager_->DiscardTabById(tab_id);
}
void TabManagerDelegate::LowMemoryKillImpl(
const TabStatsList& tab_list,
const std::vector<arc::ArcProcess>& arc_processes) {
VLOG(2) << "LowMemoryKilleImpl";
std::vector<TabManagerDelegate::Candidate> candidates =
GetSortedCandidates(tab_list, arc_processes);
int target_memory_to_free_kb = mem_stat_->TargetMemoryToFreeKB();
for (const auto& entry : candidates) {
VLOG(3) << "Target memory to free: " << target_memory_to_free_kb << " KB";
// Never kill selected tab or Android foreground app, regardless whether
// they're in the active window. Since the user experience would be bad.
if ((!entry.is_arc_app &&
entry.priority >= ProcessPriority::CHROME_SELECTED) ||
(entry.is_arc_app &&
entry.priority >= ProcessPriority::ANDROID_TOP_INACTIVE)) {
VLOG(2) << "Skipped killing " << entry;
continue;
}
if (entry.is_arc_app) {
int estimated_memory_freed_kb =
mem_stat_->EstimatedMemoryFreedKB(entry.app->pid());
if (KillArcProcess(entry.app->nspid())) {
target_memory_to_free_kb -= estimated_memory_freed_kb;
uma_->ReportKill(estimated_memory_freed_kb);
VLOG(2) << "Killed " << entry;
}
} else {
int64_t tab_id = entry.tab->tab_contents_id;
int estimated_memory_freed_kb =
mem_stat_->EstimatedMemoryFreedKB(entry.tab->renderer_handle);
if (KillTab(tab_id)) {
target_memory_to_free_kb -= estimated_memory_freed_kb;
uma_->ReportKill(estimated_memory_freed_kb);
VLOG(2) << "Killed " << entry;
}
}
if (target_memory_to_free_kb < 0)
break;
}
}
void TabManagerDelegate::AdjustOomPrioritiesImpl(
const TabStatsList& tab_list,
const std::vector<arc::ArcProcess>& arc_processes) {
// Least important first.
auto candidates = GetSortedCandidates(tab_list, arc_processes);
// 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.
// Break the processes into 2 parts. This is to help lower the chance of
// altering OOM score for many processes on any small change.
int range_middle =
(chrome::kLowestRendererOomScore + chrome::kHighestRendererOomScore) / 2;
// Find some pivot point. For now processes with priority >= CHROME_INTERNAL
// are prone to be affected by LRU change. Taking them as "high priority"
// processes.
auto lower_priority_part = candidates.rend();
// Iterate in reverse order since the list is sorted by least importance.
for (auto it = candidates.rbegin(); it != candidates.rend(); ++it) {
if (it->priority < ProcessPriority::CHROME_INTERNAL) {
lower_priority_part = it;
break;
}
}
ProcessScoreMap new_map;
// Higher priority part.
DistributeOomScoreInRange(candidates.rbegin(), lower_priority_part,
chrome::kLowestRendererOomScore, range_middle,
&new_map);
// Lower priority part.
DistributeOomScoreInRange(lower_priority_part, candidates.rend(),
range_middle, chrome::kHighestRendererOomScore,
&new_map);
base::AutoLock oom_score_autolock(oom_score_lock_);
oom_score_map_.swap(new_map);
}
void TabManagerDelegate::SetOomScoreAdjForApp(int nspid, int score) {
if (!arc_process_instance_)
return;
if (arc_process_instance_version_ < 2) {
VLOG(1) << "ProcessInstance version < 2 does not "
"support SetOomScoreAdj() yet.";
return;
}
arc_process_instance_->SetOomScoreAdj(nspid, score);
}
void TabManagerDelegate::SetOomScoreAdjForTabs(
const std::vector<std::pair<base::ProcessHandle, int>>& entries) {
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&TabManagerDelegate::SetOomScoreAdjForTabsOnFileThread,
base::Unretained(this), entries));
}
void TabManagerDelegate::SetOomScoreAdjForTabsOnFileThread(
const std::vector<std::pair<base::ProcessHandle, int>>& entries) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
for (const auto& entry : entries) {
content::ZygoteHost::GetInstance()->AdjustRendererOOMScore(entry.first,
entry.second);
}
}
void TabManagerDelegate::DistributeOomScoreInRange(
std::vector<TabManagerDelegate::Candidate>::reverse_iterator rbegin,
std::vector<TabManagerDelegate::Candidate>::reverse_iterator rend,
int range_begin,
int range_end,
ProcessScoreMap* new_map) {
// OOM score setting for tabs involves file system operation so should be
// done on file thread.
std::vector<std::pair<base::ProcessHandle, int>> oom_score_for_tabs;
// Though there might be duplicate process handles, it doesn't matter to
// overestimate the number of processes here since the we don't need to
// use up the full range.
int num = (rend - rbegin);
const float priority_increment =
static_cast<float>(range_end - range_begin) / num;
float priority = range_begin;
for (auto cur = rbegin; cur != rend; ++cur) {
int score = static_cast<int>(priority + 0.5f);
if (cur->is_arc_app) {
// Use pid as map keys so it's globally unique.
(*new_map)[cur->app->pid()] = score;
int cur_app_pid_score = 0;
{
base::AutoLock oom_score_autolock(oom_score_lock_);
cur_app_pid_score = oom_score_map_[cur->app->pid()];
}
if (cur_app_pid_score != score) {
VLOG(3) << "Set OOM score " << score << " for " << *cur;
SetOomScoreAdjForApp(cur->app->nspid(), score);
}
} else {
base::ProcessHandle process_handle = cur->tab->renderer_handle;
// 1. tab_list contains entries for already-discarded tabs. If the PID
// (renderer_handle) is zero, we don't need to adjust the oom_score.
// 2. Only add unseen process handle so if there's multiple tab maps to
// the same process, the process is set to an OOM score based on its "most
// important" tab.
if (process_handle != 0 &&
new_map->find(process_handle) == new_map->end()) {
(*new_map)[process_handle] = score;
int process_handle_score = 0;
{
base::AutoLock oom_score_autolock(oom_score_lock_);
process_handle_score = oom_score_map_[process_handle];
}
if (process_handle_score != score) {
oom_score_for_tabs.push_back(std::make_pair(process_handle, score));
VLOG(3) << "Set OOM score " << score << " for " << *cur;
}
} else {
continue; // Skip priority increment.
}
}
priority += priority_increment;
}
if (oom_score_for_tabs.size())
SetOomScoreAdjForTabs(oom_score_for_tabs);
}
} // namespace memory