OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
Primiano Tucci (use gerrit)
2017/03/09 11:47:44
ditto
| |
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 |