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

#include "chrome/browser/task_manager/sampling/task_manager_impl.h"

#include <algorithm>
#include <memory>
#include <optional>
#include <ranges>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

#include "base/byte_size.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/notimplemented.h"
#include "base/task/thread_pool.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/task_manager/common/task_manager_features.h"
#include "chrome/browser/task_manager/providers/browser_process_task_provider.h"
#include "chrome/browser/task_manager/providers/child_process_task_provider.h"
#include "chrome/browser/task_manager/providers/fallback_task_provider.h"
#include "chrome/browser/task_manager/providers/render_process_host_task_provider.h"
#include "chrome/browser/task_manager/providers/spare_render_process_host_task_provider.h"
#include "chrome/browser/task_manager/providers/web_contents/web_contents_task_provider.h"
#include "chrome/browser/task_manager/providers/worker_task_provider.h"
#include "chrome/browser/task_manager/sampling/shared_sampler.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_host.h"
#include "content/public/browser/gpu_data_manager.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/mojom/network_service.mojom.h"
#include "services/resource_coordinator/public/cpp/memory_instrumentation/memory_instrumentation.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/task_manager/providers/arc/arc_process_task_provider.h"
#include "chrome/browser/task_manager/providers/vm/vm_process_task_provider.h"
#include "chromeos/ash/experiences/arc/arc_util.h"
#include "chromeos/ash/experiences/arc/process/arc_process_service.h"
#endif  // BUILDFLAG(IS_CHROMEOS)

namespace task_manager {

namespace {

bool g_instance_created = false;

TaskId ComputeRootTaskId(const Task* task) {
  CHECK(task);

  // Walk up the process tree to find the epoch task.
  // Note: It is not guaranteed that the nodes returned are a tree (acyclic).
  std::unordered_set<const Task*> visited;
  const Task* curr = task;
  while (curr != nullptr && curr->HasParentTask()) {
    if (!visited.insert(curr).second) {
      LOG(ERROR)
          << "Cycle detected in task hierarchy. Returning original task id.";
      return task->task_id();
    }
    curr = curr->GetParentTask().get();
  }
  return curr->task_id();
}

}  // namespace

TaskManagerImpl::TaskManagerImpl()
    : on_background_data_ready_callback_(base::BindRepeating(
          &TaskManagerImpl::OnTaskGroupBackgroundCalculationsDone,
          base::Unretained(this))),
      blocking_pool_runner_(base::ThreadPool::CreateSequencedTaskRunner(
          {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
           base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})),
      shared_sampler_(new SharedSampler(blocking_pool_runner_)) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  task_providers_.push_back(std::make_unique<BrowserProcessTaskProvider>());
  task_providers_.push_back(std::make_unique<ChildProcessTaskProvider>());

  // Put all task providers for various types of RenderProcessHosts in this
  // section. All of them should be added as primary subproviders for the
  // FallbackTaskProvider, so that a fallback task can be shown for a renderer
  // process if no other provider is shown for it.
  std::vector<std::unique_ptr<TaskProvider>> primary_subproviders;

  primary_subproviders.push_back(
      std::make_unique<SpareRenderProcessHostTaskProvider>());
  primary_subproviders.push_back(std::make_unique<WorkerTaskProvider>());

  primary_subproviders.push_back(std::make_unique<WebContentsTaskProvider>());

  task_providers_.push_back(std::make_unique<FallbackTaskProvider>(
      std::move(primary_subproviders),
      std::make_unique<RenderProcessHostTaskProvider>()));

#if BUILDFLAG(IS_CHROMEOS)
  if (arc::IsArcAvailable())
    task_providers_.push_back(std::make_unique<ArcProcessTaskProvider>());
  task_providers_.push_back(std::make_unique<VmProcessTaskProvider>());
  arc_shared_sampler_ = std::make_unique<ArcSharedSampler>();
#endif  // BUILDFLAG(IS_CHROMEOS)

  g_instance_created = true;
}

