[go: nahoru, domu]

blob: ee031a270ff8a4e0193634e15b8ad77640a3fe3b [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/metrics/power/process_monitor.h"
#include <stddef.h>
#include <utility>
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/observer_list.h"
#include "base/process/process_handle.h"
#include "base/process/process_metrics.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/chrome_content_browser_client_extensions_part.h"
#include "chrome/browser/metrics/power/power_metrics_constants.h"
#include "content/public/browser/browser_child_process_host.h"
#include "content/public/browser/browser_child_process_host_iterator.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_data.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/content_constants.h"
#include "extensions/buildflags/buildflags.h"
#include "services/network/public/mojom/network_service.mojom.h"
#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/process_map.h"
#include "extensions/common/manifest_handlers/background_info.h"
#endif
#if BUILDFLAG(IS_WIN)
#include "sandbox/policy/mojom/sandbox.mojom-shared.h"
#endif
using content::BrowserThread;
namespace {
std::unique_ptr<base::ProcessMetrics> CreateProcessMetrics(
base::ProcessHandle process_handle) {
#if BUILDFLAG(IS_MAC)
return base::ProcessMetrics::CreateProcessMetrics(
process_handle, content::BrowserChildProcessHost::GetPortProvider());
#else
return base::ProcessMetrics::CreateProcessMetrics(process_handle);
#endif
}
// Samples the process metrics the ProcessMonitor cares about.
ProcessMonitor::Metrics SampleMetrics(base::ProcessMetrics& process_metrics) {
ProcessMonitor::Metrics metrics;
#if BUILDFLAG(IS_WIN)
metrics.cpu_usage = process_metrics.GetPreciseCPUUsage();
#else
metrics.cpu_usage = process_metrics.GetPlatformIndependentCPUUsage();
#endif
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || \
BUILDFLAG(IS_AIX)
metrics.idle_wakeups = process_metrics.GetIdleWakeupsPerSecond();
#endif
#if BUILDFLAG(IS_MAC)
metrics.package_idle_wakeups =
process_metrics.GetPackageIdleWakeupsPerSecond();
metrics.energy_impact = process_metrics.GetEnergyImpact();
#endif
return metrics;
}
// Scales every metrics by |factor|.
void ScaleMetrics(ProcessMonitor::Metrics* metrics, double factor) {
metrics->cpu_usage *= factor;
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || \
BUILDFLAG(IS_AIX)
metrics->idle_wakeups *= factor;
#endif
#if BUILDFLAG(IS_MAC)
metrics->package_idle_wakeups *= factor;
metrics->energy_impact *= factor;
#endif
}
ProcessMonitor::Metrics GetLastIntervalMetrics(
base::ProcessMetrics& process_metrics,
base::TimeDelta cumulative_cpu_usage) {
ProcessMonitor::Metrics metrics;
#if BUILDFLAG(IS_WIN)
metrics.cpu_usage = process_metrics.GetPreciseCPUUsage(cumulative_cpu_usage);
#else
metrics.cpu_usage =
process_metrics.GetPlatformIndependentCPUUsage(cumulative_cpu_usage);
#endif
// TODO: Add other values in ProcessMonitor::Metrics.
return metrics;
}
MonitoredProcessType GetMonitoredProcessTypeForRenderProcess(
content::RenderProcessHost* host) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
#if BUILDFLAG(ENABLE_EXTENSIONS)
content::BrowserContext* browser_context = host->GetBrowserContext();
if (extensions::ChromeContentBrowserClientExtensionsPart::
AreExtensionsDisabledForProfile(browser_context)) {
return MonitoredProcessType::kRenderer;
}
extensions::ProcessMap* extension_process_map =
extensions::ProcessMap::Get(browser_context);
DCHECK(extension_process_map);
std::set<std::string> extension_ids =
extension_process_map->GetExtensionsInProcess(host->GetID());
// We only collect more granular metrics when there's only one extension
// running in a given renderer, to reduce noise.
if (extension_ids.size() != 1)
return MonitoredProcessType::kRenderer;
extensions::ExtensionRegistry* extension_registry =
extensions::ExtensionRegistry::Get(browser_context);
const extensions::Extension* extension =
extension_registry->enabled_extensions().GetByID(*extension_ids.begin());
if (!extension)
return MonitoredProcessType::kRenderer;
return extensions::BackgroundInfo::HasPersistentBackgroundPage(extension)
? MonitoredProcessType::kExtensionPersistent
: MonitoredProcessType::kExtensionEvent;
#else
return MonitoredProcessType::kRenderer;
#endif
}
MonitoredProcessType GetMonitoredProcessTypeForNonRendererChildProcess(
const content::ChildProcessData& data) {
switch (data.process_type) {
case content::PROCESS_TYPE_BROWSER:
case content::PROCESS_TYPE_RENDERER:
// Not a non-renderer child process.
NOTREACHED();
return kCount;
case content::PROCESS_TYPE_GPU:
return MonitoredProcessType::kGpu;
case content::PROCESS_TYPE_UTILITY: {
// Special case for the network process.
if (data.metrics_name == network::mojom::NetworkService::Name_)
return MonitoredProcessType::kNetwork;
return MonitoredProcessType::kUtility;
}
default:
return MonitoredProcessType::kOther;
}
}
// Adds the values from |rhs| to |lhs|.
ProcessMonitor::Metrics& operator+=(ProcessMonitor::Metrics& lhs,
const ProcessMonitor::Metrics& rhs) {
lhs.cpu_usage += rhs.cpu_usage;
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || \
BUILDFLAG(IS_AIX)
lhs.idle_wakeups += rhs.idle_wakeups;
#endif
#if BUILDFLAG(IS_MAC)
lhs.package_idle_wakeups += rhs.package_idle_wakeups;
lhs.energy_impact += rhs.energy_impact;
#endif
return lhs;
}
} // namespace
MonitoredProcessType
GetMonitoredProcessTypeForNonRendererChildProcessForTesting(
const content::ChildProcessData& data) {
return GetMonitoredProcessTypeForNonRendererChildProcess(data);
}
ProcessInfo::ProcessInfo(MonitoredProcessType type,
std::unique_ptr<base::ProcessMetrics> process_metrics)
: type(type),
process_metrics(std::move(process_metrics)),
first_sample_time(base::TimeTicks::Now()) {
// Do an initial call to SampleMetrics() so that the next one returns
// meaningful data.
SampleMetrics(*this->process_metrics);
#if BUILDFLAG(IS_WIN) && !defined(ARCH_CPU_ARM64)
// Record the value of HasConstantRateTSC to get a feel of the proportion of
// users that don't record the average CPU usage histogram.
base::UmaHistogramBoolean("PerformanceMonitor.HasPreciseCPUUsage",
base::time_internal::HasConstantRateTSC());
#endif
}
ProcessInfo::~ProcessInfo() = default;
ProcessMonitor::Metrics::Metrics() = default;
ProcessMonitor::Metrics::Metrics(const ProcessMonitor::Metrics& other) =
default;
ProcessMonitor::Metrics& ProcessMonitor::Metrics::operator=(
const ProcessMonitor::Metrics& other) = default;
ProcessMonitor::Metrics::~Metrics() = default;
ProcessMonitor::ProcessMonitor()
: browser_process_info_(
MonitoredProcessType::kBrowser,
CreateProcessMetrics(base::GetCurrentProcessHandle())) {
// Ensure ProcessMonitor is created before any child process so that none is
// missed.
DCHECK(content::BrowserChildProcessHostIterator().Done());
DCHECK(content::RenderProcessHost::AllHostsIterator().IsAtEnd());
content::BrowserChildProcessObserver::Add(this);
}
ProcessMonitor::~ProcessMonitor() {
content::BrowserChildProcessObserver::Remove(this);
}
void ProcessMonitor::SampleAllProcesses(Observer* observer) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Accumulate all the different processes.
std::vector<ProcessInfo*> process_infos;
process_infos.reserve(1 + render_process_infos_.size() +
browser_child_process_infos_.size());
process_infos.push_back(&browser_process_info_);
for (auto& [_, process_info] : render_process_infos_)
process_infos.push_back(&process_info);
for (auto& [_, process_info] : browser_child_process_infos_)
process_infos.push_back(&process_info);
const base::TimeTicks now = base::TimeTicks::Now();
// Aggregate all metrics into a single sum, but also per their process type.
Metrics aggregated_metrics;
std::array<Metrics, MonitoredProcessType::kCount> per_type_metrics;
for (auto* process_info : process_infos) {
Metrics metrics = SampleMetrics(*process_info->process_metrics);
// If this is the first interval calculated for this process, then the
// metrics values must be scaled down over the
// |kLongPowerMetricsIntervalDuration|.
if (process_info->first_sample_time.has_value()) {
// Scale the amount.
auto first_interval_duration = now - *process_info->first_sample_time;
ScaleMetrics(&metrics,
first_interval_duration / kLongPowerMetricsIntervalDuration);
// No longer the first interval after this one.
process_info->first_sample_time = absl::nullopt;
}
aggregated_metrics += metrics;
per_type_metrics[process_info->type] += metrics;
}
for (int i = 0; i < MonitoredProcessType::kCount; i++) {
// Add the metrics for the processes that exited during this interval and
// zero out.
per_type_metrics[i] += exited_processes_metrics_[i];
exited_processes_metrics_[i] = Metrics();
observer->OnMetricsSampled(static_cast<MonitoredProcessType>(i),
per_type_metrics[i]);
}
observer->OnAggregatedMetricsSampled(aggregated_metrics);
}
void ProcessMonitor::OnRenderProcessHostCreated(
content::RenderProcessHost* render_process_host) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// If the host is reused after the process exited, it is possible to get a
// second created notification for the same host.
if (!render_process_host_observations_.IsObservingSource(render_process_host))
render_process_host_observations_.AddObservation(render_process_host);
}
void ProcessMonitor::RenderProcessReady(
content::RenderProcessHost* render_process_host) {
// TODO(pmonette): It's possible for a process to be launched and then teared
// down without it being ever ready, which mean they will not
// affect the performance metrics, even though they should.
// Consider using `OnRenderProcessHostCreated()` instead of
// `RenderProcessReady()`.
bool inserted =
render_process_infos_
.emplace(
std::piecewise_construct,
std::forward_as_tuple(render_process_host),
std::forward_as_tuple(
GetMonitoredProcessTypeForRenderProcess(render_process_host),
CreateProcessMetrics(
render_process_host->GetProcess().Handle())))
.second;
DCHECK(inserted);
}
void ProcessMonitor::RenderProcessExited(
content::RenderProcessHost* render_process_host,
const content::ChildProcessTerminationInfo& info) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto it = render_process_infos_.find(render_process_host);
if (it == render_process_infos_.end()) {
// This process was never ready.
return;
}
// Remember the metrics from when the process exited.
const ProcessInfo& process_info = it->second;
exited_processes_metrics_[process_info.type] +=
GetLastIntervalMetrics(*process_info.process_metrics, info.cpu_usage);
render_process_infos_.erase(it);
}
void ProcessMonitor::RenderProcessHostDestroyed(
content::RenderProcessHost* render_process_host) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
render_process_host_observations_.RemoveObservation(render_process_host);
}
void ProcessMonitor::BrowserChildProcessLaunchedAndConnected(
const content::ChildProcessData& data) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
#if BUILDFLAG(IS_WIN)
// Cannot gather process metrics for elevated process as browser has no
// access to them.
if (data.sandbox_type ==
sandbox::mojom::Sandbox::kNoSandboxAndElevatedPrivileges) {
return;
}
#endif
MonitoredProcessType type =
GetMonitoredProcessTypeForNonRendererChildProcess(data);
bool inserted =
browser_child_process_infos_
.emplace(std::piecewise_construct, std::forward_as_tuple(data.id),
std::forward_as_tuple(
type, CreateProcessMetrics(data.GetProcess().Handle())))
.second;
DCHECK(inserted);
}
void ProcessMonitor::BrowserChildProcessHostDisconnected(
const content::ChildProcessData& data) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
#if BUILDFLAG(IS_WIN)
// Cannot gather process metrics for elevated process as browser has no
// access to them.
if (data.sandbox_type ==
sandbox::mojom::Sandbox::kNoSandboxAndElevatedPrivileges) {
return;
}
#endif
DCHECK(browser_child_process_infos_.find(data.id) ==
browser_child_process_infos_.end());
}
void ProcessMonitor::BrowserChildProcessCrashed(
const content::ChildProcessData& data,
const content::ChildProcessTerminationInfo& info) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
OnBrowserChildProcessExited(data, info);
}
void ProcessMonitor::BrowserChildProcessKilled(
const content::ChildProcessData& data,
const content::ChildProcessTerminationInfo& info) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
OnBrowserChildProcessExited(data, info);
}
void ProcessMonitor::BrowserChildProcessExitedNormally(
const content::ChildProcessData& data,
const content::ChildProcessTerminationInfo& info) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
OnBrowserChildProcessExited(data, info);
}
void ProcessMonitor::OnBrowserChildProcessExited(
const content::ChildProcessData& data,
const content::ChildProcessTerminationInfo& info) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
#if BUILDFLAG(IS_WIN)
// Cannot gather process metrics for elevated process as browser has no
// access to them.
if (data.sandbox_type ==
sandbox::mojom::Sandbox::kNoSandboxAndElevatedPrivileges) {
return;
}
#endif
auto it = browser_child_process_infos_.find(data.id);
if (it == browser_child_process_infos_.end()) {
// It is possible to receive this notification without a launch-and-connect
// notification. See https://crbug.com/942500 for a similar issue.
return;
}
DCHECK(it != browser_child_process_infos_.end());
// Remember the metrics from when the process exited.
const ProcessInfo& process_info = it->second;
exited_processes_metrics_[process_info.type] +=
GetLastIntervalMetrics(*process_info.process_metrics, info.cpu_usage);
browser_child_process_infos_.erase(it);
}