| // 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); |
| } |