TaskManagerImpl::~TaskManagerImpl() {
  StopUpdating();
}

// static
TaskManagerImpl* TaskManagerImpl::GetInstance() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  static base::NoDestructor<TaskManagerImpl> instance;
  return instance.get();
}

bool TaskManagerImpl::IsCreated() {
  // In minimal mode, BrowserThread doesn't exist.
  if (g_browser_process) {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  }
  return g_instance_created;
}

void TaskManagerImpl::ActivateTask(TaskId task_id) {
  GetTaskByTaskId(task_id)->Activate();
}

bool TaskManagerImpl::IsTaskKillable(TaskId task_id) {
  return GetTaskByTaskId(task_id)->IsKillable();
}

bool TaskManagerImpl::KillTask(TaskId task_id) {
  return GetTaskByTaskId(task_id)->Kill();
}

double TaskManagerImpl::GetPlatformIndependentCPUUsage(TaskId task_id) const {
  return GetTaskGroupByTaskId(task_id)->platform_independent_cpu_usage();
}

base::Time TaskManagerImpl::GetStartTime(TaskId task_id) const {
#if BUILDFLAG(IS_WIN)
  return GetTaskGroupByTaskId(task_id)->start_time();
#else
  NOTIMPLEMENTED();
  return base::Time();
#endif
}

base::TimeDelta TaskManagerImpl::GetCpuTime(TaskId task_id) const {
#if BUILDFLAG(IS_WIN)
  return GetTaskGroupByTaskId(task_id)->cpu_time();
#else
  NOTIMPLEMENTED();
  return base::TimeDelta();
#endif
}

std::optional<base::ByteSize> TaskManagerImpl::GetMemoryFootprintUsage(
    TaskId task_id) const {
  return GetTaskGroupByTaskId(task_id)->footprint_bytes();
}

std::optional<base::ByteSize> TaskManagerImpl::GetSwappedMemoryUsage(
    TaskId task_id) const {
#if BUILDFLAG(IS_CHROMEOS)
  return GetTaskGroupByTaskId(task_id)->swapped_bytes();
#else
  return std::nullopt;
#endif
}

std::optional<base::ByteSize> TaskManagerImpl::GetGpuMemoryUsage(
    TaskId task_id,
    bool* has_duplicates) const {
  const TaskGroup* task_group = GetTaskGroupByTaskId(task_id);
  if (has_duplicates) {
    *has_duplicates = task_group->gpu_memory_has_duplicates();
  }
  return task_group->gpu_memory();
}

int TaskManagerImpl::GetIdleWakeupsPerSecond(TaskId task_id) const {
  return GetTaskGroupByTaskId(task_id)->idle_wakeups_per_second();
}

int TaskManagerImpl::GetHardFaultsPerSecond(TaskId task_id) const {
#if BUILDFLAG(IS_WIN)
  return GetTaskGroupByTaskId(task_id)->hard_faults_per_second();
#else
  return -1;
#endif
}

void TaskManagerImpl::GetGDIHandles(TaskId task_id,
                                    int64_t* current,
                                    int64_t* peak) const {
#if BUILDFLAG(IS_WIN)
  const TaskGroup* task_group = GetTaskGroupByTaskId(task_id);
  *current = task_group->gdi_current_handles();
  *peak = task_group->gdi_peak_handles();
#else
  *current = -1;
  *peak = -1;
#endif  // BUILDFLAG(IS_WIN)
}

void TaskManagerImpl::GetUSERHandles(TaskId task_id,
                                     int64_t* current,
                                     int64_t* peak) const {
#if BUILDFLAG(IS_WIN)
  const TaskGroup* task_group = GetTaskGroupByTaskId(task_id);
  *current = task_group->user_current_handles();
  *peak = task_group->user_peak_handles();
#else
  *current = -1;
  *peak = -1;
#endif  // BUILDFLAG(IS_WIN)
}

