1 // Copyright 2017 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 "services/resource_coordinator/memory_instrumentation/queued_request_dispatcher.h"
6
7 #include <inttypes.h>
8 #include <utility>
9
10 #include "base/bind.h"
11 #include "base/command_line.h"
12 #include "base/format_macros.h"
13 #include "base/logging.h"
14 #include "base/metrics/histogram_macros.h"
15 #include "base/strings/pattern.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/trace_event/trace_event.h"
18 #include "build/build_config.h"
19 #include "services/resource_coordinator/memory_instrumentation/aggregate_metrics_processor.h"
20 #include "services/resource_coordinator/memory_instrumentation/memory_dump_map_converter.h"
21 #include "services/resource_coordinator/memory_instrumentation/switches.h"
22 #include "services/resource_coordinator/public/cpp/memory_instrumentation/global_memory_dump.h"
23 #include "services/resource_coordinator/public/cpp/memory_instrumentation/tracing_observer_proto.h"
24 #include "services/resource_coordinator/public/cpp/memory_instrumentation/tracing_observer_traced_value.h"
25 #include "third_party/perfetto/include/perfetto/ext/trace_processor/importers/memory_tracker/graph_processor.h"
26 #include "third_party/perfetto/protos/perfetto/trace/memory_graph.pbzero.h"
27 #include "third_party/perfetto/protos/perfetto/trace/trace_packet.pbzero.h"
28
29 #if defined(OS_MAC)
30 #include "base/mac/mac_util.h"
31 #endif
32
33 using base::trace_event::TracedValue;
34 using perfetto::trace_processor::GlobalNodeGraph;
35 using perfetto::trace_processor::LevelOfDetail;
36 using perfetto::trace_processor::RawMemoryGraphNode;
37 using Node = perfetto::trace_processor::GlobalNodeGraph::Node;
38 using perfetto::trace_processor::GraphProcessor;
39
40 namespace memory_instrumentation {
41
42 namespace {
43
44 // Returns the private memory footprint calculated from given |os_dump|.
45 //
46 // See design docs linked in the bugs for the rationale of the computation:
47 // - Linux/Android: https://crbug.com/707019 .
48 // - Mac OS: https://crbug.com/707021 .
49 // - Win: https://crbug.com/707022 .
CalculatePrivateFootprintKb(const mojom::RawOSMemDump & os_dump,uint32_t shared_resident_kb)50 uint32_t CalculatePrivateFootprintKb(const mojom::RawOSMemDump& os_dump,
51 uint32_t shared_resident_kb) {
52 DCHECK(os_dump.platform_private_footprint);
53 #if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) || defined(OS_BSD) || \
54 defined(OS_FUCHSIA)
55 uint64_t rss_anon_bytes = os_dump.platform_private_footprint->rss_anon_bytes;
56 uint64_t vm_swap_bytes = os_dump.platform_private_footprint->vm_swap_bytes;
57 return (rss_anon_bytes + vm_swap_bytes) / 1024;
58 #elif defined(OS_MAC)
59 if (base::mac::IsAtLeastOS10_12()) {
60 uint64_t phys_footprint_bytes =
61 os_dump.platform_private_footprint->phys_footprint_bytes;
62 return base::saturated_cast<uint32_t>(
63 base::saturated_cast<int32_t>(phys_footprint_bytes / 1024) -
64 base::saturated_cast<int32_t>(shared_resident_kb));
65 } else {
66 uint64_t internal_bytes =
67 os_dump.platform_private_footprint->internal_bytes;
68 uint64_t compressed_bytes =
69 os_dump.platform_private_footprint->compressed_bytes;
70 return base::saturated_cast<uint32_t>(
71 base::saturated_cast<int32_t>((internal_bytes + compressed_bytes) /
72 1024) -
73 base::saturated_cast<int32_t>(shared_resident_kb));
74 }
75 #elif defined(OS_WIN)
76 return base::saturated_cast<int32_t>(
77 os_dump.platform_private_footprint->private_bytes / 1024);
78 #else
79 return 0;
80 #endif
81 }
82
CreatePublicOSDump(const mojom::RawOSMemDump & internal_os_dump,uint32_t shared_resident_kb)83 memory_instrumentation::mojom::OSMemDumpPtr CreatePublicOSDump(
84 const mojom::RawOSMemDump& internal_os_dump,
85 uint32_t shared_resident_kb) {
86 mojom::OSMemDumpPtr os_dump = mojom::OSMemDump::New();
87
88 os_dump->resident_set_kb = internal_os_dump.resident_set_kb;
89 os_dump->peak_resident_set_kb = internal_os_dump.peak_resident_set_kb;
90 os_dump->is_peak_rss_resettable = internal_os_dump.is_peak_rss_resettable;
91 os_dump->private_footprint_kb =
92 CalculatePrivateFootprintKb(internal_os_dump, shared_resident_kb);
93 #if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) || defined(OS_BSD)
94 os_dump->private_footprint_swap_kb =
95 internal_os_dump.platform_private_footprint->vm_swap_bytes / 1024;
96 #endif
97 return os_dump;
98 }
99
NodeAsValueIntoRecursively(const GlobalNodeGraph::Node & node,TracedValue * value,std::vector<base::StringPiece> * path)100 void NodeAsValueIntoRecursively(const GlobalNodeGraph::Node& node,
101 TracedValue* value,
102 std::vector<base::StringPiece>* path) {
103 // Don't dump the root node.
104 if (!path->empty()) {
105 std::string string_conversion_buffer;
106
107 std::string name = base::JoinString(*path, "/");
108 value->BeginDictionaryWithCopiedName(name);
109
110 if (!node.id().empty())
111 value->SetString("id", node.id().ToString());
112
113 value->BeginDictionary("attrs");
114 for (const auto& name_to_entry : node.const_entries()) {
115 const auto& entry = name_to_entry.second;
116 value->BeginDictionaryWithCopiedName(name_to_entry.first);
117 switch (entry.type) {
118 case GlobalNodeGraph::Node::Entry::kUInt64:
119 base::SStringPrintf(&string_conversion_buffer, "%" PRIx64,
120 entry.value_uint64);
121 value->SetString("type", RawMemoryGraphNode::kTypeScalar);
122 value->SetString("value", string_conversion_buffer);
123 break;
124 case GlobalNodeGraph::Node::Entry::kString:
125 value->SetString("type", RawMemoryGraphNode::kTypeString);
126 value->SetString("value", entry.value_string);
127 break;
128 }
129 switch (entry.units) {
130 case GlobalNodeGraph::Node::Entry::ScalarUnits::kBytes:
131 value->SetString("units", RawMemoryGraphNode::kUnitsBytes);
132 break;
133 case GlobalNodeGraph::Node::Entry::ScalarUnits::kObjects:
134 value->SetString("units", RawMemoryGraphNode::kUnitsObjects);
135 break;
136 }
137 value->EndDictionary();
138 }
139 value->EndDictionary(); // "attrs": { ... }
140
141 if (node.is_weak())
142 value->SetInteger("flags", RawMemoryGraphNode::Flags::kWeak);
143
144 value->EndDictionary(); // "allocator_name/heap_subheap": { ... }
145 }
146
147 for (const auto& name_to_child : node.const_children()) {
148 path->push_back(name_to_child.first);
149 NodeAsValueIntoRecursively(*name_to_child.second, value, path);
150 path->pop_back();
151 }
152 }
153
GetChromeDumpTracedValue(const GlobalNodeGraph::Process & process)154 std::unique_ptr<TracedValue> GetChromeDumpTracedValue(
155 const GlobalNodeGraph::Process& process) {
156 std::unique_ptr<TracedValue> traced_value = std::make_unique<TracedValue>();
157 if (!process.root()->const_children().empty()) {
158 traced_value->BeginDictionary("allocators");
159 std::vector<base::StringPiece> path;
160 NodeAsValueIntoRecursively(*process.root(), traced_value.get(), &path);
161 traced_value->EndDictionary();
162 }
163 return traced_value;
164 }
165
GetChromeDumpAndGlobalAndEdgesTracedValue(const GlobalNodeGraph::Process & process,const GlobalNodeGraph::Process & global_process,const std::forward_list<GlobalNodeGraph::Edge> & edges)166 std::unique_ptr<TracedValue> GetChromeDumpAndGlobalAndEdgesTracedValue(
167 const GlobalNodeGraph::Process& process,
168 const GlobalNodeGraph::Process& global_process,
169 const std::forward_list<GlobalNodeGraph::Edge>& edges) {
170 std::unique_ptr<TracedValue> traced_value = std::make_unique<TracedValue>();
171 bool suppress_graphs = process.root()->const_children().empty() &&
172 global_process.root()->const_children().empty();
173
174 if (!suppress_graphs) {
175 traced_value->BeginDictionary("allocators");
176 std::vector<base::StringPiece> path;
177 NodeAsValueIntoRecursively(*process.root(), traced_value.get(), &path);
178 NodeAsValueIntoRecursively(*global_process.root(), traced_value.get(),
179 &path);
180 traced_value->EndDictionary();
181 }
182 traced_value->BeginArray("allocators_graph");
183 for (const auto& edge : edges) {
184 traced_value->BeginDictionary();
185 traced_value->SetString("source", edge.source()->id().ToString());
186 traced_value->SetString("target", edge.target()->id().ToString());
187 traced_value->SetInteger("importance", edge.priority());
188 traced_value->EndDictionary();
189 }
190 traced_value->EndArray();
191 return traced_value;
192 }
193
194 } // namespace
195
196 // static
SetUpAndDispatch(QueuedRequest * request,const std::vector<ClientInfo> & clients,const ChromeCallback & chrome_callback,const OsCallback & os_callback)197 void QueuedRequestDispatcher::SetUpAndDispatch(
198 QueuedRequest* request,
199 const std::vector<ClientInfo>& clients,
200 const ChromeCallback& chrome_callback,
201 const OsCallback& os_callback) {
202 using ResponseType = QueuedRequest::PendingResponse::Type;
203 DCHECK(!request->dump_in_progress);
204 request->dump_in_progress = true;
205
206 request->start_time = base::TimeTicks::Now();
207
208 TRACE_EVENT_NESTABLE_ASYNC_BEGIN2(
209 base::trace_event::MemoryDumpManager::kTraceCategory, "GlobalMemoryDump",
210 TRACE_ID_LOCAL(request->dump_guid), "dump_type",
211 base::trace_event::MemoryDumpTypeToString(request->args.dump_type),
212 "level_of_detail",
213 base::trace_event::MemoryDumpLevelOfDetailToString(
214 request->args.level_of_detail));
215
216 request->failed_memory_dump_count = 0;
217
218 // Note: the service process itself is registered as a ClientProcess and
219 // will be treated like any other process for the sake of memory dumps.
220 request->pending_responses.clear();
221
222 for (const auto& client_info : clients) {
223 mojom::ClientProcess* client = client_info.client;
224
225 // If we're only looking for a single pid process, then ignore clients
226 // with different pid.
227 if (request->args.pid != base::kNullProcessId &&
228 request->args.pid != client_info.pid) {
229 continue;
230 }
231
232 request->responses[client_info.pid].process_id = client_info.pid;
233 request->responses[client_info.pid].process_type = client_info.process_type;
234 request->responses[client_info.pid].service_name = client_info.service_name;
235
236 // Don't request a chrome memory dump at all if the client only wants the
237 // a memory footprint.
238 //
239 // This must occur before the call to RequestOSMemoryDump, as
240 // ClientProcessImpl will [for macOS], delay the calculations for the
241 // OSMemoryDump until the Chrome memory dump is finished. See
242 // https://bugs.chromium.org/p/chromium/issues/detail?id=812346#c16 for more
243 // details.
244 if (!request->args.memory_footprint_only) {
245 request->pending_responses.insert(
246 {client_info.pid, ResponseType::kChromeDump});
247 client->RequestChromeMemoryDump(
248 request->GetRequestArgs(),
249 base::BindOnce(chrome_callback, client_info.pid));
250 }
251
252 // On most platforms each process can dump data about their own process
253 // so ask each process to do so Linux is special see below.
254 #if !defined(OS_LINUX) && !defined(OS_CHROMEOS)
255 request->pending_responses.insert({client_info.pid, ResponseType::kOSDump});
256 client->RequestOSMemoryDump(request->memory_map_option(),
257 {base::kNullProcessId},
258 base::BindOnce(os_callback, client_info.pid));
259 #endif // !defined(OS_LINUX) && !defined(OS_CHROMEOS)
260
261 // If we are in the single pid case, then we've already found the only
262 // process we're looking for.
263 if (request->args.pid != base::kNullProcessId)
264 break;
265 }
266
267 // In some cases, OS stats can only be dumped from a privileged process to
268 // get around to sandboxing/selinux restrictions (see crbug.com/461788).
269 #if defined(OS_LINUX) || defined(OS_CHROMEOS)
270 std::vector<base::ProcessId> pids;
271 mojom::ClientProcess* browser_client = nullptr;
272 base::ProcessId browser_client_pid = base::kNullProcessId;
273 pids.reserve(request->args.pid == base::kNullProcessId ? clients.size() : 1);
274 for (const auto& client_info : clients) {
275 if (request->args.pid == base::kNullProcessId ||
276 client_info.pid == request->args.pid) {
277 pids.push_back(client_info.pid);
278 }
279 if (client_info.process_type == mojom::ProcessType::BROWSER) {
280 browser_client = client_info.client;
281 browser_client_pid = client_info.pid;
282 }
283 }
284 if (clients.size() > 0) {
285 DCHECK(browser_client);
286 }
287 if (browser_client && pids.size() > 0) {
288 request->pending_responses.insert(
289 {browser_client_pid, ResponseType::kOSDump});
290 auto callback = base::BindOnce(os_callback, browser_client_pid);
291 browser_client->RequestOSMemoryDump(request->memory_map_option(), pids,
292 std::move(callback));
293 }
294 #endif // defined(OS_LINUX) || defined(OS_CHROMEOS)
295
296 // In this case, we have not found the pid we are looking for so increment
297 // the failed dump count and exit.
298 if (request->args.pid != base::kNullProcessId &&
299 request->pending_responses.empty()) {
300 request->failed_memory_dump_count++;
301 return;
302 }
303 }
304
305 // static
SetUpAndDispatchVmRegionRequest(QueuedVmRegionRequest * request,const std::vector<ClientInfo> & clients,const std::vector<base::ProcessId> & desired_pids,const OsCallback & os_callback)306 void QueuedRequestDispatcher::SetUpAndDispatchVmRegionRequest(
307 QueuedVmRegionRequest* request,
308 const std::vector<ClientInfo>& clients,
309 const std::vector<base::ProcessId>& desired_pids,
310 const OsCallback& os_callback) {
311 // On Linux, OS stats can only be dumped from a privileged process to
312 // get around to sandboxing/selinux restrictions (see crbug.com/461788).
313 #if defined(OS_LINUX) || defined(OS_CHROMEOS)
314 mojom::ClientProcess* browser_client = nullptr;
315 base::ProcessId browser_client_pid = 0;
316 for (const auto& client_info : clients) {
317 if (client_info.process_type == mojom::ProcessType::BROWSER) {
318 browser_client = client_info.client;
319 browser_client_pid = client_info.pid;
320 break;
321 }
322 }
323
324 if (!browser_client) {
325 DLOG(ERROR) << "Missing browser client.";
326 return;
327 }
328
329 request->pending_responses.insert(browser_client_pid);
330 request->responses[browser_client_pid].process_id = browser_client_pid;
331 auto callback = base::BindOnce(os_callback, browser_client_pid);
332 browser_client->RequestOSMemoryDump(mojom::MemoryMapOption::MODULES,
333 desired_pids, std::move(callback));
334 #else
335 for (const auto& client_info : clients) {
336 if (std::find(desired_pids.begin(), desired_pids.end(), client_info.pid) !=
337 desired_pids.end()) {
338 mojom::ClientProcess* client = client_info.client;
339 request->pending_responses.insert(client_info.pid);
340 request->responses[client_info.pid].process_id = client_info.pid;
341 request->responses[client_info.pid].service_name =
342 client_info.service_name;
343 client->RequestOSMemoryDump(mojom::MemoryMapOption::MODULES,
344 {base::kNullProcessId},
345 base::BindOnce(os_callback, client_info.pid));
346 }
347 }
348 #endif // defined(OS_LINUX) || defined(OS_CHROMEOS)
349 }
350
351 // static
352 QueuedRequestDispatcher::VmRegions
FinalizeVmRegionRequest(QueuedVmRegionRequest * request)353 QueuedRequestDispatcher::FinalizeVmRegionRequest(
354 QueuedVmRegionRequest* request) {
355 VmRegions results;
356 for (auto& response : request->responses) {
357 const base::ProcessId& original_pid = response.second.process_id;
358
359 // |response| accumulates the replies received by each client process.
360 // On Linux, the browser process will provide all OS dumps. On non-Linux,
361 // each client process provides 1 OS dump, % the case where the client is
362 // disconnected mid dump.
363 OSMemDumpMap& extra_os_dumps = response.second.os_dumps;
364 #if defined(OS_LINUX) || defined(OS_CHROMEOS)
365 for (auto& kv : extra_os_dumps) {
366 auto pid = kv.first == base::kNullProcessId ? original_pid : kv.first;
367 DCHECK(results.find(pid) == results.end());
368 results.emplace(pid, std::move(kv.second->memory_maps));
369 }
370 #else
371 // This can be empty if the client disconnects before providing both
372 // dumps. See UnregisterClientProcess().
373 DCHECK_LE(extra_os_dumps.size(), 1u);
374 for (auto& kv : extra_os_dumps) {
375 // When the OS dump comes from child processes, the pid is supposed to be
376 // not used. We know the child process pid at the time of the request and
377 // also wouldn't trust pids coming from child processes.
378 DCHECK_EQ(base::kNullProcessId, kv.first);
379
380 // Check we don't receive duplicate OS dumps for the same process.
381 DCHECK(results.find(original_pid) == results.end());
382
383 results.emplace(original_pid, std::move(kv.second->memory_maps));
384 }
385 #endif
386 }
387 return results;
388 }
389
Finalize(QueuedRequest * request,TracingObserver * tracing_observer,bool use_proto_writer)390 void QueuedRequestDispatcher::Finalize(QueuedRequest* request,
391 TracingObserver* tracing_observer,
392 bool use_proto_writer) {
393 DCHECK(request->dump_in_progress);
394 DCHECK(request->pending_responses.empty());
395
396 // Reconstruct a map of pid -> ProcessMemoryDump by reassembling the responses
397 // received by the clients for this dump. In some cases the response coming
398 // from one client can also provide the dump of OS counters for other
399 // processes. A concrete case is Linux, where the browser process provides
400 // details for the child processes to get around sandbox restrictions on
401 // opening /proc pseudo files.
402
403 // All the pointers in the maps will continue to be owned by |request|
404 // which outlives these containers.
405 std::map<base::ProcessId, mojom::ProcessType> pid_to_process_type;
406 std::map<base::ProcessId, const base::trace_event::ProcessMemoryDump*>
407 pid_to_pmd;
408 std::map<base::ProcessId, mojom::RawOSMemDump*> pid_to_os_dump;
409 for (auto& response : request->responses) {
410 const base::ProcessId& original_pid = response.second.process_id;
411 pid_to_process_type[original_pid] = response.second.process_type;
412
413 // |chrome_dump| can be nullptr if this was a OS-counters only response.
414 pid_to_pmd[original_pid] = response.second.chrome_dump.get();
415
416 // |response| accumulates the replies received by each client process.
417 // Depending on the OS each client process might return 1 chrome + 1 OS
418 // dump each or, in the case of Linux, only 1 chrome dump each % the
419 // browser process which will provide all the OS dumps.
420 // In the former case (!OS_LINUX) we expect client processes to have
421 // exactly one OS dump in their |response|, % the case when they
422 // unexpectedly disconnect in the middle of a dump (e.g. because they
423 // crash). In the latter case (OS_LINUX) we expect the full map to come
424 // from the browser process response.
425 OSMemDumpMap& extra_os_dumps = response.second.os_dumps;
426 #if defined(OS_LINUX) || defined(OS_CHROMEOS)
427 for (const auto& kv : extra_os_dumps) {
428 auto pid = kv.first == base::kNullProcessId ? original_pid : kv.first;
429 DCHECK_EQ(pid_to_os_dump[pid], nullptr);
430 pid_to_os_dump[pid] = kv.second.get();
431 }
432 #else
433 // This can be empty if the client disconnects before providing both
434 // dumps. See UnregisterClientProcess().
435 DCHECK_LE(extra_os_dumps.size(), 1u);
436 for (const auto& kv : extra_os_dumps) {
437 // When the OS dump comes from child processes, the pid is supposed to be
438 // not used. We know the child process pid at the time of the request and
439 // also wouldn't trust pids coming from child processes.
440 DCHECK_EQ(base::kNullProcessId, kv.first);
441
442 // Check we don't receive duplicate OS dumps for the same process.
443 DCHECK_EQ(pid_to_os_dump[original_pid], nullptr);
444
445 pid_to_os_dump[original_pid] = kv.second.get();
446 }
447 #endif
448 }
449
450 MemoryDumpMapConverter converter;
451 perfetto::trace_processor::GraphProcessor::RawMemoryNodeMap perfettoNodeMap =
452 converter.Convert(pid_to_pmd);
453
454 // Generate the global memory graph from the map of pids to dumps, removing
455 // weak nodes.
456 // TODO (crbug.com/1112671): We should avoid graph processing once we moved
457 // the shared footprint computation to perfetto.
458 std::unique_ptr<GlobalNodeGraph> global_graph =
459 GraphProcessor::CreateMemoryGraph(perfettoNodeMap);
460 GraphProcessor::RemoveWeakNodesFromGraph(global_graph.get());
461
462 // Compute the shared memory footprint for each process from the graph.
463 auto original =
464 GraphProcessor::ComputeSharedFootprintFromGraph(*global_graph);
465 std::map<base::ProcessId, uint64_t> shared_footprints;
466 for (const auto& item : original) {
467 shared_footprints.emplace(static_cast<base::ProcessId>(item.first),
468 item.second);
469 }
470 // Perform the rest of the computation on the graph.
471 GraphProcessor::AddOverheadsAndPropagateEntries(global_graph.get());
472 GraphProcessor::CalculateSizesForGraph(global_graph.get());
473
474 // The same timestamp needs to be set for all dumps in the memory snapshot.
475 base::TimeTicks timestamp = TRACE_TIME_TICKS_NOW();
476
477 // Build up the global dump by iterating on the |valid| process dumps.
478 mojom::GlobalMemoryDumpPtr global_dump(mojom::GlobalMemoryDump::New());
479 global_dump->start_time = request->start_time;
480 global_dump->process_dumps.reserve(request->responses.size());
481 for (const auto& response : request->responses) {
482 base::ProcessId pid = response.second.process_id;
483
484 // On Linux, we may also have the browser process as a response.
485 // Just ignore it if we are looking for the single pid case.
486 if (request->args.pid != base::kNullProcessId && pid != request->args.pid)
487 continue;
488
489 // These pointers are owned by |request|.
490 mojom::RawOSMemDump* raw_os_dump = pid_to_os_dump[pid];
491 auto* raw_chrome_dump = pid_to_pmd[pid];
492
493 // If we have the OS dump we should have the platform private footprint.
494 DCHECK(!raw_os_dump || raw_os_dump->platform_private_footprint);
495
496 // If the raw dump exists, create a summarised version of it.
497 mojom::OSMemDumpPtr os_dump = nullptr;
498 if (raw_os_dump) {
499 uint64_t shared_resident_kb = 0;
500 #if defined(OS_MAC)
501 // The resident, anonymous shared memory for each process is only
502 // relevant on macOS.
503 const auto process_graph_it =
504 global_graph->process_node_graphs().find(pid);
505 if (process_graph_it != global_graph->process_node_graphs().end()) {
506 const auto& process_graph = process_graph_it->second;
507 auto* node = process_graph->FindNode("shared_memory");
508 if (node) {
509 const auto& entry =
510 node->entries()->find(RawMemoryGraphNode::kNameSize);
511 if (entry != node->entries()->end())
512 shared_resident_kb = entry->second.value_uint64 / 1024;
513 }
514 }
515 #endif
516 os_dump = CreatePublicOSDump(
517 *raw_os_dump, base::saturated_cast<uint32_t>(shared_resident_kb));
518 os_dump->shared_footprint_kb =
519 base::saturated_cast<uint32_t>(shared_footprints[pid] / 1024);
520 }
521
522 // Trace the OS and Chrome dumps if they exist.
523 if (request->args.add_to_trace) {
524 if (raw_os_dump) {
525 bool trace_os_success = tracing_observer->AddOsDumpToTraceIfEnabled(
526 request->GetRequestArgs(), pid, *os_dump, raw_os_dump->memory_maps,
527 timestamp);
528 if (!trace_os_success)
529 request->failed_memory_dump_count++;
530 }
531
532 if (raw_chrome_dump) {
533 bool trace_chrome_success = AddChromeMemoryDumpToTrace(
534 request->GetRequestArgs(), pid, *raw_chrome_dump, *global_graph,
535 pid_to_process_type, tracing_observer, use_proto_writer, timestamp);
536 if (!trace_chrome_success)
537 request->failed_memory_dump_count++;
538 }
539 }
540
541 bool valid = false;
542 if (request->args.memory_footprint_only) {
543 valid = raw_os_dump;
544 } else {
545 // Ignore incomplete results (can happen if the client
546 // crashes/disconnects).
547 valid = raw_os_dump && raw_chrome_dump &&
548 (request->memory_map_option() == mojom::MemoryMapOption::NONE ||
549 (raw_os_dump && !raw_os_dump->memory_maps.empty()));
550 }
551
552 if (!valid)
553 continue;
554
555 mojom::ProcessMemoryDumpPtr pmd = mojom::ProcessMemoryDump::New();
556 pmd->pid = pid;
557 pmd->process_type = pid_to_process_type[pid];
558 pmd->os_dump = std::move(os_dump);
559 pmd->service_name = response.second.service_name;
560
561 // If we have to return a summary, add all entries for the requested
562 // allocator dumps.
563 if (request->should_return_summaries() &&
564 !request->args.memory_footprint_only) {
565 const auto& process_graph =
566 global_graph->process_node_graphs().find(pid)->second;
567 for (const std::string& name : request->args.allocator_dump_names) {
568 auto* node = process_graph->FindNode(name);
569 // Silently ignore any missing node in the process graph.
570 if (!node)
571 continue;
572 base::flat_map<std::string, uint64_t> numeric_entries;
573 for (const auto& entry : *node->entries()) {
574 if (entry.second.type == Node::Entry::Type::kUInt64)
575 numeric_entries.emplace(entry.first, entry.second.value_uint64);
576 }
577 pmd->chrome_allocator_dumps.emplace(
578 name, mojom::AllocatorMemDump::New(std::move(numeric_entries)));
579 }
580 }
581
582 global_dump->process_dumps.push_back(std::move(pmd));
583 }
584 global_dump->aggregated_metrics =
585 ComputeGlobalNativeCodeResidentMemoryKb(pid_to_os_dump);
586
587 const bool global_success = request->failed_memory_dump_count == 0;
588
589 // In the single process-case, we want to ensure that global_success
590 // is true if and only if global_dump is not nullptr.
591 if (request->args.pid != base::kNullProcessId && !global_success) {
592 global_dump = nullptr;
593 }
594 auto& callback = request->callback;
595 std::move(callback).Run(global_success, request->dump_guid,
596 std::move(global_dump));
597 UMA_HISTOGRAM_MEDIUM_TIMES("Memory.Experimental.Debug.GlobalDumpDuration",
598 base::TimeTicks::Now() - request->start_time);
599 UMA_HISTOGRAM_COUNTS_1000(
600 "Memory.Experimental.Debug.FailedProcessDumpsPerGlobalDump",
601 request->failed_memory_dump_count);
602
603 char guid_str[20];
604 sprintf(guid_str, "0x%" PRIx64, request->dump_guid);
605 TRACE_EVENT_NESTABLE_ASYNC_END2(
606 base::trace_event::MemoryDumpManager::kTraceCategory, "GlobalMemoryDump",
607 TRACE_ID_LOCAL(request->dump_guid), "dump_guid", TRACE_STR_COPY(guid_str),
608 "success", global_success);
609 }
610
AddChromeMemoryDumpToTrace(const base::trace_event::MemoryDumpRequestArgs & args,base::ProcessId pid,const base::trace_event::ProcessMemoryDump & raw_chrome_dump,const GlobalNodeGraph & global_graph,const std::map<base::ProcessId,mojom::ProcessType> & pid_to_process_type,TracingObserver * tracing_observer,bool use_proto_writer,const base::TimeTicks & timestamp)611 bool QueuedRequestDispatcher::AddChromeMemoryDumpToTrace(
612 const base::trace_event::MemoryDumpRequestArgs& args,
613 base::ProcessId pid,
614 const base::trace_event::ProcessMemoryDump& raw_chrome_dump,
615 const GlobalNodeGraph& global_graph,
616 const std::map<base::ProcessId, mojom::ProcessType>& pid_to_process_type,
617 TracingObserver* tracing_observer,
618 bool use_proto_writer,
619 const base::TimeTicks& timestamp) {
620 bool is_chrome_tracing_enabled =
621 base::CommandLine::ForCurrentProcess()->HasSwitch(
622 switches::kEnableChromeTracingComputation);
623 if (!is_chrome_tracing_enabled) {
624 return tracing_observer->AddChromeDumpToTraceIfEnabled(
625 args, pid, &raw_chrome_dump, timestamp);
626 }
627 if (!tracing_observer->ShouldAddToTrace(args))
628 return false;
629
630 if (use_proto_writer) {
631 return tracing_observer->AddChromeDumpToTraceIfEnabled(
632 args, pid, &raw_chrome_dump, timestamp);
633 }
634
635 const GlobalNodeGraph::Process& process =
636 *global_graph.process_node_graphs().find(pid)->second;
637
638 std::unique_ptr<TracedValue> traced_value;
639 if (pid_to_process_type.find(pid)->second == mojom::ProcessType::BROWSER) {
640 traced_value = GetChromeDumpAndGlobalAndEdgesTracedValue(
641 process, *global_graph.shared_memory_graph(), global_graph.edges());
642 } else {
643 traced_value = GetChromeDumpTracedValue(process);
644 }
645 TracingObserverTracedValue::AddToTrace(args, pid, std::move(traced_value));
646
647 return true;
648 }
649
ClientInfo(mojom::ClientProcess * client,base::ProcessId pid,mojom::ProcessType process_type,base::Optional<std::string> service_name)650 QueuedRequestDispatcher::ClientInfo::ClientInfo(
651 mojom::ClientProcess* client,
652 base::ProcessId pid,
653 mojom::ProcessType process_type,
654 base::Optional<std::string> service_name)
655 : client(client),
656 pid(pid),
657 process_type(process_type),
658 service_name(std::move(service_name)) {}
659
660 QueuedRequestDispatcher::ClientInfo::ClientInfo(ClientInfo&& other) = default;
661
662 QueuedRequestDispatcher::ClientInfo::~ClientInfo() = default;
663
664 } // namespace memory_instrumentation
665