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

#include <optional>
#include <utility>
#include <variant>

#include "base/byte_size.h"
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/trace_event/named_trigger.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"
#include "components/performance_manager/graph/worker_node_impl.h"
#include "components/performance_manager/public/execution_context/execution_context_registry.h"
#include "components/performance_manager/scenarios/browser_performance_scenarios.h"
#include "components/performance_manager/v8_memory/v8_context_tracker.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/tracing_support.h"
#include "content/public/common/content_switches.h"
#include "third_party/perfetto/include/perfetto/tracing/track.h"

namespace performance_manager {

namespace {

// CHECK's that `process_type` is appropriate for a BrowserChildProcessHost and
// returns it. This is called from a ProcessNodeImpl initializer so that the
// type is checked before the constructor body.
content::ProcessType ValidateBrowserChildProcessType(
    content::ProcessType process_type) {
  CHECK_NE(process_type, content::PROCESS_TYPE_BROWSER);
  CHECK_NE(process_type, content::PROCESS_TYPE_RENDERER);
  return process_type;
}

perfetto::StaticString PriorityToString(
    const base::Process::Priority& priority) {
  return perfetto::StaticString(base::ProcessPriorityToString(priority));
}

}  // namespace

ProcessNodeImpl::ProcessNodeImpl(BrowserProcessNodeTag tag)
    : ProcessNodeImpl(content::PROCESS_TYPE_BROWSER,
                      AnyChildProcessHostProxy{},
                      base::Process::Priority::kMaxValue) {}

ProcessNodeImpl::ProcessNodeImpl(RenderProcessHostProxy proxy,
                                 base::Process::Priority priority)
    : ProcessNodeImpl(content::PROCESS_TYPE_RENDERER,
                      AnyChildProcessHostProxy(std::move(proxy)),
                      priority) {}

ProcessNodeImpl::ProcessNodeImpl(content::ProcessType process_type,
                                 BrowserChildProcessHostProxy proxy)
    : ProcessNodeImpl(ValidateBrowserChildProcessType(process_type),
                      AnyChildProcessHostProxy(std::move(proxy)),
                      base::Process::Priority::kMaxValue) {}

ProcessNodeImpl::ProcessNodeImpl(content::ProcessType process_type,
                                 AnyChildProcessHostProxy proxy,
                                 base::Process::Priority priority)
    : process_type_(process_type),
      child_process_host_proxy_(std::move(proxy)),
      tracing_track_(GetTracingTrack(process_type_, child_process_host_proxy_)),
      priority_(priority,
                perfetto::StateTrack("Priority", 0, tracing_track_),
                PriorityToString) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  // Child process nodes must have a valid proxy.
  switch (process_type) {
    case content::PROCESS_TYPE_BROWSER:
      // Do nothing.
      break;
    case content::PROCESS_TYPE_RENDERER:
      CHECK(std::get<RenderProcessHostProxy>(child_process_host_proxy_)
                .is_valid());
      break;
    default:
      CHECK(std::get<BrowserChildProcessHostProxy>(child_process_host_proxy_)
                .is_valid());
      break;
  }
}

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

void ProcessNodeImpl::BindRenderProcessCoordinationUnit(
    mojo::PendingReceiver<mojom::ProcessCoordinationUnit> receiver) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // 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.
  render_process_receiver_.reset();
  render_process_receiver_.Bind(std::move(receiver));
}

void ProcessNodeImpl::BindChildProcessCoordinationUnit(
    mojo::PendingReceiver<mojom::ChildProcessCoordinationUnit> receiver) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // 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.
  child_process_receiver_.reset();
  child_process_receiver_.Bind(std::move(receiver));
}

void ProcessNodeImpl::SetMainThreadTaskLoadIsLow(
    bool main_thread_task_load_is_low) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(process_type_, content::PROCESS_TYPE_RENDERER);

  main_thread_task_load_is_low_.SetAndMaybeNotify(this,
                                                  main_thread_task_load_is_low);
}

void ProcessNodeImpl::OnV8ContextCreated(
    mojom::V8ContextDescriptionPtr description,
    mojom::IframeAttributionDataPtr iframe_attribution_data) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(process_type_, content::PROCESS_TYPE_RENDERER);
  if (auto* tracker = v8_memory::V8ContextTracker::GetFromGraph(graph())) {
    tracker->OnV8ContextCreated(PassKey(), this, *description,
                                std::move(iframe_attribution_data));
  }
}

void ProcessNodeImpl::OnV8ContextDetached(
    const blink::V8ContextToken& v8_context_token) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(process_type_, content::PROCESS_TYPE_RENDERER);
  if (auto* tracker = v8_memory::V8ContextTracker::GetFromGraph(graph())) {
    tracker->OnV8ContextDetached(PassKey(), this, v8_context_token);
  }
}

