| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // 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 "base/bind.h" |
| #include "base/pending_task.h" |
| #include "base/power_monitor/power_monitor.h" |
| #include "base/task/post_task.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_task_traits.h" |
| #include "content/public/browser/browser_thread.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>(); |
| } |
| |
| 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); |
| |
| // It's safe to use base::Unretained since the callback will be synchronously |
| // invoked. |
| TaskOrEventFinishedCallback callback = |
| base::BindOnce(&Calculator::TaskOrEventFinishedOnUIThread, |
| base::Unretained(calculator_.get())); |
| |
| DidRunTask(task, ¤tly_running_metadata_ui_, |
| &mismatched_task_identifiers_ui_, std::move(callback)); |
| } |
| |
| 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); |
| |
| // It's safe to use base::Unretained since the callback will be synchronously |
| // invoked. |
| TaskOrEventFinishedCallback callback = |
| base::BindOnce(&Calculator::TaskOrEventFinishedOnIOThread, |
| base::Unretained(calculator_io_)); |
| DidRunTask(task, ¤tly_running_metadata_io_, |
| &mismatched_task_identifiers_io_, std::move(callback)); |
| } |
| |
| void Watcher::WillRunTask(const base::PendingTask* task, |
| bool was_blocked_or_low_priority, |
| std::vector<Metadata>* currently_running_metadata) { |
| // Reentrancy should be rare. |
| if (UNLIKELY(!currently_running_metadata->empty())) { |
| 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 (UNLIKELY(currently_running_metadata->empty() || |
| (task != currently_running_metadata->back().identifier))) { |
| *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 (UNLIKELY(metadata.caused_reentrancy)) |
| 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 (UNLIKELY(task->queue_time.is_null()) && |
| UNLIKELY(task->delayed_run_time.is_null())) { |
| 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); |
| |
| std::move(callback).Run(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 (UNLIKELY(!currently_running_metadata_ui_.empty())) { |
| 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 (UNLIKELY(currently_running_metadata_ui_.empty() || |
| (opaque_identifier != |
| currently_running_metadata_ui_.back().identifier))) { |
| 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 (UNLIKELY(caused_reentrancy)) |
| 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); |
| } |
| |
| void Watcher::OnSuspend() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| calculator_->SetProcessSuspended(true); |
| } |
| |
| void Watcher::OnResume() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| calculator_->SetProcessSuspended(false); |
| } |
| |
| 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(); |
| |
| base::PowerMonitor::AddObserver(this); |
| } |
| |
| 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)); |
| |
| base::PowerMonitor::RemoveObserver(this); |
| } |
| |
| 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 |