| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include <stdlib.h> | 5 #include <set> |
| 6 | |
| 7 #include <iterator> | |
| 8 #include <string> | 6 #include <string> |
| 9 | 7 |
| 10 #include "base/json/json_reader.h" | 8 #include "base/json/json_reader.h" |
| 9 #include "base/macros.h" |
| 11 #include "base/memory/ref_counted.h" | 10 #include "base/memory/ref_counted.h" |
| 12 #include "base/memory/scoped_ptr.h" | 11 #include "base/memory/scoped_ptr.h" |
| 13 #include "base/trace_event/heap_profiler_allocation_context.h" | 12 #include "base/trace_event/heap_profiler_allocation_context.h" |
| 14 #include "base/trace_event/heap_profiler_heap_dump_writer.h" | 13 #include "base/trace_event/heap_profiler_heap_dump_writer.h" |
| 15 #include "base/trace_event/heap_profiler_stack_frame_deduplicator.h" | 14 #include "base/trace_event/heap_profiler_stack_frame_deduplicator.h" |
| 16 #include "base/trace_event/heap_profiler_type_name_deduplicator.h" | 15 #include "base/trace_event/heap_profiler_type_name_deduplicator.h" |
| 17 #include "base/trace_event/trace_event_argument.h" | 16 #include "base/trace_event/trace_event_argument.h" |
| 18 #include "base/values.h" | 17 #include "base/values.h" |
| 19 #include "testing/gtest/include/gtest/gtest.h" | 18 #include "testing/gtest/include/gtest/gtest.h" |
| 20 | 19 |
| 21 namespace { | 20 namespace { |
| 22 | 21 |
| 23 // Define all strings once, because the deduplicator requires pointer equality, | 22 // Define all strings once, because the deduplicator requires pointer equality, |
| 24 // and string interning is unreliable. | 23 // and string interning is unreliable. |
| 25 const char kBrowserMain[] = "BrowserMain"; | 24 const char kBrowserMain[] = "BrowserMain"; |
| 26 const char kRendererMain[] = "RendererMain"; | 25 const char kRendererMain[] = "RendererMain"; |
| 27 const char kCreateWidget[] = "CreateWidget"; | 26 const char kCreateWidget[] = "CreateWidget"; |
| 28 const char kInitialize[] = "Initialize"; | 27 const char kInitialize[] = "Initialize"; |
| 29 | 28 |
| 30 const char kInt[] = "int"; | 29 const char kInt[] = "int"; |
| 31 const char kBool[] = "bool"; | 30 const char kBool[] = "bool"; |
| 32 const char kString[] = "string"; | 31 const char kString[] = "string"; |
| 33 | 32 |
| 34 } // namespace | 33 } // namespace |
| 35 | 34 |
| 36 namespace base { | 35 namespace base { |
| 37 namespace trace_event { | 36 namespace trace_event { |
| 37 namespace heap_dump { |
| 38 | 38 |
| 39 // Asserts that an integer stored in the json as a string has the correct value. | 39 scoped_ptr<const Value> WriteAndReadBack(const std::set<HeapEntry>& entries) { |
| 40 void AssertIntEq(const DictionaryValue* entry, | 40 scoped_refptr<TracedValue> traced_value = Write(entries); |
| 41 const char* key, | |
| 42 int expected_value) { | |
| 43 std::string str; | |
| 44 ASSERT_TRUE(entry->GetString(key, &str)); | |
| 45 ASSERT_EQ(expected_value, atoi(str.c_str())); | |
| 46 } | |
| 47 | |
| 48 scoped_ptr<Value> DumpAndReadBack(HeapDumpWriter* writer) { | |
| 49 scoped_refptr<TracedValue> traced_value = writer->WriteHeapDump(); | |
| 50 std::string json; | 41 std::string json; |
| 51 traced_value->AppendAsTraceFormat(&json); | 42 traced_value->AppendAsTraceFormat(&json); |
| 52 return JSONReader::Read(json); | 43 return JSONReader::Read(json); |
| 53 } | 44 } |
| 54 | 45 |
| 46 scoped_ptr<const DictionaryValue> WriteAndReadBackEntry(HeapEntry entry) { |
| 47 std::set<HeapEntry> input_entries; |
| 48 input_entries.insert(entry); |
| 49 |
| 50 scoped_ptr<const Value> json_dict = WriteAndReadBack(input_entries); |
| 51 |
| 52 // Note: Ideally these should use |ASSERT_TRUE| instead of |EXPECT_TRUE|, but |
| 53 // |ASSERT_TRUE| can only be used in void functions. |
| 54 const DictionaryValue* dictionary; |
| 55 EXPECT_TRUE(json_dict->GetAsDictionary(&dictionary)); |
| 56 |
| 57 const ListValue* json_entries; |
| 58 EXPECT_TRUE(dictionary->GetList("entries", &json_entries)); |
| 59 |
| 60 const DictionaryValue* json_entry; |
| 61 EXPECT_TRUE(json_entries->GetDictionary(0, &json_entry)); |
| 62 |
| 63 return json_entry->CreateDeepCopy(); |
| 64 } |
| 65 |
| 66 // Given a desired stack frame ID and type ID, looks up the entry in the set and |
| 67 // asserts that it is present and has the expected size. |
| 68 void AssertSizeEq(const std::set<HeapEntry>& entries, |
| 69 int stack_frame_id, |
| 70 int type_id, |
| 71 size_t expected_size) { |
| 72 // The comparison operator for |HeapEntry| does not take size into account, |
| 73 // so by setting only stack frame ID and type ID, the real entry can be |
| 74 // found. |
| 75 HeapEntry entry; |
| 76 entry.stack_frame_id = stack_frame_id; |
| 77 entry.type_id = type_id; |
| 78 auto it = entries.find(entry); |
| 79 |
| 80 ASSERT_NE(entries.end(), it) << "No entry found for sf = " << stack_frame_id |
| 81 << ", type = " << type_id << "."; |
| 82 ASSERT_EQ(expected_size, it->size) << "Wrong size for sf = " << stack_frame_id |
| 83 << ", type = " << type_id << "."; |
| 84 } |
| 85 |
| 86 TEST(HeapDumpWriterTest, BacktraceIndex) { |
| 87 HeapEntry entry; |
| 88 entry.stack_frame_id = -1; // -1 means empty backtrace. |
| 89 entry.type_id = 0; |
| 90 entry.size = 1; |
| 91 |
| 92 scoped_ptr<const DictionaryValue> json_entry = WriteAndReadBackEntry(entry); |
| 93 |
| 94 // For an empty backtrace, the "bt" key cannot reference a stack frame. |
| 95 // Instead it should be set to the empty string. |
| 96 std::string backtrace_index; |
| 97 ASSERT_TRUE(json_entry->GetString("bt", &backtrace_index)); |
| 98 ASSERT_EQ("", backtrace_index); |
| 99 |
| 100 // Also verify that a non-negative backtrace index is dumped properly. |
| 101 entry.stack_frame_id = 2; |
| 102 json_entry = WriteAndReadBackEntry(entry); |
| 103 ASSERT_TRUE(json_entry->GetString("bt", &backtrace_index)); |
| 104 ASSERT_EQ("2", backtrace_index); |
| 105 } |
| 106 |
| 107 TEST(HeapDumpWriterTest, TypeId) { |
| 108 HeapEntry entry; |
| 109 entry.type_id = -1; // -1 means sum over all types. |
| 110 entry.stack_frame_id = 0; |
| 111 entry.size = 1; |
| 112 |
| 113 scoped_ptr<const DictionaryValue> json_entry = WriteAndReadBackEntry(entry); |
| 114 |
| 115 // Entries for the cumulative size of all types should not have the "type" |
| 116 // key set. |
| 117 ASSERT_FALSE(json_entry->HasKey("type")); |
| 118 |
| 119 // Also verify that a non-negative type ID is dumped properly. |
| 120 entry.type_id = 2; |
| 121 json_entry = WriteAndReadBackEntry(entry); |
| 122 std::string type_id; |
| 123 ASSERT_TRUE(json_entry->GetString("type", &type_id)); |
| 124 ASSERT_EQ("2", type_id); |
| 125 } |
| 126 |
| 127 TEST(HeapDumpWriterTest, SizeIsHexadecimalString) { |
| 128 // Take a number between 2^63 and 2^64 (or between 2^31 and 2^32 if |size_t| |
| 129 // is not 64 bits). |
| 130 const size_t large_value = |
| 131 sizeof(size_t) == 8 ? 0xffffffffffffffc5 : 0xffffff9d; |
| 132 const char* large_value_str = |
| 133 sizeof(size_t) == 8 ? "ffffffffffffffc5" : "ffffff9d"; |
| 134 HeapEntry entry; |
| 135 entry.type_id = 0; |
| 136 entry.stack_frame_id = 0; |
| 137 entry.size = large_value; |
| 138 |
| 139 scoped_ptr<const DictionaryValue> json_entry = WriteAndReadBackEntry(entry); |
| 140 |
| 141 std::string size; |
| 142 ASSERT_TRUE(json_entry->GetString("size", &size)); |
| 143 ASSERT_EQ(large_value_str, size); |
| 144 } |
| 145 |
| 55 TEST(HeapDumpWriterTest, BacktraceTypeNameTable) { | 146 TEST(HeapDumpWriterTest, BacktraceTypeNameTable) { |
| 56 auto sf_deduplicator = make_scoped_refptr(new StackFrameDeduplicator); | 147 base::hash_map<AllocationContext, size_t> allocations; |
| 57 auto tn_deduplicator = make_scoped_refptr(new TypeNameDeduplicator); | |
| 58 HeapDumpWriter writer(sf_deduplicator.get(), tn_deduplicator.get()); | |
| 59 | 148 |
| 60 AllocationContext ctx = AllocationContext::Empty(); | 149 AllocationContext ctx = AllocationContext::Empty(); |
| 61 ctx.backtrace.frames[0] = kBrowserMain; | 150 ctx.backtrace.frames[0] = kBrowserMain; |
| 62 ctx.backtrace.frames[1] = kCreateWidget; | 151 ctx.backtrace.frames[1] = kCreateWidget; |
| 63 ctx.type_name = kInt; | 152 ctx.type_name = kInt; |
| 64 | 153 |
| 65 // 10 bytes with context { type: int, bt: [BrowserMain, CreateWidget] }. | 154 // 10 bytes with context { type: int, bt: [BrowserMain, CreateWidget] }. |
| 66 writer.InsertAllocation(ctx, 2); | 155 allocations[ctx] = 10; |
| 67 writer.InsertAllocation(ctx, 3); | |
| 68 writer.InsertAllocation(ctx, 5); | |
| 69 | 156 |
| 70 ctx.type_name = kBool; | 157 ctx.type_name = kBool; |
| 71 | 158 |
| 72 // 18 bytes with context { type: bool, bt: [BrowserMain, CreateWidget] }. | 159 // 18 bytes with context { type: bool, bt: [BrowserMain, CreateWidget] }. |
| 73 writer.InsertAllocation(ctx, 7); | 160 allocations[ctx] = 18; |
| 74 writer.InsertAllocation(ctx, 11); | |
| 75 | 161 |
| 76 ctx.backtrace.frames[0] = kRendererMain; | 162 ctx.backtrace.frames[0] = kRendererMain; |
| 77 ctx.backtrace.frames[1] = kInitialize; | 163 ctx.backtrace.frames[1] = kInitialize; |
| 78 | 164 |
| 79 // 30 bytes with context { type: bool, bt: [RendererMain, Initialize] }. | 165 // 30 bytes with context { type: bool, bt: [RendererMain, Initialize] }. |
| 80 writer.InsertAllocation(ctx, 13); | 166 allocations[ctx] = 30; |
| 81 writer.InsertAllocation(ctx, 17); | |
| 82 | 167 |
| 83 ctx.type_name = kString; | 168 ctx.type_name = kString; |
| 84 | 169 |
| 85 // 19 bytes with context { type: string, bt: [RendererMain, Initialize] }. | 170 // 19 bytes with context { type: string, bt: [RendererMain, Initialize] }. |
| 86 writer.InsertAllocation(ctx, 19); | 171 allocations[ctx] = 19; |
| 87 | 172 |
| 88 // At this point the heap looks like this: | 173 // At this point the heap looks like this: |
| 89 // | 174 // |
| 90 // | | CrWidget <- BrMain | Init <- RenMain | Sum | | 175 // | | CrWidget <- BrMain | Init <- RenMain | Sum | |
| 91 // +--------+--------------------+-----------------+-----+ | 176 // +--------+--------------------+-----------------+-----+ |
| 92 // | int | 10 | 0 | 10 | | 177 // | int | 10 | 0 | 10 | |
| 93 // | bool | 18 | 30 | 48 | | 178 // | bool | 18 | 30 | 48 | |
| 94 // | string | 0 | 19 | 19 | | 179 // | string | 0 | 19 | 19 | |
| 95 // +--------+--------------------+-----------------+-----+ | 180 // +--------+--------------------+-----------------+-----+ |
| 96 // | Sum | 28 | 49 | 77 | | 181 // | Sum | 28 | 49 | 77 | |
| 97 | 182 |
| 98 scoped_ptr<Value> heap_dump = DumpAndReadBack(&writer); | 183 auto sf_deduplicator = make_scoped_refptr(new StackFrameDeduplicator); |
| 99 | 184 auto tn_deduplicator = make_scoped_refptr(new TypeNameDeduplicator); |
| 100 // The json heap dump should at least include this: | 185 std::set<HeapEntry> dump = |
| 101 // { | 186 Dump(allocations, sf_deduplicator.get(), tn_deduplicator.get()); |
| 102 // "entries": [ | |
| 103 // { "size": "4d" }, // 77 = 0x4d. | |
| 104 // { "size": "31", "bt": "id_of(Init <- RenMain)" }, // 49 = 0x31. | |
| 105 // { "size": "1c", "bt": "id_of(CrWidget <- BrMain)" }, // 28 = 0x1c. | |
| 106 // { "size": "30", "type": "id_of(bool)" }, // 48 = 0x30. | |
| 107 // { "size": "13", "type": "id_of(string)" }, // 19 = 0x13. | |
| 108 // { "size": "a", "type": "id_of(int)" } // 10 = 0xa. | |
| 109 // ] | |
| 110 // } | |
| 111 | 187 |
| 112 // Get the indices of the backtraces and types by adding them again to the | 188 // Get the indices of the backtraces and types by adding them again to the |
| 113 // deduplicator. Because they were added before, the same number is returned. | 189 // deduplicator. Because they were added before, the same number is returned. |
| 114 StackFrame bt0[] = {kRendererMain, kInitialize}; | 190 StackFrame bt0[] = {kRendererMain, kInitialize}; |
| 115 StackFrame bt1[] = {kBrowserMain, kCreateWidget}; | 191 StackFrame bt1[] = {kBrowserMain, kCreateWidget}; |
| 116 int bt_renderer_main_initialize = | 192 int bt_renderer_main = sf_deduplicator->Insert(bt0, bt0 + 1); |
| 117 sf_deduplicator->Insert(std::begin(bt0), std::end(bt0)); | 193 int bt_browser_main = sf_deduplicator->Insert(bt1, bt1 + 1); |
| 118 int bt_browser_main_create_widget = | 194 int bt_renderer_main_initialize = sf_deduplicator->Insert(bt0, bt0 + 2); |
| 119 sf_deduplicator->Insert(std::begin(bt1), std::end(bt1)); | 195 int bt_browser_main_create_widget = sf_deduplicator->Insert(bt1, bt1 + 2); |
| 120 int type_id_int = tn_deduplicator->Insert(kInt); | 196 int type_id_int = tn_deduplicator->Insert(kInt); |
| 121 int type_id_bool = tn_deduplicator->Insert(kBool); | 197 int type_id_bool = tn_deduplicator->Insert(kBool); |
| 122 int type_id_string = tn_deduplicator->Insert(kString); | 198 int type_id_string = tn_deduplicator->Insert(kString); |
| 123 | 199 |
| 124 const DictionaryValue* dictionary; | 200 // Full heap should have size 77. |
| 125 ASSERT_TRUE(heap_dump->GetAsDictionary(&dictionary)); | 201 AssertSizeEq(dump, -1, -1, 77); |
| 126 | 202 |
| 127 const ListValue* entries; | 203 // 49 bytes were allocated in RendererMain and children. Also check the type |
| 128 ASSERT_TRUE(dictionary->GetList("entries", &entries)); | 204 // breakdown. |
| 205 AssertSizeEq(dump, bt_renderer_main, -1, 49); |
| 206 AssertSizeEq(dump, bt_renderer_main, type_id_bool, 30); |
| 207 AssertSizeEq(dump, bt_renderer_main, type_id_string, 19); |
| 129 | 208 |
| 130 // Keep counters to verify that every entry is present exactly once. | 209 // 28 bytes were allocated in BrowserMain and children. Also check the type |
| 131 int x4d_seen = 0; | 210 // breakdown. |
| 132 int x31_seen = 0; | 211 AssertSizeEq(dump, bt_browser_main, -1, 28); |
| 133 int x1c_seen = 0; | 212 AssertSizeEq(dump, bt_browser_main, type_id_int, 10); |
| 134 int x30_seen = 0; | 213 AssertSizeEq(dump, bt_browser_main, type_id_bool, 18); |
| 135 int x13_seen = 0; | |
| 136 int xa_seen = 0; | |
| 137 | 214 |
| 138 for (const Value* entry_as_value : *entries) { | 215 // In this test all bytes are allocated in leaf nodes, so check again one |
| 139 const DictionaryValue* entry; | 216 // level deeper. |
| 140 ASSERT_TRUE(entry_as_value->GetAsDictionary(&entry)); | 217 AssertSizeEq(dump, bt_renderer_main_initialize, -1, 49); |
| 218 AssertSizeEq(dump, bt_renderer_main_initialize, type_id_bool, 30); |
| 219 AssertSizeEq(dump, bt_renderer_main_initialize, type_id_string, 19); |
| 220 AssertSizeEq(dump, bt_browser_main_create_widget, -1, 28); |
| 221 AssertSizeEq(dump, bt_browser_main_create_widget, type_id_int, 10); |
| 222 AssertSizeEq(dump, bt_browser_main_create_widget, type_id_bool, 18); |
| 141 | 223 |
| 142 // The "size" field, not to be confused with |entry->size()| which is the | 224 // The type breakdown of the entrie heap should have been dumped as well. |
| 143 // number of elements in the dictionary. | 225 AssertSizeEq(dump, -1, type_id_int, 10); |
| 144 std::string size; | 226 AssertSizeEq(dump, -1, type_id_bool, 48); |
| 145 ASSERT_TRUE(entry->GetString("size", &size)); | 227 AssertSizeEq(dump, -1, type_id_string, 19); |
| 146 | |
| 147 if (size == "4d") { | |
| 148 // Total size, should not include any other field. | |
| 149 ASSERT_EQ(1u, entry->size()); // Dictionary must have one element. | |
| 150 x4d_seen++; | |
| 151 } else if (size == "31") { | |
| 152 // Entry for backtrace "Initialize <- RendererMain". | |
| 153 ASSERT_EQ(2u, entry->size()); // Dictionary must have two elements. | |
| 154 AssertIntEq(entry, "bt", bt_renderer_main_initialize); | |
| 155 x31_seen++; | |
| 156 } else if (size == "1c") { | |
| 157 // Entry for backtrace "CreateWidget <- BrowserMain". | |
| 158 ASSERT_EQ(2u, entry->size()); // Dictionary must have two elements. | |
| 159 AssertIntEq(entry, "bt", bt_browser_main_create_widget); | |
| 160 x1c_seen++; | |
| 161 } else if (size == "30") { | |
| 162 // Entry for type bool. | |
| 163 ASSERT_EQ(2u, entry->size()); // Dictionary must have two elements. | |
| 164 AssertIntEq(entry, "type", type_id_bool); | |
| 165 x30_seen++; | |
| 166 } else if (size == "13") { | |
| 167 // Entry for type string. | |
| 168 ASSERT_EQ(2u, entry->size()); // Dictionary must have two elements. | |
| 169 AssertIntEq(entry, "type", type_id_string); | |
| 170 x13_seen++; | |
| 171 } else if (size == "a") { | |
| 172 // Entry for type int. | |
| 173 ASSERT_EQ(2u, entry->size()); // Dictionary must have two elements. | |
| 174 AssertIntEq(entry, "type", type_id_int); | |
| 175 xa_seen++; | |
| 176 } | |
| 177 } | |
| 178 | |
| 179 ASSERT_EQ(1, x4d_seen); | |
| 180 ASSERT_EQ(1, x31_seen); | |
| 181 ASSERT_EQ(1, x1c_seen); | |
| 182 ASSERT_EQ(1, x30_seen); | |
| 183 ASSERT_EQ(1, x13_seen); | |
| 184 ASSERT_EQ(1, xa_seen); | |
| 185 } | 228 } |
| 186 | 229 |
| 187 // Test that the entry for the empty backtrace ends up in the json with the | 230 // TODO(ruuda): Verify that cumulative sizes are computed correctly. |
| 188 // "bt" field set to the empty string. Also test that an entry for "unknown | 231 // TODO(ruuda): Verify that insignificant values are not dumped. |
| 189 // type" (nullptr type name) does not dereference the null pointer when writing | |
| 190 // the type names, and that the type ID is 0. | |
| 191 TEST(HeapDumpWriterTest, EmptyBacktraceIndexIsEmptyString) { | |
| 192 auto sf_deduplicator = make_scoped_refptr(new StackFrameDeduplicator); | |
| 193 auto tn_deduplicator = make_scoped_refptr(new TypeNameDeduplicator); | |
| 194 HeapDumpWriter writer(sf_deduplicator.get(), tn_deduplicator.get()); | |
| 195 | 232 |
| 196 // A context with empty backtrace and unknown type (nullptr). | 233 } // namespace heap_dump |
| 197 AllocationContext ctx = AllocationContext::Empty(); | |
| 198 | |
| 199 writer.InsertAllocation(ctx, 1); | |
| 200 | |
| 201 scoped_ptr<Value> heap_dump = DumpAndReadBack(&writer); | |
| 202 | |
| 203 const DictionaryValue* dictionary; | |
| 204 ASSERT_TRUE(heap_dump->GetAsDictionary(&dictionary)); | |
| 205 | |
| 206 const ListValue* entries; | |
| 207 ASSERT_TRUE(dictionary->GetList("entries", &entries)); | |
| 208 | |
| 209 int empty_backtrace_seen = 0; | |
| 210 int unknown_type_seen = 0; | |
| 211 | |
| 212 for (const Value* entry_as_value : *entries) { | |
| 213 const DictionaryValue* entry; | |
| 214 ASSERT_TRUE(entry_as_value->GetAsDictionary(&entry)); | |
| 215 | |
| 216 // Note that |entry->size()| is the number of elements in the dictionary. | |
| 217 if (entry->HasKey("bt") && entry->size() == 2) { | |
| 218 std::string backtrace; | |
| 219 ASSERT_TRUE(entry->GetString("bt", &backtrace)); | |
| 220 ASSERT_EQ("", backtrace); | |
| 221 empty_backtrace_seen++; | |
| 222 } | |
| 223 | |
| 224 if (entry->HasKey("type") && entry->size() == 2) { | |
| 225 std::string type_id; | |
| 226 ASSERT_TRUE(entry->GetString("type", &type_id)); | |
| 227 ASSERT_EQ("0", type_id); | |
| 228 unknown_type_seen++; | |
| 229 } | |
| 230 } | |
| 231 | |
| 232 ASSERT_EQ(1, unknown_type_seen); | |
| 233 ASSERT_EQ(1, empty_backtrace_seen); | |
| 234 } | |
| 235 | |
| 236 } // namespace trace_event | 234 } // namespace trace_event |
| 237 } // namespace base | 235 } // namespace base |
| OLD | NEW |