text: // 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
  