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