[go: nahoru, domu]

Use Resource Attribution directly in PageResourceMonitor

This stops checking the kUseResourceAttributionCPUMonitor param, and
always gets measurements from Resource Attribution.

PageTimelineCPUMonitor is now unused, but not deleted because an
upcoming experiment will re-enable it behind a different feature flag
as a retrospective study.

Since Resource Attribution is now used outside a feature flag,
enables the Performance Manager ResourceAttributionScheduler feature
by default.

Bug: 1481051
Change-Id: Ife0ed39324204e06cb7c7558fbe7eeb3ee28033d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5131420
Commit-Queue: Joe Mason <joenotcharles@google.com>
Reviewed-by: Steven Luong <stluong@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1239365}
diff --git a/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.cc b/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.cc
index dd21de6..05510b5 100644
--- a/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.cc
+++ b/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.cc
@@ -234,13 +234,9 @@
 }
 
 void ChromeBrowserMainExtraPartsPerformanceManager::PostCreateThreads() {
-  auto graph_features = performance_manager::GraphFeatures::WithDefault();
-  if (performance_manager::features::kUseResourceAttributionCPUMonitor.Get()) {
-    graph_features.EnableResourceAttributionScheduler();
-  }
   performance_manager_lifetime_ =
       std::make_unique<performance_manager::PerformanceManagerLifetime>(
-          graph_features,
+          performance_manager::GraphFeatures::WithDefault(),
           base::BindOnce(&ChromeBrowserMainExtraPartsPerformanceManager::
                              CreatePoliciesAndDecorators));
 
diff --git a/chrome/browser/performance_manager/metrics/page_resource_monitor.cc b/chrome/browser/performance_manager/metrics/page_resource_monitor.cc
index a4f0018..6ca81e59 100644
--- a/chrome/browser/performance_manager/metrics/page_resource_monitor.cc
+++ b/chrome/browser/performance_manager/metrics/page_resource_monitor.cc
@@ -24,10 +24,12 @@
 #include "base/system/sys_info.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
+#include "base/types/optional_ref.h"
 #include "build/build_config.h"
 #include "components/performance_manager/public/features.h"
 #include "components/performance_manager/public/graph/page_node.h"
-#include "components/performance_manager/public/resource_attribution/cpu_measurement_delegate.h"
+#include "components/performance_manager/public/resource_attribution/cpu_proportion_tracker.h"
+#include "components/performance_manager/public/resource_attribution/resource_types.h"
 #include "components/system_cpu/cpu_probe.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "services/metrics/public/cpp/ukm_recorder.h"
@@ -43,6 +45,12 @@
 using PageMeasurementBackgroundState =
     PageResourceMonitor::PageMeasurementBackgroundState;
 
+using MemorySummaryResult = resource_attribution::MemorySummaryResult;
+using PageContext = resource_attribution::PageContext;
+using QueryResultMap = resource_attribution::QueryResultMap;
+using ResourceContext = resource_attribution::ResourceContext;
+using ResourceType = resource_attribution::ResourceType;
+
 // CPU usage metrics are provided as a double in the [0.0, number of cores *
 // 100.0] range. The CPU usage is usually below 1%, so the UKM is
 // reported out of 10,000 instead of out of 100 to make analyzing the data
@@ -54,7 +62,7 @@
 // The values for n when calculating the total CPU usage of the top n tabs.
 constexpr std::array<size_t, 5> kTabCountSlices = {1, 2, 4, 8, 16};
 
-// The time between calls to CollectPageResourceUsage()
+// The time between calls to OnResourceUsageUpdated()
 constexpr base::TimeDelta kCollectionDelay = base::Minutes(2);
 
 PageMeasurementBackgroundState GetBackgroundStateForMeasurementPeriod(
@@ -78,32 +86,75 @@
   return PageMeasurementBackgroundState::kBackground;
 }
 
+resource_attribution::QueryBuilder CPUQueryBuilder() {
+  resource_attribution::QueryBuilder builder;
+  builder.AddAllContextsOfType<PageContext>().AddResourceType(
+      ResourceType::kCPUTime);
+  return builder;
+}
+
+const PageNode* PageNodeFromContext(const ResourceContext& context) {
+  // The query returned by CPUQueryBuilder() should only measure PageContexts.
+  // AsContext() asserts that `context` is a PageContext.
+  return resource_attribution::AsContext<PageContext>(context).GetPageNode();
+}
+
+bool ContextIsTab(const ResourceContext& context) {
+  const PageNode* page_node = PageNodeFromContext(context);
+  return page_node && page_node->GetType() == PageType::kTab;
+}
+
 }  // namespace
 
+class PageResourceMonitor::CPUResultConverter {
+ public:
+  // A callback that's invoked with the converted results.
+  using ResultCallback =
+      base::OnceCallback<void(const PageCPUUsageMap&,
+                              absl::optional<PressureSample>)>;
+
+  explicit CPUResultConverter(std::unique_ptr<CpuProbe> system_cpu_probe);
+  ~CPUResultConverter() = default;
+
+  base::WeakPtr<CPUResultConverter> GetWeakPtr();
+
+  bool HasSystemCPUProbe() const;
+
+  // Invokes `result_callback_` with the converted `results`.
+  void OnResourceUsageUpdated(ResultCallback result_callback,
+                              const QueryResultMap& results);
+
+ private:
+  void StartFirstInterval(base::TimeTicks time, const QueryResultMap& results);
+  void StartNextInterval(ResultCallback result_callback,
+                         base::TimeTicks time,
+                         const QueryResultMap& results,
+                         absl::optional<PressureSample> system_cpu);
+
+  std::unique_ptr<CpuProbe> system_cpu_probe_;
+  resource_attribution::CPUProportionTracker proportion_tracker_;
+  base::WeakPtrFactory<CPUResultConverter> weak_factory_{this};
+};
+
 PageResourceMonitor::PageResourceMonitor(bool enable_system_cpu_probe)
