blob: ddfc49672ee3909fd0b41c0d1111442332fef470 [file] [log] [blame]
// 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 "chrome/browser/task_manager/providers/fallback_task_provider.h"
#include <vector>
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/process/process.h"
#include "base/task/single_thread_task_runner.h"
#include "chrome/browser/task_manager/providers/render_process_host_task_provider.h"
#include "chrome/browser/task_manager/providers/web_contents/web_contents_task_provider.h"
#include "content/public/browser/browser_thread.h"
using content::BrowserThread;
namespace task_manager {
namespace {
constexpr base::TimeDelta kTimeDelayForPendingTask = base::Milliseconds(750);
// Returns a task that is in the vector if the task in the vector shares a Pid
// with the other task.
Task* GetTaskByPidFromVector(
base::ProcessId process_id,
std::vector<raw_ptr<Task, VectorExperimental>>* which_vector) {
for (Task* candidate : *which_vector) {
if (candidate->process_id() == process_id)
return candidate;
}
return nullptr;
}
} // namespace
FallbackTaskProvider::FallbackTaskProvider(
std::vector<std::unique_ptr<TaskProvider>> primary_subproviders,
std::unique_ptr<TaskProvider> secondary_subprovider)
: secondary_source_(std::make_unique<SubproviderSource>(
this,
std::move(secondary_subprovider))) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
for (auto& provider : primary_subproviders) {
primary_sources_.push_back(
std::make_unique<SubproviderSource>(this, std::move(provider)));
}
}
FallbackTaskProvider::~FallbackTaskProvider() = default;
Task* FallbackTaskProvider::GetTaskOfUrlRequest(int child_id, int route_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
for (const auto& source : primary_sources_) {
Task* task = source->subprovider()->GetTaskOfUrlRequest(child_id, route_id);
if (task)
return task;
}
return secondary_source_->subprovider()->GetTaskOfUrlRequest(child_id,
route_id);
}
void FallbackTaskProvider::StartUpdating() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(shown_tasks_.empty());
for (auto& source : primary_sources_) {
DCHECK(source->tasks()->empty());
source->subprovider()->SetObserver(source.get());
}
DCHECK(secondary_source_->tasks()->empty());
secondary_source_->subprovider()->SetObserver(secondary_source_.get());
}
void FallbackTaskProvider::StopUpdating() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
for (auto& source : primary_sources_) {
source->subprovider()->ClearObserver();
source->tasks()->clear();
}
secondary_source_->subprovider()->ClearObserver();
secondary_source_->tasks()->clear();
shown_tasks_.clear();
pending_shown_tasks_.clear();
}
void FallbackTaskProvider::ShowTaskLater(Task* task) {
auto it = pending_shown_tasks_.lower_bound(task);
if (it == pending_shown_tasks_.end() || it->first != task) {
it = pending_shown_tasks_.emplace_hint(it, std::piecewise_construct,
std::forward_as_tuple(task),
std::forward_as_tuple(this));
} else {
NOTREACHED();
}
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&FallbackTaskProvider::ShowPendingTask,
it->second.GetWeakPtr(), task),
kTimeDelayForPendingTask);
}
void FallbackTaskProvider::ShowPendingTask(Task* task) {
// Pending tasks belong to the secondary source, and showing one means that
// Chromium is missing a primary task provider.
if (!allow_fallback_for_testing_) {
// Log when we use the secondary task provider, to help drive this count to
// zero and have providers for all known processes.
// TODO(avi): Turn this into a DCHECK and remove the log once there are
// providers for all known processes. See https://crbug.com/1083509.
base::UmaHistogramBoolean("BrowserRenderProcessHost.LabeledInTaskManager",
false);
LOG(ERROR)
<< "Every renderer should have at least one task provided by a primary "
<< "task provider. If a \"Renderer\" fallback task is shown, it is a "
<< "bug. If you have repro steps, please file a new bug and tag it as "
<< "a dependency of crbug.com/739782.";
}
pending_shown_tasks_.erase(task);
ShowTask(task);
}
void FallbackTaskProvider::ShowTask(Task* task) {
shown_tasks_.push_back(task);
NotifyObserverTaskAdded(task);
}
void FallbackTaskProvider::HideTask(Task* task) {
pending_shown_tasks_.erase(task);
if (std::erase(shown_tasks_, task) > 0) {
NotifyObserverTaskRemoved(task);
}
}
void FallbackTaskProvider::OnTaskAddedBySource(Task* task,
SubproviderSource* source) {
if (source == secondary_source_.get()) {
// If a secondary task is added but a primary task is already shown for it,
// we can ignore showing the secondary.
for (const auto& primary_source : primary_sources_) {
if (GetTaskByPidFromVector(task->process_id(), primary_source->tasks()))
return;
}
// Always delay showing a secondary source in case a primary source comes in
// soon after.
ShowTaskLater(task);
return;
}
// Log when a primary task is shown instead, to provide a point of comparison
// for cases the secondary task is shown. Remove when there are providers for
// for all known processes. See https://crbug.com/1083509.
base::UmaHistogramBoolean("BrowserRenderProcessHost.LabeledInTaskManager",
true);
// If we get a primary task that has a secondary task that is both known and
// shown we then hide the secondary task and then show the primary task.
ShowTask(task);
for (Task* secondary_task : *secondary_source_->tasks()) {
if (task->process_id() == secondary_task->process_id())
HideTask(secondary_task);
}
}
void FallbackTaskProvider::OnTaskRemovedBySource(Task* task,
SubproviderSource* source) {
HideTask(task);
// When a task from a primary subprovider is removed, see if there are any
// other primary tasks for that process. If not, but there are secondary
// tasks, show them.
if (source != secondary_source_.get()) {
for (const auto& primary_source : primary_sources_) {
if (GetTaskByPidFromVector(task->process_id(), primary_source->tasks()))
return;
}
for (Task* secondary_task : *secondary_source_->tasks()) {
if (task->process_id() == secondary_task->process_id())
ShowTaskLater(secondary_task);
}
}
}
void FallbackTaskProvider::OnTaskUnresponsive(Task* task) {
DCHECK(task);
if (base::Contains(shown_tasks_, task))
NotifyObserverTaskUnresponsive(task);
}
FallbackTaskProvider::SubproviderSource::SubproviderSource(
FallbackTaskProvider* fallback_task_provider,
std::unique_ptr<TaskProvider> subprovider)
: fallback_task_provider_(fallback_task_provider),
subprovider_(std::move(subprovider)) {}
FallbackTaskProvider::SubproviderSource::~SubproviderSource() = default;
void FallbackTaskProvider::SubproviderSource::TaskAdded(Task* task) {
DCHECK(task);
tasks_.push_back(task);
fallback_task_provider_->OnTaskAddedBySource(task, this);
}
void FallbackTaskProvider::SubproviderSource::TaskRemoved(Task* task) {
DCHECK(task);
std::erase(tasks_, task);
fallback_task_provider_->OnTaskRemovedBySource(task, this);
}
void FallbackTaskProvider::SubproviderSource::TaskUnresponsive(Task* task) {
fallback_task_provider_->OnTaskUnresponsive(task);
}
} // namespace task_manager