| // 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 "content/browser/scheduler/responsiveness/watcher.h" |
| |
| #include <variant> |
| |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/functional/overloaded.h" |
| #include "base/pending_task.h" |
| #include "base/power_monitor/power_monitor.h" |
| #include "build/build_config.h" |
| #include "content/browser/scheduler/responsiveness/calculator.h" |
| #include "content/browser/scheduler/responsiveness/message_loop_observer.h" |
| #include "content/browser/scheduler/responsiveness/native_event_observer.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/common/content_client.h" |
| |
| namespace content { |
| namespace responsiveness { |
| |
| Watcher::Metadata::Metadata(const void* identifier, |
| bool was_blocked_or_low_priority, |
| base::TimeTicks execution_start_time) |
| : identifier(identifier), |
| was_blocked_or_low_priority(was_blocked_or_low_priority), |
| execution_start_time(execution_start_time) {} |
| |
| std::unique_ptr<Calculator> Watcher::CreateCalculator() { |
| return std::make_unique<Calculator>( |
| GetContentClient()->browser()->CreateResponsivenessCalculatorDelegate()); |
| } |
| |
| std::unique_ptr<MetricSource> Watcher::CreateMetricSource() { |
| return std::make_unique<MetricSource>(this); |
| } |
| |
| void Watcher::WillRunTaskOnUIThread(const base::PendingTask* task, |
| bool was_blocked_or_low_priority) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| WillRunTask(task, was_blocked_or_low_priority, |
| ¤tly_running_metadata_ui_); |
| } |
| |
| void Watcher::DidRunTaskOnUIThread(const base::PendingTask* task) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // Capturing `this` is safe because the callback is invoked synchronously by |
| // `DidRunTask()`. |
| auto lambda = [this](base::TimeTicks queue_time, |
| base::TimeTicks execution_start_time, |
| base::TimeTicks execution_finish_time) { |
| calculator_->TaskOrEventFinishedOnUIThread(queue_time, execution_start_time, |
| execution_finish_time); |
| }; |
| DidRunTask(task, ¤tly_running_metadata_ui_, |
| &mismatched_task_identifiers_ui_, lambda); |
| } |
| |
| void Watcher::WillRunTaskOnIOThread(const base::PendingTask* task, |
| bool was_blocked_or_low_priority) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| |
| WillRunTask(task, was_blocked_or_low_priority, |
| ¤tly_running_metadata_io_); |
| } |
| |
| void Watcher::DidRunTaskOnIOThread(const base::PendingTask* task) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| |
| // Capturing `this` is safe because the callback is invoked synchronously by |
| // `DidRunTask()`. |
| auto lambda = [this](base::TimeTicks queue_time, |
| base::TimeTicks execution_start_time, |
| base::TimeTicks execution_finish_time) { |
| calculator_io_->TaskOrEventFinishedOnIOThread( |
| queue_time, execution_start_time, execution_finish_time); |
| }; |
| DidRunTask(task, ¤tly_running_metadata_io_, |
| &mismatched_task_identifiers_io_, lambda); |
| } |
| |
| void Watcher::WillRunTask(const base::PendingTask* task, |
| bool was_blocked_or_low_priority, |
| std::vector<Metadata>* currently_running_metadata) { |
| // Reentrancy should be rare. |
| if (!currently_running_metadata->empty()) [[unlikely]] { |
| currently_running_metadata->back().caused_reentrancy = true; |
| } |
| |
| const base::TimeTicks execution_start_time = base::TimeTicks::Now(); |
| currently_running_metadata->emplace_back(task, was_blocked_or_low_priority, |
| execution_start_time); |
| } |
| |
| void Watcher::DidRunTask(const base::PendingTask* task, |
| std::vector<Metadata>* currently_running_metadata, |
| int* mismatched_task_identifiers, |
| TaskOrEventFinishedCallback callback) { |
| // Calls to DidRunTask should always be paired with WillRunTask. The only time |
| // the identifier should differ is when Watcher is first constructed. The |
| // TaskRunner Observers may be added while a task is being run, which means |
| // that there was no corresponding WillRunTask. |
| if (currently_running_metadata->empty() || |
| (task != currently_running_metadata->back().identifier)) [[unlikely]] { |
| *mismatched_task_identifiers += 1; |
| // Mismatches can happen, so just ignore them for now. See |
| // https://crbug.com/929813 and https://crbug.com/931874 for details. |
| return currently_running_metadata->clear(); |
| } |
| |
| const Metadata metadata = currently_running_metadata->back(); |
| currently_running_metadata->pop_back(); |
| |
| // Ignore tasks that caused reentrancy, since their execution latency will |
| // be very large, but Chrome was still responsive. |
| if (metadata.caused_reentrancy) [[unlikely]] { |
| return; |
| } |
| |
| // Immediate tasks which were posted before the MessageLoopObserver was |
| // created will not have a queue_time nor a delayed run time, and should be |
| // ignored. |
| if (task->queue_time.is_null() && task->delayed_run_time.is_null()) |
| [[unlikely]] { |
| return; |
| } |
| |
| // For delayed tasks and tasks that were blocked or low priority, pretend that |
| // the queuing duration is zero. It is normal to have long queueing time for |
| // these tasks, so it shouldn't be used to measure jank. |
| const bool is_delayed_task = !task->delayed_run_time.is_null(); |
| const base::TimeTicks queue_time = |
| is_delayed_task || metadata.was_blocked_or_low_priority |
| ? metadata.execution_start_time |
| : task->queue_time; |
| const base::TimeTicks execution_finish_time = base::TimeTicks::Now(); |
| |
| DCHECK(!queue_time.is_null()); |
| DCHECK(!metadata.execution_start_time.is_null()); |
| DCHECK(!execution_finish_time.is_null()); |
| DCHECK_LE(queue_time, metadata.execution_start_time); |
| DCHECK_LE(metadata.execution_start_time, execution_finish_time); |
| |
| callback(queue_time, metadata.execution_start_time, execution_finish_time); |
| } |
| |
| void Watcher::WillRunEventOnUIThread(const void* opaque_identifier) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| // Reentrancy should be rare. |
| if (!currently_running_metadata_ui_.empty()) [[unlikely]] { |
| currently_running_metadata_ui_.back().caused_reentrancy = true; |
| } |
| |
| const base::TimeTicks execution_start_time = base::TimeTicks::Now(); |
| currently_running_metadata_ui_.emplace_back( |
| opaque_identifier, /* was_blocked_or_low_priority= */ false, |
| execution_start_time); |
| } |
| |
| void Watcher::DidRunEventOnUIThread(const void* opaque_identifier) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // Calls to DidRunEventOnUIThread should always be paired with |
| // WillRunEventOnUIThread. The only time the identifier should differ is when |
| // Watcher is first constructed. The TaskRunner Observers may be added while a |
| // task is being run, which means that there was no corresponding WillRunTask. |
| if (currently_running_metadata_ui_.empty() || |
| (opaque_identifier != currently_running_metadata_ui_.back().identifier)) |
| [[unlikely]] { |
| mismatched_event_identifiers_ui_ += 1; |
| // See comment in DidRunTask() for why |currently_running_metadata_ui_| may |
| // be reset. |
| return currently_running_metadata_ui_.clear(); |
| } |
| |
| const bool caused_reentrancy = |
| currently_running_metadata_ui_.back().caused_reentrancy; |
| const base::TimeTicks execution_start_time = |
| currently_running_metadata_ui_.back().execution_start_time; |
| currently_running_metadata_ui_.pop_back(); |
| |
| // Ignore events that caused reentrancy, since their execution latency will |
| // be very large, but Chrome was still responsive. |
| if (caused_reentrancy) [[unlikely]] { |
| return; |
| } |
| |
| const base::TimeTicks queue_time = execution_start_time; |
| const base::TimeTicks execution_finish_time = base::TimeTicks::Now(); |
| calculator_->TaskOrEventFinishedOnUIThread(queue_time, execution_start_time, |
| execution_finish_time); |
| } |
| |
| Watcher::Watcher() = default; |
| Watcher::~Watcher() = default; |
| |
| void Watcher::SetUp() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // Set up |calculator_| before |metric_source_| because SetUpOnIOThread() |
| // uses |calculator_|. |
| calculator_ = CreateCalculator(); |
| currently_running_metadata_ui_.reserve(5); |
| |
| metric_source_ = CreateMetricSource(); |
| metric_source_->SetUp(); |
| } |
| |
| void Watcher::Destroy() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // This holds a ref to |this| until the destroy flow completes. |
| base::ScopedClosureRunner on_destroy_complete(base::BindOnce( |
| &Watcher::FinishDestroyMetricSource, base::RetainedRef(this))); |
| |
| metric_source_->Destroy(std::move(on_destroy_complete)); |
| } |
| |
| void Watcher::OnFirstIdle() { |
| calculator_->OnFirstIdle(); |
| } |
| |
| void Watcher::SetUpOnIOThread() { |
| currently_running_metadata_io_.reserve(5); |
| DCHECK(calculator_.get()); |
| calculator_io_ = calculator_.get(); |
| } |
| |
| void Watcher::TearDownOnUIThread() {} |
| |
| void Watcher::FinishDestroyMetricSource() { |
| metric_source_ = nullptr; |
| } |
| |
| void Watcher::TearDownOnIOThread() { |
| calculator_io_ = nullptr; |
| } |
| |
| } // namespace responsiveness |
| } // namespace content |