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

#ifndef COMPONENTS_HEAP_PROFILING_MULTI_PROCESS_SUPERVISOR_H_
#define COMPONENTS_HEAP_PROFILING_MULTI_PROCESS_SUPERVISOR_H_

#include "base/functional/callback_forward.h"
#include "base/memory/weak_ptr.h"
#include "base/no_destructor.h"
#include "base/process/process.h"
#include "base/thread_annotations.h"
#include "base/threading/thread_checker.h"
#include "components/services/heap_profiling/public/mojom/heap_profiling_client.mojom.h"
#include "components/services/heap_profiling/public/mojom/heap_profiling_service.mojom.h"
#include "services/resource_coordinator/public/mojom/memory_instrumentation/memory_instrumentation.mojom.h"

namespace heap_profiling {

class ClientConnectionManager;
class Controller;
enum class Mode;

// This class presents a single interface for both tests and embedders to use
// the HeapProfilingService. This class is intended to be used from the
// browser/privileged process of the embedder.
//
// This class must be accessed from the UI thread.
//
// Internally, this class:
//   * Starts the HeapProfilingService.
//   * Hooks up all the connections so that the appropriate processes get
//     profiled.
class Supervisor {
 public:
  static Supervisor* GetInstance();

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

  // When this returns |false|, no method other than Start() or
  // SetClientConnectionManagerConstructor() can be called.
  bool HasStarted();

  // Embedders can use this method to force the Supervisor to instantiate a
  // ClientConnectionManager subclass during Start(). The function will be
  // called on the UI thread.
  using ClientConnectionManagerConstructor =
      std::unique_ptr<ClientConnectionManager> (*)(
          base::WeakPtr<Controller> controller_weak_ptr,
          Mode mode);
  void SetClientConnectionManagerConstructor(
      ClientConnectionManagerConstructor constructor);

  // Must be called at most once.
  // The first method is a convenience method that calls the second with
  // default parameters.
  // Start is an asynchronous operation that must hop to the IO thread and then
  // back to the UI thread. |callback| will be invoked on the UI thread after
  // this is finished.
  //
  // This is a brief period of time when this object is in a semi-initialized
  // state - when Start has been called, but the thread hops haven't finished.
  // We avoid this side case by:
  //   * Providing a |callback| for callers to use, if they need to do anything
  //     shortly after Start().
  //   * Relying on the assumption that in all other cases, the object is either
  //     fully initialized or not initialized. There are DCHECKs to enforce this
  //     assumption.
  void Start(base::OnceClosure callback);
  void Start(Mode mode,
             mojom::StackMode stack_mode,
             uint32_t sampling_rate,
             base::OnceClosure callback);

  // Stops profiling in all connected clients and tears down the supervisor
  // process-wide state. |callback| is invoked on the UI thread with a boolean
  // indicating whether profiling was successfully stopped.
  void Stop(base::OnceCallback<void(bool)> callback);

  Mode GetMode();

  // Starts profiling the process with the given `pid`. Invokes
  // `started_profiling_closure` if and when profiling starts successfully.
  void StartManualProfiling(base::ProcessId pid,
                            mojom::ProfilingService::AddProfilingClientCallback
                                started_profiling_closure);

  // Returns the pids of all profiled processes. The callback is posted on the
  // UI thread.
  using GetProfiledPidsCallback =
      base::OnceCallback<void(std::vector<base::ProcessId> pids)>;
  void GetProfiledPids(GetProfiledPidsCallback callback);

  uint32_t GetSamplingRate();

  using TraceFinishedCallback =
      base::OnceCallback<void(bool success, std::string trace_json)>;

  // This method must be called from the UI thread. |callback| will be called
  // asynchronously on the UI thread.
  //
  // This function does the following:
  //   1. Starts tracing with no categories enabled.
  //   2. Requests and waits for memory_instrumentation service to dump to
  //   trace.
  //   3. Stops tracing.
  void RequestTraceWithHeapDump(TraceFinishedCallback callback, bool anonymize);

 private:
  friend class base::NoDestructor<Supervisor>;

  Supervisor();
  ~Supervisor();

  // Initialization stage 1: Register with the MemoryInstrumentationRegistry.
  // `continue_on_io_thread` is a callback to the next stage, which must be
  // bound to the IO thread.
  void RegisterProfilerOnMemoryInfraThread(
      base::OnceCallback<void(
          mojo::PendingReceiver<memory_instrumentation::mojom::HeapProfiler>,
          mojo::PendingRemote<
              memory_instrumentation::mojom::HeapProfilerHelper>)>
          continue_on_io_thread);

  // Initialization stage 2: Launch the Service on the IO thread.
  // `continue_on_ui_thread` is a callback to the next stage, which must be
  // bound to the UI thread.
  void LaunchServiceOnIOThread(
      mojom::StackMode stack_mode,
      uint32_t sampling_rate,
      base::OnceCallback<void(base::WeakPtr<Controller>)> continue_on_ui_thread,
      mojo::PendingReceiver<memory_instrumentation::mojom::HeapProfiler>
          receiver,
      mojo::PendingRemote<memory_instrumentation::mojom::HeapProfilerHelper>
          remote_helper);

  // Initialization stage 3: Save controller pointer and start the
  // ClientConnectionManager.
  void ControllerStartedOnUIThread(
      Mode mode,
      base::OnceClosure closure,
      base::WeakPtr<Controller> controller_weak_ptr);

  // Starts the ClientConnectionManager. Can be called multiple times.
  void StartClientConnectionOnUIThread(Mode mode, base::OnceClosure closure);

  void GetProfiledPidsOnIOThread(GetProfiledPidsCallback callback);

  void StopOnIOThread(base::OnceCallback<void(bool)> callback);

  // Bound to the IO thread.
  std::unique_ptr<Controller> controller_;

  THREAD_CHECKER(ui_thread_checker_);

  // Bound to the UI thread.
  std::unique_ptr<ClientConnectionManager> client_connection_manager_
      GUARDED_BY_CONTEXT(ui_thread_checker_);
  base::WeakPtr<Controller> controller_weak_ptr_
      GUARDED_BY_CONTEXT(ui_thread_checker_);

  ClientConnectionManagerConstructor constructor_ = nullptr;

  bool started_ GUARDED_BY_CONTEXT(ui_thread_checker_) = false;
  enum class InitializationState {
    kNotInitialized,
    kInitializing,
    kInitialized
  };
  InitializationState initialization_state_ GUARDED_BY_CONTEXT(
      ui_thread_checker_) = InitializationState::kNotInitialized;
};

}  // namespace heap_profiling

#endif  // COMPONENTS_HEAP_PROFILING_MULTI_PROCESS_SUPERVISOR_H_
