blob: a50f5aa3f28247ea09118f63c1da81785c5b8b62 [file] [log] [blame]
Avi Drissman8ba1bad2022-09-13 19:22:361// Copyright 2017 The Chromium Authors
brettwbd8214bf2017-06-20 03:47:032// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
erikchenfa983faa2018-04-05 18:56:425#include "components/services/heap_profiling/connection_manager.h"
brettwbd8214bf2017-06-20 03:47:036
Joe Masoncf9e0bc2023-06-06 18:30:477#include <utility>
8
Avi Drissman12be0312023-01-11 09:16:099#include "base/functional/bind.h"
Joe Masoncf9e0bc2023-06-06 18:30:4710#include "base/functional/callback.h"
Alexei Filippove48985e2019-02-01 00:27:4111#include "base/json/string_escape.h"
erikchene382a102017-10-20 00:30:1312#include "base/metrics/histogram_macros.h"
erikchenfa983faa2018-04-05 18:56:4213#include "components/services/heap_profiling/json_exporter.h"
Alexei Filippov21ec8dc2019-04-25 23:10:2914#include "components/services/heap_profiling/public/cpp/profiling_client.h"
brettwbd8214bf2017-06-20 03:47:0315
erikchen102fe212018-04-06 13:02:1016namespace heap_profiling {
brettwbd8214bf2017-06-20 03:47:0317
Erik Chene2d064472017-10-07 03:34:0718// Tracking information for DumpProcessForTracing(). This struct is
19// refcounted since there will be many background thread calls (one for each
20// AllocationTracker) and the callback is only issued when each has
21// responded.
22//
23// This class is not threadsafe, its members must only be accessed on the
24// I/O thread.
erikchenfa983faa2018-04-05 18:56:4225struct ConnectionManager::DumpProcessesForTracingTracking
Alexei Filippov77f34eb2019-04-19 21:21:1526 : public base::RefCountedThreadSafe<DumpProcessesForTracingTracking> {
Erik Chene2d064472017-10-07 03:34:0727 // Number of processes we're still waiting on responses for. When this gets
28 // to 0, the callback will be issued.
29 size_t waiting_responses = 0;
30
31 // Callback to issue when dumps are complete.
erikchen67fff822018-02-21 18:53:4332 DumpProcessesForTracingCallback callback;
Erik Chene2d064472017-10-07 03:34:0733
34 // Info about the request.
erikchen53cddfe62018-02-14 23:31:2935 VmRegions vm_regions;
Erik Chene2d064472017-10-07 03:34:0736
37 // Collects the results.
Alexei Filippovda9fb732019-04-25 22:40:3238 std::vector<memory_instrumentation::mojom::HeapProfileResultPtr> results;
Erik Chene2d064472017-10-07 03:34:0739
40 private:
41 friend class base::RefCountedThreadSafe<DumpProcessesForTracingTracking>;
42 virtual ~DumpProcessesForTracingTracking() = default;
43};
44
erikchenfa983faa2018-04-05 18:56:4245struct ConnectionManager::Connection {
Alexei Filippov1b7b8802019-04-17 22:01:4346 Connection(CompleteCallback complete_cb,
Ken Rockotced31272019-08-02 21:12:1847 mojo::PendingRemote<mojom::ProfilingClient> client,
erikchen8bc20d82018-02-14 03:21:5148 mojom::ProcessType process_type,
erikchen53cddfe62018-02-14 23:31:2949 uint32_t sampling_rate,
Takashi Sakamoto83d6a182024-04-26 06:45:0850 mojom::StackMode stack_mode,
51 mojom::ProfilingService::AddProfilingClientCallback
52 started_profiling_callback)
Alexei Filippov1b7b8802019-04-17 22:01:4353 : client(std::move(client)),
erikchene382a102017-10-20 00:30:1354 process_type(process_type),
erikchen53cddfe62018-02-14 23:31:2955 stack_mode(stack_mode),
Takashi Sakamoto83d6a182024-04-26 06:45:0856 sampling_rate(sampling_rate),
57 started_profiling_callback(std::move(started_profiling_callback)) {
Ken Rockotced31272019-08-02 21:12:1858 this->client.set_disconnect_handler(std::move(complete_cb));
Brett Wilson79b69b212017-07-12 22:29:0859 }
brettwbd8214bf2017-06-20 03:47:0360
erikchen53cddfe62018-02-14 23:31:2961 bool HeapDumpNeedsVmRegions() {
62 return stack_mode == mojom::StackMode::NATIVE_WITHOUT_THREAD_NAMES ||
Sami Kyostila57dfe042021-06-08 21:19:4963 stack_mode == mojom::StackMode::NATIVE_WITH_THREAD_NAMES;
erikchen53cddfe62018-02-14 23:31:2964 }
65
Ken Rockotced31272019-08-02 21:12:1866 mojo::Remote<mojom::ProfilingClient> client;
erikchene382a102017-10-20 00:30:1367 mojom::ProcessType process_type;
erikchen53cddfe62018-02-14 23:31:2968 mojom::StackMode stack_mode;
erikchen8bc20d82018-02-14 03:21:5169
Erik Chen7e4cccd62019-12-05 23:58:5870 bool started_profiling = false;
71
erikchen8bc20d82018-02-14 03:21:5172 // When sampling is enabled, allocations are recorded with probability (size /
73 // sampling_rate) when size < sampling_rate. When size >= sampling_rate, the
74 // aggregate probability of an allocation being recorded is 1.0, but the math
75 // and details are tricky. See
76 // https://bugs.chromium.org/p/chromium/issues/detail?id=810748#c4.
77 // A |sampling_rate| of 1 is equivalent to recording all allocations.
78 uint32_t sampling_rate = 1;
Takashi Sakamoto83d6a182024-04-26 06:45:0879
80 mojom::ProfilingService::AddProfilingClientCallback
81 started_profiling_callback;
brettwbd8214bf2017-06-20 03:47:0382};
83
Alexei Filippov631529ee2019-04-18 16:39:0584ConnectionManager::ConnectionManager() {
Peter Kastinge5a38ed2021-10-02 03:06:3585 metrics_timer_.Start(FROM_HERE, base::Hours(24),
Ken Rockot36778cfc2019-12-18 16:57:0586 base::BindRepeating(&ConnectionManager::ReportMetrics,
87 base::Unretained(this)));
erikchene382a102017-10-20 00:30:1388}
erikchenfa983faa2018-04-05 18:56:4289ConnectionManager::~ConnectionManager() = default;
brettwbd8214bf2017-06-20 03:47:0390
Ken Rockotced31272019-08-02 21:12:1891void ConnectionManager::OnNewConnection(
92 base::ProcessId pid,
93 mojo::PendingRemote<mojom::ProfilingClient> client,
94 mojom::ProcessType process_type,
Joe Masoncf9e0bc2023-06-06 18:30:4795 mojom::ProfilingParamsPtr params,
Takashi Sakamoto83d6a182024-04-26 06:45:0896 mojom::ProfilingService::AddProfilingClientCallback
97 started_profiling_closure) {
Etienne Bergeron97605fa2017-08-21 22:22:3198 base::AutoLock lock(connections_lock_);
Brett Wilson40a6bb502017-10-10 20:36:1599
Erik Chenfe6fbee2017-12-06 07:40:09100 // Attempting to start profiling on an already profiled processs should have
101 // no effect.
Takashi Sakamoto83d6a182024-04-26 06:45:08102 if (connections_.find(pid) != connections_.end()) {
103 std::move(started_profiling_closure).Run(/*success=*/false);
Erik Chenfe6fbee2017-12-06 07:40:09104 return;
Takashi Sakamoto83d6a182024-04-26 06:45:08105 }
Erik Chenfe6fbee2017-12-06 07:40:09106
107 // It's theoretically possible that we started profiling a process, the
108 // profiling was stopped [e.g. by hitting the 10-s timeout], and then we tried
109 // to start profiling again. The ProfilingClient will refuse to start again.
erikchenfa983faa2018-04-05 18:56:42110 // But the ConnectionManager will not be able to distinguish this
Erik Chenfe6fbee2017-12-06 07:40:09111 // never-started ProfilingClient from a brand new ProfilingClient that happens
112 // to share the same pid. This is a rare condition which should only happen
113 // when the user is attempting to manually start profiling for processes, so
114 // we ignore this edge case.
Brett Wilson79b69b212017-07-12 22:29:08115
Alexei Filippov1b7b8802019-04-17 22:01:43116 CompleteCallback complete_cb =
117 base::BindOnce(&ConnectionManager::OnConnectionComplete,
118 weak_factory_.GetWeakPtr(), pid);
Erik Chene2d064472017-10-07 03:34:07119
Jeremy Roman42e81a62018-03-01 19:08:59120 auto connection = std::make_unique<Connection>(
Alexei Filippov1b7b8802019-04-17 22:01:43121 std::move(complete_cb), std::move(client), process_type,
Takashi Sakamoto83d6a182024-04-26 06:45:08122 params->sampling_rate, params->stack_mode,
123 std::move(started_profiling_closure));
Erik Chen7e4cccd62019-12-05 23:58:58124 connection->client->StartProfiling(
125 std::move(params), base::BindOnce(&ConnectionManager::OnProfilingStarted,
Takashi Sakamoto83d6a182024-04-26 06:45:08126 weak_factory_.GetWeakPtr(), pid));
Alexei Filippov1b7b8802019-04-17 22:01:43127 connections_[pid] = std::move(connection);
brettwbd8214bf2017-06-20 03:47:03128}
129
erikchenfa983faa2018-04-05 18:56:42130std::vector<base::ProcessId> ConnectionManager::GetConnectionPids() {
Erik Chenfe6fbee2017-12-06 07:40:09131 base::AutoLock lock(connections_lock_);
132 std::vector<base::ProcessId> results;
133 results.reserve(connections_.size());
Erik Chen7e4cccd62019-12-05 23:58:58134 for (const auto& pair : connections_) {
135 if (pair.second->started_profiling)
136 results.push_back(pair.first);
137 }
Erik Chenfe6fbee2017-12-06 07:40:09138 return results;
139}
140
erikchen53cddfe62018-02-14 23:31:29141std::vector<base::ProcessId>
erikchenfa983faa2018-04-05 18:56:42142ConnectionManager::GetConnectionPidsThatNeedVmRegions() {
erikchen53cddfe62018-02-14 23:31:29143 base::AutoLock lock(connections_lock_);
144 std::vector<base::ProcessId> results;
145 results.reserve(connections_.size());
146 for (const auto& pair : connections_) {
147 if (pair.second->HeapDumpNeedsVmRegions())
148 results.push_back(pair.first);
149 }
150 return results;
151}
152
erikchenfa983faa2018-04-05 18:56:42153void ConnectionManager::OnConnectionComplete(base::ProcessId pid) {
Etienne Bergeron97605fa2017-08-21 22:22:31154 base::AutoLock lock(connections_lock_);
Albert J. Wong59d85acb2017-08-10 00:50:57155 auto found = connections_.find(pid);
brettwbd8214bf2017-06-20 03:47:03156 CHECK(found != connections_.end());
Takashi Sakamoto83d6a182024-04-26 06:45:08157 if (!found->second->started_profiling_callback.is_null()) {
158 std::move(found->second->started_profiling_callback).Run(/*success=*/false);
159 }
brettwbd8214bf2017-06-20 03:47:03160 connections_.erase(found);
brettwbd8214bf2017-06-20 03:47:03161}
162
Erik Chen7e4cccd62019-12-05 23:58:58163void ConnectionManager::OnProfilingStarted(base::ProcessId pid) {
164 base::AutoLock lock(connections_lock_);
165
166 // It's possible that the client disconnected in the short time before
167 // profiling started.
168 auto found = connections_.find(pid);
Takashi Sakamoto83d6a182024-04-26 06:45:08169 if (found != connections_.end()) {
Erik Chen7e4cccd62019-12-05 23:58:58170 found->second->started_profiling = true;
Takashi Sakamoto83d6a182024-04-26 06:45:08171 std::move(found->second->started_profiling_callback).Run(/*success=*/true);
172 }
Erik Chen7e4cccd62019-12-05 23:58:58173}
174
erikchenfa983faa2018-04-05 18:56:42175void ConnectionManager::ReportMetrics() {
erikchene382a102017-10-20 00:30:13176 base::AutoLock lock(connections_lock_);
177 for (auto& pair : connections_) {
Alexei Filippov29688e12019-04-29 20:26:18178 UMA_HISTOGRAM_ENUMERATION("HeapProfiling.ProfiledProcess.Type",
erikchen102fe212018-04-06 13:02:10179 pair.second->process_type,
180 static_cast<int>(mojom::ProcessType::LAST) + 1);
erikchene382a102017-10-20 00:30:13181 }
182}
183
erikchenfa983faa2018-04-05 18:56:42184void ConnectionManager::DumpProcessesForTracing(
erikchend1b8bc52017-12-21 18:12:42185 bool strip_path_from_mapped_files,
ssid035cbfb2021-07-24 20:35:57186 bool write_proto,
erikchen67fff822018-02-21 18:53:43187 DumpProcessesForTracingCallback callback,
erikchen53cddfe62018-02-14 23:31:29188 VmRegions vm_regions) {
Etienne Bergeron97605fa2017-08-21 22:22:31189 base::AutoLock lock(connections_lock_);
erikchen66af0162017-08-02 19:53:19190
Albert J. Wongbd5bd902017-11-09 19:56:15191 // Early out if there are no connections.
192 if (connections_.empty()) {
193 std::move(callback).Run(
Alexei Filippovda9fb732019-04-25 22:40:32194 std::vector<memory_instrumentation::mojom::HeapProfileResultPtr>());
Albert J. Wongbd5bd902017-11-09 19:56:15195 return;
196 }
197
Erik Chene2d064472017-10-07 03:34:07198 auto tracking = base::MakeRefCounted<DumpProcessesForTracingTracking>();
199 tracking->waiting_responses = connections_.size();
200 tracking->callback = std::move(callback);
erikchen53cddfe62018-02-14 23:31:29201 tracking->vm_regions = std::move(vm_regions);
Erik Chene2d064472017-10-07 03:34:07202 tracking->results.reserve(connections_.size());
erikchen1ca0e5f2017-10-06 22:06:14203
Erik Chene2d064472017-10-07 03:34:07204 for (auto& it : connections_) {
205 base::ProcessId pid = it.first;
206 Connection* connection = it.second.get();
ssid035cbfb2021-07-24 20:35:57207
Alexei Filippov1b7b8802019-04-17 22:01:43208 connection->client->RetrieveHeapProfile(base::BindOnce(
209 &ConnectionManager::HeapProfileRetrieved, weak_factory_.GetWeakPtr(),
210 tracking, pid, connection->process_type, strip_path_from_mapped_files,
211 connection->sampling_rate));
Erik Chene2d064472017-10-07 03:34:07212 }
213}
214
Alexei Filippovda9fb732019-04-25 22:40:32215bool ConnectionManager::ConvertProfileToExportParams(
216 mojom::HeapProfilePtr profile,
Alexei Filippove48985e2019-02-01 00:27:41217 uint32_t sampling_rate,
Alexei Filippovda9fb732019-04-25 22:40:32218 ExportParams* params) {
219 AllocationMap allocs;
Alexei Filippov1b7b8802019-04-17 22:01:43220 ContextMap context_map;
221 AddressToStringMap string_map;
Alexei Filippove48985e2019-02-01 00:27:41222
Alexei Filippove48985e2019-02-01 00:27:41223 for (const mojom::HeapProfileSamplePtr& sample : profile->samples) {
224 int context_id = 0;
225 if (sample->context_id) {
226 auto it = profile->strings.find(sample->context_id);
Alexei Filippovda9fb732019-04-25 22:40:32227 if (it == profile->strings.end())
228 return false;
Alexei Filippove48985e2019-02-01 00:27:41229 const std::string& context = it->second;
230 // Escape the strings early, to simplify exporting a heap dump.
231 std::string escaped_context;
232 base::EscapeJSONString(context, false /* put_in_quotes */,
233 &escaped_context);
234 context_id = context_map
235 .emplace(std::move(escaped_context),
236 static_cast<int>(context_map.size() + 1))
237 .first->second;
238 }
Alexei Filippov0a194542019-04-22 22:45:46239
Erik Chend18561a2019-12-09 22:15:56240 size_t alloc_size = sample->total;
241 float alloc_count = 1;
242 if (sample->size != 0)
243 alloc_count = float(sample->total) / float(sample->size);
Alexei Filippov0a194542019-04-22 22:45:46244
Alexei Filippov77f34eb2019-04-19 21:21:15245 std::vector<Address> stack(sample->stack.begin(), sample->stack.end());
Alexei Filippov0a194542019-04-22 22:45:46246 AllocationMetrics& metrics =
Alexei Filippovda9fb732019-04-25 22:40:32247 allocs
Alexei Filippov0a194542019-04-22 22:45:46248 .emplace(std::piecewise_construct,
249 std::forward_as_tuple(sample->allocator, std::move(stack),
250 context_id),
251 std::forward_as_tuple())
252 .first->second;
253 metrics.size += alloc_size;
254 metrics.count += alloc_count;
Alexei Filippove48985e2019-02-01 00:27:41255 }
256
257 for (const auto& str : profile->strings) {
258 std::string quoted_string;
259 // Escape the strings before saving them, to simplify exporting a heap dump.
260 base::EscapeJSONString(str.second, false /* put_in_quotes */,
261 &quoted_string);
262 string_map.emplace(str.first, std::move(quoted_string));
263 }
264
Alexei Filippovda9fb732019-04-25 22:40:32265 params->allocs = std::move(allocs);
266 params->context_map = std::move(context_map);
267 params->mapped_strings = std::move(string_map);
268 return true;
Alexei Filippove48985e2019-02-01 00:27:41269}
270
Alexei Filippovda9fb732019-04-25 22:40:32271void ConnectionManager::HeapProfileRetrieved(
Erik Chene2d064472017-10-07 03:34:07272 scoped_refptr<DumpProcessesForTracingTracking> tracking,
273 base::ProcessId pid,
Erik Chena610d552017-10-20 22:29:49274 mojom::ProcessType process_type,
erikchend1b8bc52017-12-21 18:12:42275 bool strip_path_from_mapped_files,
Alexei Filippovda9fb732019-04-25 22:40:32276 uint32_t sampling_rate,
277 mojom::HeapProfilePtr profile) {
Erik Chene2d064472017-10-07 03:34:07278 // All code paths through here must issue the callback when waiting_responses
279 // is 0 or the browser will wait forever for the dump.
280 DCHECK(tracking->waiting_responses > 0);
erikchen66af0162017-08-02 19:53:19281
Alexei Filippovda9fb732019-04-25 22:40:32282 ExportParams params;
283 bool success =
284 ConvertProfileToExportParams(std::move(profile), sampling_rate, &params);
285 if (success) {
286 params.process_type = process_type;
287 params.strip_path_from_mapped_files = strip_path_from_mapped_files;
288 params.next_id = next_id_;
289
290 auto it = tracking->vm_regions.find(pid);
291 if (it != tracking->vm_regions.end())
292 params.maps = std::move(it->second);
293
294 memory_instrumentation::mojom::HeapProfileResultPtr result =
295 memory_instrumentation::mojom::HeapProfileResult::New();
296 result->pid = pid;
297 result->json = ExportMemoryMapsAndV2StackTraceToJSON(&params);
298 tracking->results.push_back(std::move(result));
299 next_id_ = params.next_id;
Erik Chen5eaed0e2017-08-26 22:16:49300 }
301
Alexei Filippovda9fb732019-04-25 22:40:32302 // When all responses complete, issue done callback.
303 if (--tracking->waiting_responses == 0)
304 std::move(tracking->callback).Run(std::move(tracking->results));
Erik Chene2d064472017-10-07 03:34:07305}
Erik Chen22f66c6d2017-10-06 23:48:50306
erikchen102fe212018-04-06 13:02:10307} // namespace heap_profiling