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

#include "components/heap_profiling/in_process/browser_process_snapshot_controller.h"

#include <memory>
#include <utility>

#include "base/check.h"
#include "base/containers/flat_map.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/rand_util.h"
#include "base/sequence_checker.h"
#include "base/task/sequenced_task_runner.h"
#include "components/heap_profiling/in_process/heap_profiler_controller.h"
#include "components/heap_profiling/in_process/heap_profiler_parameters.h"
#include "components/heap_profiling/in_process/mojom/snapshot_controller.mojom.h"
#include "components/sampling_profiler/process_type.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote_set.h"

namespace heap_profiling {

// static
BrowserProcessSnapshotController*
BrowserProcessSnapshotController::GetInstance() {
  const auto* controller = HeapProfilerController::GetInstance();
  return controller ? controller->GetBrowserProcessSnapshotController()
                    : nullptr;
}

BrowserProcessSnapshotController::BrowserProcessSnapshotController(
    scoped_refptr<base::SequencedTaskRunner> snapshot_task_runner)
    : snapshot_task_runner_(std::move(snapshot_task_runner)) {
  // Label this as the main sequence.
  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);

  // Initialize with all supported process types.
  using RemoteSet = mojo::RemoteSet<mojom::SnapshotController>;
  remotes_by_process_type_.emplace(sampling_profiler::ProfilerProcessType::kGpu,
                                   std::make_unique<RemoteSet>());
  remotes_by_process_type_.emplace(
      sampling_profiler::ProfilerProcessType::kNetworkService,
      std::make_unique<RemoteSet>());
  remotes_by_process_type_.emplace(
      sampling_profiler::ProfilerProcessType::kRenderer,
      std::make_unique<RemoteSet>());
  remotes_by_process_type_.emplace(
      sampling_profiler::ProfilerProcessType::kUtility,
      std::make_unique<RemoteSet>());

  // Now that `remotes_by_process_type_` is initialized all further access
  // should be on the snapshot sequence.
  DETACH_FROM_SEQUENCE(snapshot_sequence_checker_);
}

BrowserProcessSnapshotController::~BrowserProcessSnapshotController() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(snapshot_sequence_checker_);
}

base::WeakPtr<BrowserProcessSnapshotController>
BrowserProcessSnapshotController::GetWeakPtr() {
  return weak_factory_.GetWeakPtr();
}

void BrowserProcessSnapshotController::SetBindRemoteForChildProcessCallback(
    BindRemoteCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
  bind_remote_callback_ = std::move(callback);
}

void BrowserProcessSnapshotController::BindRemoteForChildProcess(
    int child_process_id,
    sampling_profiler::ProfilerProcessType child_process_type) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
  mojo::PendingRemote<mojom::SnapshotController> remote;
  bind_remote_callback_.Run(child_process_id,
                            remote.InitWithNewPipeAndPassReceiver());
  snapshot_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(
          &BrowserProcessSnapshotController::StoreRemoteOnSnapshotSequence,
          GetWeakPtr(), std::move(remote), child_process_type));
}

void BrowserProcessSnapshotController::TakeSnapshotsOnSnapshotSequence() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(snapshot_sequence_checker_);
  for (auto& [process_type, remote_set] : remotes_by_process_type_) {
    CHECK(remote_set);
    if (remote_set->empty()) {
      // Avoid division by 0. This won't change any metrics since there are no
      // processes to measure.
      continue;
    }
    const int snapshot_probability_pct =
        GetSnapshotProbabilityForProcess(process_type);
    if (snapshot_probability_pct == 0) {
      // No need to test each process since none will be chosen.
      continue;
    }

    // Choose a random set of processes to snapshot. If randomness is
    // suppressed, choose processes by their index in the set.
    double prob_idx = 0;     // Index for simulating probability.
    size_t process_idx = 0;  // Index of chosen process.
    for (const auto& remote : *remote_set) {
      const double prob = suppress_randomness_for_testing_
                              ? (prob_idx++ / remote_set->size())
                              : base::RandDouble();
      if (prob * 100.0 < snapshot_probability_pct) {
        remote->TakeSnapshot(snapshot_probability_pct, process_idx++);
      } else {
        remote->LogMetricsWithoutSnapshot();
      }
    }
  }
}

void BrowserProcessSnapshotController::SuppressRandomnessForTesting() {
  suppress_randomness_for_testing_ = true;
}

void BrowserProcessSnapshotController::StoreRemoteOnSnapshotSequence(
    mojo::PendingRemote<mojom::SnapshotController> remote,
    sampling_profiler::ProfilerProcessType process_type) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(snapshot_sequence_checker_);
  // at() will CHECK if `process_type` wasn't added in the constructor.
  remotes_by_process_type_.at(process_type)
      ->Add(std::move(remote), snapshot_task_runner_);
}

}  // namespace heap_profiling
