// 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.

#include "components/performance_manager/performance_manager_impl.h"

#include <atomic>
#include <memory>
#include <utility>

#include "base/check_op.h"
#include "base/containers/flat_set.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/memory/scoped_refptr.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/task/delayed_task_handle.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/time/time.h"
#include "components/performance_manager/graph/frame_node_impl.h"
#include "components/performance_manager/graph/page_node_impl.h"
#include "components/performance_manager/graph/process_node_impl.h"
#include "components/performance_manager/graph/system_node_impl.h"
#include "components/performance_manager/graph/worker_node_impl.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "url/origin.h"

namespace performance_manager {

namespace {

// Singleton instance of PerformanceManagerImpl. Set from the constructor, and
// reset from the destructor. Null if the singleton instance doesn't exist,
// which only happens very early or very late in the process lifetime (or in
// some tests).
PerformanceManagerImpl* g_performance_manager = nullptr;

// Removes a frame tree from the graph starting from the leaf nodes.
void RemoveFrameAndChildrenFromGraph(FrameNodeImpl* frame_node,
                                     GraphImpl* graph) {
  // Recurse on the first child while there is one.
  while (!frame_node->child_frame_nodes().empty()) {
    RemoveFrameAndChildrenFromGraph(*(frame_node->child_frame_nodes().begin()),
                                    graph);
  }

  // Now that all children are deleted, delete this frame.
  graph->RemoveNode(frame_node);
}

}  // namespace

// static
bool PerformanceManager::IsAvailable() {
  return g_performance_manager;
}

PerformanceManagerImpl::~PerformanceManagerImpl() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK_EQ(g_performance_manager, this);
  // TODO(crbug.com/40629049): Move this to a TearDown function.
  graph_.TearDown();
  g_performance_manager = nullptr;
}

// static
GraphImpl* PerformanceManagerImpl::GetGraphImpl() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  CHECK(g_performance_manager);
  return &g_performance_manager->graph_;
}

// static
std::unique_ptr<PerformanceManagerImpl> PerformanceManagerImpl::Create(
    ProcessPriorityPolicySettings process_priority_policy_settings) {
  return base::WrapUnique(
      new PerformanceManagerImpl(process_priority_policy_settings));
}

// static
ProcessPriorityPolicySettings
PerformanceManagerImpl::GetProcessPriorityPolicySettings() {
  // If the singleton is uninitialized (e.g. in unit tests that don't spin up a
  // full PerformanceManager environment), fall back to default policy settings.
  if (!g_performance_manager) {
    return {};
  }
  return g_performance_manager->process_priority_policy_settings_;
}

// static
void PerformanceManagerImpl::Destroy(
    std::unique_ptr<PerformanceManager> instance) {
  // `instance` is deleted at the end of this function.
}

// static
std::unique_ptr<FrameNodeImpl> PerformanceManagerImpl::CreateFrameNode(
    ProcessNodeImpl* process_node,
    PageNodeImpl* page_node,
    FrameNodeImpl* parent_frame_node,
    FrameNodeImpl* outer_document_for_fenced_frame,
    int render_frame_id,
    const blink::LocalFrameToken& frame_token,
    const perfetto::Track& tracing_track,
    content::BrowsingInstanceId browsing_instance_id,
    content::SiteInstanceGroupId site_instance_group_id,
    bool is_current,
    bool is_active) {
  return CreateNodeImpl<FrameNodeImpl>(
      process_node, page_node, parent_frame_node,
      outer_document_for_fenced_frame, render_frame_id, frame_token,
      tracing_track, browsing_instance_id, site_instance_group_id, is_current,
      is_active);
}

// static
std::unique_ptr<PageNodeImpl> PerformanceManagerImpl::CreatePageNode(
    base::WeakPtr<content::WebContents> web_contents,
    const content::WebContents::UniqueToken& web_contents_token,
    const base::UnguessableToken& browser_context_id,
    const GURL& visible_url,
    PagePropertyFlags initial_property_flags,
    base::TimeTicks visibility_change_time,
    const perfetto::Track& tracing_track) {
  return CreateNodeImpl<PageNodeImpl>(std::move(web_contents),
                                      web_contents_token, browser_context_id,
                                      visible_url, initial_property_flags,
                                      visibility_change_time, tracing_track);
}

