chromium/chrome/browser/task_manager/providers/crosapi/task_manager_controller_lacros.cc

// Copyright 2021 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/providers/crosapi/task_manager_controller_lacros.h"

#include "base/containers/contains.h"
#include "base/time/time.h"
#include "base/uuid.h"
#include "chrome/browser/task_manager/task_manager_interface.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "third_party/blink/public/common/web_cache/web_cache_resource_type_stats.h"
#include "ui/gfx/image/image_skia.h"

namespace task_manager {

namespace {

crosapi::mojom::TaskType ToMojo(task_manager::Task::Type type) {
  switch (type) {
    case task_manager::Task::BROWSER:
      return crosapi::mojom::TaskType::kBrowser;
    case task_manager::Task::GPU:
      return crosapi::mojom::TaskType::kGpu;
    case task_manager::Task::ZYGOTE:
      return crosapi::mojom::TaskType::kZygote;
    case task_manager::Task::UTILITY:
      return crosapi::mojom::TaskType::kUtility;
    case task_manager::Task::RENDERER:
      return crosapi::mojom::TaskType::kRenderer;
    case task_manager::Task::EXTENSION:
      return crosapi::mojom::TaskType::kExtension;
    case task_manager::Task::GUEST:
      return crosapi::mojom::TaskType::kGuest;
    case task_manager::Task::PLUGIN:
      return crosapi::mojom::TaskType::kPlugin;
    case task_manager::Task::NACL:
      return crosapi::mojom::TaskType::kNacl;
    case task_manager::Task::SANDBOX_HELPER:
      return crosapi::mojom::TaskType::kSandboxHelper;
    case task_manager::Task::DEDICATED_WORKER:
      return crosapi::mojom::TaskType::kDedicatedWorker;
    case task_manager::Task::SHARED_WORKER:
      return crosapi::mojom::TaskType::kSharedWorker;
    case task_manager::Task::SERVICE_WORKER:
      return crosapi::mojom::TaskType::kServiceWorker;
    default:
      return crosapi::mojom::TaskType::kUnknown;
  }
}

crosapi::mojom::WebCacheResourceTypeStatPtr ToMojo(
    const blink::WebCacheResourceTypeStat& stat) {
  return crosapi::mojom::WebCacheResourceTypeStat::New(stat.count, stat.size,
                                                       stat.decoded_size);
}

crosapi::mojom::WebCacheResourceTypeStatsPtr ToMojo(
    const blink::WebCacheResourceTypeStats& stats) {
  return crosapi::mojom::WebCacheResourceTypeStats::New(
      ToMojo(stats.images), ToMojo(stats.css_style_sheets),
      ToMojo(stats.scripts), ToMojo(stats.xsl_style_sheets),
      ToMojo(stats.fonts), ToMojo(stats.other));
}

}  // namespace

TaskManagerControllerLacros::TaskManagerControllerLacros()
    : TaskManagerObserver(base::Seconds(1), REFRESH_TYPE_NONE) {}

TaskManagerControllerLacros::~TaskManagerControllerLacros() {
  if (observed_task_manager())
    TaskManagerInterface::GetTaskManager()->RemoveObserver(this);
}

void TaskManagerControllerLacros::SetRefreshFlags(int64_t refresh_flags) {
  // Update the refresh flags and notify its observed task manager.
  if (refresh_flags != desired_resources_flags())
    SetRefreshTypesFlags(refresh_flags);

  // Add the TaskManagerControllerLacros to observe the lacros task manager if
  // |refresh_flags| is valid, which indicates ash task manager has started
  // updating.
  if (!observed_task_manager() && refresh_flags != REFRESH_TYPE_NONE)
    TaskManagerInterface::GetTaskManager()->AddObserver(this);
}

void TaskManagerControllerLacros::GetTaskManagerTasks(
    GetTaskManagerTasksCallback callback) {
  DCHECK(observed_task_manager());

  std::optional<TaskId> active_task_id;
  std::string active_task_uuid;
  Browser* browser = chrome::FindLastActive();
  if (browser) {
    if (content::WebContents* active_web_contents =
            browser->tab_strip_model()->GetActiveWebContents()) {
      active_task_id =
          observed_task_manager()->GetTaskIdForWebContents(active_web_contents);
    }
  }

  std::set<TaskId> task_ids_to_remove;
  for (const auto& item : id_to_tasks_)
    task_ids_to_remove.insert(item.first);

  // Get the sorted list of the task IDs from Lacros task manager.
  // Place Lacros tasks in the same order when sending to ash.
  std::vector<crosapi::mojom::TaskPtr> task_results;
  for (const auto& task_id : observed_task_manager()->GetTaskIdsList()) {
    if (task_ids_to_remove.erase(task_id) == 0) {
      // New task.
      id_to_tasks_[task_id] = ToMojoTask(task_id);
    } else {
      // Update existing task.
      crosapi::mojom::TaskPtr& mojo_task = id_to_tasks_[task_id];
      UpdateTask(task_id, mojo_task);
    }
    task_results.push_back(id_to_tasks_[task_id].Clone());

    if (task_id == active_task_id) {
      active_task_uuid = id_to_tasks_[task_id]->task_uuid;
    }
  }

  // Remove stale tasks.
  for (const auto& task_id : task_ids_to_remove)
    id_to_tasks_.erase(task_id);

  // Retrieve and return the task groups.
  std::set<base::ProcessId> pids;
  std::vector<crosapi::mojom::TaskGroupPtr> task_group_results;
  for (const auto& item : id_to_tasks_) {
    const TaskId task_id = item.first;
    const base::ProcessId pid = observed_task_manager()->GetProcessId(task_id);
    auto result = pids.insert(pid);
    if (result.second) {
      // New task group.
      task_group_results.push_back(ToMojoTaskGroup(pid, task_id));
    }
  }

  std::move(callback).Run(std::move(task_results),
                          std::move(task_group_results), active_task_uuid);
}

void TaskManagerControllerLacros::OnTaskManagerClosed() {
  // Task manager closed in ash, clean up cached task data and stop observing
  // lacros task manager.
  id_to_tasks_.clear();

  if (observed_task_manager())
    observed_task_manager()->RemoveObserver(this);
}

void TaskManagerControllerLacros::ActivateTask(const std::string& task_uuid) {
  for (const auto& item : id_to_tasks_) {
    if (item.second->task_uuid == task_uuid) {
      TaskId task_id = item.first;
      // Check if the task is still valid.
      // Note: It is very rare but possible that lacros receives the request
      // from ash to remove a task after the task has been removed from lacros
      // task manager but before the cached |id_to_tasks_| is refreshed in
      // GetTaskManagerTasks() call.
      if (base::Contains(observed_task_manager()->GetTaskIdsList(), task_id)) {
        observed_task_manager()->ActivateTask(task_id);
      }
      return;
    }
  }
}

crosapi::mojom::TaskPtr TaskManagerControllerLacros::ToMojoTask(TaskId id) {
  auto mojo_task = crosapi::mojom::Task::New();
  mojo_task->task_uuid = base::Uuid::GenerateRandomV4().AsLowercaseString();
  mojo_task->type = ToMojo(observed_task_manager()->GetType(id));
  UpdateTask(id, mojo_task);
  return mojo_task;
}

void TaskManagerControllerLacros::UpdateTask(
    TaskId id,
    crosapi::mojom::TaskPtr& mojo_task) {
  mojo_task->title = observed_task_manager()->GetTitle(id);
  mojo_task->process_id = observed_task_manager()->GetProcessId(id);
  gfx::ImageSkia icon = observed_task_manager()->GetIcon(id);
  mojo_task->icon = icon.DeepCopy();
  mojo_task->profile_name = observed_task_manager()->GetProfileName(id);
  mojo_task->used_sqlite_memory =
      observed_task_manager()->GetSqliteMemoryUsed(id);

  int64_t memory_allocated, memory_used;
  if (observed_task_manager()->GetV8Memory(id, &memory_allocated,
                                           &memory_used)) {
    mojo_task->v8_memory_allocated = memory_allocated;
    mojo_task->v8_memory_used = memory_used;
  } else {
    mojo_task->v8_memory_allocated = -1;
    mojo_task->v8_memory_used = -1;
  }

  blink::WebCacheResourceTypeStats stats;
  if (observed_task_manager()->GetWebCacheStats(id, &stats))
    mojo_task->web_cache_stats = ToMojo(stats);

  mojo_task->keep_alive_count = observed_task_manager()->GetKeepaliveCount(id);
  mojo_task->network_usage_rate = observed_task_manager()->GetNetworkUsage(id);
  mojo_task->cumulative_network_usage =
      observed_task_manager()->GetCumulativeNetworkUsage(id);
}

crosapi::mojom::TaskGroupPtr TaskManagerControllerLacros::ToMojoTaskGroup(
    base::ProcessId pid,
    TaskId task_id) {
  auto mojo_task_group = crosapi::mojom::TaskGroup::New();
  mojo_task_group->process_id = pid;
  UpdateTaskGroup(pid, task_id, mojo_task_group);
  return mojo_task_group;
}

void TaskManagerControllerLacros::UpdateTaskGroup(
    base::ProcessId pid,
    TaskId task_id,
    crosapi::mojom::TaskGroupPtr& mojo_task_group) {
  mojo_task_group->platform_independent_cpu_usage =
      observed_task_manager()->GetPlatformIndependentCPUUsage(task_id);
  mojo_task_group->memory_footprint_bytes =
      observed_task_manager()->GetMemoryFootprintUsage(task_id);
  mojo_task_group->swapped_mem_bytes =
      observed_task_manager()->GetSwappedMemoryUsage(task_id);
  mojo_task_group->gpu_memory_bytes =
      observed_task_manager()->GetGpuMemoryUsage(
          task_id, &mojo_task_group->gpu_memory_has_duplicates);
  mojo_task_group->is_backgrounded =
      observed_task_manager()->IsTaskOnBackgroundedProcess(task_id);
  mojo_task_group->nacl_debug_stub_port =
      observed_task_manager()->GetNaClDebugStubPort(task_id);
  mojo_task_group->open_fd_count =
      observed_task_manager()->GetOpenFdCount(task_id);
  mojo_task_group->idle_wakeups_per_second =
      observed_task_manager()->GetIdleWakeupsPerSecond(task_id);
}

}  // namespace task_manager