int TaskManagerImpl::GetOpenFdCount(TaskId task_id) const {
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC)
  return GetTaskGroupByTaskId(task_id)->open_fd_count();
#else
  return -1;
#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC)
}

bool TaskManagerImpl::IsTaskOnBackgroundedProcess(TaskId task_id) const {
  return GetTaskGroupByTaskId(task_id)->is_backgrounded();
}

const std::u16string& TaskManagerImpl::GetTitle(TaskId task_id) const {
  return GetTaskByTaskId(task_id)->title();
}

std::u16string TaskManagerImpl::GetProfileName(TaskId task_id) const {
  return GetTaskByTaskId(task_id)->GetProfileName();
}

const gfx::ImageSkia& TaskManagerImpl::GetIcon(TaskId task_id) const {
  return GetTaskByTaskId(task_id)->icon();
}

const base::ProcessHandle& TaskManagerImpl::GetProcessHandle(
    TaskId task_id) const {
  return GetTaskGroupByTaskId(task_id)->process_handle();
}

const base::ProcessId& TaskManagerImpl::GetProcessId(TaskId task_id) const {
  return GetTaskGroupByTaskId(task_id)->process_id();
}

TaskId TaskManagerImpl::GetRootTaskId(TaskId task_id) const {
  const auto* task = GetTaskByTaskId(task_id);
  CHECK(task);
  return ComputeRootTaskId(task);
}

Task::Type TaskManagerImpl::GetType(TaskId task_id) const {
  return GetTaskByTaskId(task_id)->GetType();
}

Task::SubType TaskManagerImpl::GetSubType(TaskId task_id) const {
  return GetTaskByTaskId(task_id)->GetSubType();
}

SessionID TaskManagerImpl::GetTabId(TaskId task_id) const {
  return GetTaskByTaskId(task_id)->GetTabId();
}

int TaskManagerImpl::GetChildProcessUniqueId(TaskId task_id) const {
  return GetTaskByTaskId(task_id)->GetChildProcessUniqueID();
}

void TaskManagerImpl::GetTerminationStatus(TaskId task_id,
                                           base::TerminationStatus* out_status,
                                           int* out_error_code) const {
  GetTaskByTaskId(task_id)->GetTerminationStatus(out_status, out_error_code);
}

base::ByteSize TaskManagerImpl::GetNetworkUsage(TaskId task_id) const {
  return GetTaskByTaskId(task_id)->GetNetworkUsageRate();
}

base::ByteSize TaskManagerImpl::GetCumulativeNetworkUsage(
    TaskId task_id) const {
  return GetTaskByTaskId(task_id)->GetCumulativeNetworkUsage();
}

std::optional<base::ByteSize> TaskManagerImpl::GetProcessTotalNetworkUsage(
    TaskId task_id) const {
  return GetTaskGroupByTaskId(task_id)->per_process_network_usage_rate();
}

std::optional<base::ByteSize> TaskManagerImpl::GetSqliteMemoryUsed(
    TaskId task_id) const {
  return GetTaskByTaskId(task_id)->GetSqliteMemoryUsed();
}

bool TaskManagerImpl::GetV8Memory(TaskId task_id,
                                  base::ByteSize* allocated,
                                  base::ByteSize* used) const {
  const Task* task = GetTaskByTaskId(task_id);
  const std::optional<base::ByteSize> allocated_memory =
      task->GetV8MemoryAllocated();
  const std::optional<base::ByteSize> used_memory = task->GetV8MemoryUsed();
  if (!allocated_memory.has_value() || !used_memory.has_value()) {
    return false;
  }

  *allocated = allocated_memory.value();
  *used = used_memory.value();

  return true;
}

bool TaskManagerImpl::GetWebCacheStats(
    TaskId task_id,
    blink::WebCacheResourceTypeStats* stats) const {
  const Task* task = GetTaskByTaskId(task_id);
  if (!task->ReportsWebCacheStats())
    return false;

  *stats = task->GetWebCacheStats();

  return true;
}

