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