123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- // Copyright 2017 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "components/services/heap_profiling/json_exporter.h"
- #include <sstream>
- #include "base/gtest_prod_util.h"
- #include "base/json/json_reader.h"
- #include "base/json/json_writer.h"
- #include "base/process/process.h"
- #include "base/strings/string_number_conversions.h"
- #include "base/values.h"
- #include "build/build_config.h"
- #include "services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics.h"
- #include "testing/gtest/include/gtest/gtest.h"
- namespace heap_profiling {
- namespace {
- using MemoryMap = std::vector<memory_instrumentation::mojom::VmRegionPtr>;
- static constexpr int kNoParent = -1;
- #if !defined(ADDRESS_SANITIZER)
- // Finds the first vm region in the given periodic interval. Returns null on
- // failure.
- const base::Value* FindFirstRegionWithAnyName(const base::Value::Dict& root) {
- const base::Value::Dict* found_mmaps = root.FindDict("process_mmaps");
- if (!found_mmaps)
- return nullptr;
- const base::Value::List* found_regions = found_mmaps->FindList("vm_regions");
- if (!found_regions)
- return nullptr;
- for (const base::Value& cur : *found_regions) {
- const std::string* found_name = cur.GetDict().FindString("mf");
- if (!found_name)
- return nullptr;
- if (*found_name != "")
- return &cur;
- }
- return nullptr;
- }
- #endif // !defined(ADDRESS_SANITIZER)
- // Looks up a given string id from the string table. Returns -1 if not found.
- int GetIdFromStringTable(const base::Value::List& strings, const char* text) {
- for (const auto& string : strings) {
- absl::optional<int> string_id = string.GetDict().FindInt("id");
- const std::string* string_text = string.GetDict().FindString("string");
- if (string_id.has_value() && string_text != nullptr &&
- *string_text == text) {
- return *string_id;
- }
- }
- return -1;
- }
- // Looks up a given string from the string table. Returns empty string if not
- // found.
- std::string GetStringFromStringTable(const base::Value::List& strings,
- int sid) {
- for (const auto& string : strings) {
- absl::optional<int> string_id = string.GetDict().FindInt("id");
- if (*string_id == sid) {
- const std::string* string_text = string.GetDict().FindString("string");
- if (!string_text)
- return std::string();
- return *string_text;
- }
- }
- return std::string();
- }
- int GetNodeWithNameID(const base::Value::List& nodes, int sid) {
- for (const auto& node : nodes) {
- absl::optional<int> node_id = node.GetDict().FindInt("id");
- absl::optional<int> node_name_sid = node.GetDict().FindInt("name_sid");
- if (node_id.has_value() && node_name_sid.has_value() &&
- *node_name_sid == sid) {
- return *node_id;
- }
- }
- return -1;
- }
- int GetOffsetForBacktraceID(const base::Value::List& nodes, int id) {
- int offset = 0;
- for (const auto& node : nodes) {
- if (node.GetInt() == id)
- return offset;
- offset++;
- }
- return -1;
- }
- bool IsBacktraceInList(const base::Value::List& backtraces,
- int id,
- int parent) {
- for (const auto& backtrace : backtraces) {
- absl::optional<int> backtrace_id = backtrace.GetDict().FindInt("id");
- if (!backtrace_id.has_value())
- continue;
- absl::optional<int> backtrace_parent =
- backtrace.GetDict().FindInt("parent");
- int backtrace_parent_int = kNoParent;
- if (backtrace_parent.has_value())
- backtrace_parent_int = *backtrace_parent;
- if (*backtrace_id == id && backtrace_parent_int == parent)
- return true;
- }
- return false;
- }
- void InsertAllocation(AllocationMap* allocs,
- AllocatorType type,
- size_t size,
- std::vector<Address> stack,
- int context_id) {
- AllocationMetrics& metrics =
- allocs
- ->emplace(std::piecewise_construct,
- std::forward_as_tuple(type, std::move(stack), context_id),
- std::forward_as_tuple())
- .first->second;
- metrics.size += size;
- metrics.count++;
- }
- } // namespace
- TEST(ProfilingJsonExporterTest, Simple) {
- std::vector<Address> stack1{Address(0x5678), Address(0x1234)};
- std::vector<Address> stack2{Address(0x9013), Address(0x9012),
- Address(0x1234)};
- AllocationMap allocs;
- InsertAllocation(&allocs, AllocatorType::kMalloc, 20, stack1, 0);
- InsertAllocation(&allocs, AllocatorType::kMalloc, 32, stack2, 0);
- InsertAllocation(&allocs, AllocatorType::kMalloc, 20, stack1, 0);
- InsertAllocation(&allocs, AllocatorType::kPartitionAlloc, 20, stack1, 0);
- InsertAllocation(&allocs, AllocatorType::kMalloc, 12, stack2, 0);
- ExportParams params;
- params.allocs = std::move(allocs);
- std::string json = ExportMemoryMapsAndV2StackTraceToJSON(¶ms);
- // JSON should parse.
- absl::optional<base::Value> root = base::JSONReader::Read(json);
- ASSERT_TRUE(root);
- const base::Value::Dict* dict = root->GetIfDict();
- ASSERT_TRUE(dict);
- // Validate the allocators summary.
- const base::Value::Dict* malloc_summary =
- dict->FindDictByDottedPath("allocators.malloc");
- ASSERT_TRUE(malloc_summary);
- const std::string* malloc_size =
- malloc_summary->FindStringByDottedPath("attrs.size.value");
- ASSERT_TRUE(malloc_size);
- EXPECT_EQ("54", *malloc_size);
- const std::string* malloc_virtual_size =
- malloc_summary->FindStringByDottedPath("attrs.virtual_size.value");
- ASSERT_TRUE(malloc_virtual_size);
- EXPECT_EQ("54", *malloc_virtual_size);
- const base::Value::Dict* partition_alloc_summary =
- dict->FindDictByDottedPath("allocators.partition_alloc");
- ASSERT_TRUE(partition_alloc_summary);
- const std::string* partition_alloc_size =
- partition_alloc_summary->FindStringByDottedPath("attrs.size.value");
- ASSERT_TRUE(partition_alloc_size);
- EXPECT_EQ("14", *partition_alloc_size);
- const std::string* partition_alloc_virtual_size =
- partition_alloc_summary->FindStringByDottedPath(
- "attrs.virtual_size.value");
- ASSERT_TRUE(partition_alloc_virtual_size);
- EXPECT_EQ("14", *partition_alloc_virtual_size);
- const base::Value::Dict* heaps_v2 = dict->FindDict("heaps_v2");
- ASSERT_TRUE(heaps_v2);
- // Retrieve maps and validate their structure.
- const base::Value::List* nodes = heaps_v2->FindListByDottedPath("maps.nodes");
- const base::Value::List* strings =
- heaps_v2->FindListByDottedPath("maps.strings");
- ASSERT_TRUE(nodes);
- ASSERT_TRUE(strings);
- // Validate the strings table.
- EXPECT_EQ(5u, strings->size());
- int sid_unknown = GetIdFromStringTable(*strings, "[unknown]");
- int sid_1234 = GetIdFromStringTable(*strings, "pc:1234");
- int sid_5678 = GetIdFromStringTable(*strings, "pc:5678");
- int sid_9012 = GetIdFromStringTable(*strings, "pc:9012");
- int sid_9013 = GetIdFromStringTable(*strings, "pc:9013");
- EXPECT_NE(-1, sid_unknown);
- EXPECT_NE(-1, sid_1234);
- EXPECT_NE(-1, sid_5678);
- EXPECT_NE(-1, sid_9012);
- EXPECT_NE(-1, sid_9013);
- // Validate the nodes table.
- // Nodes should be a list with 4 items.
- // [0] => address: 1234 parent: none
- // [1] => address: 5678 parent: 0
- // [2] => address: 9012 parent: 0
- // [3] => address: 9013 parent: 2
- EXPECT_EQ(4u, nodes->size());
- int id0 = GetNodeWithNameID(*nodes, sid_1234);
- int id1 = GetNodeWithNameID(*nodes, sid_5678);
- int id2 = GetNodeWithNameID(*nodes, sid_9012);
- int id3 = GetNodeWithNameID(*nodes, sid_9013);
- EXPECT_NE(-1, id0);
- EXPECT_NE(-1, id1);
- EXPECT_NE(-1, id2);
- EXPECT_NE(-1, id3);
- EXPECT_TRUE(IsBacktraceInList(*nodes, id0, kNoParent));
- EXPECT_TRUE(IsBacktraceInList(*nodes, id1, id0));
- EXPECT_TRUE(IsBacktraceInList(*nodes, id2, id0));
- EXPECT_TRUE(IsBacktraceInList(*nodes, id3, id2));
- // Retrieve the allocations and validate their structure.
- const base::Value::List* counts =
- heaps_v2->FindListByDottedPath("allocators.malloc.counts");
- const base::Value::List* types =
- heaps_v2->FindListByDottedPath("allocators.malloc.types");
- const base::Value::List* sizes =
- heaps_v2->FindListByDottedPath("allocators.malloc.sizes");
- const base::Value::List* backtraces =
- heaps_v2->FindListByDottedPath("allocators.malloc.nodes");
- ASSERT_TRUE(counts);
- ASSERT_TRUE(types);
- ASSERT_TRUE(sizes);
- ASSERT_TRUE(backtraces);
- // Counts should be a list of two items, a 1 and a 2. The two matching 20-byte
- // allocations should be coalesced to produce the 2.
- EXPECT_EQ(2u, counts->size());
- EXPECT_EQ(2u, types->size());
- EXPECT_EQ(2u, sizes->size());
- int node1 = GetOffsetForBacktraceID(*backtraces, id1);
- int node3 = GetOffsetForBacktraceID(*backtraces, id3);
- EXPECT_NE(-1, node1);
- EXPECT_NE(-1, node3);
- // Validate node allocated with |stack1|.
- EXPECT_EQ(2, (*counts)[node1].GetInt());
- EXPECT_EQ(0, (*types)[node1].GetInt());
- EXPECT_EQ(40, (*sizes)[node1].GetInt());
- EXPECT_EQ(id1, (*backtraces)[node1].GetInt());
- // Validate node allocated with |stack2|.
- EXPECT_EQ(2, (*counts)[node3].GetInt());
- EXPECT_EQ(0, (*types)[node3].GetInt());
- EXPECT_EQ(44, (*sizes)[node3].GetInt());
- EXPECT_EQ(id3, (*backtraces)[node3].GetInt());
- // Validate that the partition alloc one got through.
- counts = heaps_v2->FindListByDottedPath("allocators.partition_alloc.counts");
- types = heaps_v2->FindListByDottedPath("allocators.partition_alloc.types");
- sizes = heaps_v2->FindListByDottedPath("allocators.partition_alloc.sizes");
- backtraces =
- heaps_v2->FindListByDottedPath("allocators.partition_alloc.nodes");
- ASSERT_TRUE(counts);
- ASSERT_TRUE(types);
- ASSERT_TRUE(sizes);
- ASSERT_TRUE(backtraces);
- // There should just be one entry for the partition_alloc allocation.
- EXPECT_EQ(1u, counts->size());
- EXPECT_EQ(1u, types->size());
- EXPECT_EQ(1u, sizes->size());
- }
- #if BUILDFLAG(IS_FUCHSIA)
- // TODO(crbug.com/1314087): Re-enable when MemoryMaps works on Fuchsia.
- #define MAYBE_MemoryMaps DISABLED_MemoryMaps
- #else
- #define MAYBE_MemoryMaps MemoryMaps
- #endif
- // GetProcessMemoryMaps iterates through every memory region, making allocations
- // for each one. ASAN will potentially, for each allocation, make memory
- // regions. This will cause the test to time out.
- #if !defined(ADDRESS_SANITIZER)
- TEST(ProfilingJsonExporterTest, MAYBE_MemoryMaps) {
- ExportParams params;
- params.maps = memory_instrumentation::OSMetrics::GetProcessMemoryMaps(
- base::Process::Current().Pid());
- ASSERT_GT(params.maps.size(), 2u);
- std::string json = ExportMemoryMapsAndV2StackTraceToJSON(¶ms);
- // JSON should parse.
- absl::optional<base::Value> root = base::JSONReader::Read(json);
- ASSERT_TRUE(root);
- const base::Value::Dict* dict = root->GetIfDict();
- ASSERT_TRUE(dict);
- const base::Value* region = FindFirstRegionWithAnyName(*dict);
- ASSERT_TRUE(region) << "Array contains no named vm regions";
- const std::string* start_address = region->GetDict().FindString("sa");
- ASSERT_TRUE(start_address);
- EXPECT_NE(*start_address, "");
- EXPECT_NE(*start_address, "0");
- const std::string* size = region->GetDict().FindString("sz");
- ASSERT_TRUE(size);
- EXPECT_NE(*size, "");
- EXPECT_NE(*size, "0");
- }
- #endif // !defined(ADDRESS_SANITIZER)
- TEST(ProfilingJsonExporterTest, Context) {
- ExportParams params;
- std::vector<Address> stack{Address(0x1234)};
- std::string context_str1("Context 1");
- int context_id1 = 1;
- params.context_map[context_str1] = context_id1;
- std::string context_str2("Context 2");
- int context_id2 = 2;
- params.context_map[context_str2] = context_id2;
- // Make 4 events, all with identical metadata except context. Two share the
- // same context so should get folded, one has unique context, and one has no
- // context.
- AllocationMap allocs;
- InsertAllocation(&allocs, AllocatorType::kPartitionAlloc, 16, stack,
- context_id1);
- InsertAllocation(&allocs, AllocatorType::kPartitionAlloc, 16, stack,
- context_id2);
- InsertAllocation(&allocs, AllocatorType::kPartitionAlloc, 16, stack, 0);
- InsertAllocation(&allocs, AllocatorType::kPartitionAlloc, 16, stack,
- context_id1);
- params.allocs = std::move(allocs);
- std::string json = ExportMemoryMapsAndV2StackTraceToJSON(¶ms);
- // JSON should parse.
- absl::optional<base::Value> root = base::JSONReader::Read(json);
- ASSERT_TRUE(root);
- // Retrieve the allocations.
- const base::Value::Dict* heaps_v2 = root->GetDict().FindDict("heaps_v2");
- ASSERT_TRUE(heaps_v2);
- const base::Value::List* counts =
- heaps_v2->FindListByDottedPath("allocators.partition_alloc.counts");
- ASSERT_TRUE(counts);
- const base::Value::List* types =
- heaps_v2->FindListByDottedPath("allocators.partition_alloc.types");
- ASSERT_TRUE(types);
- // There should be three allocations, two coalesced ones, one with unique
- // context, and one with no context.
- EXPECT_EQ(3u, counts->size());
- EXPECT_EQ(3u, types->size());
- const base::Value::List* types_map =
- heaps_v2->FindListByDottedPath("maps.types");
- ASSERT_TRUE(types_map);
- const base::Value::List* strings =
- heaps_v2->FindListByDottedPath("maps.strings");
- ASSERT_TRUE(strings);
- // Reconstruct the map from type id to string.
- std::map<int, std::string> type_to_string;
- for (const auto& type : *types_map) {
- const absl::optional<int> id = type.GetDict().FindInt("id");
- ASSERT_TRUE(id.has_value());
- const absl::optional<int> name_sid = type.GetDict().FindInt("name_sid");
- ASSERT_TRUE(name_sid.has_value());
- type_to_string[*id] = GetStringFromStringTable(*strings, *name_sid);
- }
- // Track the three entries we have down to what we expect. The order is not
- // defined so this is relatively complex to do.
- bool found_double_context = false; // Allocations sharing the same context.
- bool found_single_context = false; // Allocation with unique context.
- bool found_no_context = false; // Allocation with no context.
- for (size_t i = 0; i < types->size(); i++) {
- const auto& found = type_to_string.find((*types)[i].GetInt());
- ASSERT_NE(type_to_string.end(), found);
- if (found->second == context_str1) {
- // Context string matches the one with two allocations.
- ASSERT_FALSE(found_double_context);
- found_double_context = true;
- ASSERT_EQ(2, (*counts)[i].GetInt());
- } else if (found->second == context_str2) {
- // Context string matches the one with one allocation.
- ASSERT_FALSE(found_single_context);
- found_single_context = true;
- ASSERT_EQ(1, (*counts)[i].GetInt());
- } else if (found->second == "[unknown]") {
- // Context string for the one with no context.
- ASSERT_FALSE(found_no_context);
- found_no_context = true;
- ASSERT_EQ(1, (*counts)[i].GetInt());
- }
- }
- // All three types of things should have been found in the loop.
- ASSERT_TRUE(found_double_context);
- ASSERT_TRUE(found_single_context);
- ASSERT_TRUE(found_no_context);
- }
- #if defined(ARCH_CPU_64_BITS)
- TEST(ProfilingJsonExporterTest, LargeAllocation) {
- std::vector<Address> stack1{Address(0x5678), Address(0x1234)};
- AllocationMap allocs;
- InsertAllocation(&allocs, AllocatorType::kMalloc,
- static_cast<size_t>(0x9876543210ul), stack1, 0);
- ExportParams params;
- params.allocs = std::move(allocs);
- std::string json = ExportMemoryMapsAndV2StackTraceToJSON(¶ms);
- // JSON should parse.
- auto parsed_json = base::JSONReader::ReadAndReturnValueWithError(json);
- ASSERT_TRUE(parsed_json.has_value()) << parsed_json.error().message;
- // Validate the allocators summary.
- const base::Value::Dict* malloc_summary =
- parsed_json->GetDict().FindDictByDottedPath("allocators.malloc");
- ASSERT_TRUE(malloc_summary);
- const std::string* malloc_size =
- malloc_summary->FindStringByDottedPath("attrs.size.value");
- ASSERT_TRUE(malloc_size);
- EXPECT_EQ("9876543210", *malloc_size);
- const std::string* malloc_virtual_size =
- malloc_summary->FindStringByDottedPath("attrs.virtual_size.value");
- ASSERT_TRUE(malloc_virtual_size);
- EXPECT_EQ("9876543210", *malloc_virtual_size);
- // Validate allocators details.
- // heaps_v2.allocators.malloc.sizes.reduce((a,s)=>a+s,0).
- const base::Value::Dict* malloc =
- parsed_json->GetDict().FindDictByDottedPath("heaps_v2.allocators.malloc");
- const base::Value::List* malloc_sizes = malloc->FindList("sizes");
- EXPECT_EQ(1u, malloc_sizes->size());
- EXPECT_EQ(0x9876543210ul, (*malloc_sizes)[0].GetDouble());
- }
- #endif
- } // namespace heap_profiling
|