chromium/chrome/browser/performance_manager/policies/working_set_trimmer_policy_chromeos.h

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_POLICIES_WORKING_SET_TRIMMER_POLICY_CHROMEOS_H_
#define CHROME_BROWSER_PERFORMANCE_MANAGER_POLICIES_WORKING_SET_TRIMMER_POLICY_CHROMEOS_H_

#include <map>
#include <memory>
#include <optional>
#include <utility>

#include "base/memory/memory_pressure_listener.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "base/synchronization/lock.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "base/timer/timer.h"
#include "chrome/browser/ash/arc/process/arc_process_service.h"
#include "chrome/browser/performance_manager/mechanisms/working_set_trimmer_chromeos.h"
#include "chrome/browser/performance_manager/policies/policy_features.h"
#include "chrome/browser/performance_manager/policies/working_set_trimmer_policy.h"
#include "chromeos/dbus/power/power_manager_client.h"
#include "content/public/browser/browser_thread.h"

namespace arc {
class ArcProcess;
}

namespace performance_manager {
namespace policies {

class WorkingSetTrimmerPolicyChromeOSTest;

// ChromeOS specific WorkingSetTrimmerPolicy which uses the default policy on
// all frames frozen, additionally it will add working set trim under memory
// pressure.
class WorkingSetTrimmerPolicyChromeOS : public WorkingSetTrimmerPolicy,
                                        chromeos::PowerManagerClient::Observer {
 public:
  // A delegate interface for checking ARCVM status. This interface allows us 1)
  // to test WorkingSetTrimmerPolicyChromeOS more easily, and 2) to have all the
  // complicated logic that is very ARCVM specific in a separate class.
  class ArcVmDelegate {
   public:
    virtual ~ArcVmDelegate() = default;

    // Returns ReclaimType other than kReclaimNone when ARCVM has been idle for
    // more than |arcvm_inactivity_time| and therefore is safe to reclaim its
    // memory. The function is called only on the UI thread.
    // If |trim_once_type_after_arcvm_boot| is not kReclaimNone, the function
    // returns |trim_once_type_after_arcvm_boot| when the function is called for
    // the first time after ARCVM boot - in which case it will set the
    // value of |is_first_trim_post_boot| output parameter to true.
    // Use NULL for |is_first_trim_post_boot| if you don't care about
    // whether it is the first call post-boot or not.
    virtual mechanism::ArcVmReclaimType IsEligibleForReclaim(
        const base::TimeDelta& arcvm_inactivity_time,
        mechanism::ArcVmReclaimType trim_once_type_after_arcvm_boot,
        bool* is_first_trim_post_boot) = 0;
  };

  WorkingSetTrimmerPolicyChromeOS(const WorkingSetTrimmerPolicyChromeOS&) =
      delete;
  WorkingSetTrimmerPolicyChromeOS& operator=(
      const WorkingSetTrimmerPolicyChromeOS&) = delete;

  ~WorkingSetTrimmerPolicyChromeOS() override;
  WorkingSetTrimmerPolicyChromeOS();

  // Returns true if this platform supports working set trim, in the case of
  // Windows this will check that the appropriate flags are set for working set
  // trim.
  static bool PlatformSupportsWorkingSetTrim();

  // GraphOwned implementation:
  void OnTakenFromGraph(Graph* graph) override;
  void OnPassedToGraph(Graph* graph) override;

  // ProcessNodeObserver implementation:
  void OnAllFramesInProcessFrozen(const ProcessNode* process_node) override;

  // PowerManagerClient::Observer implementations:
  void SuspendImminent(power_manager::SuspendImminent::Reason reason) override;
  void SuspendDone(base::TimeDelta duration) override;

  // We maintain the last time we reclaimed an ARC process, to allow us to not
  // reclaim too frequently, this is configurable.
  base::TimeDelta GetTimeSinceLastArcProcessTrim(base::ProcessId pid) const;
  void SetArcProcessLastTrimTime(base::ProcessId pid, base::TimeTicks time);

  bool trim_on_freeze() const { return trim_on_freeze_; }
  bool trim_arc_on_memory_pressure() const {
    return trim_arc_on_memory_pressure_;
  }
  void set_arcvm_delegate_for_testing(ArcVmDelegate* delegate);

  virtual void TrimReceivedArcProcesses(
      int allowed_to_trim,
      arc::ArcProcessService::OptionalArcProcessList arc_processes);

 protected:
  friend class WorkingSetTrimmerPolicyChromeOSTest;

  // virtual for testing
  virtual void OnMemoryPressure(
      base::MemoryPressureListener::MemoryPressureLevel level);
  virtual mechanism::WorkingSetTrimmerChromeOS* GetTrimmer();

  void set_trim_on_freeze(bool enabled) { trim_on_freeze_ = enabled; }

  void set_trim_arc_on_memory_pressure(bool enabled) {
    trim_arc_on_memory_pressure_ = enabled;
  }
  void set_trim_arcvm_on_memory_pressure(bool enabled) {
    trim_arcvm_on_memory_pressure_ = enabled;
  }

  void TrimNodesOnGraph();

  // TrimArcProcesses will walk procfs looking for ARC container processes which
  // can be trimmed. These are virtual for testing.
  virtual void TrimArcProcesses();
  virtual bool IsArcProcessEligibleForReclaim(
      const arc::ArcProcess& arc_process);
  virtual void TrimArcProcess(base::ProcessId pid);

  // TrimArcVmProcesses will ask the delegate if it is safe to reclaim memory
  // from ARCVM, and do that when it is. These are virtual for testing.
  virtual void TrimArcVmProcesses(
      base::MemoryPressureListener::MemoryPressureLevel level);

  // The functions below form a chain of callbacks that carry along
  // a WeakPtr to an instance of WorkingSetTrimmerPolicyChromeOS.
  // That instance can be destroyed at any point in the chain.
  // The following constraints apply:
  // - The WeakPtr is built on the PM thread, so it can only be
  //   checked for validity or dereferenced in the PM thread.
  // - For member functions, there is an automatic pointer check inside
  //   BindOnce at invocation, if the "this" is a WeakPtr.
  // Because of the combination of constraints above, the methods that
  // execute on threads other than the PM thread must be made static,
  // and they must not use the WeakPtr except to pass it down the chain.
  static void TrimArcVmProcessesOnUIThread(
      base::MemoryPressureListener::MemoryPressureLevel level,
      features::TrimOnMemoryPressureParams params,
      base::WeakPtr<WorkingSetTrimmerPolicyChromeOS> ptr);
  virtual void OnTrimArcVmProcesses(mechanism::ArcVmReclaimType reclaim_type,
                                    bool is_first_trim_post_boot,
                                    int pages_per_minute,
                                    int max_pages_per_iteration);
  virtual void OnArcVmTrimStarting();
  static void DoTrimArcVmOnUIThread(
      base::WeakPtr<WorkingSetTrimmerPolicyChromeOS> ptr,
      mechanism::WorkingSetTrimmerChromeOS* trimmer,
      mechanism::ArcVmReclaimType reclaim_type,
      int page_limit);
  static void OnTrimArcVmWorkingSetOnUIThread(
      base::WeakPtr<WorkingSetTrimmerPolicyChromeOS> ptr,
      mechanism::ArcVmReclaimType reclaim_type,
      bool success,
      const std::string& failure_reason);
  virtual void OnArcVmTrimEnded(mechanism::ArcVmReclaimType reclaim_type,
                                bool success);

  features::TrimOnMemoryPressureParams params_;

  // Keeps track of the last time we walked the graph looking for processes
  // to trim.
  std::optional<base::TimeTicks> last_graph_walk_;

  // We keep track of the last time we fetched the ARC process list.
  std::optional<base::TimeTicks> last_arc_process_fetch_;

  // We also keep track of the last time we reclaimed memory from ARCVM.
  std::optional<base::TimeTicks> last_arcvm_trim_;
  std::optional<base::TimeTicks> last_arcvm_trim_success_;

  std::optional<base::MemoryPressureListener> memory_pressure_listener_;

 private:
  bool trim_on_freeze_ = false;
  bool trim_arc_on_memory_pressure_ = false;
  bool trim_arcvm_on_memory_pressure_ = false;
  bool disable_trim_while_suspended_ = false;
  // The status of suspend is updated by PowerManagerClient::Observer which runs
  // on the main thread, and is referenced by
  // WorkingSetTrimmerPolicyChromeOS::OnMemoryPressure() which runs on the PM
  // sequence.
  base::Lock mutex_;
  bool is_system_suspended_ GUARDED_BY(mutex_) = false;
  std::optional<base::TimeTicks> last_suspend_done_time_ GUARDED_BY(mutex_);

  // This map contains the last trim time of arc processes.
  std::map<base::ProcessId, base::TimeTicks> arc_processes_last_trim_;

  base::ScopedObservation<chromeos::PowerManagerClient,
                          chromeos::PowerManagerClient::Observer>
      power_manager_observation_{this};

  base::WeakPtrFactory<WorkingSetTrimmerPolicyChromeOS> weak_ptr_factory_{this};
};

}  // namespace policies
}  // namespace performance_manager

#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_POLICIES_WORKING_SET_TRIMMER_POLICY_CHROMEOS_H_