void ProcessNodeImpl::OnV8ContextDestroyed(
    const blink::V8ContextToken& v8_context_token) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(process_type_, content::PROCESS_TYPE_RENDERER);
  if (auto* tracker = v8_memory::V8ContextTracker::GetFromGraph(graph())) {
    tracker->OnV8ContextDestroyed(PassKey(), this, v8_context_token);
  }
}

void ProcessNodeImpl::OnRemoteIframeAttached(
    const blink::LocalFrameToken& parent_frame_token,
    const blink::RemoteFrameToken& remote_frame_token,
    mojom::IframeAttributionDataPtr iframe_attribution_data) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(process_type_, content::PROCESS_TYPE_RENDERER);

  // Only dispatch if the frame and its parent still exist when the mojo message
  // reaches the browser process. If the frame still exists but now has no
  // parent, we don't need to record IframeAttribution data for it since it's
  // now unreachable.
  //
  // An example of this is the custom <webview> element used in Chrome UI
  // (extensions/renderer/resources/guest_view/web_view/web_view.js). This
  // element has an inner web contents with an opener relationship to the
  // webview, but no parent-child relationship. However since it is a custom
  // element implemented on top of <iframe>, the renderer has no way to
  // distinguish it from a regular iframe. At the moment the contents is
  // attached it has a transient parent frame, which is reported through
  // OnRemoteIframeAttached, but the parent frame disappears shortly
  // afterward.
  //
  // TODO(crbug.com/40132061): Write an end-to-end browsertest that covers
  // this case once all parts of the measure memory API are hooked up.

  if (auto* tracker = v8_memory::V8ContextTracker::GetFromGraph(graph())) {
    auto* ec_registry =
        execution_context::ExecutionContextRegistry::GetFromGraph(graph());
    DCHECK(ec_registry);
    auto* parent_frame_node =
        ec_registry->GetFrameNodeByFrameToken(parent_frame_token);
    if (parent_frame_node) {
      tracker->OnRemoteIframeAttached(
          PassKey(), FrameNodeImpl::FromNode(parent_frame_node),
          remote_frame_token, std::move(iframe_attribution_data));
    }
  }
}

void ProcessNodeImpl::OnRemoteIframeDetached(
    const blink::LocalFrameToken& parent_frame_token,
    const blink::RemoteFrameToken& remote_frame_token) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(process_type_, content::PROCESS_TYPE_RENDERER);
  if (auto* tracker = v8_memory::V8ContextTracker::GetFromGraph(graph())) {
    auto* ec_registry =
        execution_context::ExecutionContextRegistry::GetFromGraph(graph());
    DCHECK(ec_registry);
    auto* parent_frame_node =
        ec_registry->GetFrameNodeByFrameToken(parent_frame_token);
    if (parent_frame_node) {
      tracker->OnRemoteIframeDetached(
          PassKey(), FrameNodeImpl::FromNode(parent_frame_node),
          remote_frame_token);
    }
  }
}

void ProcessNodeImpl::InitializeChildProcessCoordination(
    InitializeChildProcessCoordinationCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kSingleProcess)) {
    // The "child process" is actually running in the same process space. The
    // global shared memory region is already mapped in, and the per-process
    // shared memory region can't be used because regions for different
    // "processes" would overwrite each other.
    std::move(callback).Run(base::ReadOnlySharedMemoryRegion(),
                            base::ReadOnlySharedMemoryRegion());
    return;
  }
  std::move(callback).Run(GetGlobalSharedScenarioRegion(),
                          GetSharedScenarioRegionForProcessNode(this));
}

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_.value();
}

resource_attribution::ProcessContext ProcessNodeImpl::GetResourceContext()
    const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return resource_attribution::ProcessContext::FromProcessNode(this);
}

base::TimeTicks ProcessNodeImpl::GetLaunchTime() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return launch_time_;
}

std::optional<int32_t> ProcessNodeImpl::GetExitStatus() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return exit_status_;
}

const std::string& ProcessNodeImpl::GetMetricsName() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return metrics_name_;
}

bool ProcessNodeImpl::GetMainThreadTaskLoadIsLow() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(process_type_, content::PROCESS_TYPE_RENDERER);
  return main_thread_task_load_is_low_.value();
}

base::ByteSize ProcessNodeImpl::GetPrivateFootprint() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return private_footprint_;
}

base::ByteSize ProcessNodeImpl::GetResidentSet() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return resident_set_;
}

base::ByteSize ProcessNodeImpl::GetPrivateSwap() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return private_swap_;
}

RenderProcessHostId ProcessNodeImpl::GetRenderProcessHostId() const {
  return GetRenderProcessHostProxy().render_process_host_id();
}

const RenderProcessHostProxy& ProcessNodeImpl::GetRenderProcessHostProxy()
    const {
  DCHECK_EQ(process_type_, content::PROCESS_TYPE_RENDERER);
  return std::get<RenderProcessHostProxy>(child_process_host_proxy_);
}

