chromium/chrome/browser/task_manager/providers/crosapi/crosapi_task_provider_ash.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/crosapi_task_provider_ash.h"

#include <set>
#include <vector>

#include "base/check_op.h"
#include "chrome/browser/ash/crosapi/crosapi_ash.h"
#include "chrome/browser/ash/crosapi/crosapi_manager.h"
#include "chrome/browser/task_manager/providers/crosapi/crosapi_task.h"
#include "chrome/browser/task_manager/sampling/task_group.h"

namespace task_manager {

CrosapiTaskProviderAsh::CrosapiTaskProviderAsh() {
  // CrosapiManager is not initialized on unit testing.
  if (crosapi::CrosapiManager::IsInitialized()) {
    crosapi::CrosapiManager::Get()
        ->crosapi_ash()
        ->task_manager_ash()
        ->SetObserver(this);
  }
}

CrosapiTaskProviderAsh::~CrosapiTaskProviderAsh() {
  // When user signs out from ash, CrospaiManager instance owned by
  // `ChromeBrowserMainPartsAsh` is destroyed before `TaskManagerImpl`
  // instance, which is a lazy instance. We should always make sure
  // CrosapiManager::IsInitialized  before accessing it.
  if (crosapi::CrosapiManager::IsInitialized()) {
    crosapi::CrosapiManager::Get()
        ->crosapi_ash()
        ->task_manager_ash()
        ->RemoveObserver();
  }
}

Task* CrosapiTaskProviderAsh::GetTaskOfUrlRequest(int child_id, int route_id) {
  return nullptr;
}

void CrosapiTaskProviderAsh::RefreshTaskGroup(TaskGroup* task_group) {
  auto it = pid_to_task_groups_.find(task_group->process_id());
  if (it == pid_to_task_groups_.end()) {
    LOG(WARNING) << "Can't find task group for pid:"
                 << task_group->process_id();
    return;
  }

  crosapi::mojom::TaskGroupPtr& cached_task_group = it->second;
  task_group->set_platform_independent_cpu_usage(
      cached_task_group->platform_independent_cpu_usage);
  task_group->set_footprint_bytes(cached_task_group->memory_footprint_bytes);
  task_group->set_swapped_bytes(cached_task_group->swapped_mem_bytes);
  task_group->set_gpu_memory(cached_task_group->gpu_memory_bytes);
  task_group->set_gpu_memory_has_duplicates(
      cached_task_group->gpu_memory_has_duplicates);
  task_group->set_is_backgrounded(cached_task_group->is_backgrounded);
#if BUILDFLAG(ENABLE_NACL)
  task_group->set_nacl_debug_stub_port(cached_task_group->nacl_debug_stub_port);
#endif  // BUILDFLAG(ENABLE_NACL)
  task_group->set_open_fd_count(cached_task_group->open_fd_count);
  task_group->set_idle_wakeups_per_second(
      cached_task_group->idle_wakeups_per_second);
}

const TaskIdList& CrosapiTaskProviderAsh::GetSortedTaskIds() {
  return sorted_task_ids_;
}

void CrosapiTaskProviderAsh::StartUpdating() {
  // CrosapiManager is not initialized on unit testing and do not start
  // refresh_timer_ on unit testing.
  if (!crosapi::CrosapiManager::IsInitialized())
    return;

  refresh_timer_.Start(
      FROM_HERE, base::Seconds(1),
      base::BindRepeating(&CrosapiTaskProviderAsh::GetCrosapiTaskManagerTasks,
                          base::Unretained(this)));
}

void CrosapiTaskProviderAsh::StopUpdating() {
  // Task manager has stopped updating (Task manager UI closed). Clean up cached
  // data and notify lacros about the event.
  refresh_timer_.Stop();
  CleanupCachedData();

  if (crosapi::CrosapiManager::IsInitialized()) {
    crosapi::CrosapiManager::Get()
        ->crosapi_ash()
        ->task_manager_ash()
        ->OnTaskManagerClosed();
  }
}

void CrosapiTaskProviderAsh::OnTaskManagerProviderDisconnected() {
  // No more task manager clients (lacros has been shut down), remove all
  // lacros tasks and clean up.
  for (const auto& item : uuid_to_task_)
    NotifyObserverTaskRemoved(item.second.get());

  CleanupCachedData();
}

void CrosapiTaskProviderAsh::GetCrosapiTaskManagerTasks() {
  if (!crosapi::CrosapiManager::IsInitialized())
    return;

  // Get lacros tasks if there is any task manager provider registered and ash
  // task manager has been set up to refresh with a valid refresh interval.
  auto* task_manager_ash =
      crosapi::CrosapiManager::Get()->crosapi_ash()->task_manager_ash();
  if (task_manager_ash->HasRegisteredProviders()) {
    task_manager_ash->GetTaskManagerTasks(
        base::BindOnce(&CrosapiTaskProviderAsh::OnGetTaskManagerTasks,
                       weak_ptr_factory_.GetWeakPtr()));
  }
}

void CrosapiTaskProviderAsh::OnGetTaskManagerTasks(
    std::vector<crosapi::mojom::TaskPtr> task_results,
    std::vector<crosapi::mojom::TaskGroupPtr> task_group_results,
    const std::optional<std::string>& active_task_uuid) {
  // Ignore the data returned from the previous crosapi GetTaskManagrTasks
  // call which is issued before StopUpdating().
  if (!IsUpdating())
    return;

  DCHECK_EQ(uuid_to_task_.size(), sorted_task_ids_.size());

  std::set<std::string> uuid_to_remove;
  for (const auto& item : uuid_to_task_)
    uuid_to_remove.insert(item.first);

  bool task_added_or_removed = false;

  for (const auto& mojo_task : task_results) {
    uuid_to_remove.erase(mojo_task->task_uuid);
  }

  // Remove the stale lacros tasks. Doing this before adding or updating task
  // can reduce chance needing to move existing tasks.
  for (const auto& uuid : uuid_to_remove) {
    NotifyObserverTaskRemoved(uuid_to_task_[uuid].get());
    std::erase(sorted_task_ids_, uuid_to_task_[uuid]->task_id());
    uuid_to_task_.erase(uuid);
    DCHECK_EQ(uuid_to_task_.size(), sorted_task_ids_.size());
    task_added_or_removed = true;
  }

  std::optional<TaskId> last_updated_task_id;
  for (const auto& mojo_task : task_results) {
    const bool new_task = !base::Contains(uuid_to_task_, mojo_task->task_uuid);
    std::unique_ptr<CrosapiTask>& task = uuid_to_task_[mojo_task->task_uuid];
    // Find the next position of |last_updated_task_id| to add or update the
    // current task.
    std::vector<TaskId>::iterator destination_it = sorted_task_ids_.begin();
    if (last_updated_task_id.has_value()) {
      auto it = std::find(sorted_task_ids_.begin(), sorted_task_ids_.end(),
                          last_updated_task_id.value());
      if (it != sorted_task_ids_.end()) {
        destination_it = it + 1;
      }
    }
    if (new_task) {
      // New lacros task.
      task_added_or_removed = true;
      DCHECK(!task.get());
      task = std::make_unique<CrosapiTask>(mojo_task);
      // Since NotifyObserverTaskAdded will cause task manager to rebuild its
      // GetSortedTaskIds() with |sorted_task_ids_|, and also trigger the UI to
      // update with new task added. We must make sure |sorted_task_ids_|
      // always contains all the crosapi tasks before calling
      // NotifyObserverTaskAdded.
      sorted_task_ids_.insert(destination_it, task->task_id());
      DCHECK_EQ(uuid_to_task_.size(), sorted_task_ids_.size());
      NotifyObserverTaskAdded(task.get());
    } else {
      // Update existing lacros task.
      // The existing task does not guarantee to have the same order so we need
      // to reinsert to the right position if needed. The following will move
      // the existing task from origin_it to destination_it if they are
      // different. Task Manager does not support move operation currently,
      // which may make selection incorrect when it happens. But since reorder
      // of existing tasks can rarely happen it's acceptable to leave it as is
      // for now.
      // TODO(b/329101660): Report move operation to |TableModelObserver|.
      std::vector<TaskId>::iterator origin_it = std::find(
          sorted_task_ids_.begin(), sorted_task_ids_.end(), task->task_id());
      // Insert or remove from the back the vector first.
      if (origin_it < destination_it) {
        sorted_task_ids_.insert(destination_it, task->task_id());
        sorted_task_ids_.erase(origin_it);
      } else if (origin_it > destination_it) {
        sorted_task_ids_.erase(origin_it);
        sorted_task_ids_.insert(destination_it, task->task_id());
      }
      task->Update(mojo_task);
      if (task->process_id() != mojo_task->process_id) {
        UpdateTaskProcessInfoAndNotifyObserver(
            task.get(), mojo_task->process_id, mojo_task->process_id);
      }
    }
    last_updated_task_id = task->task_id();
  }

  DCHECK_EQ(task_results.size(), uuid_to_task_.size());

  // Cache task group data.
  pid_to_task_groups_.clear();
  for (auto& mojo_task_group : task_group_results) {
    pid_to_task_groups_[mojo_task_group->process_id] =
        std::move(mojo_task_group);
  }

  // Notify task manager to invalidate GetTaskIdsList if there are any lacros
  // tasks added or removed, since adding or removing tasks will cause
  // lacros tasks to be re-sorted in lacros. Ash task manager UI won't get the
  // complete list of the new sorted lacros task ids until task manager
  // invalidate and rebuild the sorted task list in GetTaskIdsList().
  DCHECK_EQ(task_results.size(), sorted_task_ids_.size());
  if (task_added_or_removed)
    NotifyObserverTaskIdsListToBeInvalidated();

  if (!active_task_uuid.has_value()) {
    return;
  }
  const auto it = uuid_to_task_.find(active_task_uuid.value());
  if (it != uuid_to_task_.end()) {
    NotifyObserverActiveTaskFetched(it->second->task_id());
  }
}

void CrosapiTaskProviderAsh::CleanupCachedData() {
  uuid_to_task_.clear();
  sorted_task_ids_.clear();
  pid_to_task_groups_.clear();
}

}  // namespace task_manager