int TaskManagerImpl::GetKeepaliveCount(TaskId task_id) const {
  const Task* task = GetTaskByTaskId(task_id);
  if (!task)
    return -1;

  return task->GetKeepaliveCount();
}

const TaskIdList& TaskManagerImpl::GetTaskIdsList() const {
  DCHECK(is_running_) << "Task manager is not running. You must observe the "
                         "task manager for it to start running";

  if (sorted_task_ids_.empty()) {
    // |comparator| groups and sorts by subtask-ness (to push all subtasks to be
    // last), then by process type (e.g. the browser process should be first;
    // renderer processes should be together), then tab id (processes used by
    // the same tab should be kept together, and a tab should have a stable
    // position in the list as it cycles through processes, and tab creation
    // order is meaningful), and finally by task id (when all else is equal, put
    // the oldest tasks first).
    auto comparator = [](const Task* a, const Task* b) -> bool {
      bool should_make_adjustment = false;
#if !BUILDFLAG(IS_ANDROID)
      // Move vm processes up over the arc tasks.
      should_make_adjustment =
          (a->GetType() == Task::ARC && b->GetType() == Task::CROSTINI) ||
          (b->GetType() == Task::ARC && a->GetType() == Task::CROSTINI);
#endif
      return std::make_tuple(
                 a->HasParentTask(),
                 should_make_adjustment ? b->GetType() : a->GetType(),
                 a->GetTabId(), a->task_id()) <
             std::make_tuple(
                 b->HasParentTask(),
                 should_make_adjustment ? a->GetType() : b->GetType(),
                 b->GetTabId(), b->task_id());
    };

    const size_t num_groups =
        task_groups_by_proc_id_.size() + arc_vm_task_groups_by_proc_id_.size();

    // |task_groups_by_task_id_| contains all tasks, both VM and non-VM.
    const size_t num_tasks = task_groups_by_task_id_.size();

    // Populate |tasks_to_visit| with one task from each group.
    std::vector<const Task*> tasks_to_visit;
    tasks_to_visit.reserve(num_groups);
    std::unordered_map<const Task*, std::vector<const Task*>> children;
    for (const auto& groups_pair : task_groups_by_proc_id_) {
      // The first task in the group (per |comparator|) is the one used for
      // sorting the group relative to other groups.
      const std::vector<raw_ptr<Task, VectorExperimental>>& tasks =
          groups_pair.second->tasks();
      Task* group_task =
          *std::min_element(tasks.begin(), tasks.end(), comparator);
      tasks_to_visit.push_back(group_task);

      // Build the parent-to-child map, for use later.
      for (const Task* task : tasks) {
        if (task->HasParentTask())
          children[task->GetParentTask().get()].push_back(task);
        else
          DCHECK(!group_task->HasParentTask());
      }
    }

    for (const auto& groups_pair : arc_vm_task_groups_by_proc_id_) {
      const std::vector<raw_ptr<Task, VectorExperimental>>& tasks =
          groups_pair.second->tasks();
      Task* group_task =
          *std::min_element(tasks.begin(), tasks.end(), comparator);
      tasks_to_visit.push_back(group_task);
    }

    // Now sort |tasks_to_visit| in reverse order (putting the browser process
    // at back()). We will treat it as a stack from now on.
    std::sort(tasks_to_visit.rbegin(), tasks_to_visit.rend(), comparator);
    DCHECK_EQ(Task::BROWSER, tasks_to_visit.back()->GetType());

    // Using |tasks_to_visit| as a stack, and |visited_groups| to track which
    // groups we've already added, add groups to |sorted_task_ids_| until all
    // groups have been added.
    sorted_task_ids_.reserve(num_tasks);
    std::unordered_set<TaskGroup*> visited_groups;
    visited_groups.reserve(num_groups);
    std::vector<raw_ptr<Task, VectorExperimental>>
        current_group_tasks;  // Outside loop for fewer mallocs.
    while (visited_groups.size() < num_groups) {
      DCHECK(!tasks_to_visit.empty());
      TaskGroup* current_group =
          GetTaskGroupByTaskId(tasks_to_visit.back()->task_id());
      tasks_to_visit.pop_back();

      // Mark |current_group| as visited. If this fails, we've already added
      // the group, and should skip over it.
      if (!visited_groups.insert(current_group).second)
        continue;

      // Make a copy of |current_group->tasks()|, sort it, and append the ids.
      current_group_tasks = current_group->tasks();
      std::sort(current_group_tasks.begin(), current_group_tasks.end(),
                comparator);
      for (Task* task : current_group_tasks)
        sorted_task_ids_.push_back(task->task_id());

      // Find the children of the tasks we just added, and push them into
      // |tasks_to_visit|, so that we visit them soon. Work in reverse order,
      // so that we visit them in forward order.
      for (Task* parent : std::views::reverse(current_group_tasks)) {
        auto children_of_parent = children.find(parent);
        if (children_of_parent != children.end()) {
          // Sort children[parent], and then append in reversed order.
          std::sort(children_of_parent->second.begin(),
                    children_of_parent->second.end(), comparator);
          tasks_to_visit.insert(tasks_to_visit.end(),
                                children_of_parent->second.rbegin(),
                                children_of_parent->second.rend());
        }
      }
    }

    DCHECK_EQ(num_tasks, sorted_task_ids_.size());
  }

  return sorted_task_ids_;
}

