// Copyright 2016 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/metrics/content/subprocess_metrics_provider.h" #include #include "base/debug/leak_annotations.h" #include "base/logging.h" #include "base/memory/scoped_refptr.h" #include "base/metrics/histogram_base.h" #include "base/metrics/persistent_histogram_allocator.h" #include "base/metrics/persistent_memory_allocator.h" #include "base/task/task_traits.h" #include "base/task/thread_pool.h" #include "components/metrics/metrics_features.h" #include "content/public/browser/browser_child_process_host.h" #include "content/public/browser/browser_child_process_host_iterator.h" #include "content/public/browser/child_process_data.h" namespace metrics { namespace { SubprocessMetricsProvider* g_subprocess_metrics_provider = nullptr; scoped_refptr CreateTaskRunner() { // This task runner must block shutdown to ensure metrics are not lost. return base::ThreadPool::CreateTaskRunner( {base::TaskPriority::BEST_EFFORT, base::TaskShutdownBehavior::BLOCK_SHUTDOWN}); } } // namespace // static bool SubprocessMetricsProvider::CreateInstance() { if (g_subprocess_metrics_provider) { return false; } g_subprocess_metrics_provider = new SubprocessMetricsProvider(); ANNOTATE_LEAKING_OBJECT_PTR(g_subprocess_metrics_provider); return true; } // static SubprocessMetricsProvider* SubprocessMetricsProvider::GetInstance() { return g_subprocess_metrics_provider; } // static void SubprocessMetricsProvider::MergeHistogramDeltasForTesting( bool async, base::OnceClosure done_callback) { GetInstance()->MergeHistogramDeltas(async, std::move(done_callback)); } SubprocessMetricsProvider::RefCountedAllocator::RefCountedAllocator( std::unique_ptr allocator) : allocator_(std::move(allocator)) { CHECK(allocator_); } SubprocessMetricsProvider::RefCountedAllocator::~RefCountedAllocator() = default; SubprocessMetricsProvider::SubprocessMetricsProvider() : task_runner_(CreateTaskRunner()) { base::StatisticsRecorder::RegisterHistogramProvider( weak_ptr_factory_.GetWeakPtr()); content::BrowserChildProcessObserver::Add(this); g_subprocess_metrics_provider = this; // Ensure no child processes currently exist so that we do not miss any. CHECK(content::RenderProcessHost::AllHostsIterator().IsAtEnd()); CHECK(content::BrowserChildProcessHostIterator().Done()); } SubprocessMetricsProvider::~SubprocessMetricsProvider() { // This object should never be deleted since it is leaky. NOTREACHED(); } void SubprocessMetricsProvider::RegisterSubprocessAllocator( int id, std::unique_ptr allocator) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); CHECK(allocator); // Pass a custom RangesManager so that we do not register the BucketRanges // with the global StatisticsRecorder when creating histogram objects using // the allocator's underlying data. This avoids unnecessary contention on the // global StatisticsRecorder lock. // Note: Since |allocator| may be merged from different threads concurrently, // for example on the UI thread and on a background thread, we must use // ThreadSafeRangesManager. allocator->SetRangesManager(new base::ThreadSafeRangesManager()); // Insert the allocator into the internal map and verify that there was no // allocator with the same ID already. auto result = allocators_by_id_.emplace( id, base::MakeRefCounted(std::move(allocator))); CHECK(result.second); } void SubprocessMetricsProvider::DeregisterSubprocessAllocator(int id) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); auto it = allocators_by_id_.find(id); if (it == allocators_by_id_.end()) { return; } // Extract the matching allocator from the list of active ones. It is possible // that a background task is currently holding a reference to it. Removing it // from the internal map is fine though, as it is refcounted. scoped_refptr allocator = std::move(it->second); allocators_by_id_.erase(it); CHECK(allocator); // Merge the last deltas from the allocator before releasing the ref (and // deleting if the last one). auto* allocator_ptr = allocator.get(); task_runner_->PostTaskAndReply( FROM_HERE, base::BindOnce( &SubprocessMetricsProvider::MergeHistogramDeltasFromAllocator, id, // Unretained is needed to pass a refcounted class as a raw pointer. // It is safe because it is kept alive by the reply task. base::Unretained(allocator_ptr)), base::BindOnce( &SubprocessMetricsProvider::OnMergeHistogramDeltasFromAllocator, std::move(allocator))); } void SubprocessMetricsProvider::MergeHistogramDeltas( bool async, base::OnceClosure done_callback) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); if (async) { // Make a copy of the internal allocators map (with its own refptrs) to pass // to the background task. auto allocators = std::make_unique(allocators_by_id_); auto* allocators_ptr = allocators.get(); // This is intentionally not posted to |task_runner_| because not running // this task does not imply data loss, so no point in blocking shutdown // (hence CONTINUE_ON_SHUTDOWN). However, there might be some contention on // the StatisticsRecorder between this task and those posted to // |task_runner_|. base::ThreadPool::PostTaskAndReply( FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, base::BindOnce(&MergeHistogramDeltasFromAllocators, allocators_ptr), base::BindOnce( &SubprocessMetricsProvider::OnMergeHistogramDeltasFromAllocators, std::move(allocators), std::move(done_callback))); } else { MergeHistogramDeltasFromAllocators(&allocators_by_id_); std::move(done_callback).Run(); } } void SubprocessMetricsProvider::BrowserChildProcessLaunchedAndConnected( const content::ChildProcessData& data) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); // See if the new process has a memory allocator and take control of it if so. // This call can only be made on the browser's IO thread. content::BrowserChildProcessHost* host = content::BrowserChildProcessHost::FromID(data.id); // |host| should not be null, but such cases have been observed in the wild so // gracefully handle this scenario. if (!host) { return; } std::unique_ptr allocator = host->TakeMetricsAllocator(); // The allocator can be null in tests. if (!allocator) return; RegisterSubprocessAllocator( data.id, std::make_unique( std::move(allocator))); } void SubprocessMetricsProvider::BrowserChildProcessHostDisconnected( const content::ChildProcessData& data) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DeregisterSubprocessAllocator(data.id); } void SubprocessMetricsProvider::BrowserChildProcessCrashed( const content::ChildProcessData& data, const content::ChildProcessTerminationInfo& info) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DeregisterSubprocessAllocator(data.id); } void SubprocessMetricsProvider::BrowserChildProcessKilled( const content::ChildProcessData& data, const content::ChildProcessTerminationInfo& info) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DeregisterSubprocessAllocator(data.id); } void SubprocessMetricsProvider::OnRenderProcessHostCreated( content::RenderProcessHost* host) { // Sometimes, the same host will cause multiple notifications in tests so // could possibly do the same in a release build. if (!scoped_observations_.IsObservingSource(host)) scoped_observations_.AddObservation(host); } void SubprocessMetricsProvider::RenderProcessReady( content::RenderProcessHost* host) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); // If the render-process-host passed a persistent-memory-allocator to the // renderer process, extract it and register it here. std::unique_ptr allocator = host->TakeMetricsAllocator(); if (allocator) { RegisterSubprocessAllocator( host->GetID(), std::make_unique( std::move(allocator))); } } void SubprocessMetricsProvider::RenderProcessExited( content::RenderProcessHost* host, const content::ChildProcessTerminationInfo& info) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DeregisterSubprocessAllocator(host->GetID()); } void SubprocessMetricsProvider::RenderProcessHostDestroyed( content::RenderProcessHost* host) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); // It's possible for a Renderer to terminate without RenderProcessExited // (above) being called so it's necessary to de-register also upon the // destruction of the host. If both get called, no harm is done. DeregisterSubprocessAllocator(host->GetID()); scoped_observations_.RemoveObservation(host); } void SubprocessMetricsProvider::RecreateTaskRunnerForTesting() { task_runner_ = CreateTaskRunner(); } // static void SubprocessMetricsProvider::MergeHistogramDeltasFromAllocator( int id, RefCountedAllocator* allocator) { DCHECK(allocator); int histogram_count = 0; base::PersistentHistogramAllocator* allocator_ptr = allocator->allocator(); base::PersistentHistogramAllocator::Iterator hist_iter(allocator_ptr); while (true) { std::unique_ptr histogram = hist_iter.GetNext(); if (!histogram) { break; } allocator_ptr->MergeHistogramDeltaToStatisticsRecorder(histogram.get()); ++histogram_count; } DVLOG(1) << "Reported " << histogram_count << " histograms from subprocess #" << id; } // static void SubprocessMetricsProvider::MergeHistogramDeltasFromAllocators( AllocatorByIdMap* allocators) { for (const auto& iter : *allocators) { MergeHistogramDeltasFromAllocator(iter.first, iter.second.get()); } } // static void SubprocessMetricsProvider::OnMergeHistogramDeltasFromAllocator( scoped_refptr allocator) { // This method does nothing except have ownership on |allocator|. When this // method exits, |allocator| will be released (unless there are background // tasks currently holding references). } // static void SubprocessMetricsProvider::OnMergeHistogramDeltasFromAllocators( std::unique_ptr allocators, base::OnceClosure done_callback) { std::move(done_callback).Run(); // When this method exits, |allocators| will be released. It's possible some // allocators are from subprocesses that have already been deregistered, so // they will also be released here (assuming no other background tasks // currently hold references). } } // namespace metrics