blob: 7f182cd882594c5fa4329f2c3dace3a0a3dbb54e [file] [log] [blame]
// 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 <utility>
#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<base::TaskRunner> 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<base::PersistentHistogramAllocator> 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<base::PersistentHistogramAllocator> 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<RefCountedAllocator>(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<RefCountedAllocator> 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<AllocatorByIdMap>(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<base::PersistentMemoryAllocator> allocator =
host->TakeMetricsAllocator();
// The allocator can be null in tests.
if (!allocator)
return;
RegisterSubprocessAllocator(
data.id, std::make_unique<base::PersistentHistogramAllocator>(
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<base::PersistentMemoryAllocator> allocator =
host->TakeMetricsAllocator();
if (allocator) {
RegisterSubprocessAllocator(
host->GetID(), std::make_unique<base::PersistentHistogramAllocator>(
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<base::HistogramBase> 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<RefCountedAllocator> 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<AllocatorByIdMap> 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