TaskIdList TaskManagerImpl::GetIdsOfTasksSharingSameProcess(
    TaskId task_id) const {
  DCHECK(is_running_) << "Task manager is not running. You must observe the "
                         "task manager for it to start running";

  TaskIdList result;
  TaskGroup* group = GetTaskGroupByTaskId(task_id);
  if (group) {
    result.reserve(group->tasks().size());
    for (Task* task : group->tasks())
      result.push_back(task->task_id());
  }
  return result;
}

size_t TaskManagerImpl::GetNumberOfTasksOnSameProcess(TaskId task_id) const {
  return GetTaskGroupByTaskId(task_id)->num_tasks();
}

bool TaskManagerImpl::IsRunningInVM(TaskId task_id) const {
  return GetTaskByTaskId(task_id)->IsRunningInVM();
}

TaskId TaskManagerImpl::GetTaskIdForWebContents(
    content::WebContents* web_contents) const {
  if (!web_contents)
    return -1;
  content::RenderFrameHost* rfh = web_contents->GetPrimaryMainFrame();
  Task* task = GetTaskByRoute(rfh->GetGlobalId());
  if (!task)
    return -1;
  return task->task_id();
}

bool TaskManagerImpl::IsTaskValid(TaskId task_id) const {
  return task_groups_by_task_id_.contains(task_id);
}

void TaskManagerImpl::TaskAdded(Task* task) {
  DCHECK(task);

  const base::ProcessId proc_id = task->process_id();
  const TaskId task_id = task->task_id();
  const bool is_running_in_vm = task->IsRunningInVM();
  TaskManagerImpl::PidToTaskGroupMap& task_group_map =
      is_running_in_vm ? arc_vm_task_groups_by_proc_id_
                       : task_groups_by_proc_id_;

  std::unique_ptr<TaskGroup>& task_group = task_group_map[proc_id];
  if (!task_group) {
    task_group = std::make_unique<TaskGroup>(
        task->process_handle(), proc_id, is_running_in_vm,
        on_background_data_ready_callback_, shared_sampler_,
        blocking_pool_runner_);
#if BUILDFLAG(IS_CHROMEOS)
    if (task->GetType() == Task::ARC)
      task_group->SetArcSampler(arc_shared_sampler_.get());
#endif
  }

  task_group->AddTask(task);

  task_groups_by_task_id_[task_id] = task_group.get();

  // Invalidate the cached sorted IDs by clearing the list.
  sorted_task_ids_.clear();

  NotifyObserversOnTaskAdded(task_id);
}