-    : system_cpu_probe_(enable_system_cpu_probe ? CpuProbe::Create()
-                                                : nullptr) {
-  collect_page_resource_usage_timer_.Start(
-      FROM_HERE, kCollectionDelay,
-      base::BindRepeating(&PageResourceMonitor::CollectPageResourceUsage,
-                          weak_factory_.GetWeakPtr()));
-  if (system_cpu_probe_) {
-    system_cpu_probe_->StartSampling();
-  }
+    : resource_query_(CPUQueryBuilder()
+                          .AddResourceType(ResourceType::kMemorySummary)
+                          .CreateScopedQuery()) {
+  resource_query_.AddObserver(this);
+  resource_query_.Start(kCollectionDelay);
+  cpu_result_converter_ = std::make_unique<CPUResultConverter>(
+      enable_system_cpu_probe ? CpuProbe::Create() : nullptr);
 }
 
 PageResourceMonitor::~PageResourceMonitor() = default;
 
-void PageResourceMonitor::OnPassedToGraph(Graph* graph) {
+void PageResourceMonitor::OnResourceUsageUpdated(
+    const QueryResultMap& results) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  graph_ = graph;
-  cpu_monitor_.StartMonitoring(graph_);
-}
-
-void PageResourceMonitor::OnTakenFromGraph(Graph* graph) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  cpu_monitor_.StopMonitoring(graph_);
-  graph_ = nullptr;
+  cpu_result_converter_->OnResourceUsageUpdated(
+      base::BindOnce(&PageResourceMonitor::OnPageResourceUsageResult,
+                     weak_factory_.GetWeakPtr(), results),
+      results);
 }
 
 base::TimeDelta PageResourceMonitor::GetCollectionDelayForTesting() const {
@@ -115,32 +166,11 @@
   return performance_manager::features::kDelayBeforeLogging.Get();
 }
 
-void PageResourceMonitor::SetCPUMeasurementDelegateFactoryForTesting(
-    Graph* graph,
-    PageResourceCPUMonitor::CPUMeasurementDelegate::Factory* factory) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  // Callback should be installed before `cpu_monitor_` starts
-  // measuring the graph.
-  CHECK(graph);
-  CHECK(!graph_);
-  cpu_monitor_.SetCPUMeasurementDelegateFactoryForTesting(  // IN-TEST
-      graph, factory);
-}
-
-void PageResourceMonitor::CollectPageResourceUsage() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  CalculatePageCPUUsage(
-      /*use_delayed_system_cpu_probe=*/false,
-      base::BindOnce(&PageResourceMonitor::OnPageResourceUsageResult,
-                     weak_factory_.GetWeakPtr()));
-}
-
 void PageResourceMonitor::OnPageResourceUsageResult(
-    const PageCPUUsageVector& page_cpu_usage,
+    const QueryResultMap& results,
+    const PageCPUUsageMap& page_cpu_usage,
     absl::optional<PressureSample> system_cpu) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  const bool use_resource_attribution =
-      performance_manager::features::kUseResourceAttributionCPUMonitor.Get();
 
   // Calculate the overall CPU usage.
   double total_cpu_usage = 0;
@@ -148,27 +178,38 @@
     total_cpu_usage += cpu_usage;
   }
 
+  // Contexts in `page_cpu_usage` are a subset of contexts in `results`.
   const auto now = base::TimeTicks::Now();
-  for (const auto& [page_context, cpu_usage] : page_cpu_usage) {
-    const PageNode* page_node = page_context.GetPageNode();
+  for (const auto& [page_context, result] : results) {
+    const PageNode* page_node = PageNodeFromContext(page_context);
     if (!page_node) {
       // Page was deleted while waiting for system CPU. Nothing to log.
       continue;
     }
+    if (page_node->GetType() != PageType::kTab) {
+      continue;
+    }
     const ukm::SourceId source_id = page_node->GetUkmSourceID();
-    ukm::builders::PerformanceManager_PageResourceUsage2(source_id)
-        .SetResidentSetSizeEstimate(page_node->EstimateResidentSetSize())
-        .SetPrivateFootprintEstimate(page_node->EstimatePrivateFootprintSize())
-        .SetRecentCPUUsage(kCPUUsageFactor * cpu_usage)
-        .SetTotalRecentCPUUsageAllPages(kCPUUsageFactor * total_cpu_usage)
-        .SetBackgroundState(
-            static_cast<int64_t>(GetBackgroundStateForMeasurementPeriod(
-                page_node, now - time_of_last_resource_usage_)))
-        .SetMeasurementAlgorithm(static_cast<int64_t>(
-            use_resource_attribution
-                ? PageMeasurementAlgorithm::kEvenSplitAndAggregate
-                : PageMeasurementAlgorithm::kLegacy))
-        .Record(ukm::UkmRecorder::Get());
+    auto ukm = ukm::builders::PerformanceManager_PageResourceUsage2(source_id);
+    ukm.SetBackgroundState(
+        static_cast<int64_t>(GetBackgroundStateForMeasurementPeriod(
+            page_node, now - time_of_last_resource_usage_)));
+    ukm.SetMeasurementAlgorithm(
+        static_cast<int64_t>(PageMeasurementAlgorithm::kEvenSplitAndAggregate));
+    // Add CPU usage, if this page included it.
+    const auto it = page_cpu_usage.find(page_context);
+    if (it != page_cpu_usage.end()) {
+      ukm.SetRecentCPUUsage(kCPUUsageFactor * it->second);
+      ukm.SetTotalRecentCPUUsageAllPages(kCPUUsageFactor * total_cpu_usage);
+    }
+    // Add memory summary, if this page included it.
+    const base::optional_ref<const MemorySummaryResult> memory_result =
+        resource_attribution::AsResult<MemorySummaryResult>(result);
+    if (memory_result.has_value()) {
+      ukm.SetResidentSetSizeEstimate(memory_result->resident_set_size_kb);
+      ukm.SetPrivateFootprintEstimate(memory_result->private_footprint_kb);
+    }
+    ukm.Record(ukm::UkmRecorder::Get());
   }
   time_of_last_resource_usage_ = now;
 
@@ -186,22 +227,13 @@
         time_of_last_cpu_threshold_exceeded_ = now;
         LogCPUInterventionMetrics(page_cpu_usage, system_cpu, now,
                                   CPUInterventionSuffix::kImmediate);
-
-        // Only logged delayed metrics when using the new CPU monitor.
-        if (use_resource_attribution) {
-          if (system_cpu_probe_) {
-            // `system_cpu_probe_` needs to be called at fixed intervals, so
-            // start a second probe  to measure the CPU until the delay timer
-            // fires.
-            CHECK(!delayed_system_cpu_probe_);
-            delayed_system_cpu_probe_ = CpuProbe::Create();
-            delayed_system_cpu_probe_->StartSampling();
-          }
-          log_cpu_on_delay_timer_.Start(
-              FROM_HERE,
-              performance_manager::features::kDelayBeforeLogging.Get(), this,
-              &PageResourceMonitor::CheckDelayedCPUInterventionMetrics);
-        }
+        CHECK(!delayed_cpu_result_converter_);
+        delayed_cpu_result_converter_ = std::make_unique<CPUResultConverter>(
+            cpu_result_converter_->HasSystemCPUProbe() ? CpuProbe::Create()
+                                                       : nullptr);
+        log_cpu_on_delay_timer_.Start(
+            FROM_HERE, performance_manager::features::kDelayBeforeLogging.Get(),
+            this, &PageResourceMonitor::CheckDelayedCPUInterventionMetrics);
       }
     } else if (!is_cpu_over_threshold) {
       base::UmaHistogramCustomTimes(
@@ -211,7 +243,7 @@
           base::Hours(24), 50);
       log_cpu_on_delay_timer_.AbandonAndStop();
       time_of_last_cpu_threshold_exceeded_ = absl::nullopt;
-      delayed_system_cpu_probe_.reset();
+      delayed_cpu_result_converter_.reset();
     }
   }
 #endif