// static
std::unique_ptr<ProcessNodeImpl> PerformanceManagerImpl::CreateProcessNode(
    BrowserProcessNodeTag tag) {
  return CreateNodeImpl<ProcessNodeImpl>(tag);
}

// static
std::unique_ptr<ProcessNodeImpl> PerformanceManagerImpl::CreateProcessNode(
    RenderProcessHostProxy render_process_host_proxy,
    base::Process::Priority priority) {
  return CreateNodeImpl<ProcessNodeImpl>(std::move(render_process_host_proxy),
                                         priority);
}

// static
std::unique_ptr<ProcessNodeImpl> PerformanceManagerImpl::CreateProcessNode(
    content::ProcessType process_type,
    BrowserChildProcessHostProxy browser_child_process_host_proxy) {
  return CreateNodeImpl<ProcessNodeImpl>(
      process_type, std::move(browser_child_process_host_proxy));
}

// static
std::unique_ptr<WorkerNodeImpl> PerformanceManagerImpl::CreateWorkerNode(
    const base::UnguessableToken& browser_context_id,
    WorkerNode::WorkerType worker_type,
    ProcessNodeImpl* process_node,
    const blink::WorkerToken& worker_token,
    const url::Origin& origin) {
  return CreateNodeImpl<WorkerNodeImpl>(browser_context_id, worker_type,
                                        process_node, worker_token, origin);
}

// static
void PerformanceManagerImpl::DeleteNode(std::unique_ptr<NodeBase> node) {
  CHECK(IsAvailable());
  GetGraphImpl()->RemoveNode(node.get());
  // The node is deleted at the end of this function.
}

// static
void PerformanceManagerImpl::BatchDeleteNodes(
    std::vector<std::unique_ptr<NodeBase>> nodes) {
  GraphImpl* graph = GetGraphImpl();

  base::flat_set<ProcessNodeImpl*> process_nodes;

  for (const auto& node : nodes) {
    switch (node->GetNodeType()) {
      case PageNodeImpl::Type(): {
        auto* page_node = PageNodeImpl::FromNodeBase(node.get());

        // Delete the main frame nodes until no more exist.
        while (!page_node->main_frame_nodes().empty()) {
          RemoveFrameAndChildrenFromGraph(
              *(page_node->main_frame_nodes().begin()), graph);
        }

        graph->RemoveNode(page_node);
        break;
      }
      case ProcessNodeImpl::Type(): {
        // Keep track of the process nodes for removing once all frames nodes
        // are removed.
        auto* process_node = ProcessNodeImpl::FromNodeBase(node.get());
        process_nodes.insert(process_node);
        break;
      }
      case FrameNodeImpl::Type():
        break;
      case WorkerNodeImpl::Type(): {
        auto* worker_node = WorkerNodeImpl::FromNodeBase(node.get());
        graph->RemoveNode(worker_node);
        break;
      }
      case SystemNodeImpl::Type(): {
        NOTREACHED();
      }
    }
  }

  // Remove the process nodes from the graph.
  for (auto* process_node : process_nodes)
    graph->RemoveNode(process_node);

  // When |nodes| goes out of scope, all nodes are deleted.
}

PerformanceManagerImpl::PerformanceManagerImpl(
    ProcessPriorityPolicySettings process_priority_policy_settings)
    : process_priority_policy_settings_(process_priority_policy_settings) {
  DETACH_FROM_SEQUENCE(sequence_checker_);
  CHECK(!g_performance_manager);
  g_performance_manager = this;

  graph_.SetUp();
  graph_.set_ukm_recorder(ukm::UkmRecorder::Get());
}

// static
template <typename NodeType, typename... Args>
std::unique_ptr<NodeType> PerformanceManagerImpl::CreateNodeImpl(
    Args&&... constructor_args) {
  std::unique_ptr<NodeType> new_node =
      std::make_unique<NodeType>(std::forward<Args>(constructor_args)...);
  PerformanceManagerImpl::GetGraphImpl()->AddNewNode(new_node.get());
  return new_node;
}

}  // namespace performance_manager
