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_event_writer.h" |
| 6 |
| 7 #include <stdint.h> |
| 8 |
| 9 #include <algorithm> |
| 10 |
| 11 #include "base/memory/ptr_util.h" |
| 12 #include "base/trace_event/heap_profiler_allocation_context.h" |
| 13 #include "base/trace_event/heap_profiler_allocation_register.h" |
| 14 #include "base/trace_event/heap_profiler_stack_frame_deduplicator.h" |
| 15 #include "base/trace_event/heap_profiler_type_name_deduplicator.h" |
| 16 #include "base/trace_event/memory_dump_session_state.h" |
| 17 #include "base/trace_event/trace_event_argument.h" |
| 18 #include "base/values.h" |
| 19 #include "testing/gtest/include/gtest/gtest.h" |
| 20 |
| 21 namespace base { |
| 22 namespace trace_event { |
| 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 |
| 39 class AllocationRegisterHelper : public AllocationRegister { |
| 40 public: |
| 41 AllocationRegisterHelper() |
| 42 : AllocationRegister(kAllocationCapacity, kBacktraceCapacity), |
| 43 next_address_(0x100000) {} |
| 44 |
| 45 void Allocate(size_t size, |
| 46 const char* type_name, |
| 47 std::initializer_list<StackFrame> backtrace) { |
| 48 AllocationContext context; |
| 49 context.backtrace.frame_count = backtrace.size(); |
| 50 std::copy(backtrace.begin(), backtrace.end(), |
| 51 std::begin(context.backtrace.frames)); |
| 52 context.type_name = type_name; |
| 53 Insert(reinterpret_cast<void*>(next_address_), size, context); |
| 54 next_address_ += size; |
| 55 } |
| 56 |
| 57 private: |
| 58 constexpr static size_t kAllocationCapacity = 100; |
| 59 constexpr static size_t kBacktraceCapacity = 10; |
| 60 |
| 61 uintptr_t next_address_; |
| 62 }; |
| 63 |
| 64 struct HeapDumpEntry { |
| 65 int backtrace_id; |
| 66 int type_id; |
| 67 int size; |
| 68 int count; |
| 69 |
| 70 bool operator==(const HeapDumpEntry& other) const { |
| 71 return backtrace_id == other.backtrace_id && type_id == other.type_id && |
| 72 size == other.size && count == other.count; |
| 73 } |
| 74 }; |
| 75 |
| 76 ::testing::AssertionResult AssertHeapDump( |
| 77 const DictionaryValue& heap_dump, |
| 78 std::initializer_list<HeapDumpEntry> expected_entries) { |
| 79 auto get_list_value = [&](const char* list_name, size_t index, |
| 80 int* value) -> ::testing::AssertionResult { |
| 81 const ListValue* list = nullptr; |
| 82 if (!heap_dump.GetList(list_name, &list)) { |
| 83 return ::testing::AssertionFailure() |
| 84 << "'" << list_name << "' doesn't exist or is not a list"; |
| 85 } |
| 86 if (list->GetSize() != expected_entries.size()) { |
| 87 return ::testing::AssertionFailure() |
| 88 << "size of '" << list_name << "' is " << list->GetSize() |
| 89 << ", expected " << expected_entries.size(); |
| 90 } |
| 91 if (!list->GetInteger(index, value)) { |
| 92 return ::testing::AssertionFailure() |
| 93 << "'" << list_name << "' value at index " << index |
| 94 << " is not an integer"; |
| 95 } |
| 96 return ::testing::AssertionSuccess(); |
| 97 }; |
| 98 |
| 99 constexpr size_t kValueCount = 4; // nodes, types, counts, sizes |
| 100 if (heap_dump.size() != kValueCount) { |
| 101 return ::testing::AssertionFailure() |
| 102 << "heap dump has " << heap_dump.size() << " values" |
| 103 << ", expected " << kValueCount; |
| 104 } |
| 105 |
| 106 for (size_t i = 0; i != expected_entries.size(); ++i) { |
| 107 HeapDumpEntry entry; |
| 108 |
| 109 ::testing::AssertionResult assertion = ::testing::AssertionSuccess(); |
| 110 if (!(assertion = get_list_value("nodes", i, &entry.backtrace_id)) || |
| 111 !(assertion = get_list_value("types", i, &entry.type_id)) || |
| 112 !(assertion = get_list_value("sizes", i, &entry.size)) || |
| 113 !(assertion = get_list_value("counts", i, &entry.count))) { |
| 114 return assertion; |
| 115 } |
| 116 |
| 117 auto* entry_iter = |
| 118 std::find(expected_entries.begin(), expected_entries.end(), entry); |
| 119 if (entry_iter == expected_entries.end()) { |
| 120 return ::testing::AssertionFailure() |
| 121 << "unexpected HeapDumpEntry{" << entry.backtrace_id << ", " |
| 122 << entry.type_id << ", " << entry.size << ", " << entry.count |
| 123 << "} at index " << i; |
| 124 } |
| 125 } |
| 126 |
| 127 return ::testing::AssertionSuccess(); |
| 128 } |
| 129 |
| 130 std::unique_ptr<DictionaryValue> ToDictionary( |
| 131 const std::unique_ptr<TracedValue>& traced_value) { |
| 132 if (!traced_value) { |
| 133 return nullptr; |
| 134 } |
| 135 return DictionaryValue::From(traced_value->ToBaseValue()); |
| 136 } |
| 137 |
| 138 } // namespace |
| 139 |
| 140 TEST(EventWriterTest, HeapDumpNoBacktraceNoType) { |
| 141 AllocationRegisterHelper allocation_register; |
| 142 auto bt = {kBrowserMain}; |
| 143 std::initializer_list<StackFrame> empty_bt = {}; |
| 144 allocation_register.Allocate(10, nullptr, bt); |
| 145 allocation_register.Allocate(100, kInt, empty_bt); |
| 146 allocation_register.Allocate(1000, nullptr, empty_bt); |
| 147 |
| 148 auto state = make_scoped_refptr(new MemoryDumpSessionState); |
| 149 state->CreateDeduplicators(); |
| 150 auto heap_dump = ToDictionary(ExportHeapDump(allocation_register, *state)); |
| 151 ASSERT_TRUE(heap_dump); |
| 152 |
| 153 int bt_id = |
| 154 state->stack_frame_deduplicator()->Insert(std::begin(bt), std::end(bt)); |
| 155 int int_id = state->type_name_deduplicator()->Insert(kInt); |
| 156 |
| 157 // NULL type and empty backtrace ids should be 0. |
| 158 auto expected_entries = { |
| 159 HeapDumpEntry{bt_id, 0, 10, 1}, // no type |
| 160 HeapDumpEntry{0, int_id, 100, 1}, // no backtrace |
| 161 HeapDumpEntry{0, 0, 1000, 1}, // no type, no backtrace |
| 162 }; |
| 163 ASSERT_TRUE(AssertHeapDump(*heap_dump, expected_entries)) |
| 164 << "heap_dump = " << *heap_dump; |
| 165 } |
| 166 |
| 167 TEST(EventWriterTest, HeapDumpAggregation) { |
| 168 // |
| 169 // |- (no backtrace) int*1, int*2, bool*3, |
| 170 // | (no type)*4, (no type)*5 |
| 171 // | |
| 172 // |- kBrowserMain (no type)*6, (no type)*7, (no type)*8 |
| 173 // |-- kCreateWidget int*10, bool*20 |
| 174 // |---- kGetBitmap int*100, int*200, bool*300 |
| 175 // |
| 176 // Aggregation is done by {backtrace_id, type_id}, so the following |
| 177 // entries should be aggregated: |
| 178 // - int*1 + int*2 |
| 179 // - (no type)*4 + (no type)*5 |
| 180 // - (no type)*6 + (no type)*7 + (no type)*8 |
| 181 // - int*100 + int*200 |
| 182 |
| 183 AllocationRegisterHelper allocation_register; |
| 184 |
| 185 std::initializer_list<StackFrame> empty_bt = {}; |
| 186 allocation_register.Allocate(1, kInt, empty_bt); |
| 187 allocation_register.Allocate(2, kInt, empty_bt); |
| 188 allocation_register.Allocate(3, kBool, empty_bt); |
| 189 allocation_register.Allocate(4, nullptr, empty_bt); |
| 190 allocation_register.Allocate(5, nullptr, empty_bt); |
| 191 |
| 192 auto bt1 = {kBrowserMain}; |
| 193 allocation_register.Allocate(6, nullptr, bt1); |
| 194 allocation_register.Allocate(7, nullptr, bt1); |
| 195 allocation_register.Allocate(8, nullptr, bt1); |
| 196 |
| 197 auto bt2 = {kBrowserMain, kCreateWidget}; |
| 198 allocation_register.Allocate(10, kInt, bt2); |
| 199 allocation_register.Allocate(20, kBool, bt2); |
| 200 |
| 201 auto bt3 = {kBrowserMain, kCreateWidget, kGetBitmap}; |
| 202 allocation_register.Allocate(100, kInt, bt3); |
| 203 allocation_register.Allocate(200, kInt, bt3); |
| 204 allocation_register.Allocate(300, kBool, bt3); |
| 205 |
| 206 auto state = make_scoped_refptr(new MemoryDumpSessionState); |
| 207 state->CreateDeduplicators(); |
| 208 |
| 209 auto heap_dump = ToDictionary(ExportHeapDump(allocation_register, *state)); |
| 210 ASSERT_TRUE(heap_dump); |
| 211 |
| 212 int bt1_id = |
| 213 state->stack_frame_deduplicator()->Insert(std::begin(bt1), std::end(bt1)); |
| 214 int bt2_id = |
| 215 state->stack_frame_deduplicator()->Insert(std::begin(bt2), std::end(bt2)); |
| 216 int bt3_id = |
| 217 state->stack_frame_deduplicator()->Insert(std::begin(bt3), std::end(bt3)); |
| 218 |
| 219 int int_id = state->type_name_deduplicator()->Insert(kInt); |
| 220 int bool_id = state->type_name_deduplicator()->Insert(kBool); |
| 221 |
| 222 auto expected_entries = { |
| 223 HeapDumpEntry{0, int_id, 3, 2}, |
| 224 HeapDumpEntry{0, bool_id, 3, 1}, |
| 225 HeapDumpEntry{0, 0, 9, 2}, |
| 226 HeapDumpEntry{bt1_id, 0, 21, 3}, |
| 227 HeapDumpEntry{bt2_id, int_id, 10, 1}, |
| 228 HeapDumpEntry{bt2_id, bool_id, 20, 1}, |
| 229 HeapDumpEntry{bt3_id, int_id, 300, 2}, |
| 230 HeapDumpEntry{bt3_id, bool_id, 300, 1}, |
| 231 }; |
| 232 ASSERT_TRUE(AssertHeapDump(*heap_dump, expected_entries)) |
| 233 << "heap_dump = " << *heap_dump; |
| 234 } |
| 235 |
| 236 TEST(EventWriterTest, ExportHeapProfileEventData) { |
| 237 AllocationRegisterHelper foo_register; |
| 238 foo_register.Allocate(10, "Widget", {kBrowserMain, kCreateWidget}); |
| 239 foo_register.Allocate(16, "int[]", {kBrowserMain, kCreateWidget}); |
| 240 |
| 241 AllocationRegisterHelper bar_register; |
| 242 bar_register.Allocate(10, "Widget", {kRendererMain, kCreateWidget}); |
| 243 bar_register.Allocate(71, "char[]", {kRendererMain}); |
| 244 |
| 245 auto state = make_scoped_refptr(new MemoryDumpSessionState); |
| 246 state->CreateDeduplicators(); |
| 247 |
| 248 ExportedHeapDumpsMap heap_dumps; |
| 249 heap_dumps["foo"] = ExportHeapDump(foo_register, *state); |
| 250 heap_dumps["bar"] = ExportHeapDump(bar_register, *state); |
| 251 |
| 252 auto event_data = |
| 253 ToDictionary(ExportHeapProfileEventData(heap_dumps, *state)); |
| 254 ASSERT_TRUE(event_data); |
| 255 |
| 256 constexpr size_t kTopCount = 3; // version, allocators, maps |
| 257 ASSERT_EQ(kTopCount, event_data->size()); |
| 258 |
| 259 int version; |
| 260 ASSERT_TRUE(event_data->GetInteger("version", &version)); |
| 261 ASSERT_EQ(1, version); |
| 262 |
| 263 const DictionaryValue* allocators; |
| 264 ASSERT_TRUE(event_data->GetDictionary("allocators", &allocators)); |
| 265 { |
| 266 constexpr size_t kAllocatorCount = 2; // foo, bar |
| 267 ASSERT_EQ(kAllocatorCount, allocators->size()); |
| 268 |
| 269 const DictionaryValue* foo_dump; |
| 270 ASSERT_TRUE(allocators->GetDictionary("foo", &foo_dump)); |
| 271 ASSERT_TRUE(ToDictionary(heap_dumps["foo"])->Equals(foo_dump)); |
| 272 |
| 273 const DictionaryValue* bar_dump; |
| 274 ASSERT_TRUE(allocators->GetDictionary("bar", &bar_dump)); |
| 275 ASSERT_TRUE(ToDictionary(heap_dumps["bar"])->Equals(bar_dump)); |
| 276 } |
| 277 |
| 278 const DictionaryValue* maps; |
| 279 ASSERT_TRUE(event_data->GetDictionary("maps", &maps)); |
| 280 { |
| 281 constexpr size_t kMapCount = 3; // nodes, types, strings |
| 282 ASSERT_EQ(kMapCount, maps->size()); |
| 283 |
| 284 ASSERT_TRUE(maps->HasKey("nodes")); |
| 285 ASSERT_TRUE(maps->HasKey("types")); |
| 286 ASSERT_TRUE(maps->HasKey("strings")); |
| 287 } |
| 288 } |
| 289 |
| 290 } // namespace trace_event |
| 291 } // namespace base |
OLD | NEW |