// 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 Watcher::CreateCalculator() { return std::make_unique(); } std::unique_ptr Watcher::CreateMetricSource() { return std::make_unique(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* 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* 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