@@ -219,22 +251,22 @@
 
 void PageResourceMonitor::CheckDelayedCPUInterventionMetrics() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  CHECK(performance_manager::features::kUseResourceAttributionCPUMonitor.Get());
-  CalculatePageCPUUsage(
-      /*use_delayed_system_cpu_probe=*/true,
+  CHECK(delayed_cpu_result_converter_);
+  CPUQueryBuilder().QueryOnce(base::BindOnce(
+      &CPUResultConverter::OnResourceUsageUpdated,
+      delayed_cpu_result_converter_->GetWeakPtr(),
       base::BindOnce(
           &PageResourceMonitor::OnDelayedCPUInterventionMetricsResult,
-          weak_factory_.GetWeakPtr()));
+          weak_factory_.GetWeakPtr())));
 }
 
 void PageResourceMonitor::OnDelayedCPUInterventionMetricsResult(
-    const PageCPUUsageVector& page_cpu_usage,
+    const PageCPUUsageMap& page_cpu_usage,
     absl::optional<PressureSample> system_cpu) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  CHECK(performance_manager::features::kUseResourceAttributionCPUMonitor.Get());
-  // Now that `system_cpu` is received, stop the delayed CPU probe. This is a
-  // no-op if it was already nullptr.
-  delayed_system_cpu_probe_.reset();
+  // Now that results are received, stop the delayed CPU probe and proportion
+  // tracking.
+  delayed_cpu_result_converter_.reset();
   double total_cpu_usage = 0;
   for (const auto& [page_context, cpu_usage] : page_cpu_usage) {
     total_cpu_usage += cpu_usage;
@@ -250,7 +282,7 @@
 }
 
 void PageResourceMonitor::LogCPUInterventionMetrics(
-    const PageCPUUsageVector& page_cpu_usage,
+    const PageCPUUsageMap& page_cpu_usage,
     const absl::optional<PressureSample>& system_cpu,
     const base::TimeTicks now,
     CPUInterventionSuffix histogram_suffix) {
@@ -262,7 +294,7 @@
   int background_tab_count = 0;
 
   for (const auto& [page_context, cpu_usage] : page_cpu_usage) {
-    const PageNode* page_node = page_context.GetPageNode();
+    const PageNode* page_node = PageNodeFromContext(page_context);
     if (!page_node) {
       // Page was deleted while waiting for system CPU.
       continue;
@@ -352,15 +384,15 @@
         std::max(system_cpu_percent - total_background_cpu_percent -
                      total_foreground_cpu_percent,
                  0));
-  } else if (system_cpu_probe_) {
+  } else if (cpu_result_converter_->HasSystemCPUProbe()) {
     // System CPU probing is available, so there must have been an error in
     // CpuProbe::RequestSample().
     //
-    // For .Delayed histograms this can also include a failure to initialize
-    // `delayed_system_cpu_probe_` when `system_cpu_probe_` initialized
-    // successfully. Failure to even initialize `system_cpu_probe_` isn't
-    // recorded because that means system CPU isn't available at all on this
-    // system rather than a transient error.
+    // For .Delayed histograms this can also include a failure to initialize the
+    // CpuProbe in `delayed_cpu_result_converter_` when `cpu_result_converter_`
+    // initialized successfully. Failure to even initialize
+    // `cpu_result_converter_` isn't recorded because that means system CPU
+    // isn't available at all on this system rather than a transient error.
     base::UmaHistogramBoolean(
         base::StrCat({"PerformanceManager.PerformanceInterventions.CPU."
                       "SystemCPUError.",
@@ -418,46 +450,55 @@
   }
 }
 
-void PageResourceMonitor::CalculatePageCPUUsage(
-    bool use_delayed_system_cpu_probe,
-    base::OnceCallback<void(const PageCPUUsageVector&,
-                            absl::optional<PressureSample>)> callback) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  cpu_monitor_.UpdateCPUMeasurements(base::BindOnce(
-      &PageResourceMonitor::OnPageCPUUsageResult, weak_factory_.GetWeakPtr(),
-      use_delayed_system_cpu_probe, std::move(callback)));
+PageResourceMonitor::CPUResultConverter::CPUResultConverter(
+    std::unique_ptr<CpuProbe> system_cpu_probe)
+    : system_cpu_probe_(std::move(system_cpu_probe)),
+      // Only calculate results for tabs, not extensions.
+      proportion_tracker_(base::BindRepeating(&ContextIsTab)) {
+  CPUQueryBuilder().QueryOnce(
+      base::BindOnce(&CPUResultConverter::StartFirstInterval, GetWeakPtr(),
+                     base::TimeTicks::Now()));
+  if (system_cpu_probe_) {
+    system_cpu_probe_->StartSampling();
+  }
 }
 
-void PageResourceMonitor::OnPageCPUUsageResult(
-    bool use_delayed_system_cpu_probe,
-    base::OnceCallback<void(const PageCPUUsageVector&,
-                            absl::optional<PressureSample>)> callback,
-    const PageResourceCPUMonitor::CPUUsageMap& cpu_usage_map) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  CHECK(graph_);
+base::WeakPtr<PageResourceMonitor::CPUResultConverter>
+PageResourceMonitor::CPUResultConverter::GetWeakPtr() {
+  return weak_factory_.GetWeakPtr();
+}
 
-  // Calculate the overall CPU usage.
-  PageCPUUsageVector page_cpu_usage;
-  graph_->VisitAllPageNodes([&page_cpu_usage,
-                             &cpu_usage_map](const PageNode* page_node) {
-    if (page_node->GetType() == PageType::kTab) {
-      page_cpu_usage.emplace_back(page_node->GetResourceContext(),
-                                  PageResourceCPUMonitor::EstimatePageCPUUsage(
-                                      page_node, cpu_usage_map));
-    }
-    return true;
-  });
+bool PageResourceMonitor::CPUResultConverter::HasSystemCPUProbe() const {
+  return static_cast<bool>(system_cpu_probe_);
+}
 
-  // Now fetch the system CPU usage if available.
-  CpuProbe* cpu_probe = use_delayed_system_cpu_probe
-                            ? delayed_system_cpu_probe_.get()
-                            : system_cpu_probe_.get();
-  if (cpu_probe) {
-    cpu_probe->RequestSample(
-        base::BindOnce(std::move(callback), page_cpu_usage));
+void PageResourceMonitor::CPUResultConverter::OnResourceUsageUpdated(
+    CPUResultConverter::ResultCallback result_callback,
+    const QueryResultMap& results) {
+  auto next_update_callback = base::BindOnce(
+      &CPUResultConverter::StartNextInterval, GetWeakPtr(),
+      std::move(result_callback), base::TimeTicks::Now(), results);
+  if (system_cpu_probe_) {
+    system_cpu_probe_->RequestSample(std::move(next_update_callback));
   } else {
-    std::move(callback).Run(page_cpu_usage, absl::nullopt);
+    std::move(next_update_callback).Run(absl::nullopt);
   }
 }
 
+void PageResourceMonitor::CPUResultConverter::StartFirstInterval(
+    base::TimeTicks time,
+    const QueryResultMap& results) {
+  proportion_tracker_.StartFirstInterval(time, results);
+}
+
+void PageResourceMonitor::CPUResultConverter::StartNextInterval(
+    CPUResultConverter::ResultCallback result_callback,
+    base::TimeTicks time,
+    const QueryResultMap& results,
+    absl::optional<PressureSample> system_cpu) {
+  std::move(result_callback)
+      .Run(proportion_tracker_.StartNextInterval(time, results),
+           std::move(system_cpu));
+}
+
 }  // namespace performance_manager::metrics
diff --git a/chrome/browser/performance_manager/metrics/page_resource_monitor.h b/chrome/browser/performance_manager/metrics/page_resource_monitor.h
index 4ebf373..b7fa9f49 100644
--- a/chrome/browser/performance_manager/metrics/page_resource_monitor.h
+++ b/chrome/browser/performance_manager/metrics/page_resource_monitor.h
@@ -5,30 +5,25 @@
 #ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_METRICS_PAGE_RESOURCE_MONITOR_H_
 #define CHROME_BROWSER_PERFORMANCE_MANAGER_METRICS_PAGE_RESOURCE_MONITOR_H_
 
+#include <map>
 #include <memory>
-#include <utility>
-#include <vector>
 
-#include "base/functional/callback_forward.h"
-#include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
-#include "chrome/browser/performance_manager/metrics/page_resource_cpu_monitor.h"
 #include "components/performance_manager/public/graph/graph.h"
-#include "components/performance_manager/public/resource_attribution/page_context.h"
+#include "components/performance_manager/public/resource_attribution/queries.h"
+#include "components/performance_manager/public/resource_attribution/query_results.h"
+#include "components/performance_manager/public/resource_attribution/resource_contexts.h"
 #include "components/system_cpu/pressure_sample.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
-namespace system_cpu {
-class CpuProbe;
-}
-
 namespace performance_manager::metrics {
 
 // Periodically reports tab resource usage via UKM.
-class PageResourceMonitor : public GraphOwned {
+class PageResourceMonitor : public resource_attribution::QueryResultObserver,
+                            public GraphOwnedDefaultImpl {
  public:
   // These values are logged to UKM. Entries should not be renumbered and
   // numeric values should never be reused. Please keep in sync with
@@ -59,25 +54,20 @@
   PageResourceMonitor(const PageResourceMonitor& other) = delete;
   PageResourceMonitor& operator=(const PageResourceMonitor&) = delete;
 
-  // GraphOwned:
-  void OnPassedToGraph(Graph* graph) override;
-  void OnTakenFromGraph(Graph* graph) override;
+  // QueryResultObserver:
+  void OnResourceUsageUpdated(
+      const resource_attribution::QueryResultMap& results) override;
 
-  // Returns the time between calls to CollectPageResourceUsage(). Tests can
+  // Returns the time between calls to OnResourceUsageUpdated(). Tests can
   // advance the mock clock by this amount to trigger metrics collection.
   base::TimeDelta GetCollectionDelayForTesting() const;
 
   // Returns the delay before logging
   // PerformanceManager.PerformanceInterventions.CPU.*.Delayed. Tests can
-  // advance the mock clock by this amount after CollectPageResourceUsage() to
+  // advance the mock clock by this amount after OnResourceUsageUpdated() to
   // trigger the delayed metrics logging.
   base::TimeDelta GetDelayedMetricsTimeoutForTesting() const;
 
-  // Passes the given `factory` to PageResourceCPUMonitor.
-  void SetCPUMeasurementDelegateFactoryForTesting(
-      Graph* graph,
-      PageResourceCPUMonitor::CPUMeasurementDelegate::Factory* factory);
-
  private:
   // Suffix for CPU intervention histograms.
   enum class CPUInterventionSuffix {
@@ -87,65 +77,46 @@
   };
 
   // The percent CPU usage for each PageNode that was measured. This stores a
-  // PageContext instead of a node pointer in case the PageNode is deleted while
-  // taking asynchronous system CPU measurements.
-  using PageCPUUsageVector =
-      std::vector<std::pair<resource_attribution::PageContext, double>>;
+  // ResourceContext instead of a node pointer in case the PageNode is deleted
+  // while taking asynchronous system CPU measurements.
+  using PageCPUUsageMap =
+      std::map<resource_attribution::ResourceContext, double>;
 
-  // Asynchronously collects the PageResourceUsage UKM.
-  void CollectPageResourceUsage();
+  // Helper class that converts CPUTimeResult to proportion of CPU used over a
+  // fixed interval, and adds system CPU to the result.
+  class CPUResultConverter;
 
-  // Invoked asynchronously from CollectPageResourceUsage() when measurements
-  // are ready.
+  // Invoked asynchronously from OnResourceUsageUpdate() when both page and
+  // system CPU measurements are ready. `results` contains the original query
+  // results, with both CPU and memory measurements. `page_cpu_usage` is the
+  // proportion of CPU usage calculated for each page from the original results,
+  // and `system_cpu` is the overall system CPU usage.
   void OnPageResourceUsageResult(
-      const PageCPUUsageVector& page_cpu_usage,
+      const resource_attribution::QueryResultMap& results,
+      const PageCPUUsageMap& page_cpu_usage,
       absl::optional<system_cpu::PressureSample> system_cpu);
 
   // Asynchronously checks if the CPU metrics are still above the threshold
   // after a delay.
   void CheckDelayedCPUInterventionMetrics();
 
-  // Invoked asynchronously from CheckDelayedCPUInterventionMetrics() when
-  // measurements are ready.
+  // Invoked asynchronously from CheckDelayedCPUInterventionMetrics() when both
+  // page and system CPU measurements are ready.
   void OnDelayedCPUInterventionMetricsResult(
-      const PageCPUUsageVector& page_cpu_usage,
+      const PageCPUUsageMap& page_cpu_usage,
       absl::optional<system_cpu::PressureSample> system_cpu);
 
   // Log CPU intervention metrics with the provided suffix.
   void LogCPUInterventionMetrics(
-      const PageCPUUsageVector& page_cpu_usage,
+      const PageCPUUsageMap& page_cpu_usage,
       const absl::optional<system_cpu::PressureSample>& system_cpu,
       const base::TimeTicks now,
       CPUInterventionSuffix histogram_suffix);
 
-  // Asynchronously calculates per-PageNode CPU usage, converts the results to a
-  // vector, and passes them to `callback`. Also queries either
-  // `system_cpu_probe_` or `delayed_system_cpu_probe_`, depending on the value
-  // of `use_delayed_system_cpu_probe`, for a PressureSample to pass to
-  // `callback`.
-  void CalculatePageCPUUsage(
-      bool use_delayed_system_cpu_probe,
-      base::OnceCallback<void(const PageCPUUsageVector&,
-                              absl::optional<system_cpu::PressureSample>)>
-          callback);
-
-  // Invoked asynchronously from CalculatePageCPUUsage() when page CPU
-  // measurements are ready. Converts the measurements in `cpu_usage_map`
-  // to a vector, collects system CPU from either `system_cpu_probe_` or
-  // `delayed_system_cpu_probe_` (depending on the value of
-  // `use_delayed_system_cpu_probe`) and passes both page and system results to
-  // `callback`.
-  void OnPageCPUUsageResult(
-      bool use_delayed_system_cpu_probe,
-      base::OnceCallback<void(const PageCPUUsageVector&,
-                              absl::optional<system_cpu::PressureSample>)>
-          callback,
-      const PageResourceCPUMonitor::CPUUsageMap& cpu_usage_map);
-
   SEQUENCE_CHECKER(sequence_checker_);
 
-  // Timer which is used to trigger CollectPageResourceUsage().
-  base::RepeatingTimer collect_page_resource_usage_timer_
+  // Repeating query that triggers OnResourceUsageUpdated on a timer.
+  resource_attribution::ScopedResourceUsageQuery resource_query_
       GUARDED_BY_CONTEXT(sequence_checker_);
 
   // Timer which handles logging high CPU after a potential delay.
@@ -156,23 +127,16 @@
   absl::optional<base::TimeTicks> time_of_last_cpu_threshold_exceeded_
       GUARDED_BY_CONTEXT(sequence_checker_) = absl::nullopt;
 
-  // Pointer to this process' graph.
-  raw_ptr<Graph> graph_ GUARDED_BY_CONTEXT(sequence_checker_) = nullptr;
-
   // Time of last PageResourceUsage collection.
   base::TimeTicks time_of_last_resource_usage_
       GUARDED_BY_CONTEXT(sequence_checker_) = base::TimeTicks::Now();
 
-  // Helper to take CPU measurements for the UKM.
-  PageResourceCPUMonitor cpu_monitor_ GUARDED_BY_CONTEXT(sequence_checker_);
-
-  // Helpers to take system CPU measurements for UMA.
-  std::unique_ptr<system_cpu::CpuProbe> system_cpu_probe_
+  // Helpers to convert CPU measurements for UMA.
+  std::unique_ptr<CPUResultConverter> cpu_result_converter_
       GUARDED_BY_CONTEXT(sequence_checker_);
-  std::unique_ptr<system_cpu::CpuProbe> delayed_system_cpu_probe_
+  std::unique_ptr<CPUResultConverter> delayed_cpu_result_converter_
       GUARDED_BY_CONTEXT(sequence_checker_);
 
-  // WeakPtrFactory for the RepeatingTimer to call a method on this object.
   base::WeakPtrFactory<PageResourceMonitor> weak_factory_{this};
 };
 
diff --git a/chrome/browser/performance_manager/metrics/page_resource_monitor_unittest.cc b/chrome/browser/performance_manager/metrics/page_resource_monitor_unittest.cc
index 41d025e..ea5403a 100644
--- a/chrome/browser/performance_manager/metrics/page_resource_monitor_unittest.cc
+++ b/chrome/browser/performance_manager/metrics/page_resource_monitor_unittest.cc
@@ -33,6 +33,8 @@
 #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/cpu_measurement_delegate.h"
+#include "components/performance_manager/public/resource_attribution/memory_measurement_delegate.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/resource_attribution/measurement_delegates.h"
@@ -51,6 +53,16 @@
 using PageMeasurementBackgroundState =
     PageResourceMonitor::PageMeasurementBackgroundState;
 
+using CPUMeasurementDelegate = resource_attribution::CPUMeasurementDelegate;
+using FakeMemoryMeasurementDelegateFactory =
+    resource_attribution::FakeMemoryMeasurementDelegateFactory;
+using MemoryMeasurementDelegate =
+    resource_attribution::MemoryMeasurementDelegate;
+using SimulatedCPUMeasurementDelegate =
+    resource_attribution::SimulatedCPUMeasurementDelegate;
+using SimulatedCPUMeasurementDelegateFactory =
+    resource_attribution::SimulatedCPUMeasurementDelegateFactory;
+
 // Helper class to repeatedly test a HistogramTester for histograms with a
 // common naming pattern. The default pattern is
 // PerformanceManager.PerformanceInterventions.CPU.*.
@@ -172,27 +184,35 @@
 
 class PageResourceMonitorUnitTest : public GraphTestHarness {
  public:
-  PageResourceMonitorUnitTest() = default;
+  PageResourceMonitorUnitTest() {
+    scoped_feature_list_.InitAndEnableFeatureWithParameters(
+        performance_manager::features::kCPUInterventionEvaluationLogging,
+        {{"threshold_chrome_cpu_percent", "50"}});
+  }
+
   ~PageResourceMonitorUnitTest() override = default;
-  PageResourceMonitorUnitTest(const PageResourceMonitorUnitTest& other) =
-      delete;
+
+  PageResourceMonitorUnitTest(const PageResourceMonitorUnitTest&) = delete;
   PageResourceMonitorUnitTest& operator=(const PageResourceMonitorUnitTest&) =
       delete;
 
   void SetUp() override {
+    GetGraphFeatures().EnableResourceAttributionScheduler();
     GraphTestHarness::SetUp();
 
     // Return 50% CPU used by default.
     cpu_delegate_factory_.SetDefaultCPUUsage(0.5);
+    CPUMeasurementDelegate::SetDelegateFactoryForTesting(
+        graph(), &cpu_delegate_factory_);
+    MemoryMeasurementDelegate::SetDelegateFactoryForTesting(
+        graph(), &memory_delegate_factory_);
 
     std::unique_ptr<PageResourceMonitor> monitor =
         std::make_unique<PageResourceMonitor>(enable_system_cpu_probe_);
     monitor_ = monitor.get();
-    monitor_->SetCPUMeasurementDelegateFactoryForTesting(
-        graph(), &cpu_delegate_factory_);
-    last_collection_time_ = base::TimeTicks::Now();
     graph()->PassToGraph(std::move(monitor));
     ResetUkmRecorder();
+    last_collection_time_ = base::TimeTicks::Now();
   }
 
   void TearDown() override {
@@ -200,23 +220,23 @@
     GraphTestHarness::TearDown();
   }
 
-  // To allow tests to call its methods and view its state.
-  raw_ptr<PageResourceMonitor> monitor_;
-
-  // Factory to return CPUMeasurementDelegates. This must be deleted after
-  // `monitor_` to ensure that it outlives all delegates it creates.
-  resource_attribution::SimulatedCPUMeasurementDelegateFactory
-      cpu_delegate_factory_;
-
  protected:
   ukm::TestUkmRecorder* test_ukm_recorder() { return test_ukm_recorder_.get(); }
   PageResourceMonitor* monitor() { return monitor_; }
 
+  SimulatedCPUMeasurementDelegate& GetCPUDelegate(const ProcessNode* node) {
+    return cpu_delegate_factory_.GetDelegate(node);
+  }
+
+  FakeMemoryMeasurementDelegateFactory& GetMemoryDelegate() {
+    return memory_delegate_factory_;
+  }
+
   // Advances the clock to trigger the PageResourceUsage UKM.
   void TriggerCollectPageResourceUsage();
 
   // Advances the mock clock slightly to give enough time to make asynchronous
-  // measurements after CollectPageResourceUsage is triggered. If
+  // measurements after TriggerCollectPageResourceUsage(). If
   // `include_system_cpu` is true, also waits for some real time to let the
   // system CpuProbe collect data.
   void WaitForMetrics(bool include_system_cpu = false);
@@ -248,10 +268,19 @@
     task_env().FastForwardBy(target_time - base::TimeTicks::Now());
   }
 
+  raw_ptr<PageResourceMonitor> monitor_;
+
   std::unique_ptr<ukm::TestUkmRecorder> test_ukm_recorder_;
 
-  // The last time CollectPageResourceUsage() was triggered.
+  // The last time TriggerCollectPageResourceUsage() was called.
   base::TimeTicks last_collection_time_;
+
+  // Factories to return fake measurement delegates. These must be deleted after
+  // `monitor_` to ensure that they outlive all delegates they create.
+  SimulatedCPUMeasurementDelegateFactory cpu_delegate_factory_;
+  FakeMemoryMeasurementDelegateFactory memory_delegate_factory_;
+
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 void PageResourceMonitorUnitTest::TriggerCollectPageResourceUsage() {
@@ -295,72 +324,10 @@
   ResetUkmRecorder();
 }
 
-// A test that runs with various values of the kUseResourceAttributionCPUMonitor
-// feature flag.
-class PageResourceMonitorWithFeatureTest
-    : public PageResourceMonitorUnitTest,
-      public ::testing::WithParamInterface<
-          std::tuple<bool, PageMeasurementAlgorithm>> {
- public:
-  PageResourceMonitorWithFeatureTest() {
-    std::tie(use_resource_attribution_, expected_measurement_algorithm_) =
-        GetParam();
-    scoped_feature_list_.InitWithFeaturesAndParameters(
-        {
-            {features::kPageTimelineMonitor,
-             {{"use_resource_attribution_cpu_monitor",
-               use_resource_attribution_ ? "true" : "false"}}},
-            {performance_manager::features::kCPUInterventionEvaluationLogging,
-             {{"threshold_chrome_cpu_percent", "50"}}},
-        },
-        {});
-  }
-
-  void SetUp() override {
-    if (features::kUseResourceAttributionCPUMonitor.Get()) {
-      GetGraphFeatures().EnableResourceAttributionScheduler();
-    }
-    PageResourceMonitorUnitTest::SetUp();
-  }
-
-  void TearDown() override {
-    // Destroy `monitor_` before `scoped_feature_list_` so that the feature flag
-    // doesn't change during its destructor.
-    graph()->TakeFromGraph(monitor_.ExtractAsDangling());
-    PageResourceMonitorUnitTest::TearDown();
-  }
-
- protected:
-  bool use_resource_attribution_;
-  PageMeasurementAlgorithm expected_measurement_algorithm_;
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-INSTANTIATE_TEST_SUITE_P(
-    All,
-    PageResourceMonitorWithFeatureTest,
-    ::testing::Values(
-        std::make_tuple(false, PageMeasurementAlgorithm::kLegacy),
-        std::make_tuple(true,
-                        PageMeasurementAlgorithm::kEvenSplitAndAggregate)));
-
 // A test of CPU intervention logging when the system CPUProbe is not available.
 class PageResourceMonitorNoCPUProbeTest : public PageResourceMonitorUnitTest {
  public:
-  PageResourceMonitorNoCPUProbeTest() {
-    scoped_feature_list_.InitWithFeaturesAndParameters(
-        {
-            {performance_manager::features::kCPUInterventionEvaluationLogging,
-             {{"threshold_chrome_cpu_percent", "50"}}},
-        },
-        {});
-    enable_system_cpu_probe_ = false;
-  }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
+  PageResourceMonitorNoCPUProbeTest() { enable_system_cpu_probe_ = false; }
 };
 
 TEST_F(PageResourceMonitorUnitTest, TestPageResourceUsage) {
@@ -423,19 +390,25 @@
   EXPECT_EQ(entries2.size(), 0UL);
 }
 
-TEST_P(PageResourceMonitorWithFeatureTest, TestResourceUsage) {
+TEST_F(PageResourceMonitorUnitTest, TestResourceUsage) {
   MockMultiplePagesWithMultipleProcessesGraph mock_graph(graph());
+
+  // Register fake memory results. Make sure they're divisible by 2 for easier
+  // matching when divided between frames.
+  MemoryMeasurementDelegate::MemorySummaryMap& memory_summaries =
+      GetMemoryDelegate().memory_summaries();
+  memory_summaries[mock_graph.process->GetResourceContext()] = {
+      .resident_set_size_kb = 1230};
+  memory_summaries[mock_graph.other_process->GetResourceContext()] = {
+      .resident_set_size_kb = 4560, .private_footprint_kb = 7890};
+
   const ukm::SourceId mock_source_id = ukm::AssignNewSourceId();
   mock_graph.page->SetType(performance_manager::PageType::kTab);
   mock_graph.page->SetUkmSourceId(mock_source_id);
-  mock_graph.frame->SetResidentSetKbEstimate(123);
 
   const ukm::SourceId mock_source_id2 = ukm::AssignNewSourceId();
   mock_graph.other_page->SetType(performance_manager::PageType::kTab);
   mock_graph.other_page->SetUkmSourceId(mock_source_id2);
-  mock_graph.other_frame->SetResidentSetKbEstimate(456);
-  mock_graph.other_frame->SetPrivateFootprintKbEstimate(789);
-  mock_graph.child_frame->SetPrivateFootprintKbEstimate(1000);
 
   TriggerCollectPageResourceUsage();
   WaitForMetrics();
@@ -445,16 +418,21 @@
   // Expect 1 entry per page.
   EXPECT_EQ(entries.size(), 2UL);
 
+  // `page` gets its memory from `frame`, which is 1/2 the memory from
+  // `process`.
+  // `other_page` gets its memory from `other_frame` (1/2 of `process`) +
+  // `child_frame` (all of `other_process`).
+  // See the diagram in
+  // components/performance_manager/test_support/mock_graphs.h.
   const auto kExpectedResidentSetSize =
       base::MakeFixedFlatMap<ukm::SourceId, int64_t>({
-          {mock_source_id, 123},
-          {mock_source_id2, 456},
+          {mock_source_id, 1230 / 2},
+          {mock_source_id2, 1230 / 2 + 4560},
       });
   const auto kExpectedPrivateFootprint =
       base::MakeFixedFlatMap<ukm::SourceId, int64_t>({
           {mock_source_id, 0},
-          // `other_page` is the sum of `other_frame` and `child_frame`
-          {mock_source_id2, 1789},
+          {mock_source_id2, 7890},
       });
   // The SimulatedCPUMeasurementDelegate returns 50% of the CPU is used.
   // `process` contains `frame` and `other_frame` -> each gets 25%
@@ -480,7 +458,7 @@
                                            kExpectedAllCPUUsage);
     test_ukm_recorder()->ExpectEntryMetric(
         entry, "MeasurementAlgorithm",
-        static_cast<int64_t>(expected_measurement_algorithm_));
+        static_cast<int64_t>(PageMeasurementAlgorithm::kEvenSplitAndAggregate));
   }
 }
 
@@ -543,7 +521,7 @@
 }
 
 #if !BUILDFLAG(IS_ANDROID)
-TEST_P(PageResourceMonitorWithFeatureTest, TestCPUInterventionMetrics) {
+TEST_F(PageResourceMonitorUnitTest, TestCPUInterventionMetrics) {
   MockMultiplePagesWithMultipleProcessesGraph mock_graph(graph());
 
   // Foreground page.
@@ -557,9 +535,8 @@
   // Set CPU usage to near-0, so only the .Baseline metrics should be logged.
   // 0 is used as an error value by base::ProcessMetrics so it will cause no
   // results at all for the process to be returned.
-  cpu_delegate_factory_.GetDelegate(mock_graph.process.get()).SetCPUUsage(0.01);
-  cpu_delegate_factory_.GetDelegate(mock_graph.other_process.get())
-      .SetCPUUsage(0.01);
+  GetCPUDelegate(mock_graph.process.get()).SetCPUUsage(0.01);
+  GetCPUDelegate(mock_graph.other_process.get()).SetCPUUsage(0.01);
 
   {
     PatternedHistogramTester histograms;
@@ -572,9 +549,9 @@
     // The intervention metrics measure total CPU, not percentage of each core,
     // so set the measurement delegates to return half of the total available
     // CPU (100% per processor).
-    cpu_delegate_factory_.GetDelegate(mock_graph.process.get())
+    GetCPUDelegate(mock_graph.process.get())
         .SetCPUUsage(base::SysInfo::NumberOfProcessors() / 2.0);
-    cpu_delegate_factory_.GetDelegate(mock_graph.other_process.get())
+    GetCPUDelegate(mock_graph.other_process.get())
         .SetCPUUsage(base::SysInfo::NumberOfProcessors() / 2.0);
 
     WaitForMetrics(/*include_system_cpu=*/true);
@@ -697,40 +674,26 @@
     immediate.ExpectNone("SystemCPUError");
 
     auto delayed = histograms.WithSuffix("Delayed");
-    if (use_resource_attribution_) {
-      delayed.ExpectUniqueSample("AverageBackgroundCPU", 75);
-      delayed.ExpectUniqueSample("TotalBackgroundCPU", 75);
-      delayed.ExpectUniqueSample("TotalBackgroundTabCount", 1);
-      delayed.ExpectUniqueSample("AverageForegroundCPU", 25);
-      delayed.ExpectUniqueSample("TotalForegroundCPU", 25);
-      delayed.ExpectUniqueSample("TotalForegroundTabCount", 1);
-      delayed.ExpectUniqueSample("BackgroundTabsToGetUnderCPUThreshold", 1);
-      delayed.ExpectUniqueSample("TopNBackgroundCPU.1", 75);
-      delayed.ExpectUniqueSample("TopNBackgroundCPU.2", 75);
-      delayed.ExpectSystemCPUHistograms();
-    } else {
-      delayed.ExpectNone("AverageBackgroundCPU");
-      delayed.ExpectNone("TotalBackgroundCPU");
-      delayed.ExpectNone("TotalBackgroundTabCount");
-      delayed.ExpectNone("AverageForegroundCPU");
-      delayed.ExpectNone("TotalForegroundCPU");
-      delayed.ExpectNone("TotalForegroundTabCount");
-      delayed.ExpectNone("BackgroundTabsToGetUnderCPUThreshold");
-      delayed.ExpectNone("TopNBackgroundCPU.1");
-      delayed.ExpectNone("TopNBackgroundCPU.2");
-      delayed.ExpectNone("System");
-      delayed.ExpectNone("NonChrome");
-      delayed.ExpectNone("SystemCPUError");
-    }
+    delayed.ExpectUniqueSample("AverageBackgroundCPU", 75);
+    delayed.ExpectUniqueSample("TotalBackgroundCPU", 75);
+    delayed.ExpectUniqueSample("TotalBackgroundTabCount", 1);
+    delayed.ExpectUniqueSample("AverageForegroundCPU", 25);
+    delayed.ExpectUniqueSample("TotalForegroundCPU", 25);
+    delayed.ExpectUniqueSample("TotalForegroundTabCount", 1);
+    delayed.ExpectUniqueSample("BackgroundTabsToGetUnderCPUThreshold", 1);
+    delayed.ExpectUniqueSample("TopNBackgroundCPU.1", 75);
+    delayed.ExpectUniqueSample("TopNBackgroundCPU.2", 75);
+    delayed.ExpectSystemCPUHistograms();
+
     histograms.ExpectNone("DurationOverThreshold");
   }
 
   // Finish this measurement interval, then lower the CPU measurement so the
   // average CPU usage over the next interval is under the threshold.
   TriggerCollectPageResourceUsage();
-  cpu_delegate_factory_.GetDelegate(mock_graph.process.get())
+  GetCPUDelegate(mock_graph.process.get())
       .SetCPUUsage(base::SysInfo::NumberOfProcessors() / 8.0);
-  cpu_delegate_factory_.GetDelegate(mock_graph.other_process.get())
+  GetCPUDelegate(mock_graph.other_process.get())
       .SetCPUUsage(base::SysInfo::NumberOfProcessors() / 8.0);
 
   {
@@ -786,11 +749,10 @@
   }
 }
 
-TEST_P(PageResourceMonitorWithFeatureTest,
-       CPUInterventionMetricsNoForegroundTabs) {
+TEST_F(PageResourceMonitorUnitTest, CPUInterventionMetricsNoForegroundTabs) {
   MockSinglePageInSingleProcessGraph mock_graph(graph());
   mock_graph.page->SetType(performance_manager::PageType::kTab);
-  cpu_delegate_factory_.GetDelegate(mock_graph.process.get())
+  GetCPUDelegate(mock_graph.process.get())
       .SetCPUUsage(base::SysInfo::NumberOfProcessors());
 
   // Put the only tab in the background.
@@ -822,11 +784,10 @@
   immediate.ExpectUniqueSample("TopNBackgroundCPU.2", 100);
 }
 
-TEST_P(PageResourceMonitorWithFeatureTest,
-       CPUInterventionMetricsNoBackgroundTabs) {
+TEST_F(PageResourceMonitorUnitTest, CPUInterventionMetricsNoBackgroundTabs) {
   MockSinglePageInSingleProcessGraph mock_graph(graph());
   mock_graph.page->SetType(performance_manager::PageType::kTab);
-  cpu_delegate_factory_.GetDelegate(mock_graph.process.get())
+  GetCPUDelegate(mock_graph.process.get())
       .SetCPUUsage(base::SysInfo::NumberOfProcessors());
 
   // Put the only tab in the foreground.
@@ -866,7 +827,7 @@
   mock_graph.page->SetType(performance_manager::PageType::kTab);
   mock_graph.page->SetIsVisible(false);
 
-  cpu_delegate_factory_.GetDelegate(mock_graph.process.get())
+  GetCPUDelegate(mock_graph.process.get())
       .SetCPUUsage(base::SysInfo::NumberOfProcessors());
 
   PatternedHistogramTester histograms;
diff --git a/components/performance_manager/embedder/graph_features.h b/components/performance_manager/embedder/graph_features.h
index ab25899..2db085e 100644
--- a/components/performance_manager/embedder/graph_features.h
+++ b/components/performance_manager/embedder/graph_features.h
@@ -156,6 +156,7 @@
     EnablePageLoadTrackerDecorator();
     EnablePriorityTracking();
     EnableProcessHostedContentTypesAggregator();
+    EnableResourceAttributionScheduler();
     EnableSiteDataRecorder();
     EnableTabConnectednessDecorator();
     EnableTabPageDecorator();
diff --git a/components/performance_manager/graph_features_unittest.cc b/components/performance_manager/graph_features_unittest.cc
index 0625be07..a823590 100644
--- a/components/performance_manager/graph_features_unittest.cc
+++ b/components/performance_manager/graph_features_unittest.cc
@@ -60,7 +60,7 @@
       execution_context::ExecutionContextRegistry::GetFromGraph(&graph));
   EXPECT_FALSE(v8_memory::V8ContextTracker::GetFromGraph(&graph));
 
-  size_t graph_owned_count = 15;
+  size_t graph_owned_count = 16;
 #if !BUILDFLAG(IS_ANDROID)
   // The SiteDataRecorder is not available on Android.
   graph_owned_count++;
@@ -70,7 +70,7 @@
   features.EnableDefault();
   features.ConfigureGraph(&graph);
   EXPECT_EQ(graph_owned_count, graph.GraphOwnedCountForTesting());
-  EXPECT_EQ(5u, graph.GraphRegisteredCountForTesting());
+  EXPECT_EQ(6u, graph.GraphRegisteredCountForTesting());
   EXPECT_EQ(8u, graph.NodeDataDescriberCountForTesting());
   // Ensure the GraphRegistered objects can be queried directly.
   EXPECT_TRUE(