Add the framework for the ResourceAttribution query API
This adds the QueryBuilder and ScopedResourceUsageQuery interface
classes. They register with the query scheduler but don't send any
queries yet.
R=fdoray
Bug: 1471683
Change-Id: I639b461c7ba4f63bc4cd2187971747579c3b7520
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5009744
Commit-Queue: Joe Mason <joenotcharles@google.com>
Reviewed-by: Francois Pierre Doray <fdoray@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1222097}
diff --git a/components/performance_manager/BUILD.gn b/components/performance_manager/BUILD.gn
index 2223617..9620239 100644
--- a/components/performance_manager/BUILD.gn
+++ b/components/performance_manager/BUILD.gn
@@ -169,8 +169,10 @@
"public/resource_attribution/frame_context.h",
"public/resource_attribution/page_context.h",
"public/resource_attribution/process_context.h",
+ "public/resource_attribution/queries.h",
"public/resource_attribution/query_results.h",
"public/resource_attribution/resource_contexts.h",
+ "public/resource_attribution/resource_types.h",
"public/resource_attribution/scoped_cpu_query.h",
"public/resource_attribution/type_helpers.h",
"public/resource_attribution/worker_context.h",
@@ -194,6 +196,9 @@
"resource_attribution/graph_change.h",
"resource_attribution/page_context.cc",
"resource_attribution/process_context.cc",
+ "resource_attribution/queries.cc",
+ "resource_attribution/query_params.cc",
+ "resource_attribution/query_params.h",
"resource_attribution/query_scheduler.cc",
"resource_attribution/query_scheduler.h",
"resource_attribution/scoped_cpu_query.cc",
@@ -355,6 +360,7 @@
"resource_attribution/frame_context_unittest.cc",
"resource_attribution/page_context_unittest.cc",
"resource_attribution/process_context_unittest.cc",
+ "resource_attribution/queries_unittest.cc",
"resource_attribution/query_scheduler_unittest.cc",
"resource_attribution/resource_contexts_unittest.cc",
"resource_attribution/type_helpers_unittest.cc",
diff --git a/components/performance_manager/public/resource_attribution/queries.h b/components/performance_manager/public/resource_attribution/queries.h
new file mode 100644
index 0000000..b67c52c8
--- /dev/null
+++ b/components/performance_manager/public/resource_attribution/queries.h
@@ -0,0 +1,163 @@
+// 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.
+
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_RESOURCE_ATTRIBUTION_QUERIES_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_RESOURCE_ATTRIBUTION_QUERIES_H_
+
+#include <memory>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/observer_list_threadsafe.h"
+#include "base/sequence_checker.h"
+#include "base/types/pass_key.h"
+#include "base/types/variant_util.h"
+#include "components/performance_manager/public/resource_attribution/query_results.h"
+#include "components/performance_manager/public/resource_attribution/resource_contexts.h"
+#include "components/performance_manager/public/resource_attribution/resource_types.h"
+#include "components/performance_manager/public/resource_attribution/type_helpers.h"
+
+namespace performance_manager::resource_attribution {
+
+namespace internal {
+struct QueryParams;
+}
+
+class QueryBuilder;
+
+// An observer that's notified by ScopedResourceUsageQuery whenever new results
+// are available.
+class QueryResultObserver {
+ public:
+ virtual ~QueryResultObserver() = default;
+ virtual void OnResourceUsageUpdated(const QueryResultMap& results) = 0;
+};
+
+// Repeatedly makes resource attribution queries on a schedule as long as it's
+// in scope.
+// TODO(crbug.com/1471683): Unfinished. This registers on create and delete,
+// which may have important side effects, but doesn't make any queries yet.
+class ScopedResourceUsageQuery {
+ public:
+ ~ScopedResourceUsageQuery();
+
+ // Move-only.
+ ScopedResourceUsageQuery(ScopedResourceUsageQuery&&);
+ ScopedResourceUsageQuery& operator=(ScopedResourceUsageQuery&&);
+ ScopedResourceUsageQuery(const ScopedResourceUsageQuery&) = delete;
+ ScopedResourceUsageQuery& operator=(const ScopedResourceUsageQuery&) = delete;
+
+ // Adds an observer that will be notified on the calling sequence. Can be
+ // called from any sequence.
+ void AddObserver(QueryResultObserver* observer);
+
+ // Removes an observer. Must be called from the same sequence as
+ // AddObserver().
+ void RemoveObserver(QueryResultObserver* observer);
+
+ // Restricted implementation methods:
+
+ // Gives tests access to validate the implementation.
+ internal::QueryParams* GetParamsForTesting() const;
+
+ // Private constructor for QueryBuilder. Use QueryBuilder::CreateScopedQuery()
+ // to create a query.
+ ScopedResourceUsageQuery(base::PassKey<QueryBuilder>,
+ std::unique_ptr<internal::QueryParams> params);
+
+ private:
+ using ObserverList = base::ObserverListThreadSafe<
+ QueryResultObserver,
+ base::RemoveObserverPolicy::kAddingSequenceOnly>;
+
+ FRIEND_TEST_ALL_PREFIXES(ScopedResourceUsageQueryTest, Movable);
+ FRIEND_TEST_ALL_PREFIXES(ScopedResourceUsageQueryTest, Observers);
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ // Parameters passed from the QueryBuilder.
+ std::unique_ptr<internal::QueryParams> params_
+ GUARDED_BY_CONTEXT(sequence_checker_);
+
+ scoped_refptr<ObserverList> observer_list_ =
+ base::MakeRefCounted<ObserverList>();
+};
+
+// Creates a query to request resource usage measurements on a schedule.
+//
+// Use CreateScopedQuery() to return an object that makes repeated measurements
+// as long as it's in scope, or QueryOnce() to take a single measurement.
+//
+// Example usage:
+//
+// // To invoke `callback` with the CPU usage of all processes.
+// QueryBuilder()
+// .AddAllContextsOfType<ProcessContext>()
+// .AddResourceType(ResourceType::kCPUTime)
+// .QueryOnce(callback);
+//
+// QueryBuilder is move-only to prevent accidentally copying large state. Use
+// Clone() to make an explicit copy.
+//
+// TODO(crbug.com/1471683): Unfinished. This collects parameters but doesn't
+// make any queries yet.
+class QueryBuilder {
+ public:
+ QueryBuilder();
+ ~QueryBuilder();
+
+ // Move-only.
+ QueryBuilder(QueryBuilder&&);
+ QueryBuilder& operator=(QueryBuilder&&);
+ QueryBuilder(const QueryBuilder&) = delete;
+ QueryBuilder& operator=(const QueryBuilder&) = delete;
+
+ // Adds `context` to the list of resource contexts to query.
+ QueryBuilder& AddResourceContext(const ResourceContext& context);
+
+ // Adds all resource contexts of type ContextType to the list of resource
+ // contexts to query. Whenever the query causes a resource measurement, all
+ // resource contexts of the given type that exist at that moment will be
+ // measured.
+ template <typename ContextType,
+ internal::EnableIfIsVariantAlternative<ContextType,
+ ResourceContext> = true>
+ QueryBuilder& AddAllContextsOfType() {
+ return AddAllContextsWithTypeIndex(
+ base::VariantIndexOfType<ResourceContext, ContextType>());
+ }
+
+ // Add `type` to the lists of resources to query.
+ QueryBuilder& AddResourceType(ResourceType resource_type);
+
+ // Returns a scoped object that will repeatedly run the query and notify
+ // observers with the results. Once this is called the QueryBuilder becomes
+ // invalid.
+ ScopedResourceUsageQuery CreateScopedQuery();
+
+ // Makes a copy of the QueryBuilder to use as a base for similar queries.
+ QueryBuilder Clone() const;
+
+ // Restricted implementation methods:
+
+ // Gives tests access to validate the implementation.
+ internal::QueryParams* GetParamsForTesting() const;
+
+ private:
+ // Private constructor for Clone().
+ explicit QueryBuilder(std::unique_ptr<internal::QueryParams> params);
+
+ // Implementation of AddAllContextsOfType().
+ QueryBuilder& AddAllContextsWithTypeIndex(size_t index);
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ // Parameters built up by the builder.
+ std::unique_ptr<internal::QueryParams> params_
+ GUARDED_BY_CONTEXT(sequence_checker_);
+};
+
+} // namespace performance_manager::resource_attribution
+
+#endif // COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_RESOURCE_ATTRIBUTION_QUERIES_H_
diff --git a/components/performance_manager/public/resource_attribution/query_results.h b/components/performance_manager/public/resource_attribution/query_results.h
index 432c7b5..81d9949 100644
--- a/components/performance_manager/public/resource_attribution/query_results.h
+++ b/components/performance_manager/public/resource_attribution/query_results.h
@@ -83,6 +83,31 @@
return internal::GetFromVariantVector<T>(results);
}
+inline bool operator==(const ResultMetadata& a, const ResultMetadata& b) {
+ static_assert(sizeof(ResultMetadata) ==
+ sizeof(decltype(ResultMetadata::measurement_time)),
+ "update operator== when changing ResultMetadata");
+ return a.measurement_time == b.measurement_time;
+}
+
+inline bool operator!=(const ResultMetadata& a, const ResultMetadata& b) {
+ return !(a == b);
+}
+
+inline bool operator==(const CPUTimeResult& a, const CPUTimeResult& b) {
+ static_assert(sizeof(CPUTimeResult) ==
+ sizeof(decltype(CPUTimeResult::metadata)) +
+ sizeof(decltype(CPUTimeResult::start_time)) +
+ sizeof(decltype(CPUTimeResult::cumulative_cpu)),
+ "update operator== when changing CPUTimeResult");
+ return a.metadata == b.metadata && a.start_time == b.start_time &&
+ a.cumulative_cpu == b.cumulative_cpu;
+}
+
+inline bool operator!=(const CPUTimeResult& a, const CPUTimeResult& b) {
+ return !(a == b);
+}
+
} // namespace performance_manager::resource_attribution
#endif // COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_RESOURCE_ATTRIBUTION_QUERY_RESULTS_H_
diff --git a/components/performance_manager/public/resource_attribution/resource_types.h b/components/performance_manager/public/resource_attribution/resource_types.h
new file mode 100644
index 0000000..eaa9c31a
--- /dev/null
+++ b/components/performance_manager/public/resource_attribution/resource_types.h
@@ -0,0 +1,23 @@
+// 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.
+
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_RESOURCE_ATTRIBUTION_RESOURCE_TYPES_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_RESOURCE_ATTRIBUTION_RESOURCE_TYPES_H_
+
+#include "base/containers/enum_set.h"
+
+namespace performance_manager::resource_attribution {
+
+// Types of resources that Resource Attribution can measure.
+enum class ResourceType {
+ // CPU usage, measured in time spent on CPU.
+ kCPUTime,
+};
+
+using ResourceTypeSet =
+ base::EnumSet<ResourceType, ResourceType::kCPUTime, ResourceType::kCPUTime>;
+
+} // namespace performance_manager::resource_attribution
+
+#endif // COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_RESOURCE_ATTRIBUTION_RESOURCE_TYPES_H_
diff --git a/components/performance_manager/resource_attribution/queries.cc b/components/performance_manager/resource_attribution/queries.cc
new file mode 100644
index 0000000..ccca88b
--- /dev/null
+++ b/components/performance_manager/resource_attribution/queries.cc
@@ -0,0 +1,132 @@
+// 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 <bitset>
+#include <set>
+#include <utility>
+
+#include "base/check.h"
+#include "base/containers/enum_set.h"
+#include "base/functional/bind.h"
+#include "components/performance_manager/resource_attribution/query_params.h"
+#include "components/performance_manager/resource_attribution/query_scheduler.h"
+
+namespace performance_manager::resource_attribution {
+
+namespace {
+
+using QueryParams = internal::QueryParams;
+
+void AddScopedQueryToScheduler(QueryParams* query_params,
+ QueryScheduler* scheduler) {
+ scheduler->AddScopedQuery(query_params);
+}
+
+void RemoveScopedQueryFromScheduler(std::unique_ptr<QueryParams> query_params,
+ QueryScheduler* scheduler) {
+ scheduler->RemoveScopedQuery(std::move(query_params));
+}
+
+} // namespace
+
+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.
+ QueryScheduler::CallOnGraphWithScheduler(
+ base::BindOnce(&RemoveScopedQueryFromScheduler, 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);
+}
+
+internal::QueryParams* ScopedResourceUsageQuery::GetParamsForTesting() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return params_.get();
+}
+
+ScopedResourceUsageQuery::ScopedResourceUsageQuery(
+ base::PassKey<QueryBuilder>,
+ std::unique_ptr<QueryParams> params)
+ : params_(std::move(params)) {
+ // Notify the scheduler this query exists.
+ QueryScheduler::CallOnGraphWithScheduler(
+ base::BindOnce(&AddScopedQueryToScheduler, params_.get()));
+}
+
+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_->resource_contexts.insert(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_);
+ // Pass ownership of `params_` to the scoped query, to avoid copying the
+ // parameter contents.
+ return ScopedResourceUsageQuery(base::PassKey<QueryBuilder>(),
+ std::move(params_));
+}
+
+QueryBuilder QueryBuilder::Clone() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // Clone the parameter contents to a newly-allocated QueryParams with the copy
+ // constructor.
+ auto cloned_params = std::make_unique<QueryParams>(*params_);
+ return QueryBuilder(std::move(cloned_params));
+}
+
+internal::QueryParams* QueryBuilder::GetParamsForTesting() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return params_.get();
+}
+
+QueryBuilder::QueryBuilder(std::unique_ptr<internal::QueryParams> params)
+ : params_(std::move(params)) {}
+
+QueryBuilder& QueryBuilder::AddAllContextsWithTypeIndex(size_t index) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ CHECK(params_);
+ params_->all_context_types.set(index);
+ return *this;
+}
+
+} // namespace performance_manager::resource_attribution
diff --git a/components/performance_manager/resource_attribution/queries_unittest.cc b/components/performance_manager/resource_attribution/queries_unittest.cc
new file mode 100644
index 0000000..0dfbff8
--- /dev/null
+++ b/components/performance_manager/resource_attribution/queries_unittest.cc
@@ -0,0 +1,310 @@
+// 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 <bitset>
+#include <map>
+#include <set>
+#include <type_traits>
+#include <utility>
+
+#include "base/barrier_closure.h"
+#include "base/containers/enum_set.h"
+#include "base/dcheck_is_on.h"
+#include "base/functional/callback.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/observer_list_threadsafe.h"
+#include "base/run_loop.h"
+#include "base/time/time.h"
+#include "components/performance_manager/embedder/graph_features.h"
+#include "components/performance_manager/public/graph/graph.h"
+#include "components/performance_manager/public/graph/page_node.h"
+#include "components/performance_manager/public/graph/process_node.h"
+#include "components/performance_manager/public/resource_attribution/query_results.h"
+#include "components/performance_manager/public/resource_attribution/resource_contexts.h"
+#include "components/performance_manager/public/resource_attribution/resource_types.h"
+#include "components/performance_manager/resource_attribution/cpu_measurement_monitor.h"
+#include "components/performance_manager/resource_attribution/query_params.h"
+#include "components/performance_manager/resource_attribution/query_scheduler.h"
+#include "components/performance_manager/test_support/graph_test_harness.h"
+#include "components/performance_manager/test_support/mock_graphs.h"
+#include "components/performance_manager/test_support/performance_manager_test_harness.h"
+#include "components/performance_manager/test_support/run_in_graph.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/navigation_simulator.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/abseil-cpp/absl/types/variant.h"
+#include "url/gurl.h"
+
+namespace performance_manager::resource_attribution {
+
+namespace {
+
+using QueryParams = internal::QueryParams;
+
+class LenientMockQueryResultObserver : public QueryResultObserver {
+ public:
+ MOCK_METHOD(void,
+ OnResourceUsageUpdated,
+ (const QueryResultMap& results),
+ (override));
+};
+using MockQueryResultObserver =
+ ::testing::StrictMock<LenientMockQueryResultObserver>;
+
+// Test QueryBuilder using mock graphs.
+using QueryBuilderTest = GraphTestHarness;
+
+// Test ScopedResourceUsageQuery with PerformanceManagerTestHarness to test its
+// interactions on the PM sequence.
+class ScopedResourceUsageQueryTest : public PerformanceManagerTestHarness {
+ protected:
+ using Super = PerformanceManagerTestHarness;
+
+ void SetUp() override {
+ GetGraphFeatures().EnableResourceAttributionScheduler();
+ Super::SetUp();
+
+ // Navigate to an initial page.
+ SetContents(CreateTestWebContents());
+ content::RenderFrameHost* rfh =
+ content::NavigationSimulator::NavigateAndCommitFromBrowser(
+ web_contents(), GURL("https://a.com/"));
+ ASSERT_TRUE(rfh);
+ main_frame_context = FrameContext::FromRenderFrameHost(rfh);
+ ASSERT_TRUE(main_frame_context.has_value());
+ }
+
+ // A ResourceContext for the main frame.
+ absl::optional<FrameContext> main_frame_context;
+};
+
+} // namespace
+
+TEST_F(QueryBuilderTest, Params) {
+ MockSinglePageInSingleProcessGraph mock_graph(graph());
+
+ QueryBuilder builder;
+ ASSERT_TRUE(builder.GetParamsForTesting());
+ EXPECT_EQ(*builder.GetParamsForTesting(), QueryParams{});
+
+ QueryBuilder& builder_ref =
+ builder.AddResourceContext(mock_graph.page->GetResourceContext())
+ .AddResourceContext(mock_graph.process->GetResourceContext())
+ .AddAllContextsOfType<FrameContext>()
+ .AddAllContextsOfType<WorkerContext>()
+ .AddResourceType(ResourceType::kCPUTime);
+ EXPECT_EQ(builder.GetParamsForTesting(), builder_ref.GetParamsForTesting());
+
+ constexpr size_t kFrameContextBit = 0;
+ constexpr size_t kWorkerContextBit = 3;
+ static_assert(
+ std::is_same_v<
+ absl::variant_alternative_t<kFrameContextBit, ResourceContext>,
+ FrameContext>,
+ "FrameContext is no longer index 0 in the ResourceContext variant, "
+ "please update the test");
+ static_assert(
+ std::is_same_v<
+ absl::variant_alternative_t<kWorkerContextBit, ResourceContext>,
+ WorkerContext>,
+ "WorkerContext is no longer index 3 in the ResourceContext variant, "
+ "please update the test");
+
+ QueryParams expected_params;
+ expected_params.resource_contexts = {
+ mock_graph.page->GetResourceContext(),
+ mock_graph.process->GetResourceContext()};
+ expected_params.all_context_types.set(kFrameContextBit);
+ expected_params.all_context_types.set(kWorkerContextBit);
+ expected_params.resource_types = {ResourceType::kCPUTime};
+
+ EXPECT_EQ(*builder.GetParamsForTesting(), expected_params);
+
+ // Creating a ScopedQuery invalidates the builder.
+ auto scoped_query = builder.CreateScopedQuery();
+ EXPECT_FALSE(builder.GetParamsForTesting());
+ ASSERT_TRUE(scoped_query.GetParamsForTesting());
+ EXPECT_EQ(*scoped_query.GetParamsForTesting(), expected_params);
+}
+
+TEST_F(QueryBuilderTest, Clone) {
+ MockSinglePageInSingleProcessGraph mock_graph(graph());
+ QueryBuilder builder;
+ builder.AddResourceContext(mock_graph.page->GetResourceContext())
+ .AddAllContextsOfType<FrameContext>()
+ .AddResourceType(ResourceType::kCPUTime);
+ QueryBuilder cloned_builder = builder.Clone();
+
+ ASSERT_TRUE(builder.GetParamsForTesting());
+ ASSERT_TRUE(cloned_builder.GetParamsForTesting());
+ EXPECT_EQ(*builder.GetParamsForTesting(),
+ *cloned_builder.GetParamsForTesting());
+
+ // Cloned builder can be modified independently.
+ builder.AddResourceContext(mock_graph.process->GetResourceContext());
+ cloned_builder.AddResourceContext(mock_graph.frame->GetResourceContext());
+
+ const std::set<ResourceContext> expected_contexts{
+ mock_graph.page->GetResourceContext(),
+ mock_graph.process->GetResourceContext()};
+ EXPECT_EQ(builder.GetParamsForTesting()->resource_contexts,
+ expected_contexts);
+ const std::set<ResourceContext> expected_cloned_contexts{
+ mock_graph.page->GetResourceContext(),
+ mock_graph.frame->GetResourceContext()};
+ EXPECT_EQ(cloned_builder.GetParamsForTesting()->resource_contexts,
+ expected_cloned_contexts);
+}
+
+TEST_F(ScopedResourceUsageQueryTest, AddRemoveScopedQuery) {
+ QueryScheduler* scheduler = nullptr;
+ RunInGraph([&](Graph* graph) {
+ scheduler = QueryScheduler::GetFromGraph(graph);
+ ASSERT_TRUE(scheduler);
+ EXPECT_FALSE(scheduler->GetCPUMonitorForTesting().IsMonitoring());
+ });
+ // Abort the whole test if the scheduler wasn't found.
+ ASSERT_TRUE(scheduler);
+
+ absl::optional<ScopedResourceUsageQuery> scoped_query =
+ QueryBuilder()
+ .AddResourceType(ResourceType::kCPUTime)
+ .CreateScopedQuery();
+ RunInGraph([&] {
+ EXPECT_TRUE(scheduler->GetCPUMonitorForTesting().IsMonitoring());
+ });
+ scoped_query.reset();
+ RunInGraph([&] {
+ EXPECT_FALSE(scheduler->GetCPUMonitorForTesting().IsMonitoring());
+ });
+}
+
+TEST_F(ScopedResourceUsageQueryTest, Movable) {
+ QueryScheduler* scheduler = nullptr;
+ RunInGraph([&](Graph* graph) {
+ scheduler = QueryScheduler::GetFromGraph(graph);
+ ASSERT_TRUE(scheduler);
+ EXPECT_FALSE(scheduler->GetCPUMonitorForTesting().IsMonitoring());
+ });
+ // Abort the whole test if the scheduler wasn't found.
+ ASSERT_TRUE(scheduler);
+
+ absl::optional<ScopedResourceUsageQuery> outer_query;
+ {
+ ScopedResourceUsageQuery inner_query =
+ QueryBuilder()
+ .AddResourceType(ResourceType::kCPUTime)
+ .CreateScopedQuery();
+ RunInGraph([&] {
+ EXPECT_TRUE(scheduler->GetCPUMonitorForTesting().IsMonitoring());
+ });
+
+ auto* params = inner_query.GetParamsForTesting();
+ EXPECT_TRUE(params);
+ scoped_refptr<ScopedResourceUsageQuery::ObserverList> observer_list =
+ inner_query.observer_list_;
+ EXPECT_TRUE(observer_list);
+
+ outer_query = std::move(inner_query);
+
+ // Moving invalidates the original query.
+ EXPECT_FALSE(inner_query.GetParamsForTesting());
+ EXPECT_EQ(outer_query->GetParamsForTesting(), params);
+
+ // There shouldn't be duplicate observers, to prevent extra notifications.
+ EXPECT_FALSE(inner_query.observer_list_);
+ EXPECT_EQ(outer_query->observer_list_, observer_list);
+ }
+
+ // `inner_query` should not notify the scheduler when it goes out of scope.
+ RunInGraph([&] {
+ EXPECT_TRUE(scheduler->GetCPUMonitorForTesting().IsMonitoring());
+ });
+ outer_query.reset();
+ RunInGraph([&] {
+ EXPECT_FALSE(scheduler->GetCPUMonitorForTesting().IsMonitoring());
+ });
+}
+
+TEST_F(ScopedResourceUsageQueryTest, Observers) {
+ ScopedResourceUsageQuery scoped_query =
+ QueryBuilder()
+ .AddResourceContext(main_frame_context.value())
+ .AddResourceType(ResourceType::kCPUTime)
+ .CreateScopedQuery();
+
+ const QueryResultMap test_results{
+ {main_frame_context.value(),
+ {CPUTimeResult{.cumulative_cpu = base::Minutes(1)}}},
+ };
+
+ // Safely do nothing when no observers are registered.
+ Graph* graph_ptr = nullptr;
+ RunInGraph([&](Graph* graph) {
+ graph_ptr = graph;
+ // TODO(crbug.com/1471683): QueryScheduler should be notifying the
+ // observers.
+ scoped_query.observer_list_->Notify(
+ FROM_HERE, &QueryResultObserver::OnResourceUsageUpdated, test_results);
+ });
+ ASSERT_TRUE(graph_ptr);
+
+ // Observer can be notified from the graph sequence when installed on any
+ // thread.
+ MockQueryResultObserver main_thread_observer;
+ MockQueryResultObserver graph_sequence_observer;
+ scoped_query.AddObserver(&main_thread_observer);
+ RunInGraph([&] { scoped_query.AddObserver(&graph_sequence_observer); });
+
+ auto check_graph_sequence = [&](bool expect_on_graph_sequence) {
+#if DCHECK_IS_ON()
+ EXPECT_EQ(graph_ptr->IsOnGraphSequence(), expect_on_graph_sequence);
+#endif
+ };
+
+ // Quit the RunLoop when both observers receive results.
+ base::RunLoop run_loop;
+ auto quit_closure = base::BarrierClosure(2, run_loop.QuitClosure());
+ EXPECT_CALL(main_thread_observer, OnResourceUsageUpdated(test_results))
+ .WillOnce([&] {
+ check_graph_sequence(false);
+ quit_closure.Run();
+ });
+ EXPECT_CALL(graph_sequence_observer, OnResourceUsageUpdated(test_results))
+ .WillOnce([&] {
+ check_graph_sequence(true);
+ quit_closure.Run();
+ });
+
+ RunInGraph([&] {
+ scoped_query.observer_list_->Notify(
+ FROM_HERE, &QueryResultObserver::OnResourceUsageUpdated, test_results);
+ });
+
+ // Wait for all notifications.
+ run_loop.Run();
+}
+
+TEST_F(ScopedResourceUsageQueryTest, GraphTeardown) {
+ // ScopedResourceUsageQuery registers with the QueryScheduler on creation and
+ // unregisters on destruction. Make sure it's safe for it to outlive the
+ // scheduler, which is deleted during graph teardown.
+ absl::optional<ScopedResourceUsageQuery> scoped_query =
+ QueryBuilder()
+ .AddResourceContext(main_frame_context.value())
+ .AddResourceType(ResourceType::kCPUTime)
+ .CreateScopedQuery();
+
+ TearDownNow();
+
+ // The test passes as long as this doesn't crash.
+ scoped_query.reset();
+}
+
+} // namespace performance_manager::resource_attribution
diff --git a/components/performance_manager/resource_attribution/query_params.cc b/components/performance_manager/resource_attribution/query_params.cc
new file mode 100644
index 0000000..a0d32546
--- /dev/null
+++ b/components/performance_manager/resource_attribution/query_params.cc
@@ -0,0 +1,17 @@
+// 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/resource_attribution/query_params.h"
+
+namespace performance_manager::resource_attribution::internal {
+
+QueryParams::QueryParams() = default;
+
+QueryParams::~QueryParams() = default;
+
+QueryParams::QueryParams(const QueryParams& other) = default;
+
+QueryParams& QueryParams::operator=(const QueryParams& other) = default;
+
+} // namespace performance_manager::resource_attribution::internal
diff --git a/components/performance_manager/resource_attribution/query_params.h b/components/performance_manager/resource_attribution/query_params.h
new file mode 100644
index 0000000..bf8ceb0
--- /dev/null
+++ b/components/performance_manager/resource_attribution/query_params.h
@@ -0,0 +1,56 @@
+// 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.
+
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_RESOURCE_ATTRIBUTION_QUERY_PARAMS_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_RESOURCE_ATTRIBUTION_QUERY_PARAMS_H_
+
+#include <bitset>
+#include <set>
+
+#include "components/performance_manager/public/resource_attribution/resource_contexts.h"
+#include "components/performance_manager/public/resource_attribution/resource_types.h"
+#include "third_party/abseil-cpp/absl/types/variant.h"
+
+namespace performance_manager::resource_attribution::internal {
+
+struct QueryParams {
+ // Context types that can be added with AddAllContextsOfType, based on their
+ // index in the ResourceContexts variant.
+ using ContextTypeSet =
+ std::bitset<absl::variant_size<ResourceContext>::value>;
+
+ QueryParams();
+ ~QueryParams();
+
+ QueryParams(const QueryParams& other);
+ QueryParams& operator=(const QueryParams& other);
+
+ // Individual resource contexts to measure.
+ std::set<ResourceContext> resource_contexts;
+
+ // For each of these context types, all contexts that exist will be measured.
+ ContextTypeSet all_context_types;
+
+ // Resource types to measure.
+ ResourceTypeSet resource_types;
+};
+
+inline bool operator==(const QueryParams& a, const QueryParams& b) {
+ static_assert(sizeof(QueryParams) ==
+ sizeof(decltype(QueryParams::resource_contexts)) +
+ sizeof(decltype(QueryParams::all_context_types)) +
+ sizeof(decltype(QueryParams::resource_types)),
+ "update operator== when changing QueryParams");
+ return a.resource_contexts == b.resource_contexts &&
+ a.all_context_types == b.all_context_types &&
+ a.resource_types == b.resource_types;
+}
+
+inline bool operator!=(const QueryParams& a, const QueryParams& b) {
+ return !(a == b);
+}
+
+} // namespace performance_manager::resource_attribution::internal
+
+#endif // COMPONENTS_PERFORMANCE_MANAGER_RESOURCE_ATTRIBUTION_QUERY_PARAMS_H_
diff --git a/components/performance_manager/resource_attribution/query_scheduler.cc b/components/performance_manager/resource_attribution/query_scheduler.cc
index de3167a..1779244 100644
--- a/components/performance_manager/resource_attribution/query_scheduler.cc
+++ b/components/performance_manager/resource_attribution/query_scheduler.cc
@@ -4,13 +4,30 @@
#include "components/performance_manager/resource_attribution/query_scheduler.h"
+#include <utility>
+
#include "base/check_op.h"
+#include "base/containers/enum_set.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/task/task_runner.h"
+#include "components/performance_manager/public/performance_manager.h"
+#include "components/performance_manager/resource_attribution/query_params.h"
namespace performance_manager::resource_attribution {
+namespace {
+
+using QueryParams = internal::QueryParams;
+
+QueryScheduler* GetSchedulerFromGraph(Graph* graph) {
+ auto* scheduler = QueryScheduler::GetFromGraph(graph);
+ CHECK(scheduler);
+ return scheduler;
+}
+
+} // namespace
+
QueryScheduler::QueryScheduler() = default;
QueryScheduler::~QueryScheduler() = default;
@@ -19,6 +36,15 @@
return weak_factory_.GetWeakPtr();
}
+// static
+void QueryScheduler::CallOnGraphWithScheduler(
+ base::OnceCallback<void(QueryScheduler*)> callback,
+ const base::Location& location) {
+ PerformanceManager::CallOnGraph(
+ location,
+ base::BindOnce(&GetSchedulerFromGraph).Then(std::move(callback)));
+}
+
void QueryScheduler::AddCPUQuery() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK_NE(graph_, nullptr);
@@ -42,6 +68,28 @@
}
}
+void QueryScheduler::AddScopedQuery(QueryParams* query_params) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ CHECK(query_params);
+ // TODO(crbug.com/1471683): Associate a notifier with the params so that when
+ // a scheduled measurement is done, the correct ScopedResourceUsageQuery can
+ // be notified.
+ if (query_params->resource_types.Has(ResourceType::kCPUTime)) {
+ AddCPUQuery();
+ }
+}
+
+void QueryScheduler::RemoveScopedQuery(
+ std::unique_ptr<QueryParams> query_params) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ CHECK(query_params);
+ // TODO(crbug.com/1471683): Forget the notifier associated with the params.
+ if (query_params->resource_types.Has(ResourceType::kCPUTime)) {
+ RemoveCPUQuery();
+ }
+ // `query_params` goes out of scope and is deleted here.
+}
+
void QueryScheduler::RequestCPUResults(
base::OnceCallback<void(const QueryResultMap&)> callback,
scoped_refptr<base::TaskRunner> task_runner) {
diff --git a/components/performance_manager/resource_attribution/query_scheduler.h b/components/performance_manager/resource_attribution/query_scheduler.h
index 034f481..505d248 100644
--- a/components/performance_manager/resource_attribution/query_scheduler.h
+++ b/components/performance_manager/resource_attribution/query_scheduler.h
@@ -5,7 +5,10 @@
#ifndef COMPONENTS_PERFORMANCE_MANAGER_RESOURCE_ATTRIBUTION_QUERY_SCHEDULER_H_
#define COMPONENTS_PERFORMANCE_MANAGER_RESOURCE_ATTRIBUTION_QUERY_SCHEDULER_H_
+#include <memory>
+
#include "base/functional/callback_forward.h"
+#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
@@ -21,7 +24,9 @@
namespace performance_manager::resource_attribution {
-class CPUMeasurementMonitor;
+namespace internal {
+struct QueryParams;
+}
// QueryScheduler keeps track of all queries for a particular resource type and
// owns the machinery that performs measurements.
@@ -36,18 +41,36 @@
base::WeakPtr<QueryScheduler> GetWeakPtr();
- // CPU measurement accessors.
+ // Invokes `callback` on the PM sequence with a pointer to the registered
+ // QueryScheduler.
+ static void CallOnGraphWithScheduler(
+ base::OnceCallback<void(QueryScheduler*)> callback,
+ const base::Location& location = base::Location::Current());
// Increases the CPU query count. `cpu_monitor_` will start monitoring CPU
// usage when the count > 0.
+ // TODO(crbug.com/1471683): Make this private. It should only be called by
+ // AddScopedQuery().
void AddCPUQuery();
// Decreases the CPU query count. `cpu_monitor_` will stop monitoring CPU
// usage when the count == 0.
+ // TODO(crbug.com/1471683): Make this private. It should only be called by
+ // RemoveScopedQuery().
void RemoveCPUQuery();
+ // Adds a scoped query for `query_params`. Increases the query count for all
+ // resource types and contexts referenced in `query_params`.
+ void AddScopedQuery(internal::QueryParams* query_params);
+
+ // Decreases the query count for all resource types and contexts referenced in
+ // `query_params` and deletes `query_params`.
+ void RemoveScopedQuery(std::unique_ptr<internal::QueryParams> query_params);
+
// Requests the latest CPU measurements from `cpu_monitor_`, and posts them
// to `callback` on `task_runner`. Asserts that the CPU query count > 0.
+ // TODO(crbug.com/1471683): Replace with a general RequestResults that handles
+ // any QueryParams.
void RequestCPUResults(
base::OnceCallback<void(const QueryResultMap&)> callback,
scoped_refptr<base::TaskRunner> task_runner);
diff --git a/components/performance_manager/resource_attribution/query_scheduler_unittest.cc b/components/performance_manager/resource_attribution/query_scheduler_unittest.cc
index 7989689..571df16 100644
--- a/components/performance_manager/resource_attribution/query_scheduler_unittest.cc
+++ b/components/performance_manager/resource_attribution/query_scheduler_unittest.cc
@@ -5,20 +5,33 @@
#include "components/performance_manager/resource_attribution/query_scheduler.h"
#include <memory>
+#include <utility>
+#include "base/containers/enum_set.h"
+#include "base/dcheck_is_on.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
+#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "components/performance_manager/embedder/graph_features.h"
+#include "components/performance_manager/public/graph/graph.h"
+#include "components/performance_manager/public/graph/process_node.h"
+#include "components/performance_manager/public/resource_attribution/cpu_measurement_delegate.h"
+#include "components/performance_manager/public/resource_attribution/process_context.h"
#include "components/performance_manager/public/resource_attribution/query_results.h"
+#include "components/performance_manager/public/resource_attribution/resource_types.h"
#include "components/performance_manager/public/resource_attribution/scoped_cpu_query.h"
+#include "components/performance_manager/resource_attribution/cpu_measurement_monitor.h"
+#include "components/performance_manager/resource_attribution/query_params.h"
#include "components/performance_manager/test_support/graph_test_harness.h"
#include "components/performance_manager/test_support/mock_graphs.h"
+#include "components/performance_manager/test_support/performance_manager_test_harness.h"
#include "components/performance_manager/test_support/resource_attribution/simulated_cpu_measurement_delegate.h"
+#include "components/performance_manager/test_support/run_in_graph.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -28,6 +41,13 @@
using ::testing::Contains;
using ::testing::Key;
+using QueryParams = internal::QueryParams;
+
+std::unique_ptr<QueryParams> CreateQueryParams(ResourceTypeSet resource_types) {
+ auto params = std::make_unique<QueryParams>();
+ params->resource_types = std::move(resource_types);
+ return params;
+}
// Waits for a result from `query` and tests that it matches `matcher`.
void ExpectQueryResult(ScopedCPUQuery* query, auto matcher) {
@@ -57,6 +77,8 @@
SimulatedCPUMeasurementDelegateFactory delegate_factory_;
};
+using QuerySchedulerPMTest = PerformanceManagerTestHarness;
+
TEST_F(QuerySchedulerTest, CPUQueries) {
MockSinglePageInSingleProcessGraph mock_graph(graph());
@@ -85,6 +107,33 @@
EXPECT_FALSE(scheduler->GetCPUMonitorForTesting().IsMonitoring());
}
+TEST_F(QuerySchedulerTest, ScopedQueries) {
+ auto* scheduler = QueryScheduler::GetFromGraph(graph());
+ ASSERT_TRUE(scheduler);
+
+ // Query without kCPUTime should not start CPU monitoring.
+ auto no_cpu_params = CreateQueryParams({});
+ scheduler->AddScopedQuery(no_cpu_params.get());
+ EXPECT_FALSE(scheduler->GetCPUMonitorForTesting().IsMonitoring());
+
+ // First kCPUTime query should start monitoring.
+ auto cpu_params1 = CreateQueryParams({ResourceType::kCPUTime});
+ scheduler->AddScopedQuery(cpu_params1.get());
+ EXPECT_TRUE(scheduler->GetCPUMonitorForTesting().IsMonitoring());
+
+ // Removing non-CPU query should not affect CPU monitoring.
+ scheduler->RemoveScopedQuery(std::move(no_cpu_params));
+ EXPECT_TRUE(scheduler->GetCPUMonitorForTesting().IsMonitoring());
+
+ // CPU monitoring should not stop until the last CPU query is deleted.
+ auto cpu_params2 = CreateQueryParams({ResourceType::kCPUTime});
+ scheduler->AddScopedQuery(cpu_params2.get());
+ scheduler->RemoveScopedQuery(std::move(cpu_params1));
+ EXPECT_TRUE(scheduler->GetCPUMonitorForTesting().IsMonitoring());
+ scheduler->RemoveScopedQuery(std::move(cpu_params2));
+ EXPECT_FALSE(scheduler->GetCPUMonitorForTesting().IsMonitoring());
+}
+
TEST_F(QuerySchedulerTest, GraphTeardown) {
// Make sure queries that still exist when the scheduler is deleted during
// graph teardown safely return no data.
@@ -109,4 +158,26 @@
base::ScopedClosureRunner(run_loop.QuitClosure())));
}
+TEST_F(QuerySchedulerPMTest, CallOnGraphWithScheduler) {
+ QueryScheduler* scheduler_ptr = nullptr;
+ Graph* graph_ptr = nullptr;
+ RunInGraph([&](Graph* graph) {
+ auto scheduler = std::make_unique<QueryScheduler>();
+ scheduler_ptr = scheduler.get();
+ graph_ptr = graph;
+ graph->PassToGraph(std::move(scheduler));
+ });
+ ASSERT_TRUE(scheduler_ptr);
+ ASSERT_TRUE(graph_ptr);
+ base::RunLoop run_loop;
+ QueryScheduler::CallOnGraphWithScheduler(
+ base::BindLambdaForTesting([&](QueryScheduler* scheduler) {
+#if DCHECK_IS_ON()
+ EXPECT_TRUE(graph_ptr->IsOnGraphSequence());
+#endif
+ EXPECT_EQ(scheduler, scheduler_ptr);
+ }).Then(run_loop.QuitClosure()));
+ run_loop.Run();
+}
+
} // namespace performance_manager::resource_attribution