blob: 3f10e66e850b8651ca4620515b1fe259d58bf944 [file] [log] [blame]
erikchena9db3a72018-04-12 16:24:001// Copyright 2018 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "components/heap_profiling/client_connection_manager.h"
6
7#include "base/rand_util.h"
8#include "components/services/heap_profiling/public/cpp/controller.h"
9#include "components/services/heap_profiling/public/cpp/settings.h"
10#include "components/services/heap_profiling/public/mojom/heap_profiling_client.mojom.h"
11#include "components/services/heap_profiling/public/mojom/heap_profiling_service.mojom.h"
12#include "content/public/browser/browser_child_process_host.h"
13#include "content/public/browser/browser_child_process_host_iterator.h"
14#include "content/public/browser/browser_thread.h"
15#include "content/public/browser/notification_service.h"
16#include "content/public/browser/notification_types.h"
17#include "content/public/browser/render_process_host.h"
18#include "content/public/common/bind_interface_helpers.h"
19#include "content/public/common/child_process_host.h"
20#include "content/public/common/process_type.h"
21#include "content/public/common/service_names.mojom.h"
22#include "services/service_manager/public/cpp/connector.h"
23
24namespace heap_profiling {
25
26namespace {
27
28// This helper class cleans up initialization boilerplate for the callers who
29// need to create ProfilingClients bound to various different things.
30class ProfilingClientBinder {
31 public:
32 // Binds to a non-renderer-child-process' ProfilingClient.
33 explicit ProfilingClientBinder(content::BrowserChildProcessHost* host)
34 : ProfilingClientBinder() {
35 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
36 content::BindInterface(host->GetHost(), std::move(request_));
37 }
38
39 // Binds to a renderer's ProfilingClient.
40 explicit ProfilingClientBinder(content::RenderProcessHost* host)
41 : ProfilingClientBinder() {
42 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
43 content::BindInterface(host, std::move(request_));
44 }
45
46 // Binds to the local connector to get the browser process' ProfilingClient.
47 explicit ProfilingClientBinder(service_manager::Connector* connector)
48 : ProfilingClientBinder() {
49 connector->BindInterface(content::mojom::kBrowserServiceName,
50 std::move(request_));
51 }
52
53 mojom::ProfilingClientPtr take() { return std::move(memlog_client_); }
54
55 private:
56 ProfilingClientBinder()
57 : memlog_client_(), request_(mojo::MakeRequest(&memlog_client_)) {}
58
59 mojom::ProfilingClientPtr memlog_client_;
60 mojom::ProfilingClientRequest request_;
61};
62
63bool ShouldProfileNonRendererProcessType(Mode mode, int process_type) {
64 switch (mode) {
65 case Mode::kAll:
66 return true;
67
68 case Mode::kAllRenderers:
69 // Renderer logic is handled in ClientConnectionManager::Observe.
70 return false;
71
72 case Mode::kManual:
73 return false;
74
75 case Mode::kMinimal:
76 return (process_type == content::ProcessType::PROCESS_TYPE_GPU ||
77 process_type == content::ProcessType::PROCESS_TYPE_BROWSER);
78
79 case Mode::kGpu:
80 return process_type == content::ProcessType::PROCESS_TYPE_GPU;
81
82 case Mode::kBrowser:
83 return process_type == content::ProcessType::PROCESS_TYPE_BROWSER;
84
85 case Mode::kRendererSampling:
86 // Renderer logic is handled in ClientConnectionManager::Observe.
87 return false;
88
89 case Mode::kNone:
90 return false;
91
92 case Mode::kCount:
93 // Fall through to hit NOTREACHED() below.
94 {}
95 }
96
97 NOTREACHED();
98 return false;
99}
100
101void StartProfilingNonRendererChildOnIOThread(
102 base::WeakPtr<Controller> controller,
103 const content::ChildProcessData& data,
104 base::ProcessId pid) {
105 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
106
107 if (!controller)
108 return;
109
110 content::BrowserChildProcessHost* host =
111 content::BrowserChildProcessHost::FromID(data.id);
112 if (!host)
113 return;
114
115 mojom::ProcessType process_type =
116 (data.process_type == content::ProcessType::PROCESS_TYPE_GPU)
117 ? mojom::ProcessType::GPU
118 : mojom::ProcessType::OTHER;
119
120 // Tell the child process to start profiling.
121 ProfilingClientBinder client(host);
122 controller->StartProfilingClient(client.take(), pid, process_type);
123}
124
125void StartProfilingClientOnIOThread(base::WeakPtr<Controller> controller,
126 ProfilingClientBinder client,
127 base::ProcessId pid,
128 mojom::ProcessType process_type) {
129 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
130
131 if (!controller)
132 return;
133
134 controller->StartProfilingClient(client.take(), pid, process_type);
135}
136
137void StartProfilingBrowserProcessOnIOThread(
138 base::WeakPtr<Controller> controller) {
139 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
140
141 if (!controller)
142 return;
143
144 ProfilingClientBinder client(controller->GetConnector());
145 StartProfilingClientOnIOThread(controller, std::move(client),
146 base::GetCurrentProcId(),
147 mojom::ProcessType::BROWSER);
148}
149
150void StartProfilingPidOnIOThread(base::WeakPtr<Controller> controller,
151 base::ProcessId pid) {
152 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
153
154 if (!controller)
155 return;
156
157 // Check if the request is for the current process.
158 if (pid == base::GetCurrentProcId()) {
159 ProfilingClientBinder client(controller->GetConnector());
160 StartProfilingClientOnIOThread(controller, std::move(client), pid,
161 mojom::ProcessType::BROWSER);
162 return;
163 }
164
165 // Check if the request is for a non-renderer child process.
166 for (content::BrowserChildProcessHostIterator browser_child_iter;
167 !browser_child_iter.Done(); ++browser_child_iter) {
168 const content::ChildProcessData& data = browser_child_iter.GetData();
169 if (base::GetProcId(data.handle) == pid) {
170 StartProfilingNonRendererChildOnIOThread(controller, data, pid);
171 return;
172 }
173 }
174
175 DLOG(WARNING)
176 << "Attempt to start profiling failed as no process was found with pid: "
177 << pid;
178}
179
180void StartProfilingNonRenderersIfNecessaryOnIOThread(
181 Mode mode,
182 base::WeakPtr<Controller> controller) {
183 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
184
185 if (!controller)
186 return;
187
188 for (content::BrowserChildProcessHostIterator browser_child_iter;
189 !browser_child_iter.Done(); ++browser_child_iter) {
190 const content::ChildProcessData& data = browser_child_iter.GetData();
191 if (ShouldProfileNonRendererProcessType(mode, data.process_type) &&
192 data.handle != base::kNullProcessHandle) {
193 StartProfilingNonRendererChildOnIOThread(controller, data,
194 base::GetProcId(data.handle));
195 }
196 }
197}
198
199} // namespace
200
201ClientConnectionManager::ClientConnectionManager(
202 base::WeakPtr<Controller> controller,
203 Mode mode)
204 : controller_(controller), mode_(mode) {}
205
206ClientConnectionManager::~ClientConnectionManager() {
207 Remove(this);
208}
209
210void ClientConnectionManager::Start() {
211 Add(this);
212 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED,
213 content::NotificationService::AllBrowserContextsAndSources());
214 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
215 content::NotificationService::AllBrowserContextsAndSources());
216 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_TERMINATED,
217 content::NotificationService::AllBrowserContextsAndSources());
218
219 StartProfilingExistingProcessesIfNecessary();
220}
221
222Mode ClientConnectionManager::GetMode() {
223 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
224 return mode_;
225}
226
227void ClientConnectionManager::StartProfilingProcess(base::ProcessId pid) {
228 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
229
230 mode_ = Mode::kManual;
231
232 // The RenderProcessHost iterator must be used on the UI thread.
233 for (auto iter = content::RenderProcessHost::AllHostsIterator();
234 !iter.IsAtEnd(); iter.Advance()) {
Sigurdur Asgeirssoneb95666d2018-04-16 16:16:03235 if (pid == iter.GetCurrentValue()->GetProcess().Pid()) {
erikchena9db3a72018-04-12 16:24:00236 StartProfilingRenderer(iter.GetCurrentValue());
237 return;
238 }
239 }
240
241 // The BrowserChildProcessHostIterator iterator must be used on the IO thread.
242 content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::IO)
243 ->PostTask(FROM_HERE, base::BindOnce(&StartProfilingPidOnIOThread,
244 controller_, pid));
245}
246
247bool ClientConnectionManager::AllowedToProfileRenderer(
248 content::RenderProcessHost* host) {
249 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
250 return true;
251}
252
253void ClientConnectionManager::SetModeForTesting(Mode mode) {
254 mode_ = mode;
255}
256
257void ClientConnectionManager::StartProfilingExistingProcessesIfNecessary() {
258 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
259
260 // Start profiling the current process.
261 if (ShouldProfileNonRendererProcessType(
262 mode_, content::ProcessType::PROCESS_TYPE_BROWSER)) {
263 content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::IO)
264 ->PostTask(FROM_HERE,
265 base::BindOnce(&StartProfilingBrowserProcessOnIOThread,
266 controller_));
267 }
268
269 // Start profiling connected renderers.
270 for (auto iter = content::RenderProcessHost::AllHostsIterator();
271 !iter.IsAtEnd(); iter.Advance()) {
272 if (ShouldProfileNewRenderer(iter.GetCurrentValue()) &&
Sigurdur Asgeirssoneb95666d2018-04-16 16:16:03273 iter.GetCurrentValue()->GetProcess().Handle() !=
274 base::kNullProcessHandle) {
erikchena9db3a72018-04-12 16:24:00275 StartProfilingRenderer(iter.GetCurrentValue());
276 }
277 }
278
279 content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::IO)
280 ->PostTask(
281 FROM_HERE,
282 base::BindOnce(&StartProfilingNonRenderersIfNecessaryOnIOThread,
283 GetMode(), controller_));
284}
285
286void ClientConnectionManager::BrowserChildProcessLaunchedAndConnected(
287 const content::ChildProcessData& data) {
288 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
289
290 // Ensure this is only called for all non-renderer browser child processes
291 // so as not to collide with logic in ClientConnectionManager::Observe().
292 DCHECK_NE(data.process_type, content::ProcessType::PROCESS_TYPE_RENDERER);
293
294 if (!ShouldProfileNonRendererProcessType(mode_, data.process_type))
295 return;
296
297 StartProfilingNonRendererChild(data);
298}
299
300void ClientConnectionManager::StartProfilingNonRendererChild(
301 const content::ChildProcessData& data) {
302 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
303 content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::IO)
304 ->PostTask(
305 FROM_HERE,
306 base::BindOnce(&StartProfilingNonRendererChildOnIOThread, controller_,
307 data, base::GetProcId(data.handle)));
308}
309
310void ClientConnectionManager::Observe(
311 int type,
312 const content::NotificationSource& source,
313 const content::NotificationDetails& details) {
314 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
315 content::RenderProcessHost* host =
316 content::Source<content::RenderProcessHost>(source).ptr();
317
318 // NOTIFICATION_RENDERER_PROCESS_CLOSED corresponds to death of an underlying
319 // RenderProcess. NOTIFICATION_RENDERER_PROCESS_TERMINATED corresponds to when
320 // the RenderProcessHost's lifetime is ending. Ideally, we'd only listen to
321 // the former, but if the RenderProcessHost is destroyed before the
322 // RenderProcess, then the former is never sent.
323 if ((type == content::NOTIFICATION_RENDERER_PROCESS_TERMINATED ||
324 type == content::NOTIFICATION_RENDERER_PROCESS_CLOSED)) {
325 profiled_renderers_.erase(host);
326 }
327
328 if (type == content::NOTIFICATION_RENDERER_PROCESS_CREATED &&
329 ShouldProfileNewRenderer(host)) {
330 StartProfilingRenderer(host);
331 }
332}
333
334bool ClientConnectionManager::ShouldProfileNewRenderer(
335 content::RenderProcessHost* renderer) {
336 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
337
338 // Allow subclasses to not profile renderers.
339 if (!AllowedToProfileRenderer(renderer))
340 return false;
341
342 Mode mode = GetMode();
343 if (mode == Mode::kAll || mode == Mode::kAllRenderers) {
344 return true;
345 } else if (mode == Mode::kRendererSampling && profiled_renderers_.empty()) {
346 // Sample renderers with a 1/3 probability.
347 return (base::RandUint64() % 100000) < 33333;
348 }
349
350 return false;
351}
352
353void ClientConnectionManager::StartProfilingRenderer(
354 content::RenderProcessHost* host) {
355 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
356
357 profiled_renderers_.insert(host);
358
359 // Tell the child process to start profiling.
360 ProfilingClientBinder client(host);
361
362 content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::IO)
Sigurdur Asgeirssoneb95666d2018-04-16 16:16:03363 ->PostTask(FROM_HERE,
364 base::BindOnce(&StartProfilingClientOnIOThread, controller_,
365 std::move(client), host->GetProcess().Pid(),
366 mojom::ProcessType::RENDERER));
erikchena9db3a72018-04-12 16:24:00367}
368
369} // namespace heap_profiling