| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "base/trace_event/heap_profiler_heap_dump_writer.h" | |
| 6 | |
| 7 #include <stddef.h> | |
| 8 | |
| 9 #include <memory> | |
| 10 #include <set> | |
| 11 #include <string> | |
| 12 | |
| 13 #include "base/json/json_reader.h" | |
| 14 #include "base/macros.h" | |
| 15 #include "base/memory/ptr_util.h" | |
| 16 #include "base/trace_event/heap_profiler_allocation_context.h" | |
| 17 #include "base/trace_event/heap_profiler_stack_frame_deduplicator.h" | |
| 18 #include "base/trace_event/heap_profiler_type_name_deduplicator.h" | |
| 19 #include "base/trace_event/memory_dump_session_state.h" | |
| 20 #include "base/trace_event/trace_event_argument.h" | |
| 21 #include "base/values.h" | |
| 22 #include "testing/gtest/include/gtest/gtest.h" | |
| 23 | |
| 24 namespace { | |
| 25 | |
| 26 using base::trace_event::StackFrame; | |
| 27 | |
| 28 // Define all strings once, because the deduplicator requires pointer equality, | |
| 29 // and string interning is unreliable. | |
| 30 StackFrame kBrowserMain = StackFrame::FromTraceEventName("BrowserMain"); | |
| 31 StackFrame kRendererMain = StackFrame::FromTraceEventName("RendererMain"); | |
| 32 StackFrame kCreateWidget = StackFrame::FromTraceEventName("CreateWidget"); | |
| 33 StackFrame kInitialize = StackFrame::FromTraceEventName("Initialize"); | |
| 34 StackFrame kGetBitmap = StackFrame::FromTraceEventName("GetBitmap"); | |
| 35 | |
| 36 const char kInt[] = "int"; | |
| 37 const char kBool[] = "bool"; | |
| 38 const char kString[] = "string"; | |
| 39 | |
| 40 } // namespace | |
| 41 | |
| 42 namespace base { | |
| 43 namespace trace_event { | |
| 44 namespace internal { | |
| 45 | |
| 46 std::unique_ptr<const Value> WriteAndReadBack(const std::set<Entry>& entries) { | |
| 47 std::unique_ptr<TracedValue> traced_value = Serialize(entries); | |
| 48 std::string json; | |
| 49 traced_value->AppendAsTraceFormat(&json); | |
| 50 return JSONReader::Read(json); | |
| 51 } | |
| 52 | |
| 53 std::unique_ptr<const DictionaryValue> WriteAndReadBackEntry(Entry entry) { | |
| 54 std::set<Entry> input_entries; | |
| 55 input_entries.insert(entry); | |
| 56 | |
| 57 std::unique_ptr<const Value> json_dict = WriteAndReadBack(input_entries); | |
| 58 | |
| 59 // Note: Ideally these should use |ASSERT_TRUE| instead of |EXPECT_TRUE|, but | |
| 60 // |ASSERT_TRUE| can only be used in void functions. | |
| 61 const DictionaryValue* dictionary; | |
| 62 EXPECT_TRUE(json_dict->GetAsDictionary(&dictionary)); | |
| 63 | |
| 64 const ListValue* json_entries; | |
| 65 EXPECT_TRUE(dictionary->GetList("entries", &json_entries)); | |
| 66 | |
| 67 const DictionaryValue* json_entry; | |
| 68 EXPECT_TRUE(json_entries->GetDictionary(0, &json_entry)); | |
| 69 | |
| 70 return json_entry->CreateDeepCopy(); | |
| 71 } | |
| 72 | |
| 73 // Given a desired stack frame ID and type ID, looks up the entry in the set and | |
| 74 // asserts that it is present and has the expected size and count. | |
| 75 void AssertSizeAndCountEq(const std::set<Entry>& entries, | |
| 76 int stack_frame_id, | |
| 77 int type_id, | |
| 78 const AllocationMetrics& expected) { | |
| 79 // The comparison operator for |Entry| does not take size into account, so by | |
| 80 // setting only stack frame ID and type ID, the real entry can be found. | |
| 81 Entry entry; | |
| 82 entry.stack_frame_id = stack_frame_id; | |
| 83 entry.type_id = type_id; | |
| 84 auto it = entries.find(entry); | |
| 85 | |
| 86 ASSERT_NE(entries.end(), it) << "No entry found for sf = " << stack_frame_id | |
| 87 << ", type = " << type_id << "."; | |
| 88 ASSERT_EQ(expected.size, it->size) << "Wrong size for sf = " << stack_frame_id | |
| 89 << ", type = " << type_id << "."; | |
| 90 ASSERT_EQ(expected.count, it->count) | |
| 91 << "Wrong count for sf = " << stack_frame_id << ", type = " << type_id | |
| 92 << "."; | |
| 93 } | |
| 94 | |
| 95 // Given a desired stack frame ID and type ID, asserts that no entry was dumped | |
| 96 // for that that particular combination of stack frame and type. | |
| 97 void AssertNotDumped(const std::set<Entry>& entries, | |
| 98 int stack_frame_id, | |
| 99 int type_id) { | |
| 100 // The comparison operator for |Entry| does not take size into account, so by | |
| 101 // setting only stack frame ID and type ID, the real entry can be found. | |
| 102 Entry entry; | |
| 103 entry.stack_frame_id = stack_frame_id; | |
| 104 entry.type_id = type_id; | |
| 105 auto it = entries.find(entry); | |
| 106 ASSERT_EQ(entries.end(), it) | |
| 107 << "Entry should not be present for sf = " << stack_frame_id | |
| 108 << ", type = " << type_id << "."; | |
| 109 } | |
| 110 | |
| 111 TEST(HeapDumpWriterTest, BacktraceIndex) { | |
| 112 Entry entry; | |
| 113 entry.stack_frame_id = -1; // -1 means empty backtrace. | |
| 114 entry.type_id = 0; | |
| 115 entry.size = 1; | |
| 116 entry.count = 1; | |
| 117 | |
| 118 std::unique_ptr<const DictionaryValue> json_entry = | |
| 119 WriteAndReadBackEntry(entry); | |
| 120 | |
| 121 // For an empty backtrace, the "bt" key cannot reference a stack frame. | |
| 122 // Instead it should be set to the empty string. | |
| 123 std::string backtrace_index; | |
| 124 ASSERT_TRUE(json_entry->GetString("bt", &backtrace_index)); | |
| 125 ASSERT_EQ("", backtrace_index); | |
| 126 | |
| 127 // Also verify that a non-negative backtrace index is dumped properly. | |
| 128 entry.stack_frame_id = 2; | |
| 129 json_entry = WriteAndReadBackEntry(entry); | |
| 130 ASSERT_TRUE(json_entry->GetString("bt", &backtrace_index)); | |
| 131 ASSERT_EQ("2", backtrace_index); | |
| 132 } | |
| 133 | |
| 134 TEST(HeapDumpWriterTest, TypeId) { | |
| 135 Entry entry; | |
| 136 entry.type_id = -1; // -1 means sum over all types. | |
| 137 entry.stack_frame_id = 0; | |
| 138 entry.size = 1; | |
| 139 entry.count = 1; | |
| 140 | |
| 141 std::unique_ptr<const DictionaryValue> json_entry = | |
| 142 WriteAndReadBackEntry(entry); | |
| 143 | |
| 144 // Entries for the cumulative size of all types should not have the "type" | |
| 145 // key set. | |
| 146 ASSERT_FALSE(json_entry->HasKey("type")); | |
| 147 | |
| 148 // Also verify that a non-negative type ID is dumped properly. | |
| 149 entry.type_id = 2; | |
| 150 json_entry = WriteAndReadBackEntry(entry); | |
| 151 std::string type_id; | |
| 152 ASSERT_TRUE(json_entry->GetString("type", &type_id)); | |
| 153 ASSERT_EQ("2", type_id); | |
| 154 } | |
| 155 | |
| 156 TEST(HeapDumpWriterTest, SizeAndCountAreHexadecimal) { | |
| 157 // Take a number between 2^63 and 2^64 (or between 2^31 and 2^32 if |size_t| | |
| 158 // is not 64 bits). | |
| 159 const size_t large_value = | |
| 160 sizeof(size_t) == 8 ? 0xffffffffffffffc5 : 0xffffff9d; | |
| 161 const char* large_value_str = | |
| 162 sizeof(size_t) == 8 ? "ffffffffffffffc5" : "ffffff9d"; | |
| 163 Entry entry; | |
| 164 entry.type_id = 0; | |
| 165 entry.stack_frame_id = 0; | |
| 166 entry.size = large_value; | |
| 167 entry.count = large_value; | |
| 168 | |
| 169 std::unique_ptr<const DictionaryValue> json_entry = | |
| 170 WriteAndReadBackEntry(entry); | |
| 171 | |
| 172 std::string size; | |
| 173 ASSERT_TRUE(json_entry->GetString("size", &size)); | |
| 174 ASSERT_EQ(large_value_str, size); | |
| 175 | |
| 176 std::string count; | |
| 177 ASSERT_TRUE(json_entry->GetString("count", &count)); | |
| 178 ASSERT_EQ(large_value_str, count); | |
| 179 } | |
| 180 | |
| 181 TEST(HeapDumpWriterTest, BacktraceTypeNameTable) { | |
| 182 hash_map<AllocationContext, AllocationMetrics> metrics_by_context; | |
| 183 | |
| 184 AllocationContext ctx; | |
| 185 ctx.backtrace.frames[0] = kBrowserMain; | |
| 186 ctx.backtrace.frames[1] = kCreateWidget; | |
| 187 ctx.backtrace.frame_count = 2; | |
| 188 ctx.type_name = kInt; | |
| 189 | |
| 190 // 10 bytes with context { type: int, bt: [BrowserMain, CreateWidget] }. | |
| 191 metrics_by_context[ctx] = {10, 5}; | |
| 192 | |
| 193 ctx.type_name = kBool; | |
| 194 | |
| 195 // 18 bytes with context { type: bool, bt: [BrowserMain, CreateWidget] }. | |
| 196 metrics_by_context[ctx] = {18, 18}; | |
| 197 | |
| 198 ctx.backtrace.frames[0] = kRendererMain; | |
| 199 ctx.backtrace.frames[1] = kInitialize; | |
| 200 ctx.backtrace.frame_count = 2; | |
| 201 | |
| 202 // 30 bytes with context { type: bool, bt: [RendererMain, Initialize] }. | |
| 203 metrics_by_context[ctx] = {30, 30}; | |
| 204 | |
| 205 ctx.type_name = kString; | |
| 206 | |
| 207 // 19 bytes with context { type: string, bt: [RendererMain, Initialize] }. | |
| 208 metrics_by_context[ctx] = {19, 4}; | |
| 209 | |
| 210 // At this point the heap looks like this: | |
| 211 // | |
| 212 // | | CrWidget <- BrMain | Init <- RenMain | Sum | | |
| 213 // +--------+--------------------+-----------------+-------------+ | |
| 214 // | | size count | size count | size count | | |
| 215 // | int | 10 5 | 0 0 | 10 5 | | |
| 216 // | bool | 18 18 | 30 30 | 48 48 | | |
| 217 // | string | 0 0 | 19 4 | 19 4 | | |
| 218 // +--------+--------------------+-----------------+-------------+ | |
| 219 // | Sum | 28 23 | 49 34 | 77 57 | | |
| 220 | |
| 221 auto stack_frame_deduplicator = WrapUnique(new StackFrameDeduplicator); | |
| 222 auto type_name_deduplicator = WrapUnique(new TypeNameDeduplicator); | |
| 223 HeapDumpWriter writer(stack_frame_deduplicator.get(), | |
| 224 type_name_deduplicator.get(), | |
| 225 10u); | |
| 226 const std::set<Entry>& dump = writer.Summarize(metrics_by_context); | |
| 227 | |
| 228 // Get the indices of the backtraces and types by adding them again to the | |
| 229 // deduplicator. Because they were added before, the same number is returned. | |
| 230 StackFrame bt0[] = {kRendererMain, kInitialize}; | |
| 231 StackFrame bt1[] = {kBrowserMain, kCreateWidget}; | |
| 232 int bt_renderer_main = stack_frame_deduplicator->Insert(bt0, bt0 + 1); | |
| 233 int bt_browser_main = stack_frame_deduplicator->Insert(bt1, bt1 + 1); | |
| 234 int bt_renderer_main_initialize = | |
| 235 stack_frame_deduplicator->Insert(bt0, bt0 + 2); | |
| 236 int bt_browser_main_create_widget = | |
| 237 stack_frame_deduplicator->Insert(bt1, bt1 + 2); | |
| 238 int type_id_int = type_name_deduplicator->Insert(kInt); | |
| 239 int type_id_bool = type_name_deduplicator->Insert(kBool); | |
| 240 int type_id_string = type_name_deduplicator->Insert(kString); | |
| 241 | |
| 242 // Full heap should have size 77. | |
| 243 AssertSizeAndCountEq(dump, -1, -1, {77, 57}); | |
| 244 | |
| 245 // 49 bytes in 34 chunks were allocated in RendererMain and children. Also | |
| 246 // check the type breakdown. | |
| 247 AssertSizeAndCountEq(dump, bt_renderer_main, -1, {49, 34}); | |
| 248 AssertSizeAndCountEq(dump, bt_renderer_main, type_id_bool, {30, 30}); | |
| 249 AssertSizeAndCountEq(dump, bt_renderer_main, type_id_string, {19, 4}); | |
| 250 | |
| 251 // 28 bytes in 23 chunks were allocated in BrowserMain and children. Also | |
| 252 // check the type breakdown. | |
| 253 AssertSizeAndCountEq(dump, bt_browser_main, -1, {28, 23}); | |
| 254 AssertSizeAndCountEq(dump, bt_browser_main, type_id_int, {10, 5}); | |
| 255 AssertSizeAndCountEq(dump, bt_browser_main, type_id_bool, {18, 18}); | |
| 256 | |
| 257 // In this test all bytes are allocated in leaf nodes, so check again one | |
| 258 // level deeper. | |
| 259 AssertSizeAndCountEq(dump, bt_renderer_main_initialize, -1, {49, 34}); | |
| 260 AssertSizeAndCountEq(dump, bt_renderer_main_initialize, type_id_bool, | |
| 261 {30, 30}); | |
| 262 AssertSizeAndCountEq(dump, bt_renderer_main_initialize, type_id_string, | |
| 263 {19, 4}); | |
| 264 AssertSizeAndCountEq(dump, bt_browser_main_create_widget, -1, {28, 23}); | |
| 265 AssertSizeAndCountEq(dump, bt_browser_main_create_widget, type_id_int, | |
| 266 {10, 5}); | |
| 267 AssertSizeAndCountEq(dump, bt_browser_main_create_widget, type_id_bool, | |
| 268 {18, 18}); | |
| 269 | |
| 270 // The type breakdown of the entrie heap should have been dumped as well. | |
| 271 AssertSizeAndCountEq(dump, -1, type_id_int, {10, 5}); | |
| 272 AssertSizeAndCountEq(dump, -1, type_id_bool, {48, 48}); | |
| 273 AssertSizeAndCountEq(dump, -1, type_id_string, {19, 4}); | |
| 274 } | |
| 275 | |
| 276 TEST(HeapDumpWriterTest, InsignificantValuesNotDumped) { | |
| 277 hash_map<AllocationContext, AllocationMetrics> metrics_by_context; | |
| 278 | |
| 279 AllocationContext ctx; | |
| 280 ctx.backtrace.frames[0] = kBrowserMain; | |
| 281 ctx.backtrace.frames[1] = kCreateWidget; | |
| 282 ctx.backtrace.frame_count = 2; | |
| 283 | |
| 284 // 0.5 KiB and 1 chunk in BrowserMain -> CreateWidget itself. | |
| 285 metrics_by_context[ctx] = {512, 1}; | |
| 286 | |
| 287 // 1 MiB and 1 chunk in BrowserMain -> CreateWidget -> GetBitmap. | |
| 288 ctx.backtrace.frames[2] = kGetBitmap; | |
| 289 ctx.backtrace.frame_count = 3; | |
| 290 metrics_by_context[ctx] = {1024 * 1024, 1}; | |
| 291 | |
| 292 // 400B and 1 chunk in BrowserMain -> CreateWidget -> Initialize. | |
| 293 ctx.backtrace.frames[2] = kInitialize; | |
| 294 ctx.backtrace.frame_count = 3; | |
| 295 metrics_by_context[ctx] = {400, 1}; | |
| 296 | |
| 297 auto stack_frame_deduplicator = WrapUnique(new StackFrameDeduplicator); | |
| 298 auto type_name_deduplicator = WrapUnique(new TypeNameDeduplicator); | |
| 299 HeapDumpWriter writer(stack_frame_deduplicator.get(), | |
| 300 type_name_deduplicator.get(), | |
| 301 512u); | |
| 302 const std::set<Entry>& dump = writer.Summarize(metrics_by_context); | |
| 303 | |
| 304 // Get the indices of the backtraces and types by adding them again to the | |
| 305 // deduplicator. Because they were added before, the same number is returned. | |
| 306 StackFrame bt0[] = {kBrowserMain, kCreateWidget, kGetBitmap}; | |
| 307 StackFrame bt1[] = {kBrowserMain, kCreateWidget, kInitialize}; | |
| 308 int bt_browser_main = stack_frame_deduplicator->Insert(bt0, bt0 + 1); | |
| 309 int bt_create_widget = stack_frame_deduplicator->Insert(bt0, bt0 + 2); | |
| 310 int bt_get_bitmap = stack_frame_deduplicator->Insert(bt0, bt0 + 3); | |
| 311 int bt_initialize = stack_frame_deduplicator->Insert(bt1, bt1 + 3); | |
| 312 | |
| 313 // Full heap should have size of 1 MiB + .9 KiB and 3 chunks. | |
| 314 AssertSizeAndCountEq(dump, -1, -1 /* No type specified */, | |
| 315 {1024 * 1024 + 512 + 400, 3}); | |
| 316 | |
| 317 // |GetBitmap| allocated 1 MiB and 1 chunk. | |
| 318 AssertSizeAndCountEq(dump, bt_get_bitmap, -1, {1024 * 1024, 1}); | |
| 319 | |
| 320 // Because |GetBitmap| was dumped, all of its parent nodes should have been | |
| 321 // dumped too. |CreateWidget| has 1 MiB in |GetBitmap|, 400 bytes in | |
| 322 // |Initialize|, and 512 bytes of its own and each in 1 chunk. | |
| 323 AssertSizeAndCountEq(dump, bt_create_widget, -1, | |
| 324 {1024 * 1024 + 400 + 512, 3}); | |
| 325 AssertSizeAndCountEq(dump, bt_browser_main, -1, {1024 * 1024 + 400 + 512, 3}); | |
| 326 | |
| 327 // Initialize was not significant, it should not have been dumped. | |
| 328 AssertNotDumped(dump, bt_initialize, -1); | |
| 329 } | |
| 330 | |
| 331 } // namespace internal | |
| 332 } // namespace trace_event | |
| 333 } // namespace base | |
| OLD | NEW |