blob: 9a281e761e15d02600665d63d872b6f0a3fd42ad [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,
Francois Doray884da022018-10-30 23:45:58114 const content::ChildProcessData& data) {
erikchena9db3a72018-04-12 16:24:00115 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
116
117 if (!controller)
118 return;
119
120 content::BrowserChildProcessHost* host =
121 content::BrowserChildProcessHost::FromID(data.id);
122 if (!host)
123 return;
124
125 mojom::ProcessType process_type =
126 (data.process_type == content::ProcessType::PROCESS_TYPE_GPU)
127 ? mojom::ProcessType::GPU
128 : mojom::ProcessType::OTHER;
129
130 // Tell the child process to start profiling.
131 ProfilingClientBinder client(host);
Francois Doray884da022018-10-30 23:45:58132 controller->StartProfilingClient(client.take(), data.GetProcess().Pid(),
133 process_type);
erikchena9db3a72018-04-12 16:24:00134}
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();
Francois Doray884da022018-10-30 23:45:58180 if (data.GetProcess().Pid() == pid) {
181 StartProfilingNonRendererChildOnIOThread(controller, data);
erikchena9db3a72018-04-12 16:24:00182 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) &&
Francois Doray884da022018-10-30 23:45:58203 data.GetProcess().IsValid()) {
204 StartProfilingNonRendererChildOnIOThread(controller, data);
erikchena9db3a72018-04-12 16:24:00205 }
206 }
207}
208
209} // namespace
210
211ClientConnectionManager::ClientConnectionManager(
212 base::WeakPtr<Controller> controller,
213 Mode mode)
214 : controller_(controller), mode_(mode) {}
215
216ClientConnectionManager::~ClientConnectionManager() {
217 Remove(this);
218}
219
220void ClientConnectionManager::Start() {
221 Add(this);
222 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED,
223 content::NotificationService::AllBrowserContextsAndSources());
224 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
225 content::NotificationService::AllBrowserContextsAndSources());
226 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_TERMINATED,
227 content::NotificationService::AllBrowserContextsAndSources());
228
229 StartProfilingExistingProcessesIfNecessary();
230}
231
232Mode ClientConnectionManager::GetMode() {
233 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
234 return mode_;
235}
236
237void ClientConnectionManager::StartProfilingProcess(base::ProcessId pid) {
238 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
239
240 mode_ = Mode::kManual;
241
242 // The RenderProcessHost iterator must be used on the UI thread.
243 for (auto iter = content::RenderProcessHost::AllHostsIterator();
244 !iter.IsAtEnd(); iter.Advance()) {
Sigurdur Asgeirssoneb95666d2018-04-16 16:16:03245 if (pid == iter.GetCurrentValue()->GetProcess().Pid()) {
erikchena9db3a72018-04-12 16:24:00246 StartProfilingRenderer(iter.GetCurrentValue());
247 return;
248 }
249 }
250
251 // The BrowserChildProcessHostIterator iterator must be used on the IO thread.
Eric Seckler8652dcd52018-09-20 10:42:28252 base::CreateSingleThreadTaskRunnerWithTraits({content::BrowserThread::IO})
erikchena9db3a72018-04-12 16:24:00253 ->PostTask(FROM_HERE, base::BindOnce(&StartProfilingPidOnIOThread,
254 controller_, pid));
255}
256
257bool ClientConnectionManager::AllowedToProfileRenderer(
258 content::RenderProcessHost* host) {
259 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
260 return true;
261}
262
263void ClientConnectionManager::SetModeForTesting(Mode mode) {
264 mode_ = mode;
265}
266
267void ClientConnectionManager::StartProfilingExistingProcessesIfNecessary() {
268 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
269
270 // Start profiling the current process.
271 if (ShouldProfileNonRendererProcessType(
272 mode_, content::ProcessType::PROCESS_TYPE_BROWSER)) {
Eric Seckler8652dcd52018-09-20 10:42:28273 base::CreateSingleThreadTaskRunnerWithTraits({content::BrowserThread::IO})
erikchena9db3a72018-04-12 16:24:00274 ->PostTask(FROM_HERE,
275 base::BindOnce(&StartProfilingBrowserProcessOnIOThread,
276 controller_));
277 }
278
279 // Start profiling connected renderers.
280 for (auto iter = content::RenderProcessHost::AllHostsIterator();
281 !iter.IsAtEnd(); iter.Advance()) {
282 if (ShouldProfileNewRenderer(iter.GetCurrentValue()) &&
Sigurdur Asgeirssoneb95666d2018-04-16 16:16:03283 iter.GetCurrentValue()->GetProcess().Handle() !=
284 base::kNullProcessHandle) {
erikchena9db3a72018-04-12 16:24:00285 StartProfilingRenderer(iter.GetCurrentValue());
286 }
287 }
288
Eric Seckler8652dcd52018-09-20 10:42:28289 base::CreateSingleThreadTaskRunnerWithTraits({content::BrowserThread::IO})
erikchena9db3a72018-04-12 16:24:00290 ->PostTask(
291 FROM_HERE,
292 base::BindOnce(&StartProfilingNonRenderersIfNecessaryOnIOThread,
293 GetMode(), controller_));
294}
295
296void ClientConnectionManager::BrowserChildProcessLaunchedAndConnected(
297 const content::ChildProcessData& data) {
298 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
299
300 // Ensure this is only called for all non-renderer browser child processes
301 // so as not to collide with logic in ClientConnectionManager::Observe().
302 DCHECK_NE(data.process_type, content::ProcessType::PROCESS_TYPE_RENDERER);
303
304 if (!ShouldProfileNonRendererProcessType(mode_, data.process_type))
305 return;
306
307 StartProfilingNonRendererChild(data);
308}
309
310void ClientConnectionManager::StartProfilingNonRendererChild(
311 const content::ChildProcessData& data) {
312 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
Eric Seckler8652dcd52018-09-20 10:42:28313 base::CreateSingleThreadTaskRunnerWithTraits({content::BrowserThread::IO})
Francois Doray884da022018-10-30 23:45:58314 ->PostTask(FROM_HERE,
315 base::BindOnce(&StartProfilingNonRendererChildOnIOThread,
316 controller_, data.Duplicate()));
erikchena9db3a72018-04-12 16:24:00317}
318
319void ClientConnectionManager::Observe(
320 int type,
321 const content::NotificationSource& source,
322 const content::NotificationDetails& details) {
323 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
324 content::RenderProcessHost* host =
325 content::Source<content::RenderProcessHost>(source).ptr();
326
327 // NOTIFICATION_RENDERER_PROCESS_CLOSED corresponds to death of an underlying
328 // RenderProcess. NOTIFICATION_RENDERER_PROCESS_TERMINATED corresponds to when
329 // the RenderProcessHost's lifetime is ending. Ideally, we'd only listen to
330 // the former, but if the RenderProcessHost is destroyed before the
331 // RenderProcess, then the former is never sent.
332 if ((type == content::NOTIFICATION_RENDERER_PROCESS_TERMINATED ||
333 type == content::NOTIFICATION_RENDERER_PROCESS_CLOSED)) {
334 profiled_renderers_.erase(host);
335 }
336
337 if (type == content::NOTIFICATION_RENDERER_PROCESS_CREATED &&
338 ShouldProfileNewRenderer(host)) {
339 StartProfilingRenderer(host);
340 }
341}
342
343bool ClientConnectionManager::ShouldProfileNewRenderer(
344 content::RenderProcessHost* renderer) {
345 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
346
347 // Allow subclasses to not profile renderers.
348 if (!AllowedToProfileRenderer(renderer))
349 return false;
350
351 Mode mode = GetMode();
352 if (mode == Mode::kAll || mode == Mode::kAllRenderers) {
353 return true;
354 } else if (mode == Mode::kRendererSampling && profiled_renderers_.empty()) {
355 // Sample renderers with a 1/3 probability.
356 return (base::RandUint64() % 100000) < 33333;
357 }
358
359 return false;
360}
361
362void ClientConnectionManager::StartProfilingRenderer(
363 content::RenderProcessHost* host) {
364 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
365
366 profiled_renderers_.insert(host);
367
368 // Tell the child process to start profiling.
369 ProfilingClientBinder client(host);
370
Eric Seckler8652dcd52018-09-20 10:42:28371 base::CreateSingleThreadTaskRunnerWithTraits({content::BrowserThread::IO})
Sigurdur Asgeirssoneb95666d2018-04-16 16:16:03372 ->PostTask(FROM_HERE,
373 base::BindOnce(&StartProfilingClientOnIOThread, controller_,
374 std::move(client), host->GetProcess().Pid(),
375 mojom::ProcessType::RENDERER));
erikchena9db3a72018-04-12 16:24:00376}
377
378} // namespace heap_profiling