// Copyright 2012 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_METRICS_POWER_PROCESS_MONITOR_H_
#define CHROME_BROWSER_METRICS_POWER_PROCESS_MONITOR_H_

#include <array>
#include <map>
#include <memory>
#include <optional>

#include "base/observer_list.h"
#include "base/process/process_handle.h"
#include "base/process/process_metrics.h"
#include "base/scoped_multi_source_observation.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "build/build_config.h"
#include "content/public/browser/browser_child_process_observer.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_process_host_creation_observer.h"
#include "content/public/browser/render_process_host_observer.h"
#include "content/public/common/process_type.h"

enum MonitoredProcessType {
  kBrowser,
  kRenderer,
  kExtensionPersistent,
  kExtensionEvent,
  kGpu,
  kUtility,
  kNetwork,
  kOther,  // Contains all other process types that are not explicitly tracked.
  kCount,
};

// A wrapper around base::ProcessMetrics that can be overridden in tests.
class ProcessMetricsDelegate {
 public:
  virtual ~ProcessMetricsDelegate() = default;

  virtual double GetPlatformIndependentCPUUsage(
      base::TimeDelta cumulative_cpu) = 0;

  virtual base::expected<double, base::ProcessCPUUsageError>
  GetPlatformIndependentCPUUsage() = 0;

#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || \
    BUILDFLAG(IS_AIX)
  virtual int GetIdleWakeupsPerSecond() = 0;
#endif

#if BUILDFLAG(IS_MAC)
  virtual int GetPackageIdleWakeupsPerSecond() = 0;
#endif
};

struct ProcessInfo {
  struct Key {
    Key(MonitoredProcessType type, std::optional<std::string> subtype);
    Key(const Key& other);
    ~Key();

    bool operator<(const Key& other) const;
    bool operator==(const Key& other) const;

    MonitoredProcessType type;
    std::optional<std::string> subtype;
  };

  ProcessInfo(Key key, std::unique_ptr<ProcessMetricsDelegate> process_metrics);
  ~ProcessInfo();

  Key key;
  std::unique_ptr<ProcessMetricsDelegate> process_metrics;

  // The time at which the first process sample was taken (i.e. when the
  // constructor is called). Used to distribute the calculated resource usage of
  // the first interval over the full kLongIntervalDuration. Set to nullopt
  // after the metrics for the first interval is calculated because the
  // subsequent intervals will always take the full duration of
  // kLongIntervalDuration.
  std::optional<base::TimeTicks> first_sample_time;
};

template <typename H>
H AbslHashValue(H h, const ProcessInfo::Key& k) {
  return H::combine(std::move(h), k.type, k.subtype);  // Combine all members
}

// ProcessMonitor is a tool which allows the sampling of power-related metrics
// for all Chrome processes. The metrics sampling is driven externally by
// calling SampleAllProcesses() periodically.
class ProcessMonitor : public content::BrowserChildProcessObserver,
                       public content::RenderProcessHostCreationObserver,
                       public content::RenderProcessHostObserver {
 public:
  struct Metrics {
    Metrics();
    Metrics(const Metrics& other);
    Metrics& operator=(const Metrics& other);
    ~Metrics();

    // The percentage of time spent executing, across all threads of the
    // process, in the interval since the last time the metric was sampled. This
    // can exceed 100% in multi-thread processes running on multi-core systems.
    // nullopt if there was an error calculating the CPU usage.
    std::optional<double> cpu_usage;

#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || \
    BUILDFLAG(IS_AIX)
    // Returns the number of average idle cpu wakeups per second since the last
    // time the metric was sampled.
    int idle_wakeups = 0;
#endif

#if BUILDFLAG(IS_MAC)
    // The number of average "package idle exits" per second since the last
    // time the metric was sampled. See base/process/process_metrics.h for a
    // more detailed explanation.
    int package_idle_wakeups = 0;
#endif
  };

  class Observer : public base::CheckedObserver {
   public:
    // Provides aggregated sampled metrics for all Chrome process of type
    // `type`. This is called once per process type whenever
    // `SampleAllProcesses` is called.
    virtual void OnMetricsSampled(ProcessInfo::Key key,
                                  const Metrics& metrics) {}

    // Provides the aggregated sampled metrics from every Chrome process. This
    // is called once whenever `SampleAllProcesses` is called.
    virtual void OnAggregatedMetricsSampled(const Metrics& metrics) {}
  };

  ProcessMonitor();

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

  ~ProcessMonitor() override;

  // Gather the metrics for all the processes.
  virtual void SampleAllProcesses(Observer* observer);

  void AddChildProcessInfoForTesting(
      int id,
      ProcessInfo::Key key,
      std::unique_ptr<ProcessMetricsDelegate> process_metrics);

 private:
  friend class ProcessMonitorIntervalTest;

  // content::RenderProcessHostCreationObserver:
  void OnRenderProcessHostCreated(
      content::RenderProcessHost* render_process_host) override;

  // content::RenderProcessHostObserver:
  void RenderProcessReady(
      content::RenderProcessHost* render_process_host) override;
  void RenderProcessExited(
      content::RenderProcessHost* render_process_host,
      const content::ChildProcessTerminationInfo& info) override;
  void RenderProcessHostDestroyed(
      content::RenderProcessHost* render_process_host) override;

  // content::BrowserChildProcessObserver:
  void BrowserChildProcessLaunchedAndConnected(
      const content::ChildProcessData& data) override;
  void BrowserChildProcessHostDisconnected(
      const content::ChildProcessData& data) override;
  void BrowserChildProcessCrashed(
      const content::ChildProcessData& data,
      const content::ChildProcessTerminationInfo& info) override;
  void BrowserChildProcessKilled(
      const content::ChildProcessData& data,
      const content::ChildProcessTerminationInfo& info) override;
  void BrowserChildProcessExitedNormally(
      const content::ChildProcessData& data,
      const content::ChildProcessTerminationInfo& info) override;

  void OnBrowserChildProcessExited(
      const content::ChildProcessData& data,
      const content::ChildProcessTerminationInfo& info);

  base::ScopedMultiSourceObservation<content::RenderProcessHost,
                                     content::RenderProcessHostObserver>
      render_process_host_observations_{this};

  ProcessInfo browser_process_info_;

  std::map<content::RenderProcessHost*, ProcessInfo> render_process_infos_;

  std::map<int /*ChildProcessId*/, ProcessInfo> browser_child_process_infos_;

  // The metrics for the processes that exited during the last interval. Added
  // to the current interval's sample and then reset to zero.
  std::map<ProcessInfo::Key, Metrics> exited_processes_metrics_;

  // The time at which the last interval sampling was completed. Used to
  // calculate the duration of time an exited process lived during the current
  // interval.
  base::TimeTicks last_sample_time_ = base::TimeTicks::Now();
};

ProcessInfo::Key GetMonitoredProcessInfoKeyForNonRendererChildProcessForTesting(
    const content::ChildProcessData& data);

#endif  // CHROME_BROWSER_METRICS_POWER_PROCESS_MONITOR_H_
