blob: 3967d22dd21e6dd4c12dabc07135adcaf4f7c01b [file] [log] [blame]
// 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