void TaskManagerImpl::TaskRemoved(Task* task) {
  DCHECK(task);

  const base::ProcessId proc_id = task->process_id();
  const TaskId task_id = task->task_id();
  const bool is_running_in_vm = task->IsRunningInVM();

  TaskManagerImpl::PidToTaskGroupMap& task_group_map =
      is_running_in_vm ? arc_vm_task_groups_by_proc_id_
                       : task_groups_by_proc_id_;

  DCHECK(task_group_map.count(proc_id));

  NotifyObserversOnTaskToBeRemoved(task_id);

  TaskGroup* task_group = GetTaskGroupByTaskId(task_id);
  task_group->RemoveTask(task);
  task_groups_by_task_id_.erase(task_id);

  if (task_group->empty())
    task_group_map.erase(proc_id);  // Deletes |task_group|.

  // Invalidate the cached sorted IDs by clearing the list.
  sorted_task_ids_.clear();
}

void TaskManagerImpl::TaskUnresponsive(Task* task) {
  DCHECK(task);
  NotifyObserversOnTaskUnresponsive(task->task_id());
}

#if BUILDFLAG(IS_CHROMEOS)
void TaskManagerImpl::TaskIdsListToBeInvalidated() {
  sorted_task_ids_.clear();
  NotifyObserversOnRefresh(GetTaskIdsList());
}
#endif  //  BUILDFLAG(IS_CHROMEOS)

void TaskManagerImpl::UpdateAccumulatedStatsNetworkForRoute(
    content::GlobalRenderFrameHostId render_frame_host_id,
    base::ByteSize recv_bytes,
    base::ByteSize sent_bytes) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (!is_running_)
    return;
  Task* task = GetTaskByRoute(render_frame_host_id);
  if (!task) {
    // Orphaned/unaccounted activity is credited to the Browser process.
    task = GetTaskByRoute(content::GlobalRenderFrameHostId());
  }
  if (!task)
    return;
  task->OnNetworkBytesRead(recv_bytes);
  task->OnNetworkBytesSent(sent_bytes);
}

void TaskManagerImpl::OnVideoMemoryUsageStatsUpdate(
    const gpu::VideoMemoryUsageStats& gpu_memory_stats) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  gpu_memory_stats_ = gpu_memory_stats;
}

void TaskManagerImpl::OnReceivedMemoryDump(
    memory_instrumentation::mojom::RequestOutcome outcome,
    std::unique_ptr<memory_instrumentation::GlobalMemoryDump> dump) {
  waiting_for_memory_dump_ = false;
  // We can ignore `outcome` as it is a coarse grained indicator of whether the
  // global dump was successful; usually because of a missing process or OS
  // dumps. There may still be useful information for other processes in the
  // global dump when `outcome` is not `kSuccess`.
  if (dump) {
    for (const auto& pmd : dump->process_dumps()) {
      auto it = task_groups_by_proc_id_.find(pmd.pid());
      if (it == task_groups_by_proc_id_.end()) {
        continue;
      }
      it->second->set_footprint(base::KiB(pmd.os_dump().private_footprint_kb));
    }
  }

  // Notify that background calculations are done. This is necessary because
  // `TaskGroup` doesn't manage memory footprint, so it won't trigger the
  // notification if memory is the only or the last background task.
  OnTaskGroupBackgroundCalculationsDone();
}

