1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
|
// Copyright 2022 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/metrics/page_resource_monitor.h"
#include <stdint.h>
#include <optional>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/scoped_observation.h"
#include "components/performance_manager/public/graph/page_node.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"
#include "services/metrics/public/cpp/ukm_source_id.h"
namespace performance_manager::metrics {
namespace {
using system_cpu::CpuProbe;
using system_cpu::CpuSample;
using PageMeasurementBackgroundState =
PageResourceMonitor::PageMeasurementBackgroundState;
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
// easier. This is the same scale factor used by the
// PerformanceMonitor.AverageCPU9 histograms recorded in
// chrome/browser/metrics/power/process_metrics_recorder_util.cc.
constexpr int kCPUUsageFactor = 100 * 100;
// The time between calls to OnResourceUsageUpdated()
constexpr base::TimeDelta kCollectionDelay = base::Minutes(2);
PageMeasurementBackgroundState GetBackgroundStateForMeasurementPeriod(
const PageNode* page_node,
base::TimeTicks now,
base::TimeTicks time_of_last_resource_usage) {
if (time_of_last_resource_usage < page_node->GetLastVisibilityChangeTime()) {
return PageMeasurementBackgroundState::kMixedForegroundBackground;
}
if (page_node->IsVisible()) {
return PageMeasurementBackgroundState::kForeground;
}
// Check if the page was audible for the entire measurement period.
const base::TimeDelta time_since_last_resource_usage =
now - time_of_last_resource_usage;
if (page_node->GetTimeSinceLastAudibleChange().value_or(
base::TimeDelta::Max()) < time_since_last_resource_usage) {
return PageMeasurementBackgroundState::kBackgroundMixedAudible;
}
if (page_node->IsAudible()) {
return PageMeasurementBackgroundState::kAudibleInBackground;
}
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&,
std::optional<CpuSample>)>;
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,
std::optional<CpuSample> 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)
: resource_query_(CPUQueryBuilder()
.AddResourceType(ResourceType::kMemorySummary)
.CreateScopedQuery()) {
query_observation_.Observe(&resource_query_);
resource_query_.Start(kCollectionDelay);
cpu_result_converter_ = std::make_unique<CPUResultConverter>(
enable_system_cpu_probe ? CpuProbe::Create() : nullptr);
}
PageResourceMonitor::~PageResourceMonitor() = default;
void PageResourceMonitor::OnResourceUsageUpdated(
const QueryResultMap& results) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
cpu_result_converter_->OnResourceUsageUpdated(
base::BindOnce(&PageResourceMonitor::OnPageResourceUsageResult,
weak_factory_.GetWeakPtr(), results),
results);
}
base::TimeDelta PageResourceMonitor::GetCollectionDelayForTesting() const {
return kCollectionDelay;
}
void PageResourceMonitor::OnPageResourceUsageResult(
const QueryResultMap& results,
const PageCPUUsageMap& page_cpu_usage,
std::optional<CpuSample> system_cpu) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Calculate the overall CPU usage.
double total_cpu_usage = 0;
for (const auto& [page_context, cpu_usage] : page_cpu_usage) {
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, 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();
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.
if (result.memory_summary_result.has_value()) {
ukm.SetResidentSetSizeEstimate(
result.memory_summary_result->resident_set_size_kb);
ukm.SetPrivateFootprintEstimate(
result.memory_summary_result->private_footprint_kb);
}
ukm.Record(ukm::UkmRecorder::Get());
}
time_of_last_resource_usage_ = now;
}
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();
}
}
base::WeakPtr<PageResourceMonitor::CPUResultConverter>
PageResourceMonitor::CPUResultConverter::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
bool PageResourceMonitor::CPUResultConverter::HasSystemCPUProbe() const {
return static_cast<bool>(system_cpu_probe_);
}
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(next_update_callback).Run(std::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,
std::optional<CpuSample> system_cpu) {
std::move(result_callback)
.Run(proportion_tracker_.StartNextInterval(time, results),
std::move(system_cpu));
}
} // namespace performance_manager::metrics
|