// Copyright 2017 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 "components/performance_manager/graph/process_node_impl.h"

#include <utility>

#include "base/check_op.h"
#include "components/performance_manager/graph/frame_node_impl.h"
#include "components/performance_manager/graph/graph_impl.h"
#include "components/performance_manager/graph/page_node_impl.h"

namespace performance_manager {

ProcessNodeImpl::ProcessNodeImpl(content::ProcessType process_type,
                                 RenderProcessHostProxy render_process_proxy)
    : process_type_(process_type),
      render_process_host_proxy_(std::move(render_process_proxy)) {
  DETACH_FROM_SEQUENCE(sequence_checker_);
}

ProcessNodeImpl::~ProcessNodeImpl() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // Crash if this process node is destroyed while still hosting a worker node.
  // TODO(https://crbug.com/1058705): Turn this into a DCHECK once the issue is
  //                                  resolved.
  CHECK(worker_nodes_.empty());
}

void ProcessNodeImpl::Bind(
    mojo::PendingReceiver<mojom::ProcessCoordinationUnit> receiver) {
  // A RenderProcessHost can be reused if the backing process suddenly dies, in
  // which case we will receive a new receiver from the newly spawned process.
  receiver_.reset();
  receiver_.Bind(std::move(receiver));
}

void ProcessNodeImpl::SetMainThreadTaskLoadIsLow(
    bool main_thread_task_load_is_low) {
  main_thread_task_load_is_low_.SetAndMaybeNotify(this,
                                                  main_thread_task_load_is_low);
}

void ProcessNodeImpl::SetProcessExitStatus(int32_t exit_status) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // This may occur as the first event seen in the case where the process
  // fails to start or suffers a startup crash.
  exit_status_ = exit_status;

  // Close the process handle to kill the zombie.
  process_.SetAndNotify(this, base::Process());

  // No more message should be received from this process.
  receiver_.reset();
}

void ProcessNodeImpl::SetProcess(base::Process process,
                                 base::Time launch_time) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(process.IsValid());
  // Either this is the initial process associated with this process node,
  // or it's a subsequent process. In the latter case, there must have been
  // an exit status associated with the previous process.
  DCHECK(!process_.value().IsValid() || exit_status_.has_value());

  base::ProcessId pid = process.Pid();
  SetProcessImpl(std::move(process), pid, launch_time);
}

const base::flat_set<FrameNodeImpl*>& ProcessNodeImpl::frame_nodes() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return frame_nodes_;
}

PageNodeImpl* ProcessNodeImpl::GetPageNodeIfExclusive() const {
  PageNodeImpl* page_node = nullptr;
  for (auto* frame_node : frame_nodes_) {
    if (!page_node)
      page_node = frame_node->page_node();
    if (page_node != frame_node->page_node())
      return nullptr;
  }
  return page_node;
}

RenderProcessHostId ProcessNodeImpl::GetRenderProcessId() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return RenderProcessHostId(
      render_process_host_proxy_.render_process_host_id());
}

void ProcessNodeImpl::AddFrame(FrameNodeImpl* frame_node) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  const bool inserted = frame_nodes_.insert(frame_node).second;
  DCHECK(inserted);
}

void ProcessNodeImpl::RemoveFrame(FrameNodeImpl* frame_node) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(base::Contains(frame_nodes_, frame_node));
  frame_nodes_.erase(frame_node);
}

void ProcessNodeImpl::AddWorker(WorkerNodeImpl* worker_node) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  const bool inserted = worker_nodes_.insert(worker_node).second;
  DCHECK(inserted);
}

void ProcessNodeImpl::RemoveWorker(WorkerNodeImpl* worker_node) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(base::Contains(worker_nodes_, worker_node));
  worker_nodes_.erase(worker_node);
}

void ProcessNodeImpl::set_priority(base::TaskPriority priority) {
  priority_.SetAndMaybeNotify(this, priority);
}

void ProcessNodeImpl::SetProcessImpl(base::Process process,
                                     base::ProcessId new_pid,
                                     base::Time launch_time) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  graph()->BeforeProcessPidChange(this, new_pid);

  // Clear the exit status for the previous process (if any).
  exit_status_.reset();

  // Also clear the measurement data (if any), as it references the previous
  // process.
  private_footprint_kb_ = 0;
  resident_set_kb_ = 0;

  process_id_ = new_pid;
  launch_time_ = launch_time;

  // Set the process variable last, as it will fire the notification.
  process_.SetAndNotify(this, std::move(process));
}

content::ProcessType ProcessNodeImpl::GetProcessType() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return process_type();
}

base::ProcessId ProcessNodeImpl::GetProcessId() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return process_id();
}

const base::Process& ProcessNodeImpl::GetProcess() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return process();
}

base::Time ProcessNodeImpl::GetLaunchTime() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return launch_time();
}

base::Optional<int32_t> ProcessNodeImpl::GetExitStatus() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return exit_status();
}

bool ProcessNodeImpl::VisitFrameNodes(const FrameNodeVisitor& visitor) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  for (auto* frame_impl : frame_nodes()) {
    const FrameNode* frame = frame_impl;
    if (!visitor.Run(frame))
      return false;
  }
  return true;
}

base::flat_set<const FrameNode*> ProcessNodeImpl::GetFrameNodes() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  base::flat_set<const FrameNode*> frames;
  const base::flat_set<FrameNodeImpl*>& frame_impls = frame_nodes();
  for (auto* frame_impl : frame_impls) {
    const FrameNode* frame = frame_impl;
    frames.insert(frame);
  }
  return frames;
}

bool ProcessNodeImpl::GetMainThreadTaskLoadIsLow() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return main_thread_task_load_is_low();
}

uint64_t ProcessNodeImpl::GetPrivateFootprintKb() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return private_footprint_kb();
}

uint64_t ProcessNodeImpl::GetResidentSetKb() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return resident_set_kb();
}

RenderProcessHostId ProcessNodeImpl::GetRenderProcessHostId() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return GetRenderProcessId();
}

const RenderProcessHostProxy& ProcessNodeImpl::GetRenderProcessHostProxy()
    const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return render_process_host_proxy();
}

base::TaskPriority ProcessNodeImpl::GetPriority() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return priority();
}

void ProcessNodeImpl::OnAllFramesInProcessFrozen() {
  for (auto* observer : GetObservers())
    observer->OnAllFramesInProcessFrozen(this);
}

void ProcessNodeImpl::OnBeforeLeavingGraph() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // Make as if we're transitioning to the null PID before we die to clear this
  // instance from the PID map.
  if (process_id_ != base::kNullProcessId)
    graph()->BeforeProcessPidChange(this, base::kNullProcessId);

  // All child frames should have been removed before the process is removed.
  DCHECK(frame_nodes_.empty());
}

}  // namespace performance_manager
