blob: cecb5c27fcd5d839ef2ba0c3ec605eb7494ff96c [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/heap_profiling/client_connection_manager.h"
#include "base/rand_util.h"
#include "components/services/heap_profiling/public/cpp/controller.h"
#include "components/services/heap_profiling/public/cpp/settings.h"
#include "components/services/heap_profiling/public/mojom/heap_profiling_client.mojom.h"
#include "components/services/heap_profiling/public/mojom/heap_profiling_service.mojom.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_thread.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/bind_interface_helpers.h"
#include "content/public/common/child_process_host.h"
#include "content/public/common/process_type.h"
#include "content/public/common/service_names.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
namespace heap_profiling {
namespace {
// This helper class cleans up initialization boilerplate for the callers who
// need to create ProfilingClients bound to various different things.
class ProfilingClientBinder {
public:
// Binds to a non-renderer-child-process' ProfilingClient.
explicit ProfilingClientBinder(content::BrowserChildProcessHost* host)
: ProfilingClientBinder() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
content::BindInterface(host->GetHost(), std::move(request_));
}
// Binds to a renderer's ProfilingClient.
explicit ProfilingClientBinder(content::RenderProcessHost* host)
: ProfilingClientBinder() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
content::BindInterface(host, std::move(request_));
}
// Binds to the local connector to get the browser process' ProfilingClient.
explicit ProfilingClientBinder(service_manager::Connector* connector)
: ProfilingClientBinder() {
connector->BindInterface(content::mojom::kBrowserServiceName,
std::move(request_));
}
mojom::ProfilingClientPtr take() { return std::move(memlog_client_); }
private:
ProfilingClientBinder() : request_(mojo::MakeRequest(&memlog_client_)) {}
mojom::ProfilingClientPtr memlog_client_;
mojom::ProfilingClientRequest request_;
};
bool ShouldProfileNonRendererProcessType(Mode mode, int process_type) {
switch (mode) {
case Mode::kAll:
return true;
case Mode::kAllRenderers:
// Renderer logic is handled in ClientConnectionManager::Observe.
return false;
case Mode::kManual:
return false;
case Mode::kMinimal:
return (process_type == content::ProcessType::PROCESS_TYPE_GPU ||
process_type == content::ProcessType::PROCESS_TYPE_BROWSER);
case Mode::kGpu:
return process_type == content::ProcessType::PROCESS_TYPE_GPU;
case Mode::kBrowser:
return process_type == content::ProcessType::PROCESS_TYPE_BROWSER;
case Mode::kRendererSampling:
// Renderer logic is handled in ClientConnectionManager::Observe.
return false;
case Mode::kNone:
return false;
case Mode::kCount:
// Fall through to hit NOTREACHED() below.
{}
}
NOTREACHED();
return false;
}
void StartProfilingNonRendererChildOnIOThread(
base::WeakPtr<Controller> controller,
const content::ChildProcessData& data,
base::ProcessId pid) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
if (!controller)
return;
content::BrowserChildProcessHost* host =
content::BrowserChildProcessHost::FromID(data.id);
if (!host)
return;
mojom::ProcessType process_type =
(data.process_type == content::ProcessType::PROCESS_TYPE_GPU)
? mojom::ProcessType::GPU
: mojom::ProcessType::OTHER;
// Tell the child process to start profiling.
ProfilingClientBinder client(host);
controller->StartProfilingClient(client.take(), pid, process_type);
}
void StartProfilingClientOnIOThread(base::WeakPtr<Controller> controller,
ProfilingClientBinder client,
base::ProcessId pid,
mojom::ProcessType process_type) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
if (!controller)
return;
controller->StartProfilingClient(client.take(), pid, process_type);
}
void StartProfilingBrowserProcessOnIOThread(
base::WeakPtr<Controller> controller) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
if (!controller)
return;
ProfilingClientBinder client(controller->GetConnector());
StartProfilingClientOnIOThread(controller, std::move(client),
base::GetCurrentProcId(),
mojom::ProcessType::BROWSER);
}
void StartProfilingPidOnIOThread(base::WeakPtr<Controller> controller,
base::ProcessId pid) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
if (!controller)
return;
// Check if the request is for the current process.
if (pid == base::GetCurrentProcId()) {
ProfilingClientBinder client(controller->GetConnector());
StartProfilingClientOnIOThread(controller, std::move(client), pid,
mojom::ProcessType::BROWSER);
return;
}
// Check if the request is for a non-renderer child process.
for (content::BrowserChildProcessHostIterator browser_child_iter;
!browser_child_iter.Done(); ++browser_child_iter) {
const content::ChildProcessData& data = browser_child_iter.GetData();
if (base::GetProcId(data.handle) == pid) {
StartProfilingNonRendererChildOnIOThread(controller, data, pid);
return;
}
}
DLOG(WARNING)
<< "Attempt to start profiling failed as no process was found with pid: "
<< pid;
}
void StartProfilingNonRenderersIfNecessaryOnIOThread(
Mode mode,
base::WeakPtr<Controller> controller) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
if (!controller)
return;
for (content::BrowserChildProcessHostIterator browser_child_iter;
!browser_child_iter.Done(); ++browser_child_iter) {
const content::ChildProcessData& data = browser_child_iter.GetData();
if (ShouldProfileNonRendererProcessType(mode, data.process_type) &&
data.handle != base::kNullProcessHandle) {
StartProfilingNonRendererChildOnIOThread(controller, data,
base::GetProcId(data.handle));
}
}
}
} // namespace
ClientConnectionManager::ClientConnectionManager(
base::WeakPtr<Controller> controller,
Mode mode)
: controller_(controller), mode_(mode) {}
ClientConnectionManager::~ClientConnectionManager() {
Remove(this);
}
void ClientConnectionManager::Start() {
Add(this);
registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED,
content::NotificationService::AllBrowserContextsAndSources());
registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
content::NotificationService::AllBrowserContextsAndSources());
registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_TERMINATED,
content::NotificationService::AllBrowserContextsAndSources());
StartProfilingExistingProcessesIfNecessary();
}
Mode ClientConnectionManager::GetMode() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
return mode_;
}
void ClientConnectionManager::StartProfilingProcess(base::ProcessId pid) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
mode_ = Mode::kManual;
// The RenderProcessHost iterator must be used on the UI thread.
for (auto iter = content::RenderProcessHost::AllHostsIterator();
!iter.IsAtEnd(); iter.Advance()) {
if (pid == iter.GetCurrentValue()->GetProcess().Pid()) {
StartProfilingRenderer(iter.GetCurrentValue());
return;
}
}
// The BrowserChildProcessHostIterator iterator must be used on the IO thread.
content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::IO)
->PostTask(FROM_HERE, base::BindOnce(&StartProfilingPidOnIOThread,
controller_, pid));
}
bool ClientConnectionManager::AllowedToProfileRenderer(
content::RenderProcessHost* host) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
return true;
}
void ClientConnectionManager::SetModeForTesting(Mode mode) {
mode_ = mode;
}
void ClientConnectionManager::StartProfilingExistingProcessesIfNecessary() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
// Start profiling the current process.
if (ShouldProfileNonRendererProcessType(
mode_, content::ProcessType::PROCESS_TYPE_BROWSER)) {
content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::IO)
->PostTask(FROM_HERE,
base::BindOnce(&StartProfilingBrowserProcessOnIOThread,
controller_));
}
// Start profiling connected renderers.
for (auto iter = content::RenderProcessHost::AllHostsIterator();
!iter.IsAtEnd(); iter.Advance()) {
if (ShouldProfileNewRenderer(iter.GetCurrentValue()) &&
iter.GetCurrentValue()->GetProcess().Handle() !=
base::kNullProcessHandle) {
StartProfilingRenderer(iter.GetCurrentValue());
}
}
content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::IO)
->PostTask(
FROM_HERE,
base::BindOnce(&StartProfilingNonRenderersIfNecessaryOnIOThread,
GetMode(), controller_));
}
void ClientConnectionManager::BrowserChildProcessLaunchedAndConnected(
const content::ChildProcessData& data) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
// Ensure this is only called for all non-renderer browser child processes
// so as not to collide with logic in ClientConnectionManager::Observe().
DCHECK_NE(data.process_type, content::ProcessType::PROCESS_TYPE_RENDERER);
if (!ShouldProfileNonRendererProcessType(mode_, data.process_type))
return;
StartProfilingNonRendererChild(data);
}
void ClientConnectionManager::StartProfilingNonRendererChild(
const content::ChildProcessData& data) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::IO)
->PostTask(
FROM_HERE,
base::BindOnce(&StartProfilingNonRendererChildOnIOThread, controller_,
data, base::GetProcId(data.handle)));
}
void ClientConnectionManager::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
content::RenderProcessHost* host =
content::Source<content::RenderProcessHost>(source).ptr();
// NOTIFICATION_RENDERER_PROCESS_CLOSED corresponds to death of an underlying
// RenderProcess. NOTIFICATION_RENDERER_PROCESS_TERMINATED corresponds to when
// the RenderProcessHost's lifetime is ending. Ideally, we'd only listen to
// the former, but if the RenderProcessHost is destroyed before the
// RenderProcess, then the former is never sent.
if ((type == content::NOTIFICATION_RENDERER_PROCESS_TERMINATED ||
type == content::NOTIFICATION_RENDERER_PROCESS_CLOSED)) {
profiled_renderers_.erase(host);
}
if (type == content::NOTIFICATION_RENDERER_PROCESS_CREATED &&
ShouldProfileNewRenderer(host)) {
StartProfilingRenderer(host);
}
}
bool ClientConnectionManager::ShouldProfileNewRenderer(
content::RenderProcessHost* renderer) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
// Allow subclasses to not profile renderers.
if (!AllowedToProfileRenderer(renderer))
return false;
Mode mode = GetMode();
if (mode == Mode::kAll || mode == Mode::kAllRenderers) {
return true;
} else if (mode == Mode::kRendererSampling && profiled_renderers_.empty()) {
// Sample renderers with a 1/3 probability.
return (base::RandUint64() % 100000) < 33333;
}
return false;
}
void ClientConnectionManager::StartProfilingRenderer(
content::RenderProcessHost* host) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
profiled_renderers_.insert(host);
// Tell the child process to start profiling.
ProfilingClientBinder client(host);
content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::IO)
->PostTask(FROM_HERE,
base::BindOnce(&StartProfilingClientOnIOThread, controller_,
std::move(client), host->GetProcess().Pid(),
mojom::ProcessType::RENDERER));
}
} // namespace heap_profiling