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