json_exporter_unittest.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  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 <sstream>
  6. #include "base/gtest_prod_util.h"
  7. #include "base/json/json_reader.h"
  8. #include "base/json/json_writer.h"
  9. #include "base/process/process.h"
  10. #include "base/strings/string_number_conversions.h"
  11. #include "base/values.h"
  12. #include "build/build_config.h"
  13. #include "services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics.h"
  14. #include "testing/gtest/include/gtest/gtest.h"
  15. namespace heap_profiling {
  16. namespace {
  17. using MemoryMap = std::vector<memory_instrumentation::mojom::VmRegionPtr>;
  18. static constexpr int kNoParent = -1;
  19. #if !defined(ADDRESS_SANITIZER)
  20. // Finds the first vm region in the given periodic interval. Returns null on
  21. // failure.
  22. const base::Value* FindFirstRegionWithAnyName(const base::Value::Dict& root) {
  23. const base::Value::Dict* found_mmaps = root.FindDict("process_mmaps");
  24. if (!found_mmaps)
  25. return nullptr;
  26. const base::Value::List* found_regions = found_mmaps->FindList("vm_regions");
  27. if (!found_regions)
  28. return nullptr;
  29. for (const base::Value& cur : *found_regions) {
  30. const std::string* found_name = cur.GetDict().FindString("mf");
  31. if (!found_name)
  32. return nullptr;
  33. if (*found_name != "")
  34. return &cur;
  35. }
  36. return nullptr;
  37. }
  38. #endif // !defined(ADDRESS_SANITIZER)
  39. // Looks up a given string id from the string table. Returns -1 if not found.
  40. int GetIdFromStringTable(const base::Value::List& strings, const char* text) {
  41. for (const auto& string : strings) {
  42. absl::optional<int> string_id = string.GetDict().FindInt("id");
  43. const std::string* string_text = string.GetDict().FindString("string");
  44. if (string_id.has_value() && string_text != nullptr &&
  45. *string_text == text) {
  46. return *string_id;
  47. }
  48. }
  49. return -1;
  50. }
  51. // Looks up a given string from the string table. Returns empty string if not
  52. // found.
  53. std::string GetStringFromStringTable(const base::Value::List& strings,
  54. int sid) {
  55. for (const auto& string : strings) {
  56. absl::optional<int> string_id = string.GetDict().FindInt("id");
  57. if (*string_id == sid) {
  58. const std::string* string_text = string.GetDict().FindString("string");
  59. if (!string_text)
  60. return std::string();
  61. return *string_text;
  62. }
  63. }
  64. return std::string();
  65. }
  66. int GetNodeWithNameID(const base::Value::List& nodes, int sid) {
  67. for (const auto& node : nodes) {
  68. absl::optional<int> node_id = node.GetDict().FindInt("id");
  69. absl::optional<int> node_name_sid = node.GetDict().FindInt("name_sid");
  70. if (node_id.has_value() && node_name_sid.has_value() &&
  71. *node_name_sid == sid) {
  72. return *node_id;
  73. }
  74. }
  75. return -1;
  76. }
  77. int GetOffsetForBacktraceID(const base::Value::List& nodes, int id) {
  78. int offset = 0;
  79. for (const auto& node : nodes) {
  80. if (node.GetInt() == id)
  81. return offset;
  82. offset++;
  83. }
  84. return -1;
  85. }
  86. bool IsBacktraceInList(const base::Value::List& backtraces,
  87. int id,
  88. int parent) {
  89. for (const auto& backtrace : backtraces) {
  90. absl::optional<int> backtrace_id = backtrace.GetDict().FindInt("id");
  91. if (!backtrace_id.has_value())
  92. continue;
  93. absl::optional<int> backtrace_parent =
  94. backtrace.GetDict().FindInt("parent");
  95. int backtrace_parent_int = kNoParent;
  96. if (backtrace_parent.has_value())
  97. backtrace_parent_int = *backtrace_parent;
  98. if (*backtrace_id == id && backtrace_parent_int == parent)
  99. return true;
  100. }
  101. return false;
  102. }
  103. void InsertAllocation(AllocationMap* allocs,
  104. AllocatorType type,
  105. size_t size,
  106. std::vector<Address> stack,
  107. int context_id) {
  108. AllocationMetrics& metrics =
  109. allocs
  110. ->emplace(std::piecewise_construct,
  111. std::forward_as_tuple(type, std::move(stack), context_id),
  112. std::forward_as_tuple())
  113. .first->second;
  114. metrics.size += size;
  115. metrics.count++;
  116. }
  117. } // namespace
  118. TEST(ProfilingJsonExporterTest, Simple) {
  119. std::vector<Address> stack1{Address(0x5678), Address(0x1234)};
  120. std::vector<Address> stack2{Address(0x9013), Address(0x9012),
  121. Address(0x1234)};
  122. AllocationMap allocs;
  123. InsertAllocation(&allocs, AllocatorType::kMalloc, 20, stack1, 0);
  124. InsertAllocation(&allocs, AllocatorType::kMalloc, 32, stack2, 0);
  125. InsertAllocation(&allocs, AllocatorType::kMalloc, 20, stack1, 0);
  126. InsertAllocation(&allocs, AllocatorType::kPartitionAlloc, 20, stack1, 0);
  127. InsertAllocation(&allocs, AllocatorType::kMalloc, 12, stack2, 0);
  128. ExportParams params;
  129. params.allocs = std::move(allocs);
  130. std::string json = ExportMemoryMapsAndV2StackTraceToJSON(&params);
  131. // JSON should parse.
  132. absl::optional<base::Value> root = base::JSONReader::Read(json);
  133. ASSERT_TRUE(root);
  134. const base::Value::Dict* dict = root->GetIfDict();
  135. ASSERT_TRUE(dict);
  136. // Validate the allocators summary.
  137. const base::Value::Dict* malloc_summary =
  138. dict->FindDictByDottedPath("allocators.malloc");
  139. ASSERT_TRUE(malloc_summary);
  140. const std::string* malloc_size =
  141. malloc_summary->FindStringByDottedPath("attrs.size.value");
  142. ASSERT_TRUE(malloc_size);
  143. EXPECT_EQ("54", *malloc_size);
  144. const std::string* malloc_virtual_size =
  145. malloc_summary->FindStringByDottedPath("attrs.virtual_size.value");
  146. ASSERT_TRUE(malloc_virtual_size);
  147. EXPECT_EQ("54", *malloc_virtual_size);
  148. const base::Value::Dict* partition_alloc_summary =
  149. dict->FindDictByDottedPath("allocators.partition_alloc");
  150. ASSERT_TRUE(partition_alloc_summary);
  151. const std::string* partition_alloc_size =
  152. partition_alloc_summary->FindStringByDottedPath("attrs.size.value");
  153. ASSERT_TRUE(partition_alloc_size);
  154. EXPECT_EQ("14", *partition_alloc_size);
  155. const std::string* partition_alloc_virtual_size =
  156. partition_alloc_summary->FindStringByDottedPath(
  157. "attrs.virtual_size.value");
  158. ASSERT_TRUE(partition_alloc_virtual_size);
  159. EXPECT_EQ("14", *partition_alloc_virtual_size);
  160. const base::Value::Dict* heaps_v2 = dict->FindDict("heaps_v2");
  161. ASSERT_TRUE(heaps_v2);
  162. // Retrieve maps and validate their structure.
  163. const base::Value::List* nodes = heaps_v2->FindListByDottedPath("maps.nodes");
  164. const base::Value::List* strings =
  165. heaps_v2->FindListByDottedPath("maps.strings");
  166. ASSERT_TRUE(nodes);
  167. ASSERT_TRUE(strings);
  168. // Validate the strings table.
  169. EXPECT_EQ(5u, strings->size());
  170. int sid_unknown = GetIdFromStringTable(*strings, "[unknown]");
  171. int sid_1234 = GetIdFromStringTable(*strings, "pc:1234");
  172. int sid_5678 = GetIdFromStringTable(*strings, "pc:5678");
  173. int sid_9012 = GetIdFromStringTable(*strings, "pc:9012");
  174. int sid_9013 = GetIdFromStringTable(*strings, "pc:9013");
  175. EXPECT_NE(-1, sid_unknown);
  176. EXPECT_NE(-1, sid_1234);
  177. EXPECT_NE(-1, sid_5678);
  178. EXPECT_NE(-1, sid_9012);
  179. EXPECT_NE(-1, sid_9013);
  180. // Validate the nodes table.
  181. // Nodes should be a list with 4 items.
  182. // [0] => address: 1234 parent: none
  183. // [1] => address: 5678 parent: 0
  184. // [2] => address: 9012 parent: 0
  185. // [3] => address: 9013 parent: 2
  186. EXPECT_EQ(4u, nodes->size());
  187. int id0 = GetNodeWithNameID(*nodes, sid_1234);
  188. int id1 = GetNodeWithNameID(*nodes, sid_5678);
  189. int id2 = GetNodeWithNameID(*nodes, sid_9012);
  190. int id3 = GetNodeWithNameID(*nodes, sid_9013);
  191. EXPECT_NE(-1, id0);
  192. EXPECT_NE(-1, id1);
  193. EXPECT_NE(-1, id2);
  194. EXPECT_NE(-1, id3);
  195. EXPECT_TRUE(IsBacktraceInList(*nodes, id0, kNoParent));
  196. EXPECT_TRUE(IsBacktraceInList(*nodes, id1, id0));
  197. EXPECT_TRUE(IsBacktraceInList(*nodes, id2, id0));
  198. EXPECT_TRUE(IsBacktraceInList(*nodes, id3, id2));
  199. // Retrieve the allocations and validate their structure.
  200. const base::Value::List* counts =
  201. heaps_v2->FindListByDottedPath("allocators.malloc.counts");
  202. const base::Value::List* types =
  203. heaps_v2->FindListByDottedPath("allocators.malloc.types");
  204. const base::Value::List* sizes =
  205. heaps_v2->FindListByDottedPath("allocators.malloc.sizes");
  206. const base::Value::List* backtraces =
  207. heaps_v2->FindListByDottedPath("allocators.malloc.nodes");
  208. ASSERT_TRUE(counts);
  209. ASSERT_TRUE(types);
  210. ASSERT_TRUE(sizes);
  211. ASSERT_TRUE(backtraces);
  212. // Counts should be a list of two items, a 1 and a 2. The two matching 20-byte
  213. // allocations should be coalesced to produce the 2.
  214. EXPECT_EQ(2u, counts->size());
  215. EXPECT_EQ(2u, types->size());
  216. EXPECT_EQ(2u, sizes->size());
  217. int node1 = GetOffsetForBacktraceID(*backtraces, id1);
  218. int node3 = GetOffsetForBacktraceID(*backtraces, id3);
  219. EXPECT_NE(-1, node1);
  220. EXPECT_NE(-1, node3);
  221. // Validate node allocated with |stack1|.
  222. EXPECT_EQ(2, (*counts)[node1].GetInt());
  223. EXPECT_EQ(0, (*types)[node1].GetInt());
  224. EXPECT_EQ(40, (*sizes)[node1].GetInt());
  225. EXPECT_EQ(id1, (*backtraces)[node1].GetInt());
  226. // Validate node allocated with |stack2|.
  227. EXPECT_EQ(2, (*counts)[node3].GetInt());
  228. EXPECT_EQ(0, (*types)[node3].GetInt());
  229. EXPECT_EQ(44, (*sizes)[node3].GetInt());
  230. EXPECT_EQ(id3, (*backtraces)[node3].GetInt());
  231. // Validate that the partition alloc one got through.
  232. counts = heaps_v2->FindListByDottedPath("allocators.partition_alloc.counts");
  233. types = heaps_v2->FindListByDottedPath("allocators.partition_alloc.types");
  234. sizes = heaps_v2->FindListByDottedPath("allocators.partition_alloc.sizes");
  235. backtraces =
  236. heaps_v2->FindListByDottedPath("allocators.partition_alloc.nodes");
  237. ASSERT_TRUE(counts);
  238. ASSERT_TRUE(types);
  239. ASSERT_TRUE(sizes);
  240. ASSERT_TRUE(backtraces);
  241. // There should just be one entry for the partition_alloc allocation.
  242. EXPECT_EQ(1u, counts->size());
  243. EXPECT_EQ(1u, types->size());
  244. EXPECT_EQ(1u, sizes->size());
  245. }
  246. #if BUILDFLAG(IS_FUCHSIA)
  247. // TODO(crbug.com/1314087): Re-enable when MemoryMaps works on Fuchsia.
  248. #define MAYBE_MemoryMaps DISABLED_MemoryMaps
  249. #else
  250. #define MAYBE_MemoryMaps MemoryMaps
  251. #endif
  252. // GetProcessMemoryMaps iterates through every memory region, making allocations
  253. // for each one. ASAN will potentially, for each allocation, make memory
  254. // regions. This will cause the test to time out.
  255. #if !defined(ADDRESS_SANITIZER)
  256. TEST(ProfilingJsonExporterTest, MAYBE_MemoryMaps) {
  257. ExportParams params;
  258. params.maps = memory_instrumentation::OSMetrics::GetProcessMemoryMaps(
  259. base::Process::Current().Pid());
  260. ASSERT_GT(params.maps.size(), 2u);
  261. std::string json = ExportMemoryMapsAndV2StackTraceToJSON(&params);
  262. // JSON should parse.
  263. absl::optional<base::Value> root = base::JSONReader::Read(json);
  264. ASSERT_TRUE(root);
  265. const base::Value::Dict* dict = root->GetIfDict();
  266. ASSERT_TRUE(dict);
  267. const base::Value* region = FindFirstRegionWithAnyName(*dict);
  268. ASSERT_TRUE(region) << "Array contains no named vm regions";
  269. const std::string* start_address = region->GetDict().FindString("sa");
  270. ASSERT_TRUE(start_address);
  271. EXPECT_NE(*start_address, "");
  272. EXPECT_NE(*start_address, "0");
  273. const std::string* size = region->GetDict().FindString("sz");
  274. ASSERT_TRUE(size);
  275. EXPECT_NE(*size, "");
  276. EXPECT_NE(*size, "0");
  277. }
  278. #endif // !defined(ADDRESS_SANITIZER)
  279. TEST(ProfilingJsonExporterTest, Context) {
  280. ExportParams params;
  281. std::vector<Address> stack{Address(0x1234)};
  282. std::string context_str1("Context 1");
  283. int context_id1 = 1;
  284. params.context_map[context_str1] = context_id1;
  285. std::string context_str2("Context 2");
  286. int context_id2 = 2;
  287. params.context_map[context_str2] = context_id2;
  288. // Make 4 events, all with identical metadata except context. Two share the
  289. // same context so should get folded, one has unique context, and one has no
  290. // context.
  291. AllocationMap allocs;
  292. InsertAllocation(&allocs, AllocatorType::kPartitionAlloc, 16, stack,
  293. context_id1);
  294. InsertAllocation(&allocs, AllocatorType::kPartitionAlloc, 16, stack,
  295. context_id2);
  296. InsertAllocation(&allocs, AllocatorType::kPartitionAlloc, 16, stack, 0);
  297. InsertAllocation(&allocs, AllocatorType::kPartitionAlloc, 16, stack,
  298. context_id1);
  299. params.allocs = std::move(allocs);
  300. std::string json = ExportMemoryMapsAndV2StackTraceToJSON(&params);
  301. // JSON should parse.
  302. absl::optional<base::Value> root = base::JSONReader::Read(json);
  303. ASSERT_TRUE(root);
  304. // Retrieve the allocations.
  305. const base::Value::Dict* heaps_v2 = root->GetDict().FindDict("heaps_v2");
  306. ASSERT_TRUE(heaps_v2);
  307. const base::Value::List* counts =
  308. heaps_v2->FindListByDottedPath("allocators.partition_alloc.counts");
  309. ASSERT_TRUE(counts);
  310. const base::Value::List* types =
  311. heaps_v2->FindListByDottedPath("allocators.partition_alloc.types");
  312. ASSERT_TRUE(types);
  313. // There should be three allocations, two coalesced ones, one with unique
  314. // context, and one with no context.
  315. EXPECT_EQ(3u, counts->size());
  316. EXPECT_EQ(3u, types->size());
  317. const base::Value::List* types_map =
  318. heaps_v2->FindListByDottedPath("maps.types");
  319. ASSERT_TRUE(types_map);
  320. const base::Value::List* strings =
  321. heaps_v2->FindListByDottedPath("maps.strings");
  322. ASSERT_TRUE(strings);
  323. // Reconstruct the map from type id to string.
  324. std::map<int, std::string> type_to_string;
  325. for (const auto& type : *types_map) {
  326. const absl::optional<int> id = type.GetDict().FindInt("id");
  327. ASSERT_TRUE(id.has_value());
  328. const absl::optional<int> name_sid = type.GetDict().FindInt("name_sid");
  329. ASSERT_TRUE(name_sid.has_value());
  330. type_to_string[*id] = GetStringFromStringTable(*strings, *name_sid);
  331. }
  332. // Track the three entries we have down to what we expect. The order is not
  333. // defined so this is relatively complex to do.
  334. bool found_double_context = false; // Allocations sharing the same context.
  335. bool found_single_context = false; // Allocation with unique context.
  336. bool found_no_context = false; // Allocation with no context.
  337. for (size_t i = 0; i < types->size(); i++) {
  338. const auto& found = type_to_string.find((*types)[i].GetInt());
  339. ASSERT_NE(type_to_string.end(), found);
  340. if (found->second == context_str1) {
  341. // Context string matches the one with two allocations.
  342. ASSERT_FALSE(found_double_context);
  343. found_double_context = true;
  344. ASSERT_EQ(2, (*counts)[i].GetInt());
  345. } else if (found->second == context_str2) {
  346. // Context string matches the one with one allocation.
  347. ASSERT_FALSE(found_single_context);
  348. found_single_context = true;
  349. ASSERT_EQ(1, (*counts)[i].GetInt());
  350. } else if (found->second == "[unknown]") {
  351. // Context string for the one with no context.
  352. ASSERT_FALSE(found_no_context);
  353. found_no_context = true;
  354. ASSERT_EQ(1, (*counts)[i].GetInt());
  355. }
  356. }
  357. // All three types of things should have been found in the loop.
  358. ASSERT_TRUE(found_double_context);
  359. ASSERT_TRUE(found_single_context);
  360. ASSERT_TRUE(found_no_context);
  361. }
  362. #if defined(ARCH_CPU_64_BITS)
  363. TEST(ProfilingJsonExporterTest, LargeAllocation) {
  364. std::vector<Address> stack1{Address(0x5678), Address(0x1234)};
  365. AllocationMap allocs;
  366. InsertAllocation(&allocs, AllocatorType::kMalloc,
  367. static_cast<size_t>(0x9876543210ul), stack1, 0);
  368. ExportParams params;
  369. params.allocs = std::move(allocs);
  370. std::string json = ExportMemoryMapsAndV2StackTraceToJSON(&params);
  371. // JSON should parse.
  372. auto parsed_json = base::JSONReader::ReadAndReturnValueWithError(json);
  373. ASSERT_TRUE(parsed_json.has_value()) << parsed_json.error().message;
  374. // Validate the allocators summary.
  375. const base::Value::Dict* malloc_summary =
  376. parsed_json->GetDict().FindDictByDottedPath("allocators.malloc");
  377. ASSERT_TRUE(malloc_summary);
  378. const std::string* malloc_size =
  379. malloc_summary->FindStringByDottedPath("attrs.size.value");
  380. ASSERT_TRUE(malloc_size);
  381. EXPECT_EQ("9876543210", *malloc_size);
  382. const std::string* malloc_virtual_size =
  383. malloc_summary->FindStringByDottedPath("attrs.virtual_size.value");
  384. ASSERT_TRUE(malloc_virtual_size);
  385. EXPECT_EQ("9876543210", *malloc_virtual_size);
  386. // Validate allocators details.
  387. // heaps_v2.allocators.malloc.sizes.reduce((a,s)=>a+s,0).
  388. const base::Value::Dict* malloc =
  389. parsed_json->GetDict().FindDictByDottedPath("heaps_v2.allocators.malloc");
  390. const base::Value::List* malloc_sizes = malloc->FindList("sizes");
  391. EXPECT_EQ(1u, malloc_sizes->size());
  392. EXPECT_EQ(0x9876543210ul, (*malloc_sizes)[0].GetDouble());
  393. }
  394. #endif
  395. } // namespace heap_profiling