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