const BrowserChildProcessHostProxy&
ProcessNodeImpl::GetBrowserChildProcessHostProxy() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_NE(process_type_, content::PROCESS_TYPE_BROWSER);
  DCHECK_NE(process_type_, content::PROCESS_TYPE_RENDERER);
  return std::get<BrowserChildProcessHostProxy>(child_process_host_proxy_);
}

base::Process::Priority ProcessNodeImpl::GetPriority() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return priority_.value();
}

ProcessNode::ContentTypes ProcessNodeImpl::GetHostedContentTypes() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(process_type_, content::PROCESS_TYPE_RENDERER);
  return hosted_content_types_;
}

ProcessNode::NodeSetView<FrameNodeImpl*> ProcessNodeImpl::frame_nodes() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(process_type_, content::PROCESS_TYPE_RENDERER);
  return NodeSetView<FrameNodeImpl*>(frame_nodes_);
}

ProcessNode::NodeSetView<WorkerNodeImpl*> ProcessNodeImpl::worker_nodes()
    const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(process_type_, content::PROCESS_TYPE_RENDERER);
  return NodeSetView<WorkerNodeImpl*>(worker_nodes_);
}

perfetto::Track ProcessNodeImpl::tracing_track() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return tracing_track_;
}

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.
  render_process_receiver_.reset();
  child_process_receiver_.reset();
}

void ProcessNodeImpl::SetProcessMetricsName(const std::string& metrics_name) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  metrics_name_ = metrics_name;
}

void ProcessNodeImpl::SetProcess(base::Process process,
                                 base::TimeTicks 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);
}

void ProcessNodeImpl::AddFrame(FrameNodeImpl* frame_node) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(process_type_, content::PROCESS_TYPE_RENDERER);
  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_EQ(process_type_, content::PROCESS_TYPE_RENDERER);
  DCHECK(frame_nodes_.contains(frame_node));
  frame_nodes_.erase(frame_node);
}

void ProcessNodeImpl::AddWorker(WorkerNodeImpl* worker_node) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(process_type_, content::PROCESS_TYPE_RENDERER);
  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_EQ(process_type_, content::PROCESS_TYPE_RENDERER);
  DCHECK(worker_nodes_.contains(worker_node));
  worker_nodes_.erase(worker_node);
}

void ProcessNodeImpl::set_priority(base::Process::Priority priority) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  priority_.SetAndMaybeNotify(this, priority);
}

void ProcessNodeImpl::add_hosted_content_type(ContentType content_type) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(process_type_, content::PROCESS_TYPE_RENDERER);
  hosted_content_types_.Put(content_type);
}

base::WeakPtr<ProcessNodeImpl> ProcessNodeImpl::GetWeakPtr() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return weak_factory_.GetWeakPtr();
}

void ProcessNodeImpl::SetProcessImpl(base::Process process,
                                     base::ProcessId new_pid,
                                     base::TimeTicks launch_time) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(process.IsValid());

  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_ = base::ByteSize(0);
  resident_set_ = base::ByteSize(0);
  private_swap_ = base::ByteSize(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));
}

ProcessNode::NodeSetView<const FrameNode*> ProcessNodeImpl::GetFrameNodes()
    const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(graph()->NodeEdgesArePublic(this) || frame_nodes_.empty());
  return NodeSetView<const FrameNode*>(frame_nodes_);
}

ProcessNode::NodeSetView<const WorkerNode*> ProcessNodeImpl::GetWorkerNodes()
    const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(graph()->NodeEdgesArePublic(this) || worker_nodes_.empty());
  return NodeSetView<const WorkerNode*>(worker_nodes_);
}

void ProcessNodeImpl::OnAllFramesInProcessFrozen() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(process_type_, content::PROCESS_TYPE_RENDERER);
  for (auto& observer : GetObservers()) {
    observer.OnAllFramesInProcessFrozen(this);
  }
}

// static
perfetto::Track ProcessNodeImpl::GetTracingTrack(
    content::ProcessType process_type,
    const AnyChildProcessHostProxy& proxy) {
  if (process_type == content::PROCESS_TYPE_BROWSER) {
    return perfetto::ProcessTrack::Current();
  }
  return content::GetChildProcessTracingTrack(std::visit(
      [](const auto& proxy) { return proxy.child_process_id(); }, proxy));
}

void ProcessNodeImpl::OnInitializingProperties() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  NodeAttachedDataStorage::Create(this);
}

void ProcessNodeImpl::OnUninitializingEdges() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // All child frames should have been removed before the process is removed.
  DCHECK(frame_nodes_.empty());
}

void ProcessNodeImpl::CleanUpNodeState() {
  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);
  }

  DestroyNodeInlineDataStorage();
}

}  // namespace performance_manager
