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