| // 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 <map> |
| #include <optional> |
| #include <set> |
| #include <utility> |
| |
| #include "base/barrier_closure.h" |
| #include "base/containers/enum_set.h" |
| #include "base/functional/callback.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/observer_list_threadsafe.h" |
| #include "base/run_loop.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/thread_pool.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/page_node.h" |
| #include "components/performance_manager/public/graph/process_node.h" |
| #include "components/performance_manager/public/performance_manager.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/context_collection.h" |
| #include "components/performance_manager/resource_attribution/performance_manager_aliases.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/resource_attribution/gtest_util.h" |
| #include "components/performance_manager/test_support/resource_attribution/measurement_delegates.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_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 "url/gurl.h" |
| |
| namespace resource_attribution { |
| |
| namespace { |
| |
| using ::testing::_; |
| using ::testing::ElementsAre; |
| using ::testing::IsEmpty; |
| using QueryParams = internal::QueryParams; |
| using QueryScheduler = internal::QueryScheduler; |
| using ResourceContextTypeId = internal::ResourceContextTypeId; |
| |
| constexpr auto kFrameContextTypeId = |
| ResourceContextTypeId::ForType<FrameContext>(); |
| constexpr auto kWorkerContextTypeId = |
| ResourceContextTypeId::ForType<WorkerContext>(); |
| |
| // Fake memory results. |
| constexpr base::ByteCount kFakeResidentSetSize = base::KiB(123); |
| constexpr base::ByteCount kFakePrivateFootprint = base::KiB(456); |
| |
| class LenientMockQueryResultObserver : public QueryResultObserver { |
| public: |
| MOCK_METHOD(void, |
| OnResourceUsageUpdated, |
| (const QueryResultMap& results), |
| (override)); |
| }; |
| using MockQueryResultObserver = |
| ::testing::StrictMock<LenientMockQueryResultObserver>; |
| |
| using ResourceAttrQueriesTest = performance_manager::GraphTestHarness; |
| |
| // Tests that interact with the QueryScheduler use PerformanceManagerTestHarness |
| // to test its interactions on the PM sequence. |
| class ResourceAttrQueriesPMTest |
| : public performance_manager::PerformanceManagerTestHarness { |
| protected: |
| using Super = performance_manager::PerformanceManagerTestHarness; |
| |
| ResourceAttrQueriesPMTest() |
| : Super(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} |
| |
| void SetUp() override { |
| GetGraphFeatures().EnableResourceAttributionScheduler(); |
| Super::SetUp(); |
| |
| graph_ = performance_manager::PerformanceManager::GetGraph(); |
| CPUMeasurementDelegate::SetDelegateFactoryForTesting( |
| graph_, &cpu_delegate_factory_); |
| MemoryMeasurementDelegate::SetDelegateFactoryForTesting( |
| graph_, &memory_delegate_factory_); |
| |
| // 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()); |
| |
| // Set fake memory results for the page's process. |
| content::RenderProcessHost* rph = rfh->GetProcess(); |
| ASSERT_TRUE(rph); |
| memory_delegate_factory_.memory_summaries() |
| [ProcessContext::FromRenderProcessHost(rph).value()] = |
| MemoryMeasurementDelegate::MemorySummaryMeasurement{ |
| .resident_set_size = kFakeResidentSetSize, |
| .private_footprint = kFakePrivateFootprint, |
| }; |
| } |
| |
| void TearDown() override { |
| graph_ = nullptr; |
| Super::TearDown(); |
| } |
| |
| void TearDownGraph() { |
| graph_ = nullptr; |
| Super::TearDownNow(); |
| } |
| |
| Graph* graph() { return graph_.get(); } |
| |
| // A ResourceContext for the main frame. |
| ResourceContext main_frame_context() const { |
| return main_frame_context_.value(); |
| } |
| |
| // Lets tests update the fake results for kMemorySummary queries. |
| MemoryMeasurementDelegate::MemorySummaryMap& fake_memory_summaries() { |
| return memory_delegate_factory_.memory_summaries(); |
| } |
| |
| private: |
| raw_ptr<Graph> graph_ = nullptr; |
| |
| std::optional<FrameContext> main_frame_context_; |
| |
| // These must be deleted after TearDown() so that they outlive the |
| // CPUMeasurementMonitor and MemoryMeasurementProvider. |
| SimulatedCPUMeasurementDelegateFactory cpu_delegate_factory_; |
| FakeMemoryMeasurementDelegateFactory memory_delegate_factory_; |
| }; |
| |
| QueryParams CreateQueryParams( |
| ResourceTypeSet resource_types = {}, |
| std::set<ResourceContext> resource_contexts = {}, |
| std::set<ResourceContextTypeId> all_context_types = {}) { |
| QueryParams params; |
| params.resource_types = std::move(resource_types); |
| params.contexts = ContextCollection::CreateForTesting( |
| std::move(resource_contexts), std::move(all_context_types)); |
| return params; |
| } |
| |
| // Returns a MemorySummaryResult containing the default fake memory results. |
| // This can be used for the results from a process, or a page or frame that gets |
| // all the memory from one process. `expected_algorithm` is the measurement |
| // algorithm for that context type, and `expected_measurement_time` is the time |
| // the measurement should be taken. By default, since the tests use the mock |
| // clock, the expected measurement time is the same time the fake result is |
| // created. |
| MemorySummaryResult FakeMemorySummaryResult( |
| MeasurementAlgorithm expected_algorithm, |
| base::TimeTicks expected_measurement_time = base::TimeTicks::Now()) { |
| return { |
| .metadata = ResultMetadata(expected_measurement_time, expected_algorithm), |
| .resident_set_size = kFakeResidentSetSize, |
| .private_footprint = kFakePrivateFootprint, |
| }; |
| } |
| |
| } // namespace |
| |
| namespace internal { |
| |
| // Allow EXPECT_EQ to compare QueryParams, not including the QueryId. |
| bool operator==(const QueryParams& a, const QueryParams& b) { |
| return a.resource_types == b.resource_types && a.contexts == b.contexts; |
| } |
| |
| } // namespace internal |
| |
| TEST_F(ResourceAttrQueriesTest, QueryBuilder_Params) { |
| performance_manager::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()); |
| |
| const QueryParams expected_params = |
| CreateQueryParams({ResourceType::kCPUTime}, |
| {mock_graph.page->GetResourceContext(), |
| mock_graph.process->GetResourceContext()}, |
| {kFrameContextTypeId, kWorkerContextTypeId}); |
| 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(ResourceAttrQueriesTest, QueryBuilder_Clone) { |
| performance_manager::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()); |
| cloned_builder.AddResourceType(ResourceType::kMemorySummary); |
| |
| EXPECT_EQ(*builder.GetParamsForTesting(), |
| CreateQueryParams({ResourceType::kCPUTime}, |
| {mock_graph.page->GetResourceContext(), |
| mock_graph.process->GetResourceContext()}, |
| {kFrameContextTypeId})); |
| EXPECT_EQ( |
| *cloned_builder.GetParamsForTesting(), |
| CreateQueryParams({ResourceType::kCPUTime, ResourceType::kMemorySummary}, |
| {mock_graph.page->GetResourceContext(), |
| mock_graph.frame->GetResourceContext()}, |
| {kFrameContextTypeId})); |
| } |
| |
| TEST_F(ResourceAttrQueriesPMTest, QueryBuilder_QueryOnce_CPU) { |
| auto expect_no_results = [&](const QueryResultMap& results) { |
| // CPU measurements need to cover a period of time, so without a scoped |
| // query to start the monitoring period there will be no results. This just |
| // tests that the query request and empty result are delivered to and from |
| // the scheduler. |
| EXPECT_TRUE(results.empty()); |
| }; |
| |
| base::RunLoop run_loop; |
| QueryBuilder() |
| .AddResourceContext(main_frame_context()) |
| .AddResourceType(ResourceType::kCPUTime) |
| .QueryOnce(base::BindLambdaForTesting(expect_no_results) |
| .Then(run_loop.QuitClosure())); |
| run_loop.Run(); |
| } |
| |
| TEST_F(ResourceAttrQueriesPMTest, QueryBuilder_QueryOnce_Memory) { |
| auto expect_memory_results = [&](const QueryResultMap& results) { |
| EXPECT_THAT(results, |
| ElementsAre(ResultForContextMatches<MemorySummaryResult>( |
| main_frame_context(), |
| FakeMemorySummaryResult(MeasurementAlgorithm::kSplit)))); |
| }; |
| |
| base::RunLoop run_loop; |
| QueryBuilder() |
| .AddResourceContext(main_frame_context()) |
| .AddResourceType(ResourceType::kMemorySummary) |
| .QueryOnce(base::BindLambdaForTesting(expect_memory_results) |
| .Then(run_loop.QuitClosure())); |
| run_loop.Run(); |
| } |
| |
| TEST_F(ResourceAttrQueriesPMTest, QueryBuilder_QueryOnce_CPUAndMemory) { |
| auto expect_only_memory_results = [&](const QueryResultMap& results) { |
| // CPU measurements need to cover a period of time, so without a scoped |
| // query to start the monitoring period there will be no results. The memory |
| // result should be delivered from the scheduler without a CPU measurement. |
| EXPECT_THAT(results, |
| ElementsAre(ResultForContextMatches<MemorySummaryResult>( |
| main_frame_context(), |
| FakeMemorySummaryResult(MeasurementAlgorithm::kSplit)))); |
| }; |
| |
| base::RunLoop run_loop; |
| QueryBuilder() |
| .AddResourceContext(main_frame_context()) |
| .AddResourceType(ResourceType::kCPUTime) |
| .AddResourceType(ResourceType::kMemorySummary) |
| .QueryOnce(base::BindLambdaForTesting(expect_only_memory_results) |
| .Then(run_loop.QuitClosure())); |
| run_loop.Run(); |
| } |
| |
| TEST_F(ResourceAttrQueriesPMTest, AddRemoveScopedQuery) { |
| QueryScheduler* scheduler = nullptr; |
| scheduler = QueryScheduler::GetFromGraph( |
| performance_manager::PerformanceManager::GetGraph()); |
| ASSERT_TRUE(scheduler); |
| EXPECT_EQ(scheduler->GetQueryCountForTesting(ResourceType::kCPUTime), 0U); |
| EXPECT_EQ(scheduler->GetQueryCountForTesting(ResourceType::kMemorySummary), |
| 0U); |
| // Abort the whole test if the scheduler wasn't found. |
| ASSERT_TRUE(scheduler); |
| |
| std::optional<ScopedResourceUsageQuery> scoped_memory_query = |
| QueryBuilder() |
| .AddResourceContext(main_frame_context()) |
| .AddResourceType(ResourceType::kMemorySummary) |
| .CreateScopedQuery(); |
| EXPECT_EQ(scheduler->GetQueryCountForTesting(ResourceType::kCPUTime), 0U); |
| EXPECT_EQ(scheduler->GetQueryCountForTesting(ResourceType::kMemorySummary), |
| 1U); |
| std::optional<ScopedResourceUsageQuery> scoped_cpu_query = |
| QueryBuilder() |
| .AddResourceContext(main_frame_context()) |
| .AddResourceType(ResourceType::kCPUTime) |
| .CreateScopedQuery(); |
| EXPECT_EQ(scheduler->GetQueryCountForTesting(ResourceType::kCPUTime), 1U); |
| EXPECT_EQ(scheduler->GetQueryCountForTesting(ResourceType::kMemorySummary), |
| 1U); |
| scoped_memory_query.reset(); |
| EXPECT_EQ(scheduler->GetQueryCountForTesting(ResourceType::kCPUTime), 1U); |
| EXPECT_EQ(scheduler->GetQueryCountForTesting(ResourceType::kMemorySummary), |
| 0U); |
| std::optional<ScopedResourceUsageQuery> scoped_cpu_memory_query = |
| QueryBuilder() |
| .AddResourceContext(main_frame_context()) |
| .AddResourceType(ResourceType::kCPUTime) |
| .AddResourceType(ResourceType::kMemorySummary) |
| .CreateScopedQuery(); |
| EXPECT_EQ(scheduler->GetQueryCountForTesting(ResourceType::kCPUTime), 2U); |
| EXPECT_EQ(scheduler->GetQueryCountForTesting(ResourceType::kMemorySummary), |
| 1U); |
| scoped_cpu_query.reset(); |
| EXPECT_EQ(scheduler->GetQueryCountForTesting(ResourceType::kCPUTime), 1U); |
| EXPECT_EQ(scheduler->GetQueryCountForTesting(ResourceType::kMemorySummary), |
| 1U); |
| scoped_cpu_memory_query.reset(); |
| EXPECT_EQ(scheduler->GetQueryCountForTesting(ResourceType::kCPUTime), 0U); |
| EXPECT_EQ(scheduler->GetQueryCountForTesting(ResourceType::kMemorySummary), |
| 0U); |
| } |
| |
| TEST_F(ResourceAttrQueriesPMTest, ScopedQueryIsMovable) { |
| QueryScheduler* scheduler = nullptr; |
| scheduler = QueryScheduler::GetFromGraph( |
| performance_manager::PerformanceManager::GetGraph()); |
| ASSERT_TRUE(scheduler); |
| EXPECT_EQ(scheduler->GetQueryCountForTesting(ResourceType::kCPUTime), 0U); |
| |
| std::optional<ScopedResourceUsageQuery> outer_query; |
| { |
| ScopedResourceUsageQuery inner_query = |
| QueryBuilder() |
| .AddResourceContext(main_frame_context()) |
| .AddResourceType(ResourceType::kCPUTime) |
| .CreateScopedQuery(); |
| EXPECT_EQ(scheduler->GetQueryCountForTesting(ResourceType::kCPUTime), 1U); |
| |
| 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. |
| EXPECT_EQ(scheduler->GetQueryCountForTesting(ResourceType::kCPUTime), 1U); |
| outer_query.reset(); |
| EXPECT_EQ(scheduler->GetQueryCountForTesting(ResourceType::kCPUTime), 0U); |
| } |
| |
| TEST_F(ResourceAttrQueriesPMTest, Observers) { |
| ScopedResourceUsageQuery::ScopedDisableMemoryQueryDelayForTesting disable; |
| |
| ScopedResourceUsageQuery scoped_query = |
| QueryBuilder() |
| .AddResourceContext(main_frame_context()) |
| .AddResourceType(ResourceType::kCPUTime) |
| .AddResourceType(ResourceType::kMemorySummary) |
| .CreateScopedQuery(); |
| |
| // Allow some time to pass to measure. |
| task_environment()->FastForwardBy(base::Minutes(1)); |
| |
| // Safely do nothing when no observers are registered. |
| scoped_query.QueryOnce(); |
| |
| // Allow some time to pass to give time for the query to run there. Nothing |
| // should happen. |
| task_environment()->FastForwardBy(base::Minutes(1)); |
| |
| // Observer can be notified from the graph sequence when installed on any |
| // thread. |
| // TODO(crbug.com/40755583): This test can be simplified once observers use |
| // ObserverList instead of ObserverListThreadSafe. |
| MockQueryResultObserver main_thread_observer; |
| scoped_query.AddObserver(&main_thread_observer); |
| auto main_thread_task_runner = base::SequencedTaskRunner::GetCurrentDefault(); |
| |
| MockQueryResultObserver graph_sequence_observer; |
| scoped_refptr<base::SequencedTaskRunner> graph_sequence_task_runner; |
| scoped_query.AddObserver(&graph_sequence_observer); |
| graph_sequence_task_runner = base::SequencedTaskRunner::GetCurrentDefault(); |
| |
| // Quit the RunLoop when both observers receive results. Expect each result to |
| // contain a single ResourceContext with both results. |
| base::RunLoop run_loop; |
| auto barrier_closure = base::BarrierClosure(2, run_loop.QuitClosure()); |
| EXPECT_CALL( |
| main_thread_observer, |
| OnResourceUsageUpdated(ElementsAre( |
| ResultForContextMatchesAll<MemorySummaryResult, CPUTimeResult>( |
| main_frame_context(), |
| FakeMemorySummaryResult(MeasurementAlgorithm::kSplit), _)))) |
| .WillOnce([&] { |
| EXPECT_TRUE(main_thread_task_runner->RunsTasksInCurrentSequence()); |
| barrier_closure.Run(); |
| }); |
| EXPECT_CALL( |
| graph_sequence_observer, |
| OnResourceUsageUpdated(ElementsAre( |
| ResultForContextMatchesAll<MemorySummaryResult, CPUTimeResult>( |
| main_frame_context(), |
| FakeMemorySummaryResult(MeasurementAlgorithm::kSplit), _)))) |
| .WillOnce([&] { |
| EXPECT_TRUE(graph_sequence_task_runner->RunsTasksInCurrentSequence()); |
| barrier_closure.Run(); |
| }); |
| scoped_query.QueryOnce(); |
| run_loop.Run(); |
| } |
| |
| TEST_F(ResourceAttrQueriesPMTest, 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. |
| std::optional<ScopedResourceUsageQuery> scoped_query = |
| QueryBuilder() |
| .AddResourceContext(main_frame_context()) |
| .AddResourceType(ResourceType::kCPUTime) |
| .CreateScopedQuery(); |
| MockQueryResultObserver observer; |
| scoped_query->AddObserver(&observer); |
| |
| TearDownGraph(); |
| |
| // The test passes as long as these don't crash. `observer` should not be |
| // notified (StrictMock will test this). |
| scoped_query->QueryOnce(); |
| scoped_query.reset(); |
| } |
| |
| TEST_F(ResourceAttrQueriesPMTest, ScopedQueryAndQueryOnce) { |
| QueryBuilder builder; |
| builder.AddResourceContext(main_frame_context()) |
| .AddResourceType(ResourceType::kCPUTime) |
| .AddResourceType(ResourceType::kMemorySummary); |
| |
| // Create a scoped query to start the CPU monitor. |
| auto scoped_query = builder.Clone().CreateScopedQuery(); |
| |
| // Allow some time to pass to measure. |
| task_environment()->FastForwardBy(base::Minutes(1)); |
| |
| auto expect_results = [&](const QueryResultMap& results) { |
| // QueryOnce should get measurements that were collected for `scoped_query`, |
| // including CPU time. |
| EXPECT_THAT( |
| results, |
| ElementsAre( |
| ResultForContextMatchesAll<MemorySummaryResult, CPUTimeResult>( |
| main_frame_context(), |
| FakeMemorySummaryResult(MeasurementAlgorithm::kSplit), _))); |
| }; |
| |
| base::RunLoop run_loop; |
| builder.Clone().QueryOnce( |
| base::BindLambdaForTesting(expect_results).Then(run_loop.QuitClosure())); |
| run_loop.Run(); |
| } |
| |
| TEST_F(ResourceAttrQueriesPMTest, RepeatingQueries) { |
| constexpr auto kDelay = base::Minutes(1); |
| constexpr int kRepetitions = 3; |
| |
| ScopedResourceUsageQuery::ScopedDisableMemoryQueryDelayForTesting disable; |
| |
| std::optional<ScopedResourceUsageQuery> scoped_query = |
| QueryBuilder() |
| .AddResourceContext(main_frame_context()) |
| .AddResourceType(ResourceType::kMemorySummary) |
| .CreateScopedQuery(); |
| |
| MockQueryResultObserver observer; |
| scoped_query->AddObserver(&observer); |
| |
| // Query should not get a QueryId until it's started. Id's are assigned on the |
| // PM sequence. |
| EXPECT_EQ(scoped_query->GetParamsForTesting()->GetIdForTesting(), |
| std::nullopt); |
| |
| // Returns a gMock matcher expecting that a QueryResultMap has a |
| // MemorySummaryResult for main_frame_context(). |
| auto memory_result_matcher = [&](base::TimeTicks expected_measurement_time) { |
| return ElementsAre(ResultForContextMatches<MemorySummaryResult>( |
| main_frame_context(), |
| FakeMemorySummaryResult(MeasurementAlgorithm::kSplit, |
| expected_measurement_time))); |
| }; |
| |
| // Expect exactly 1 query per repetition, with exactly kDelay between |
| // measurements. |
| { |
| ::testing::InSequence s; |
| base::TimeTicks next_measurement_time = base::TimeTicks::Now(); |
| for (int i = 0; i < kRepetitions; ++i) { |
| next_measurement_time += kDelay; |
| EXPECT_CALL(observer, OnResourceUsageUpdated( |
| memory_result_matcher(next_measurement_time))) |
| .Times(1); |
| } |
| } |
| |
| scoped_query->Start(kDelay); |
| |
| auto* params = scoped_query->GetParamsForTesting(); |
| EXPECT_NE(params->GetIdForTesting(), std::nullopt); |
| |
| // Cloning the params should not clone the id. |
| std::unique_ptr<QueryParams> cloned_params = params->Clone(); |
| EXPECT_EQ(*cloned_params, *params); |
| EXPECT_EQ(cloned_params->GetIdForTesting(), std::nullopt); |
| |
| task_environment()->FastForwardBy(kDelay * kRepetitions); |
| |
| // Test changes that happen between repetitions. |
| { |
| ::testing::InSequence s; |
| base::TimeTicks next_measurement_time = base::TimeTicks::Now(); |
| |
| // Repetition 1. |
| next_measurement_time += kDelay; |
| EXPECT_CALL(observer, OnResourceUsageUpdated( |
| memory_result_matcher(next_measurement_time))) |
| .Times(1); |
| |
| // QueryOnce called half-way to repetition 2. |
| EXPECT_CALL(observer, OnResourceUsageUpdated(memory_result_matcher( |
| next_measurement_time + kDelay / 2))) |
| .Times(1); |
| |
| // Repetition 2. |
| next_measurement_time += kDelay; |
| EXPECT_CALL(observer, OnResourceUsageUpdated( |
| memory_result_matcher(next_measurement_time))) |
| .Times(1); |
| |
| // Memory provider returns error at next repetition. Observer should still |
| // be notified. |
| next_measurement_time += kDelay; |
| EXPECT_CALL(observer, OnResourceUsageUpdated(IsEmpty())).Times(1); |
| } |
| |
| // Repetition 1. |
| task_environment()->FastForwardBy(kDelay); |
| |
| // QueryOnce called half-way to repetition 2. |
| task_environment()->FastForwardBy(kDelay / 2); |
| scoped_query->QueryOnce(); |
| |
| // Repetition 2. |
| task_environment()->FastForwardBy(kDelay / 2); |
| |
| // Memory provider returns error at next repetition. |
| fake_memory_summaries().clear(); |
| task_environment()->FastForwardBy(kDelay); |
| |
| // Reporting should stop once the query is deleted. StrictMock will give an |
| // error if OnResourceUsageUpdated() is called again. |
| scoped_query.reset(); |
| task_environment()->FastForwardBy(kDelay); |
| } |
| |
| TEST_F(ResourceAttrQueriesPMTest, ThrottleQueryOnce) { |
| const base::TimeDelta min_query_once_delay = |
| ScopedResourceUsageQuery::GetMinMemoryQueryDelayForTesting(); |
| const base::TimeDelta repeating_query_delay = min_query_once_delay * 5; |
| |
| // CPU-only query should not be throttled. |
| auto cpu_query = QueryBuilder() |
| .AddResourceContext(main_frame_context()) |
| .AddResourceType(ResourceType::kCPUTime) |
| .CreateScopedQuery(); |
| MockQueryResultObserver cpu_observer; |
| cpu_query.AddObserver(&cpu_observer); |
| |
| // Memory-only query should be throttled. |
| auto memory_query = QueryBuilder() |
| .AddResourceContext(main_frame_context()) |
| .AddResourceType(ResourceType::kMemorySummary) |
| .CreateScopedQuery(); |
| MockQueryResultObserver memory_observer; |
| memory_query.AddObserver(&memory_observer); |
| |
| // Memory+CPU query should be throttled. |
| auto memory_cpu_query = QueryBuilder() |
| .AddResourceContext(main_frame_context()) |
| .AddResourceType(ResourceType::kMemorySummary) |
| .AddResourceType(ResourceType::kCPUTime) |
| .CreateScopedQuery(); |
| MockQueryResultObserver memory_cpu_observer; |
| memory_cpu_query.AddObserver(&memory_cpu_observer); |
| |
| // Helper to fast forward to a fixed delta from the start of the test. |
| auto fast_forward_to = [this, start_time = base::TimeTicks::Now()]( |
| base::TimeDelta delta_from_start) { |
| task_environment()->FastForwardBy(start_time + delta_from_start - |
| base::TimeTicks::Now()); |
| }; |
| |
| // Queries should not get a QueryId until they're started. Id's are assigned |
| // on the PM sequence. |
| QueryParams* cpu_params = cpu_query.GetParamsForTesting(); |
| QueryParams* memory_params = memory_query.GetParamsForTesting(); |
| QueryParams* memory_cpu_params = memory_cpu_query.GetParamsForTesting(); |
| EXPECT_EQ(cpu_params->GetIdForTesting(), std::nullopt); |
| EXPECT_EQ(memory_params->GetIdForTesting(), std::nullopt); |
| EXPECT_EQ(memory_cpu_params->GetIdForTesting(), std::nullopt); |
| |
| // Each observer has its own sequence, since at each tick they could fire in |
| // any order. |
| ::testing::Sequence cpu_sequence, memory_sequence, memory_cpu_sequence; |
| |
| cpu_query.Start(repeating_query_delay); |
| memory_query.Start(repeating_query_delay); |
| memory_cpu_query.Start(repeating_query_delay); |
| |
| EXPECT_NE(cpu_params->GetIdForTesting(), std::nullopt); |
| EXPECT_NE(cpu_params->GetIdForTesting(), memory_params->GetIdForTesting()); |
| EXPECT_NE(memory_params->GetIdForTesting(), std::nullopt); |
| EXPECT_NE(memory_params->GetIdForTesting(), |
| memory_cpu_params->GetIdForTesting()); |
| EXPECT_NE(memory_cpu_params->GetIdForTesting(), std::nullopt); |
| EXPECT_NE(memory_cpu_params->GetIdForTesting(), |
| cpu_params->GetIdForTesting()); |
| |
| // QueryOnce just before the timer fires the first time. |
| EXPECT_CALL(cpu_observer, OnResourceUsageUpdated(_)).InSequence(cpu_sequence); |
| fast_forward_to(repeating_query_delay - min_query_once_delay + |
| base::Milliseconds(1)); |
| cpu_query.QueryOnce(); |
| memory_query.QueryOnce(); |
| memory_cpu_query.QueryOnce(); |
| |
| // Timer fires. |
| EXPECT_CALL(cpu_observer, OnResourceUsageUpdated(_)).InSequence(cpu_sequence); |
| EXPECT_CALL(memory_observer, OnResourceUsageUpdated(_)) |
| .InSequence(memory_sequence); |
| EXPECT_CALL(memory_cpu_observer, OnResourceUsageUpdated(_)) |
| .InSequence(memory_cpu_sequence); |
| fast_forward_to(repeating_query_delay); |
| |
| // QueryOnce just after timer fires - should be throttled until |
| // `min_query_once_delay` passes. |
| EXPECT_CALL(cpu_observer, OnResourceUsageUpdated(_)) |
| .Times(3) |
| .InSequence(cpu_sequence); |
| EXPECT_CALL(memory_observer, OnResourceUsageUpdated(_)) |
| .InSequence(memory_sequence); |
| EXPECT_CALL(memory_cpu_observer, OnResourceUsageUpdated(_)) |
| .InSequence(memory_cpu_sequence); |
| // Throttled. |
| cpu_query.QueryOnce(); |
| memory_query.QueryOnce(); |
| memory_cpu_query.QueryOnce(); |
| // Throttled. |
| fast_forward_to(repeating_query_delay + min_query_once_delay - |
| base::Milliseconds(1)); |
| cpu_query.QueryOnce(); |
| memory_query.QueryOnce(); |
| memory_cpu_query.QueryOnce(); |
| // Not throttled. |
| fast_forward_to(repeating_query_delay + min_query_once_delay); |
| cpu_query.QueryOnce(); |
| memory_query.QueryOnce(); |
| memory_cpu_query.QueryOnce(); |
| |
| // QueryOnce again just after a query - should be throttled until |
| // `min_query_once_delay` passes again. |
| EXPECT_CALL(cpu_observer, OnResourceUsageUpdated(_)) |
| .Times(3) |
| .InSequence(cpu_sequence); |
| EXPECT_CALL(memory_observer, OnResourceUsageUpdated(_)) |
| .InSequence(memory_sequence); |
| EXPECT_CALL(memory_cpu_observer, OnResourceUsageUpdated(_)) |
| .InSequence(memory_cpu_sequence); |
| // Throttled. |
| cpu_query.QueryOnce(); |
| memory_query.QueryOnce(); |
| memory_cpu_query.QueryOnce(); |
| // Throttled. |
| fast_forward_to(repeating_query_delay + 2 * min_query_once_delay - |
| base::Milliseconds(1)); |
| cpu_query.QueryOnce(); |
| memory_query.QueryOnce(); |
| memory_cpu_query.QueryOnce(); |
| // Not throttled. |
| fast_forward_to(repeating_query_delay + 2 * min_query_once_delay); |
| cpu_query.QueryOnce(); |
| memory_query.QueryOnce(); |
| memory_cpu_query.QueryOnce(); |
| |
| // QueryOnce just before the timer fires again - should not start throttling |
| // until inside `min_query_once_delay`. |
| EXPECT_CALL(cpu_observer, OnResourceUsageUpdated(_)) |
| .Times(2) |
| .InSequence(cpu_sequence); |
| EXPECT_CALL(memory_observer, OnResourceUsageUpdated(_)) |
| .InSequence(memory_sequence); |
| EXPECT_CALL(memory_cpu_observer, OnResourceUsageUpdated(_)) |
| .InSequence(memory_cpu_sequence); |
| // Not throttled. |
| fast_forward_to(2 * repeating_query_delay - min_query_once_delay); |
| cpu_query.QueryOnce(); |
| memory_query.QueryOnce(); |
| memory_cpu_query.QueryOnce(); |
| // Throttled. |
| fast_forward_to(2 * repeating_query_delay - min_query_once_delay + |
| base::Milliseconds(1)); |
| cpu_query.QueryOnce(); |
| memory_query.QueryOnce(); |
| memory_cpu_query.QueryOnce(); |
| |
| // Timer fires (not throttled). |
| EXPECT_CALL(cpu_observer, OnResourceUsageUpdated(_)).InSequence(cpu_sequence); |
| EXPECT_CALL(memory_observer, OnResourceUsageUpdated(_)) |
| .InSequence(memory_sequence); |
| EXPECT_CALL(memory_cpu_observer, OnResourceUsageUpdated(_)) |
| .InSequence(memory_cpu_sequence); |
| fast_forward_to(2 * repeating_query_delay); |
| } |
| |
| } // namespace resource_attribution |