| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/performance_manager/public/resource_attribution/queries.h" |
| |
| #include <utility> |
| |
| #include "base/check.h" |
| #include "base/containers/enum_set.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/timer/timer.h" |
| #include "components/performance_manager/resource_attribution/query_params.h" |
| #include "components/performance_manager/resource_attribution/query_scheduler.h" |
| |
| namespace resource_attribution { |
| |
| namespace { |
| |
| using QueryParams = internal::QueryParams; |
| using QueryScheduler = internal::QueryScheduler; |
| |
| // The minimum delay between QueryOnce() calls for kMemorySummary resources. |
| // This can only be updated in unit tests so doesn't need to be thread-safe. |
| // Copied from ProcessMetricsDecorator::kMinImmediateRefreshDelay. |
| // TODO(crbug.com/40926264): Manage timing centrally in QueryScheduler. |
| base::TimeDelta g_min_memory_query_delay = base::Seconds(2); |
| |
| } // namespace |
| |
| class ScopedResourceUsageQuery::ThrottledTimer { |
| public: |
| ThrottledTimer() = default; |
| ~ThrottledTimer() = default; |
| |
| ThrottledTimer(const ThrottledTimer&) = delete; |
| ThrottledTimer& operator=(const ThrottledTimer&) = delete; |
| |
| // Starts the timer to repeatedly query `params` after `delay`. |
| // `observer_list` will be notified with the results. |
| void StartTimer(base::TimeDelta delay, |
| internal::QueryParams* params, |
| scoped_refptr<ObserverList> observer_list); |
| |
| // Sends the scheduler a request for query results for `params`. |
| // `observer_list` will be notified with the results. If `timer_fired` is |
| // true, this is invoked from the timer, otherwise it's invoked from |
| // QueryOnce(). |
| void SendRequestToScheduler(internal::QueryParams* params, |
| scoped_refptr<ObserverList> observer_list, |
| bool timer_fired); |
| |
| private: |
| // Returns true if SendRequestToScheduler should be called for `params`, false |
| // otherwise. This must be called on every request to update state. |
| bool ShouldSendRequest(internal::QueryParams* params, bool timer_fired); |
| |
| base::RepeatingTimer timer_; |
| base::TimeTicks last_fire_time_; |
| base::TimeTicks next_fire_time_; |
| base::TimeTicks last_query_once_time_; |
| }; |
| |
| void ScopedResourceUsageQuery::ThrottledTimer::StartTimer( |
| base::TimeDelta delay, |
| internal::QueryParams* params, |
| scoped_refptr<ObserverList> observer_list) { |
| CHECK(!timer_.IsRunning()); |
| CHECK(delay.is_positive()); |
| // Unretained is safe because ScopedResourceUsageQuery owns both `this` and |
| // `params`. |
| timer_.Start(FROM_HERE, delay, |
| base::BindRepeating(&ThrottledTimer::SendRequestToScheduler, |
| base::Unretained(this), |
| base::Unretained(params), observer_list, |
| /*timer_fired=*/true)); |
| next_fire_time_ = base::TimeTicks::Now() + delay; |
| } |
| |
| void ScopedResourceUsageQuery::ThrottledTimer::SendRequestToScheduler( |
| internal::QueryParams* params, |
| scoped_refptr<ObserverList> observer_list, |
| bool timer_fired) { |
| if (ShouldSendRequest(params, timer_fired)) { |
| if (auto* scheduler = QueryScheduler::Get()) { |
| scheduler->RequestResults( |
| *params, base::BindOnce(&ScopedResourceUsageQuery::NotifyObservers, |
| observer_list)); |
| } |
| } |
| } |
| |
| bool ScopedResourceUsageQuery::ThrottledTimer::ShouldSendRequest( |
| internal::QueryParams* params, |
| bool timer_fired) { |
| if (!params->resource_types.Has(ResourceType::kMemorySummary)) { |
| // Only memory queries are throttled. |
| return true; |
| } |
| |
| const auto now = base::TimeTicks::Now(); |
| if (timer_fired) { |
| // Repeating queries aren't throttled, but need to save the current time to |
| // throttle QueryOnce(). |
| CHECK(timer_.IsRunning()); |
| last_fire_time_ = now; |
| next_fire_time_ = now + timer_.GetCurrentDelay(); |
| return true; |
| } |
| |
| // Check if this QueryOnce() should be throttled. |
| if (!last_query_once_time_.is_null() && |
| now < last_query_once_time_ + g_min_memory_query_delay) { |
| // QueryOnce() called recently. |
| return false; |
| } |
| if (!last_fire_time_.is_null() && |
| now < last_fire_time_ + g_min_memory_query_delay) { |
| // Timer fired recently. |
| return false; |
| } |
| if (!next_fire_time_.is_null() && |
| now > next_fire_time_ - g_min_memory_query_delay) { |
| // Timer is going to fire soon. |
| return false; |
| } |
| last_query_once_time_ = now; |
| return true; |
| } |
| |
| ScopedResourceUsageQuery::ScopedDisableMemoryQueryDelayForTesting:: |
| ScopedDisableMemoryQueryDelayForTesting() |
| : previous_delay_(g_min_memory_query_delay) { |
| g_min_memory_query_delay = base::TimeDelta(); |
| } |
| |
| ScopedResourceUsageQuery::ScopedDisableMemoryQueryDelayForTesting:: |
| ~ScopedDisableMemoryQueryDelayForTesting() { |
| CHECK(g_min_memory_query_delay.is_zero()); |
| g_min_memory_query_delay = previous_delay_; |
| } |
| |
| ScopedResourceUsageQuery::~ScopedResourceUsageQuery() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!params_) { |
| // `params_` was moved to another ScopedResourceUsageQuery. |
| return; |
| } |
| // Notify the scheduler this query no longer exists. Sends the QueryParams to |
| // the scheduler to delete to be sure they're valid until the scheduler reads |
| // them. |
| // TODO(crbug.com/40755583): No need to pass ownership of params since this is |
| // no longer asynchronous. |
| if (auto* scheduler = QueryScheduler::Get()) { |
| scheduler->RemoveScopedQuery(std::move(params_)); |
| } |
| } |
| |
| ScopedResourceUsageQuery::ScopedResourceUsageQuery(ScopedResourceUsageQuery&&) = |
| default; |
| |
| ScopedResourceUsageQuery& ScopedResourceUsageQuery::operator=( |
| ScopedResourceUsageQuery&&) = default; |
| |
| void ScopedResourceUsageQuery::AddObserver(QueryResultObserver* observer) { |
| // ObserverListThreadSafe can be called on any sequence. |
| observer_list_->AddObserver(observer); |
| } |
| |
| void ScopedResourceUsageQuery::RemoveObserver(QueryResultObserver* observer) { |
| // Must be called on the same sequence as AddObserver. ObserverListThreadSafe |
| // will validate this. |
| observer_list_->RemoveObserver(observer); |
| } |
| |
| void ScopedResourceUsageQuery::Start(base::TimeDelta delay, |
| bool observe_other_queries) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (auto* scheduler = QueryScheduler::Get()) { |
| scheduler->StartRepeatingQuery( |
| params_.get(), |
| observe_other_queries |
| ? base::BindRepeating(&ScopedResourceUsageQuery::NotifyObservers, |
| observer_list_) |
| : base::NullCallback()); |
| } |
| if (delay.is_positive()) { |
| throttled_timer_->StartTimer(delay, params_.get(), observer_list_); |
| } |
| } |
| |
| void ScopedResourceUsageQuery::QueryOnce() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| throttled_timer_->SendRequestToScheduler(params_.get(), observer_list_, |
| /*timer_fired=*/false); |
| } |
| |
| QueryParams* ScopedResourceUsageQuery::GetParamsForTesting() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return params_.get(); |
| } |
| |
| // static |
| base::TimeDelta ScopedResourceUsageQuery::GetMinMemoryQueryDelayForTesting() { |
| return g_min_memory_query_delay; |
| } |
| |
| ScopedResourceUsageQuery::ScopedResourceUsageQuery( |
| base::PassKey<QueryBuilder>, |
| std::unique_ptr<QueryParams> params) |
| : params_(std::move(params)), |
| throttled_timer_(std::make_unique<ThrottledTimer>()) { |
| if (auto* scheduler = QueryScheduler::Get()) { |
| scheduler->AddScopedQuery(params_.get()); |
| } |
| } |
| |
| // static |
| void ScopedResourceUsageQuery::NotifyObservers( |
| scoped_refptr<ObserverList> observer_list, |
| const QueryResultMap& results) { |
| observer_list->Notify(FROM_HERE, &QueryResultObserver::OnResourceUsageUpdated, |
| results); |
| } |
| |
| QueryBuilder::QueryBuilder() : params_(std::make_unique<QueryParams>()) {} |
| |
| QueryBuilder::~QueryBuilder() = default; |
| |
| QueryBuilder::QueryBuilder(QueryBuilder&&) = default; |
| |
| QueryBuilder& QueryBuilder::operator=(QueryBuilder&&) = default; |
| |
| QueryBuilder& QueryBuilder::AddResourceContext(const ResourceContext& context) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK(params_); |
| params_->contexts.AddResourceContext(context); |
| return *this; |
| } |
| |
| QueryBuilder& QueryBuilder::AddResourceType(ResourceType resource_type) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK(params_); |
| params_->resource_types.Put(resource_type); |
| return *this; |
| } |
| |
| ScopedResourceUsageQuery QueryBuilder::CreateScopedQuery() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| ValidateQuery(); |
| // Pass ownership of `params_` to the scoped query, to avoid copying the |
| // parameter contents. |
| return ScopedResourceUsageQuery(base::PassKey<QueryBuilder>(), |
| std::move(params_)); |
| } |
| |
| void QueryBuilder::QueryOnce( |
| base::OnceCallback<void(const QueryResultMap&)> callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| ValidateQuery(); |
| if (auto* scheduler = QueryScheduler::Get()) { |
| scheduler->RequestResults(*params_, std::move(callback)); |
| } |
| params_.reset(); |
| } |
| |
| QueryBuilder QueryBuilder::Clone() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return QueryBuilder(params_->Clone()); |
| } |
| |
| QueryParams* QueryBuilder::GetParamsForTesting() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return params_.get(); |
| } |
| |
| QueryBuilder::QueryBuilder(std::unique_ptr<QueryParams> params) |
| : params_(std::move(params)) {} |
| |
| QueryBuilder& QueryBuilder::AddAllContextsWithTypeId( |
| internal::ResourceContextTypeId type_id) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK(params_); |
| params_->contexts.AddAllContextsOfType(type_id); |
| return *this; |
| } |
| |
| void QueryBuilder::ValidateQuery() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK(params_); |
| CHECK(!params_->contexts.IsEmpty()); |
| CHECK(!params_->resource_types.empty()); |
| } |
| |
| } // namespace resource_attribution |