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 "components/services/heap_profiling/json_exporter.h"
6 
7 #include <inttypes.h>
8 #include <map>
9 #include <unordered_map>
10 
11 #include "base/containers/adapters.h"
12 #include "base/json/json_writer.h"
13 #include "base/json/string_escape.h"
14 #include "base/macros.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/values.h"
17 #include "services/resource_coordinator/public/cpp/memory_instrumentation/tracing_observer.h"
18 
19 namespace heap_profiling {
20 namespace {
21 
22 // Maps strings to integers for the JSON string table.
23 using StringTable = std::unordered_map<std::string, int>;
24 
25 // Maps allocation site to node_id of the top frame.
26 using AllocationToNodeId = std::unordered_map<const AllocationSite*, int>;
27 
28 constexpr int kAllocatorCount = static_cast<int>(AllocatorType::kMaxValue) + 1;
29 
30 struct BacktraceNode {
BacktraceNodeheap_profiling::__anon65b12e440111::BacktraceNode31   BacktraceNode(int string_id, int parent)
32       : string_id_(string_id), parent_(parent) {}
33 
34   static constexpr int kNoParent = -1;
35 
string_idheap_profiling::__anon65b12e440111::BacktraceNode36   int string_id() const { return string_id_; }
parentheap_profiling::__anon65b12e440111::BacktraceNode37   int parent() const { return parent_; }
38 
operator <heap_profiling::__anon65b12e440111::BacktraceNode39   bool operator<(const BacktraceNode& other) const {
40     return std::tie(string_id_, parent_) <
41            std::tie(other.string_id_, other.parent_);
42   }
43 
44  private:
45   const int string_id_;
46   const int parent_;  // kNoParent indicates no parent.
47 };
48 
49 using BacktraceTable = std::map<BacktraceNode, int>;
50 
51 // The hardcoded ID for having no context for an allocation.
52 constexpr int kUnknownTypeId = 0;
53 
StringForAllocatorType(uint32_t type)54 const char* StringForAllocatorType(uint32_t type) {
55   switch (static_cast<AllocatorType>(type)) {
56     case AllocatorType::kMalloc:
57       return "malloc";
58     case AllocatorType::kPartitionAlloc:
59       return "partition_alloc";
60     case AllocatorType::kOilpan:
61       return "blink_gc";
62     default:
63       NOTREACHED();
64       return "unknown";
65   }
66 }
67 
68 // Writes the top-level allocators section. This section is used by the tracing
69 // UI to show a small summary for each allocator. It's necessary as a
70 // placeholder to allow the stack-viewing UI to be shown.
BuildAllocatorsSummary(const AllocationMap & allocations)71 base::Value BuildAllocatorsSummary(const AllocationMap& allocations) {
72   // Aggregate stats for each allocator type.
73   size_t total_size[kAllocatorCount] = {0};
74   size_t total_count[kAllocatorCount] = {0};
75   for (const auto& alloc_pair : allocations) {
76     int index = static_cast<int>(alloc_pair.first.allocator);
77     total_size[index] += alloc_pair.second.size;
78     total_count[index] += alloc_pair.second.count;
79   }
80 
81   base::Value result(base::Value::Type::DICTIONARY);
82   for (int i = 0; i < kAllocatorCount; i++) {
83     const char* alloc_type = StringForAllocatorType(i);
84 
85     // Overall sizes.
86     base::Value sizes(base::Value::Type::DICTIONARY);
87     sizes.SetStringKey("type", "scalar");
88     sizes.SetStringKey("units", "bytes");
89     sizes.SetStringKey("value", base::StringPrintf("%zx", total_size[i]));
90 
91     base::Value attrs(base::Value::Type::DICTIONARY);
92     attrs.SetKey("virtual_size", sizes.Clone());
93     attrs.SetKey("size", std::move(sizes));
94 
95     base::Value allocator(base::Value::Type::DICTIONARY);
96     allocator.SetKey("attrs", std::move(attrs));
97     result.SetKey(alloc_type, std::move(allocator));
98 
99     // Allocated objects.
100     base::Value shim_allocated_objects_count(base::Value::Type::DICTIONARY);
101     shim_allocated_objects_count.SetStringKey("type", "scalar");
102     shim_allocated_objects_count.SetStringKey("units", "objects");
103     shim_allocated_objects_count.SetStringKey(
104         "value", base::StringPrintf("%zx", total_count[i]));
105 
106     base::Value shim_allocated_objects_size(base::Value::Type::DICTIONARY);
107     shim_allocated_objects_size.SetStringKey("type", "scalar");
108     shim_allocated_objects_size.SetStringKey("units", "bytes");
109     shim_allocated_objects_size.SetStringKey(
110         "value", base::StringPrintf("%zx", total_size[i]));
111 
112     base::Value allocated_objects_attrs(base::Value::Type::DICTIONARY);
113     allocated_objects_attrs.SetKey("shim_allocated_objects_count",
114                                    std::move(shim_allocated_objects_count));
115     allocated_objects_attrs.SetKey("shim_allocated_objects_size",
116                                    std::move(shim_allocated_objects_size));
117 
118     base::Value allocated_objects(base::Value::Type::DICTIONARY);
119     allocated_objects.SetKey("attrs", std::move(allocated_objects_attrs));
120     result.SetKey(alloc_type + std::string("/allocated_objects"),
121                   std::move(allocated_objects));
122   }
123   return result;
124 }
125 
BuildMemoryMaps(const ExportParams & params)126 base::Value BuildMemoryMaps(const ExportParams& params) {
127   base::trace_event::TracedValueJSON traced_value;
128   memory_instrumentation::TracingObserver::MemoryMapsAsValueInto(
129       params.maps, &traced_value, params.strip_path_from_mapped_files);
130   return traced_value.ToBaseValue()->Clone();
131 }
132 
133 // Inserts or retrieves the ID for a string in the string table.
AddOrGetString(const std::string & str,StringTable * string_table,ExportParams * params)134 int AddOrGetString(const std::string& str,
135                    StringTable* string_table,
136                    ExportParams* params) {
137   return string_table->emplace(str, params->next_id++).first->second;
138 }
139 
140 // Processes the context information.
141 // Strings are added for each referenced context and a mapping between
142 // context IDs and string IDs is filled in for each.
FillContextStrings(ExportParams * params,StringTable * string_table,std::map<int,int> * context_to_string_id_map)143 void FillContextStrings(ExportParams* params,
144                         StringTable* string_table,
145                         std::map<int, int>* context_to_string_id_map) {
146   // The context map is backwards from what we need, so iterate through the
147   // whole thing and see which ones are used.
148   for (const auto& context : params->context_map) {
149     int string_id = AddOrGetString(context.first, string_table, params);
150     context_to_string_id_map->emplace(context.second, string_id);
151   }
152 
153   // Hard code a string for the unknown context type.
154   context_to_string_id_map->emplace(
155       kUnknownTypeId, AddOrGetString("[unknown]", string_table, params));
156 }
157 
AddOrGetBacktraceNode(BacktraceNode node,BacktraceTable * backtrace_table,ExportParams * params)158 int AddOrGetBacktraceNode(BacktraceNode node,
159                           BacktraceTable* backtrace_table,
160                           ExportParams* params) {
161   return backtrace_table->emplace(std::move(node), params->next_id++)
162       .first->second;
163 }
164 
165 // Returns the index into nodes of the node to reference for this stack. That
166 // node will reference its parent node, etc. to allow the full stack to
167 // be represented.
AppendBacktraceStrings(const AllocationSite & alloc,BacktraceTable * backtrace_table,StringTable * string_table,ExportParams * params)168 int AppendBacktraceStrings(const AllocationSite& alloc,
169                            BacktraceTable* backtrace_table,
170                            StringTable* string_table,
171                            ExportParams* params) {
172   int parent = BacktraceNode::kNoParent;
173   // Addresses must be outputted in reverse order.
174   for (const Address addr : base::Reversed(alloc.stack)) {
175     int sid;
176     auto it = params->mapped_strings.find(addr);
177     if (it != params->mapped_strings.end()) {
178       sid = AddOrGetString(it->second, string_table, params);
179     } else {
180       char buf[20];
181       snprintf(buf, sizeof(buf), "pc:%" PRIx64, addr);
182       sid = AddOrGetString(buf, string_table, params);
183     }
184     parent = AddOrGetBacktraceNode(BacktraceNode(sid, parent), backtrace_table,
185                                    params);
186   }
187   return parent;  // Last item is the top of this stack.
188 }
189 
BuildStrings(const StringTable & string_table)190 base::Value BuildStrings(const StringTable& string_table) {
191   base::Value::ListStorage strings;
192   strings.reserve(string_table.size());
193   for (const auto& string_pair : string_table) {
194     base::Value item(base::Value::Type::DICTIONARY);
195     item.SetIntKey("id", string_pair.second);
196     item.SetStringKey("string", string_pair.first);
197     strings.push_back(std::move(item));
198   }
199   return base::Value(std::move(strings));
200 }
201 
BuildMapNodes(const BacktraceTable & nodes)202 base::Value BuildMapNodes(const BacktraceTable& nodes) {
203   base::Value::ListStorage items;
204   items.reserve(nodes.size());
205   for (const auto& node_pair : nodes) {
206     base::Value item(base::Value::Type::DICTIONARY);
207     item.SetIntKey("id", node_pair.second);
208     item.SetIntKey("name_sid", node_pair.first.string_id());
209     if (node_pair.first.parent() != BacktraceNode::kNoParent)
210       item.SetIntKey("parent", node_pair.first.parent());
211     items.push_back(std::move(item));
212   }
213   return base::Value(std::move(items));
214 }
215 
BuildTypeNodes(const std::map<int,int> & type_to_string)216 base::Value BuildTypeNodes(const std::map<int, int>& type_to_string) {
217   base::Value::ListStorage items;
218   items.reserve(type_to_string.size());
219   for (const auto& pair : type_to_string) {
220     base::Value item(base::Value::Type::DICTIONARY);
221     item.SetIntKey("id", pair.first);
222     item.SetIntKey("name_sid", pair.second);
223     items.push_back(std::move(item));
224   }
225   return base::Value(std::move(items));
226 }
227 
BuildAllocations(const AllocationMap & allocations,const AllocationToNodeId & alloc_to_node_id)228 base::Value BuildAllocations(const AllocationMap& allocations,
229                              const AllocationToNodeId& alloc_to_node_id) {
230   base::Value::ListStorage counts[kAllocatorCount];
231   base::Value::ListStorage sizes[kAllocatorCount];
232   base::Value::ListStorage types[kAllocatorCount];
233   base::Value::ListStorage nodes[kAllocatorCount];
234 
235   for (const auto& alloc : allocations) {
236     int allocator = static_cast<int>(alloc.first.allocator);
237     // We use double to store size and count, as it can precisely represent
238     // values up to 2^52 ~ 4.5 petabytes.
239     counts[allocator].push_back(
240         base::Value(static_cast<double>(round(alloc.second.count))));
241     sizes[allocator].push_back(
242         base::Value(static_cast<double>(alloc.second.size)));
243     types[allocator].push_back(base::Value(alloc.first.context_id));
244     nodes[allocator].push_back(base::Value(alloc_to_node_id.at(&alloc.first)));
245   }
246 
247   base::Value allocators(base::Value::Type::DICTIONARY);
248   for (uint32_t i = 0; i < kAllocatorCount; i++) {
249     base::Value allocator(base::Value::Type::DICTIONARY);
250     allocator.SetKey("counts", base::Value(std::move(counts[i])));
251     allocator.SetKey("sizes", base::Value(std::move(sizes[i])));
252     allocator.SetKey("types", base::Value(std::move(types[i])));
253     allocator.SetKey("nodes", base::Value(std::move(nodes[i])));
254     allocators.SetKey(StringForAllocatorType(i), std::move(allocator));
255   }
256   return allocators;
257 }
258 
259 }  // namespace
260 
261 ExportParams::ExportParams() = default;
262 ExportParams::~ExportParams() = default;
263 
ExportMemoryMapsAndV2StackTraceToJSON(ExportParams * params)264 std::string ExportMemoryMapsAndV2StackTraceToJSON(ExportParams* params) {
265   base::Value result(base::Value::Type::DICTIONARY);
266 
267   result.SetStringKey("level_of_detail", "detailed");
268   result.SetKey("process_mmaps", BuildMemoryMaps(*params));
269   result.SetKey("allocators", BuildAllocatorsSummary(params->allocs));
270 
271   base::Value heaps_v2(base::Value::Type::DICTIONARY);
272 
273   // Output Heaps_V2 format version. Currently "1" is the only valid value.
274   heaps_v2.SetIntKey("version", 1);
275 
276   // Put all required context strings in the string table and generate a
277   // mapping from allocation context_id to string ID.
278   StringTable string_table;
279   std::map<int, int> context_to_string_id_map;
280   FillContextStrings(params, &string_table, &context_to_string_id_map);
281 
282   AllocationToNodeId alloc_to_node_id;
283   BacktraceTable nodes;
284   // For each backtrace, converting the string for the stack entry to string
285   // IDs. The backtrace -> node ID will be filled in at this time.
286   for (const auto& alloc : params->allocs) {
287     int node_id =
288         AppendBacktraceStrings(alloc.first, &nodes, &string_table, params);
289     alloc_to_node_id.emplace(&alloc.first, node_id);
290   }
291 
292   // Maps section.
293   base::Value maps(base::Value::Type::DICTIONARY);
294   maps.SetKey("strings", BuildStrings(string_table));
295   maps.SetKey("nodes", BuildMapNodes(nodes));
296   maps.SetKey("types", BuildTypeNodes(context_to_string_id_map));
297   heaps_v2.SetKey("maps", std::move(maps));
298 
299   heaps_v2.SetKey("allocators",
300                   BuildAllocations(params->allocs, alloc_to_node_id));
301 
302   result.SetKey("heaps_v2", std::move(heaps_v2));
303 
304   std::string result_json;
305   bool ok = base::JSONWriter::WriteWithOptions(
306       result, base::JSONWriter::OPTIONS_OMIT_DOUBLE_TYPE_PRESERVATION,
307       &result_json);
308   DCHECK(ok);
309   return result_json;
310 }
311 
312 }  // namespace heap_profiling
313