json_exporter.cc 11 KB

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