void TaskManagerImpl::Refresh() {
  if (IsResourceRefreshEnabled(REFRESH_TYPE_GPU_MEMORY)) {
    content::GpuDataManager::GetInstance()->RequestVideoMemoryUsageStatsUpdate(
        base::BindOnce(&TaskManagerImpl::OnVideoMemoryUsageStatsUpdate,
                       weak_ptr_factory_.GetWeakPtr()));
  }

  if (IsResourceRefreshEnabled(REFRESH_TYPE_MEMORY_FOOTPRINT) &&
      !waiting_for_memory_dump_) {
    // The callback keeps this object alive until the callback is invoked.
    waiting_for_memory_dump_ = true;
    auto callback = base::BindOnce(&TaskManagerImpl::OnReceivedMemoryDump,
                                   weak_ptr_factory_.GetWeakPtr());
    memory_instrumentation::MemoryInstrumentation::GetInstance()
        ->RequestPrivateMemoryFootprint(base::kNullProcessId,
                                        std::move(callback));
  }
  for (auto& groups_itr : task_groups_by_proc_id_) {
    groups_itr.second->Refresh(gpu_memory_stats_, GetCurrentRefreshTime(),
                               enabled_resources_flags());
  }

  for (auto& groups_itr : arc_vm_task_groups_by_proc_id_) {
    groups_itr.second->Refresh(gpu_memory_stats_, GetCurrentRefreshTime(),
                               enabled_resources_flags());
  }

#if BUILDFLAG(IS_CHROMEOS)
  if (TaskManagerObserver::IsResourceRefreshEnabled(
          REFRESH_TYPE_MEMORY_FOOTPRINT, enabled_resources_flags())) {
    arc_shared_sampler_->Refresh();
  }
#endif  // BUILDFLAG(IS_CHROMEOS)

  NotifyObserversOnRefresh(GetTaskIdsList());
}

void TaskManagerImpl::StartUpdating() {
  if (is_running_)
    return;

  is_running_ = true;

  content::GetNetworkService()->EnableDataUseUpdates(true);

  for (const auto& provider : task_providers_)
    provider->SetObserver(this);

  // Kick off fetch of asynchronous data, e.g., memory footprint, so that it
  // will be displayed sooner after opening the task manager.
  Refresh();
}

void TaskManagerImpl::StopUpdating() {
  if (!is_running_)
    return;

  is_running_ = false;

  content::GetNetworkService()->EnableDataUseUpdates(false);

  for (const auto& provider : task_providers_)
    provider->ClearObserver();

  task_groups_by_proc_id_.clear();
  arc_vm_task_groups_by_proc_id_.clear();
  task_groups_by_task_id_.clear();
  sorted_task_ids_.clear();

  waiting_for_memory_dump_ = false;
  weak_ptr_factory_.InvalidateWeakPtrs();
}

Task* TaskManagerImpl::GetTaskByRoute(
    content::GlobalRenderFrameHostId render_frame_host_id) const {
  for (const auto& task_provider : task_providers_) {
    // TODO(crbug.com/379869738) Remove GetUnsafeValue.
    Task* task = task_provider->GetTaskOfUrlRequest(
        render_frame_host_id.child_id.GetUnsafeValue(),
        render_frame_host_id.frame_routing_id);
    if (task)
      return task;
  }
  return nullptr;
}

TaskGroup* TaskManagerImpl::GetTaskGroupByTaskId(TaskId task_id) const {
  auto it = task_groups_by_task_id_.find(task_id);
  CHECK(it != task_groups_by_task_id_.end());
  return it->second;
}

Task* TaskManagerImpl::GetTaskByTaskId(TaskId task_id) const {
  return GetTaskGroupByTaskId(task_id)->GetTaskById(task_id);
}

void TaskManagerImpl::OnTaskGroupBackgroundCalculationsDone() {
  // If we are still waiting for memory dump, we shouldn't notify yet.
  // `OnReceivedMemoryDump` will call this method again when it's done.
  if (waiting_for_memory_dump_) {
    return;
  }

  for (const auto& groups_itr : task_groups_by_proc_id_) {
    if (!groups_itr.second->AreBackgroundCalculationsDone())
      return;
  }
  NotifyObserversOnRefreshWithBackgroundCalculations(GetTaskIdsList());
  for (const auto& groups_itr : task_groups_by_proc_id_)
    groups_itr.second->ClearCurrentBackgroundCalculationsFlags();
}

}  // namespace task_manager
