| Index: chrome/browser/memory/tab_manager_delegate_chromeos.cc
|
| diff --git a/chrome/browser/memory/tab_manager_delegate_chromeos.cc b/chrome/browser/memory/tab_manager_delegate_chromeos.cc
|
| deleted file mode 100644
|
| index 5a075394a8875665e907d7082db72e3822fdb587..0000000000000000000000000000000000000000
|
| --- a/chrome/browser/memory/tab_manager_delegate_chromeos.cc
|
| +++ /dev/null
|
| @@ -1,776 +0,0 @@
|
| -// 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 <math.h>
|
| -#include <stdint.h>
|
| -
|
| -#include <algorithm>
|
| -#include <map>
|
| -#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/time/time.h"
|
| -#include "chrome/browser/chromeos/arc/process/arc_process.h"
|
| -#include "chrome/browser/chromeos/arc/process/arc_process_service.h"
|
| -#include "chrome/browser/memory/memory_kills_monitor.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 "chromeos/dbus/dbus_thread_manager.h"
|
| -#include "components/arc/arc_bridge_service.h"
|
| -#include "components/arc/arc_service_manager.h"
|
| -#include "components/arc/arc_util.h"
|
| -#include "components/device_event_log/device_event_log.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/wm/public/activation_client.h"
|
| -
|
| -using base::ProcessHandle;
|
| -using base::TimeDelta;
|
| -using base::TimeTicks;
|
| -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 kFocusedProcessScoreAdjustIntervalMs = 500;
|
| -
|
| -wm::ActivationClient* GetActivationClient() {
|
| - if (!ash::Shell::HasInstance())
|
| - return nullptr;
|
| - return wm::GetActivationClient(ash::Shell::GetPrimaryRootWindow());
|
| -}
|
| -
|
| -bool IsArcMemoryManagementEnabled() {
|
| - return base::FeatureList::IsEnabled(features::kArcMemoryManagement);
|
| -}
|
| -
|
| -void OnSetOomScoreAdj(bool success, const std::string& output) {
|
| - VLOG(2) << "OnSetOomScoreAdj " << success << " " << output;
|
| - if (!success)
|
| - LOG(ERROR) << "Set OOM score error: " << output;
|
| - else if (!output.empty())
|
| - LOG(WARNING) << "Set OOM score: " << output;
|
| -}
|
| -
|
| -} // namespace
|
| -
|
| -// static
|
| -const int TabManagerDelegate::kLowestOomScore = -1000;
|
| -
|
| -std::ostream& operator<<(std::ostream& os, const ProcessType& type) {
|
| - switch (type) {
|
| - case ProcessType::FOCUSED_TAB:
|
| - return os << "FOCUSED_TAB";
|
| - case ProcessType::FOCUSED_APP:
|
| - return os << "FOCUSED_APP";
|
| - case ProcessType::IMPORTANT_APP:
|
| - return os << "IMPORTANT_APP";
|
| - case ProcessType::BACKGROUND_TAB:
|
| - return os << "BACKGROUND_TAB";
|
| - case ProcessType::BACKGROUND_APP:
|
| - return os << "BACKGROUND_APP";
|
| - case ProcessType::UNKNOWN_TYPE:
|
| - return os << "UNKNOWN_TYPE";
|
| - default:
|
| - return os << "NOT_IMPLEMENTED_ERROR";
|
| - }
|
| - return os;
|
| -}
|
| -
|
| -// TabManagerDelegate::Candidate implementation.
|
| -std::ostream& operator<<(
|
| - std::ostream& out, const TabManagerDelegate::Candidate& candidate) {
|
| - if (candidate.app()) {
|
| - out << "app " << *candidate.app();
|
| - } else if (candidate.tab()) {
|
| - const TabStats* const& tab = candidate.tab();
|
| - out << "tab " << tab->title << ", renderer_handle: " << tab->renderer_handle
|
| - << ", oom_score: " << tab->oom_score
|
| - << ", is_discarded: " << tab->is_discarded
|
| - << ", discard_count: " << tab->discard_count
|
| - << ", last_active: " << tab->last_active;
|
| - }
|
| - out << ", process_type " << candidate.process_type();
|
| - return out;
|
| -}
|
| -
|
| -TabManagerDelegate::Candidate& TabManagerDelegate::Candidate::operator=(
|
| - TabManagerDelegate::Candidate&& other) {
|
| - tab_ = other.tab_;
|
| - app_ = other.app_;
|
| - process_type_ = other.process_type_;
|
| - return *this;
|
| -}
|
| -
|
| -bool TabManagerDelegate::Candidate::operator<(
|
| - const TabManagerDelegate::Candidate& rhs) const {
|
| - if (process_type() != rhs.process_type())
|
| - return process_type() < rhs.process_type();
|
| - if (app() && rhs.app())
|
| - return *app() < *rhs.app();
|
| - if (tab() && rhs.tab())
|
| - return TabManager::CompareTabStats(*tab(), *rhs.tab());
|
| - // Impossible case. If app and tab are mixed in one process type, favor
|
| - // apps.
|
| - NOTREACHED() << "Undefined comparison between apps and tabs: process_type="
|
| - << process_type();
|
| - return app();
|
| -}
|
| -
|
| -ProcessType TabManagerDelegate::Candidate::GetProcessTypeInternal() const {
|
| - if (app()) {
|
| - if (app()->is_focused())
|
| - return ProcessType::FOCUSED_APP;
|
| - if (app()->IsImportant())
|
| - return ProcessType::IMPORTANT_APP;
|
| - return ProcessType::BACKGROUND_APP;
|
| - }
|
| - if (tab()) {
|
| - if (tab()->is_selected)
|
| - return ProcessType::FOCUSED_TAB;
|
| - return ProcessType::BACKGROUND_TAB;
|
| - }
|
| - NOTREACHED() << "Unexpected process type";
|
| - return ProcessType::UNKNOWN_TYPE;
|
| -}
|
| -
|
| -// 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.
|
| -class TabManagerDelegate::FocusedProcess {
|
| - public:
|
| - static const int kInvalidArcAppNspid = 0;
|
| -
|
| - FocusedProcess() { Reset(); }
|
| -
|
| - void SetTabPid(const base::ProcessHandle pid) {
|
| - pid_ = pid;
|
| - nspid_ = kInvalidArcAppNspid;
|
| - }
|
| -
|
| - void SetArcAppNspid(const int nspid) {
|
| - pid_ = base::kNullProcessHandle;
|
| - nspid_ = nspid;
|
| - }
|
| -
|
| - base::ProcessHandle GetTabPid() const { return pid_; }
|
| -
|
| - int GetArcAppNspid() const { return nspid_; }
|
| -
|
| - // 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() {
|
| - if (nspid_ != kInvalidArcAppNspid) {
|
| - Reset();
|
| - return true;
|
| - }
|
| - return false;
|
| - }
|
| -
|
| - private:
|
| - void Reset() {
|
| - pid_ = base::kNullProcessHandle;
|
| - nspid_ = kInvalidArcAppNspid;
|
| - }
|
| -
|
| - // The focused app could be a Chrome tab or an Android app, but not both.
|
| - // At most one of them contains a valid value at any time.
|
| -
|
| - // If a chrome tab.
|
| - base::ProcessHandle pid_;
|
| - // If an Android app.
|
| - int nspid_;
|
| -};
|
| -
|
| -// 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";
|
| - static const char kMinFreeKbytes[] = "/proc/sys/vm/min_free_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);
|
| - const int min_free_kb = ReadIntFromFile(kMinFreeKbytes, 0);
|
| - // Calculate current available memory in system.
|
| - // File-backed memory should be easy to reclaim, unless they're dirty.
|
| - // TODO(cylee): On ChromeOS, kernel reports low memory condition when
|
| - // available memory is low. The following formula duplicates the logic in
|
| - // kernel to calculate how much memory should be released. In the future,
|
| - // kernel should try to report the amount of memory to release directly to
|
| - // eliminate the duplication here.
|
| - const int available_mem_kb = system_mem.free +
|
| - file_mem_kb - system_mem.dirty - min_filelist_kb +
|
| - system_mem.swap_free / kRamVsSwapWeight -
|
| - min_free_kb;
|
| -
|
| - 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;
|
| -}
|
| -
|
| -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),
|
| - 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* 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);
|
| -}
|
| -
|
| -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::OnWindowActivated(
|
| - wm::ActivationChangeObserver::ActivationReason reason,
|
| - aura::Window* gained_active,
|
| - aura::Window* lost_active) {
|
| - if (arc::IsArcAppWindow(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 (arc::IsArcAppWindow(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->RequestAppProcessList(
|
| - 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) {
|
| - DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
| - 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::OnFocusTabScoreAdjustmentTimeout() {
|
| - DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
| - 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 before the timeout. We can not avoid the race.
|
| - if (pid == base::kNullProcessHandle)
|
| - return;
|
| -
|
| - // Update the OOM score cache.
|
| - oom_score_map_[pid] = chrome::kLowestRendererOomScore;
|
| -
|
| - // Sets OOM score.
|
| - VLOG(3) << "Set OOM score " << chrome::kLowestRendererOomScore
|
| - << " for focused tab " << pid;
|
| - std::map<int, int> dict;
|
| - dict[pid] = chrome::kLowestRendererOomScore;
|
| - GetDebugDaemonClient()->SetOomScoreAdj(dict, base::Bind(&OnSetOomScoreAdj));
|
| -}
|
| -
|
| -void TabManagerDelegate::AdjustFocusedTabScore(base::ProcessHandle pid) {
|
| - DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
| -
|
| - // 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);
|
| -
|
| - // 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);
|
| - const bool 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();
|
| - 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->RequestAppProcessList(
|
| - 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
|
| -// focused ARC apps. The latter ones should not be killed by TabManager here,
|
| -// but we 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.emplace_back(&tab);
|
| - }
|
| -
|
| - for (const auto& app : arc_processes) {
|
| - candidates.emplace_back(&app);
|
| - }
|
| -
|
| - // Sort candidates according to priority.
|
| - std::sort(candidates.begin(), candidates.end());
|
| -
|
| - return candidates;
|
| -}
|
| -
|
| -bool TabManagerDelegate::IsRecentlyKilledArcProcess(
|
| - const std::string& process_name,
|
| - const TimeTicks& now) {
|
| - const auto it = recently_killed_arc_processes_.find(process_name);
|
| - if (it == recently_killed_arc_processes_.end())
|
| - return false;
|
| - return (now - it->second) <= GetArcRespawnKillDelay();
|
| -}
|
| -
|
| -bool TabManagerDelegate::KillArcProcess(const int nspid) {
|
| - auto* arc_service_manager = arc::ArcServiceManager::Get();
|
| - if (!arc_service_manager)
|
| - return false;
|
| -
|
| - auto* arc_process_instance = ARC_GET_INSTANCE_FOR_METHOD(
|
| - arc_service_manager->arc_bridge_service()->process(), KillProcess);
|
| - 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);
|
| -}
|
| -
|
| -
|
| -chromeos::DebugDaemonClient* TabManagerDelegate::GetDebugDaemonClient() {
|
| - return chromeos::DBusThreadManager::Get()->GetDebugDaemonClient();
|
| -}
|
| -
|
| -void TabManagerDelegate::LowMemoryKillImpl(
|
| - const TabStatsList& tab_list,
|
| - const std::vector<arc::ArcProcess>& arc_processes) {
|
| - DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
| - VLOG(2) << "LowMemoryKillImpl";
|
| -
|
| - const std::vector<TabManagerDelegate::Candidate> candidates =
|
| - GetSortedCandidates(tab_list, arc_processes);
|
| -
|
| - int target_memory_to_free_kb = mem_stat_->TargetMemoryToFreeKB();
|
| - const TimeTicks now = TimeTicks::Now();
|
| -
|
| - MEMORY_LOG(ERROR) << "List of low memory kill candidates "
|
| - "(sorted from low priority to high priority):";
|
| - for (auto it = candidates.rbegin(); it != candidates.rend(); ++it) {
|
| - MEMORY_LOG(ERROR) << *it;
|
| - }
|
| - // Kill processes until the estimated amount of freed memory is sufficient to
|
| - // bring the system memory back to a normal level.
|
| - // The list is sorted by descending importance, so we go through the list
|
| - // backwards.
|
| - for (auto it = candidates.rbegin(); it != candidates.rend(); ++it) {
|
| - MEMORY_LOG(ERROR) << "Target memory to free: " << target_memory_to_free_kb
|
| - << " KB";
|
| - if (target_memory_to_free_kb <= 0)
|
| - break;
|
| - // Never kill selected tab, foreground app, and important apps regardless of
|
| - // whether they're in the active window. Since the user experience would be
|
| - // bad.
|
| - ProcessType process_type = it->process_type();
|
| - if (process_type <= ProcessType::IMPORTANT_APP) {
|
| - MEMORY_LOG(ERROR) << "Skipped killing " << it->app()->process_name();
|
| - continue;
|
| - }
|
| - if (it->app()) {
|
| - if (IsRecentlyKilledArcProcess(it->app()->process_name(), now)) {
|
| - MEMORY_LOG(ERROR) << "Avoided killing " << it->app()->process_name()
|
| - << " too often";
|
| - continue;
|
| - }
|
| - int estimated_memory_freed_kb =
|
| - mem_stat_->EstimatedMemoryFreedKB(it->app()->pid());
|
| - if (KillArcProcess(it->app()->nspid())) {
|
| - recently_killed_arc_processes_[it->app()->process_name()] = now;
|
| - target_memory_to_free_kb -= estimated_memory_freed_kb;
|
| - MemoryKillsMonitor::LogLowMemoryKill("APP", estimated_memory_freed_kb);
|
| - MEMORY_LOG(ERROR) << "Killed app " << it->app()->process_name() << " ("
|
| - << it->app()->pid() << ")"
|
| - << ", estimated " << estimated_memory_freed_kb
|
| - << " KB freed";
|
| - } else {
|
| - MEMORY_LOG(ERROR) << "Failed to kill " << it->app()->process_name();
|
| - }
|
| - } else {
|
| - int64_t tab_id = it->tab()->tab_contents_id;
|
| - // The estimation is problematic since multiple tabs may share the same
|
| - // process, while the calculation counts memory used by the whole process.
|
| - // So |estimated_memory_freed_kb| is an over-estimation.
|
| - int estimated_memory_freed_kb =
|
| - mem_stat_->EstimatedMemoryFreedKB(it->tab()->renderer_handle);
|
| - if (KillTab(tab_id)) {
|
| - target_memory_to_free_kb -= estimated_memory_freed_kb;
|
| - MemoryKillsMonitor::LogLowMemoryKill("TAB", estimated_memory_freed_kb);
|
| - MEMORY_LOG(ERROR) << "Killed tab " << it->tab()->title << " ("
|
| - << it->tab()->renderer_handle << "), estimated "
|
| - << estimated_memory_freed_kb << " KB freed";
|
| - }
|
| - }
|
| - }
|
| - if (target_memory_to_free_kb > 0) {
|
| - MEMORY_LOG(ERROR)
|
| - << "Unable to kill enough candidates to meet target_memory_to_free_kb ";
|
| - }
|
| -}
|
| -
|
| -void TabManagerDelegate::AdjustOomPrioritiesImpl(
|
| - const TabStatsList& tab_list,
|
| - const std::vector<arc::ArcProcess>& arc_processes) {
|
| - std::vector<TabManagerDelegate::Candidate> candidates;
|
| - std::vector<TabManagerDelegate::Candidate> apps_non_killable;
|
| -
|
| - // Least important first.
|
| - auto all_candidates = GetSortedCandidates(tab_list, arc_processes);
|
| - for (auto& candidate : all_candidates) {
|
| - // TODO(cylee|yusukes): Consider using IsImportant() instead of
|
| - // IsKernelKillable() for simplicity.
|
| - // TODO(cylee): Also consider protecting FOCUSED_TAB from the kernel OOM
|
| - // killer so that Chrome and the kernel do the same regarding OOM handling.
|
| - if (!candidate.app() || candidate.app()->IsKernelKillable()) {
|
| - // Add tabs and killable apps to |candidates|.
|
| - candidates.emplace_back(std::move(candidate));
|
| - } else {
|
| - // Add non-killable apps to |apps_non_killable|.
|
| - apps_non_killable.emplace_back(std::move(candidate));
|
| - }
|
| - }
|
| -
|
| - // 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.end();
|
| - for (auto it = candidates.begin(); it != candidates.end(); ++it) {
|
| - if (it->process_type() >= ProcessType::BACKGROUND_APP) {
|
| - lower_priority_part = it;
|
| - break;
|
| - }
|
| - }
|
| -
|
| - ProcessScoreMap new_map;
|
| -
|
| - // Make the apps non-killable.
|
| - if (!apps_non_killable.empty())
|
| - SetOomScore(apps_non_killable, kLowestOomScore, &new_map);
|
| -
|
| - // Higher priority part.
|
| - DistributeOomScoreInRange(candidates.begin(), lower_priority_part,
|
| - chrome::kLowestRendererOomScore, range_middle,
|
| - &new_map);
|
| - // Lower priority part.
|
| - DistributeOomScoreInRange(lower_priority_part, candidates.end(), range_middle,
|
| - chrome::kHighestRendererOomScore, &new_map);
|
| -
|
| - oom_score_map_.swap(new_map);
|
| -}
|
| -
|
| -void TabManagerDelegate::SetOomScore(
|
| - const std::vector<TabManagerDelegate::Candidate>& candidates,
|
| - int score,
|
| - ProcessScoreMap* new_map) {
|
| - DistributeOomScoreInRange(candidates.begin(), candidates.end(), score, score,
|
| - new_map);
|
| -}
|
| -
|
| -void TabManagerDelegate::DistributeOomScoreInRange(
|
| - std::vector<TabManagerDelegate::Candidate>::const_iterator begin,
|
| - std::vector<TabManagerDelegate::Candidate>::const_iterator end,
|
| - int range_begin,
|
| - int range_end,
|
| - ProcessScoreMap* new_map) {
|
| - // Processes whose OOM scores should be updated. Ignore duplicated pids but
|
| - // the last occurrence.
|
| - std::map<base::ProcessHandle, int32_t> oom_scores_to_change;
|
| -
|
| - // 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 = (end - begin);
|
| - const float priority_increment =
|
| - static_cast<float>(range_end - range_begin) / num;
|
| -
|
| - float priority = range_begin;
|
| - for (auto cur = begin; cur != end; ++cur) {
|
| - const int score = round(priority);
|
| -
|
| - base::ProcessHandle pid = base::kNullProcessHandle;
|
| - if (cur->app()) {
|
| - pid = cur->app()->pid();
|
| - } else {
|
| - pid = 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 (pid == base::kNullProcessHandle ||
|
| - new_map->find(pid) != new_map->end())
|
| - continue;
|
| - }
|
| -
|
| - if (pid == base::kNullProcessHandle)
|
| - continue;
|
| -
|
| - // Update the to-be-cached OOM score map. Use pid as map keys so it's
|
| - // globally unique.
|
| - (*new_map)[pid] = score;
|
| -
|
| - // Need to update OOM score if the calculated score is different from
|
| - // current cached score.
|
| - if (oom_score_map_[pid] != score) {
|
| - VLOG(3) << "Update OOM score " << score << " for " << *cur;
|
| - oom_scores_to_change[pid] = static_cast<int32_t>(score);
|
| - }
|
| - priority += priority_increment;
|
| - }
|
| -
|
| - if (oom_scores_to_change.size()) {
|
| - GetDebugDaemonClient()->SetOomScoreAdj(
|
| - oom_scores_to_change, base::Bind(&OnSetOomScoreAdj));
|
| - }
|
| -}
|
| -
|
| -} // namespace memory
|
|
|