blob: cecb5c27fcd5d839ef2ba0c3ec605eb7494ff96c [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:
Zinovy Nisead3f7452018-05-16 18:29:1056 ProfilingClientBinder() : request_(mojo::MakeRequest(&memlog_client_)) {}
erikchena9db3a72018-04-12 16:24:0057
58 mojom::ProfilingClientPtr memlog_client_;
59 mojom::ProfilingClientRequest request_;
60};
61
62bool ShouldProfileNonRendererProcessType(Mode mode, int process_type) {
63 switch (mode) {
64 case Mode::kAll:
65 return true;
66
67 case Mode::kAllRenderers:
68 // Renderer logic is handled in ClientConnectionManager::Observe.
69 return false;
70
71 case Mode::kManual:
72 return false;
73
74 case Mode::kMinimal:
75 return (process_type == content::ProcessType::PROCESS_TYPE_GPU ||
76 process_type == content::ProcessType::PROCESS_TYPE_BROWSER);
77
78 case Mode::kGpu:
79 return process_type == content::ProcessType::PROCESS_TYPE_GPU;
80
81 case Mode::kBrowser:
82 return process_type == content::ProcessType::PROCESS_TYPE_BROWSER;
83
84 case Mode::kRendererSampling:
85 // Renderer logic is handled in ClientConnectionManager::Observe.
86 return false;
87
88 case Mode::kNone:
89 return false;
90
91 case Mode::kCount:
92 // Fall through to hit NOTREACHED() below.
93 {}
94 }
95
96 NOTREACHED();
97 return false;
98}
99
100void StartProfilingNonRendererChildOnIOThread(
101 base::WeakPtr<Controller> controller,
102 const content::ChildProcessData& data,
103 base::ProcessId pid) {
104 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
105
106 if (!controller)
107 return;
108
109 content::BrowserChildProcessHost* host =
110 content::BrowserChildProcessHost::FromID(data.id);
111 if (!host)
112 return;
113
114 mojom::ProcessType process_type =
115 (data.process_type == content::ProcessType::PROCESS_TYPE_GPU)
116 ? mojom::ProcessType::GPU
117 : mojom::ProcessType::OTHER;
118
119 // Tell the child process to start profiling.
120 ProfilingClientBinder client(host);
121 controller->StartProfilingClient(client.take(), pid, process_type);
122}
123
124void StartProfilingClientOnIOThread(base::WeakPtr<Controller> controller,
125 ProfilingClientBinder client,
126 base::ProcessId pid,
127 mojom::ProcessType process_type) {
128 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
129
130 if (!controller)
131 return;
132
133 controller->StartProfilingClient(client.take(), pid, process_type);
134}
135
136void StartProfilingBrowserProcessOnIOThread(
137 base::WeakPtr<Controller> controller) {
138 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
139
140 if (!controller)
141 return;
142
143 ProfilingClientBinder client(controller->GetConnector());
144 StartProfilingClientOnIOThread(controller, std::move(client),
145 base::GetCurrentProcId(),
146 mojom::ProcessType::BROWSER);
147}
148
149void StartProfilingPidOnIOThread(base::WeakPtr<Controller> controller,
150 base::ProcessId pid) {
151 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
152
153 if (!controller)
154 return;
155
156 // Check if the request is for the current process.
157 if (pid == base::GetCurrentProcId()) {
158 ProfilingClientBinder client(controller->GetConnector());
159 StartProfilingClientOnIOThread(controller, std::move(client), pid,
160 mojom::ProcessType::BROWSER);
161 return;
162 }
163
164 // Check if the request is for a non-renderer child process.
165 for (content::BrowserChildProcessHostIterator browser_child_iter;
166 !browser_child_iter.Done(); ++browser_child_iter) {
167 const content::ChildProcessData& data = browser_child_iter.GetData();
168 if (base::GetProcId(data.handle) == pid) {
169 StartProfilingNonRendererChildOnIOThread(controller, data, pid);
170 return;
171 }
172 }
173
174 DLOG(WARNING)
175 << "Attempt to start profiling failed as no process was found with pid: "
176 << pid;
177}
178
179void StartProfilingNonRenderersIfNecessaryOnIOThread(
180 Mode mode,
181 base::WeakPtr<Controller> controller) {
182 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
183
184 if (!controller)
185 return;
186
187 for (content::BrowserChildProcessHostIterator browser_child_iter;
188 !browser_child_iter.Done(); ++browser_child_iter) {
189 const content::ChildProcessData& data = browser_child_iter.GetData();
190 if (ShouldProfileNonRendererProcessType(mode, data.process_type) &&
191 data.handle != base::kNullProcessHandle) {
192 StartProfilingNonRendererChildOnIOThread(controller, data,
193 base::GetProcId(data.handle));
194 }
195 }
196}
197
198} // namespace
199
200ClientConnectionManager::ClientConnectionManager(
201 base::WeakPtr<Controller> controller,
202 Mode mode)
203 : controller_(controller), mode_(mode) {}
204
205ClientConnectionManager::~ClientConnectionManager() {
206 Remove(this);
207}
208
209void ClientConnectionManager::Start() {
210 Add(this);
211 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED,
212 content::NotificationService::AllBrowserContextsAndSources());
213 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
214 content::NotificationService::AllBrowserContextsAndSources());
215 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_TERMINATED,
216 content::NotificationService::AllBrowserContextsAndSources());
217
218 StartProfilingExistingProcessesIfNecessary();
219}
220
221Mode ClientConnectionManager::GetMode() {
222 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
223 return mode_;
224}
225
226void ClientConnectionManager::StartProfilingProcess(base::ProcessId pid) {
227 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
228
229 mode_ = Mode::kManual;
230
231 // The RenderProcessHost iterator must be used on the UI thread.
232 for (auto iter = content::RenderProcessHost::AllHostsIterator();
233 !iter.IsAtEnd(); iter.Advance()) {
Sigurdur Asgeirssoneb95666d2018-04-16 16:16:03234 if (pid == iter.GetCurrentValue()->GetProcess().Pid()) {
erikchena9db3a72018-04-12 16:24:00235 StartProfilingRenderer(iter.GetCurrentValue());
236 return;
237 }
238 }
239
240 // The BrowserChildProcessHostIterator iterator must be used on the IO thread.
241 content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::IO)
242 ->PostTask(FROM_HERE, base::BindOnce(&StartProfilingPidOnIOThread,
243 controller_, pid));
244}
245
246bool ClientConnectionManager::AllowedToProfileRenderer(
247 content::RenderProcessHost* host) {
248 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
249 return true;
250}
251
252void ClientConnectionManager::SetModeForTesting(Mode mode) {
253 mode_ = mode;
254}
255
256void ClientConnectionManager::StartProfilingExistingProcessesIfNecessary() {
257 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
258
259 // Start profiling the current process.
260 if (ShouldProfileNonRendererProcessType(
261 mode_, content::ProcessType::PROCESS_TYPE_BROWSER)) {
262 content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::IO)
263 ->PostTask(FROM_HERE,
264 base::BindOnce(&StartProfilingBrowserProcessOnIOThread,
265 controller_));
266 }
267
268 // Start profiling connected renderers.
269 for (auto iter = content::RenderProcessHost::AllHostsIterator();
270 !iter.IsAtEnd(); iter.Advance()) {
271 if (ShouldProfileNewRenderer(iter.GetCurrentValue()) &&
Sigurdur Asgeirssoneb95666d2018-04-16 16:16:03272 iter.GetCurrentValue()->GetProcess().Handle() !=
273 base::kNullProcessHandle) {
erikchena9db3a72018-04-12 16:24:00274 StartProfilingRenderer(iter.GetCurrentValue());
275 }
276 }
277
278 content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::IO)
279 ->PostTask(
280 FROM_HERE,
281 base::BindOnce(&StartProfilingNonRenderersIfNecessaryOnIOThread,
282 GetMode(), controller_));
283}
284
285void ClientConnectionManager::BrowserChildProcessLaunchedAndConnected(
286 const content::ChildProcessData& data) {
287 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
288
289 // Ensure this is only called for all non-renderer browser child processes
290 // so as not to collide with logic in ClientConnectionManager::Observe().
291 DCHECK_NE(data.process_type, content::ProcessType::PROCESS_TYPE_RENDERER);
292
293 if (!ShouldProfileNonRendererProcessType(mode_, data.process_type))
294 return;
295
296 StartProfilingNonRendererChild(data);
297}
298
299void ClientConnectionManager::StartProfilingNonRendererChild(
300 const content::ChildProcessData& data) {
301 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
302 content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::IO)
303 ->PostTask(
304 FROM_HERE,
305 base::BindOnce(&StartProfilingNonRendererChildOnIOThread, controller_,
306 data, base::GetProcId(data.handle)));
307}
308
309void ClientConnectionManager::Observe(
310 int type,
311 const content::NotificationSource& source,
312 const content::NotificationDetails& details) {
313 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
314 content::RenderProcessHost* host =
315 content::Source<content::RenderProcessHost>(source).ptr();
316
317 // NOTIFICATION_RENDERER_PROCESS_CLOSED corresponds to death of an underlying
318 // RenderProcess. NOTIFICATION_RENDERER_PROCESS_TERMINATED corresponds to when
319 // the RenderProcessHost's lifetime is ending. Ideally, we'd only listen to
320 // the former, but if the RenderProcessHost is destroyed before the
321 // RenderProcess, then the former is never sent.
322 if ((type == content::NOTIFICATION_RENDERER_PROCESS_TERMINATED ||
323 type == content::NOTIFICATION_RENDERER_PROCESS_CLOSED)) {
324 profiled_renderers_.erase(host);
325 }
326
327 if (type == content::NOTIFICATION_RENDERER_PROCESS_CREATED &&
328 ShouldProfileNewRenderer(host)) {
329 StartProfilingRenderer(host);
330 }
331}
332
333bool ClientConnectionManager::ShouldProfileNewRenderer(
334 content::RenderProcessHost* renderer) {
335 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
336
337 // Allow subclasses to not profile renderers.
338 if (!AllowedToProfileRenderer(renderer))
339 return false;
340
341 Mode mode = GetMode();
342 if (mode == Mode::kAll || mode == Mode::kAllRenderers) {
343 return true;
344 } else if (mode == Mode::kRendererSampling && profiled_renderers_.empty()) {
345 // Sample renderers with a 1/3 probability.
346 return (base::RandUint64() % 100000) < 33333;
347 }
348
349 return false;
350}
351
352void ClientConnectionManager::StartProfilingRenderer(
353 content::RenderProcessHost* host) {
354 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
355
356 profiled_renderers_.insert(host);
357
358 // Tell the child process to start profiling.
359 ProfilingClientBinder client(host);
360
361 content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::IO)
Sigurdur Asgeirssoneb95666d2018-04-16 16:16:03362 ->PostTask(FROM_HERE,
363 base::BindOnce(&StartProfilingClientOnIOThread, controller_,
364 std::move(client), host->GetProcess().Pid(),
365 mojom::ProcessType::RENDERER));
erikchena9db3a72018-04-12 16:24:00366}
367
368} // namespace heap_profiling