OLD | NEW |
---|---|
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include <stdlib.h> | 5 #include <set> |
6 | |
7 #include <iterator> | |
8 #include <string> | 6 #include <string> |
9 | 7 |
10 #include "base/json/json_reader.h" | 8 #include "base/json/json_reader.h" |
9 #include "base/macros.h" | |
11 #include "base/memory/ref_counted.h" | 10 #include "base/memory/ref_counted.h" |
12 #include "base/memory/scoped_ptr.h" | 11 #include "base/memory/scoped_ptr.h" |
13 #include "base/trace_event/heap_profiler_allocation_context.h" | 12 #include "base/trace_event/heap_profiler_allocation_context.h" |
14 #include "base/trace_event/heap_profiler_heap_dump_writer.h" | 13 #include "base/trace_event/heap_profiler_heap_dump_writer.h" |
15 #include "base/trace_event/heap_profiler_stack_frame_deduplicator.h" | 14 #include "base/trace_event/heap_profiler_stack_frame_deduplicator.h" |
16 #include "base/trace_event/heap_profiler_type_name_deduplicator.h" | 15 #include "base/trace_event/heap_profiler_type_name_deduplicator.h" |
17 #include "base/trace_event/trace_event_argument.h" | 16 #include "base/trace_event/trace_event_argument.h" |
18 #include "base/values.h" | 17 #include "base/values.h" |
19 #include "testing/gtest/include/gtest/gtest.h" | 18 #include "testing/gtest/include/gtest/gtest.h" |
20 | 19 |
21 namespace { | 20 namespace { |
22 | 21 |
23 // Define all strings once, because the deduplicator requires pointer equality, | 22 // Define all strings once, because the deduplicator requires pointer equality, |
24 // and string interning is unreliable. | 23 // and string interning is unreliable. |
25 const char kBrowserMain[] = "BrowserMain"; | 24 const char kBrowserMain[] = "BrowserMain"; |
26 const char kRendererMain[] = "RendererMain"; | 25 const char kRendererMain[] = "RendererMain"; |
27 const char kCreateWidget[] = "CreateWidget"; | 26 const char kCreateWidget[] = "CreateWidget"; |
28 const char kInitialize[] = "Initialize"; | 27 const char kInitialize[] = "Initialize"; |
29 | 28 |
30 const char kInt[] = "int"; | 29 const char kInt[] = "int"; |
31 const char kBool[] = "bool"; | 30 const char kBool[] = "bool"; |
32 const char kString[] = "string"; | 31 const char kString[] = "string"; |
33 | 32 |
34 } // namespace | 33 } // namespace |
35 | 34 |
36 namespace base { | 35 namespace base { |
37 namespace trace_event { | 36 namespace trace_event { |
38 | 37 |
39 // Asserts that an integer stored in the json as a string has the correct value. | 38 using Entry = HeapDumpWriter::Entry; |
40 void AssertIntEq(const DictionaryValue* entry, | |
41 const char* key, | |
42 int expected_value) { | |
43 std::string str; | |
44 ASSERT_TRUE(entry->GetString(key, &str)); | |
45 ASSERT_EQ(expected_value, atoi(str.c_str())); | |
46 } | |
47 | 39 |
48 scoped_ptr<Value> DumpAndReadBack(HeapDumpWriter* writer) { | 40 scoped_ptr<const Value> WriteAndReadBack(const std::set<Entry>& entries) { |
49 scoped_refptr<TracedValue> traced_value = writer->WriteHeapDump(); | 41 scoped_refptr<TracedValue> traced_value = HeapDumpWriter::Write(entries); |
50 std::string json; | 42 std::string json; |
51 traced_value->AppendAsTraceFormat(&json); | 43 traced_value->AppendAsTraceFormat(&json); |
52 return JSONReader::Read(json); | 44 return JSONReader::Read(json); |
53 } | 45 } |
54 | 46 |
47 scoped_ptr<const DictionaryValue> WriteAndReadBackEntry(Entry entry) { | |
48 std::set<Entry> input_entries; | |
49 input_entries.insert(entry); | |
50 | |
51 scoped_ptr<const Value> json_dict = WriteAndReadBack(input_entries); | |
52 | |
53 // Note: Using Gtest |ASSERT_TRUE| instead of |DCHECK| causes a compile error. | |
petrcermak
2015/12/04 19:13:20
Not sure this is a good idea because DCHECK won't
Ruud van Asseldonk
2015/12/07 13:53:59
You are right. Looking into it, |ASSERT_TRUE| can
| |
54 const DictionaryValue* dictionary; | |
55 DCHECK(json_dict->GetAsDictionary(&dictionary)); | |
56 | |
57 const ListValue* json_entries; | |
58 DCHECK(dictionary->GetList("entries", &json_entries)); | |
59 | |
60 const DictionaryValue* json_entry; | |
61 DCHECK(json_entries->GetDictionary(0, &json_entry)); | |
62 | |
63 return json_entry->CreateDeepCopy(); | |
64 } | |
65 | |
66 // Given a desired stack frame ID and type ID, looks up the entry in the set and | |
67 // asserts that it is present and has the expected size. | |
68 void AssertSizeEq(const std::set<Entry>& entries, | |
69 int stack_frame_id, | |
70 int type_id, | |
71 size_t expected_size) { | |
72 // The comparison operator for |Entry| does not take size into account, so by | |
73 // setting only stack frame ID and type ID, the real entry can be found. | |
74 Entry entry; | |
75 entry.stack_frame_id = stack_frame_id; | |
76 entry.type_id = type_id; | |
77 auto it = entries.find(entry); | |
78 | |
79 ASSERT_NE(entries.end(), it) << "No entry found for sf = " << stack_frame_id | |
80 << ", type = " << type_id << "."; | |
81 ASSERT_EQ(expected_size, it->size) << "Wrong size for sf = " << stack_frame_id | |
82 << ", type = " << type_id << "."; | |
83 } | |
84 | |
85 TEST(HeapDumpWriterTest, EmptyBacktraceIndexIsEmptyString) { | |
petrcermak
2015/12/04 19:13:20
I don't think the test name matches what the test
Ruud van Asseldonk
2015/12/07 13:54:00
Fixed.
| |
86 Entry entry; | |
87 entry.stack_frame_id = -1; // -1 means empty backtrace. | |
88 entry.type_id = 0; | |
89 entry.size = 1; | |
90 | |
91 scoped_ptr<const DictionaryValue> json_entry = WriteAndReadBackEntry(entry); | |
92 | |
93 std::string backtrace_index; | |
94 ASSERT_TRUE(json_entry->GetString("bt", &backtrace_index)); | |
95 ASSERT_EQ("", backtrace_index); | |
96 | |
97 // Also verify that a non-negative backtrace index is dumped properly. | |
98 entry.stack_frame_id = 2; | |
99 json_entry = WriteAndReadBackEntry(entry); | |
100 ASSERT_TRUE(json_entry->GetString("bt", &backtrace_index)); | |
101 ASSERT_EQ("2", backtrace_index); | |
102 } | |
103 | |
104 TEST(HeapDumpWriterTest, CumulativeTypeEntryOmitsTypeId) { | |
petrcermak
2015/12/04 19:13:20
ditto
Ruud van Asseldonk
2015/12/07 13:54:00
Fixed.
| |
105 Entry entry; | |
106 entry.type_id = -1; // -1 means sum over all types. | |
107 entry.stack_frame_id = 0; | |
108 entry.size = 1; | |
109 | |
110 scoped_ptr<const DictionaryValue> json_entry = WriteAndReadBackEntry(entry); | |
111 | |
112 ASSERT_FALSE(json_entry->HasKey("type")); | |
113 | |
114 // Also verify that a non-negative type ID is dumped properly. | |
115 entry.type_id = 2; | |
116 json_entry = WriteAndReadBackEntry(entry); | |
117 std::string type_id; | |
118 ASSERT_TRUE(json_entry->GetString("type", &type_id)); | |
119 ASSERT_EQ("2", type_id); | |
120 } | |
121 | |
122 TEST(HeapDumpWriterTest, SizeIsHexadecimalString) { | |
123 // Take a number between 2^63 and 2^64 (or between 2^31 and 2^32 if |size_t| | |
124 // is not 64 bits). | |
125 const size_t large_value = | |
126 sizeof(size_t) == 8 ? 0xffffffffffffffc5 : 0xffffff9d; | |
127 const char* large_value_str = | |
128 sizeof(size_t) == 8 ? "ffffffffffffffc5" : "ffffff9d"; | |
129 Entry entry; | |
130 entry.type_id = 0; | |
131 entry.stack_frame_id = 0; | |
132 entry.size = large_value; | |
133 | |
134 scoped_ptr<const DictionaryValue> json_entry = WriteAndReadBackEntry(entry); | |
135 | |
136 std::string size; | |
137 ASSERT_TRUE(json_entry->GetString("size", &size)); | |
138 ASSERT_EQ(large_value_str, size); | |
139 } | |
140 | |
55 TEST(HeapDumpWriterTest, BacktraceTypeNameTable) { | 141 TEST(HeapDumpWriterTest, BacktraceTypeNameTable) { |
56 auto sf_deduplicator = make_scoped_refptr(new StackFrameDeduplicator); | 142 auto sf_deduplicator = make_scoped_refptr(new StackFrameDeduplicator); |
57 auto tn_deduplicator = make_scoped_refptr(new TypeNameDeduplicator); | 143 auto tn_deduplicator = make_scoped_refptr(new TypeNameDeduplicator); |
58 HeapDumpWriter writer(sf_deduplicator.get(), tn_deduplicator.get()); | 144 HeapDumpWriter writer(sf_deduplicator.get(), tn_deduplicator.get()); |
59 | 145 |
60 AllocationContext ctx = AllocationContext::Empty(); | 146 AllocationContext ctx = AllocationContext::Empty(); |
61 ctx.backtrace.frames[0] = kBrowserMain; | 147 ctx.backtrace.frames[0] = kBrowserMain; |
62 ctx.backtrace.frames[1] = kCreateWidget; | 148 ctx.backtrace.frames[1] = kCreateWidget; |
63 ctx.type_name = kInt; | 149 ctx.type_name = kInt; |
64 | 150 |
(...skipping 23 matching lines...) Expand all Loading... | |
88 // At this point the heap looks like this: | 174 // At this point the heap looks like this: |
89 // | 175 // |
90 // | | CrWidget <- BrMain | Init <- RenMain | Sum | | 176 // | | CrWidget <- BrMain | Init <- RenMain | Sum | |
91 // +--------+--------------------+-----------------+-----+ | 177 // +--------+--------------------+-----------------+-----+ |
92 // | int | 10 | 0 | 10 | | 178 // | int | 10 | 0 | 10 | |
93 // | bool | 18 | 30 | 48 | | 179 // | bool | 18 | 30 | 48 | |
94 // | string | 0 | 19 | 19 | | 180 // | string | 0 | 19 | 19 | |
95 // +--------+--------------------+-----------------+-----+ | 181 // +--------+--------------------+-----------------+-----+ |
96 // | Sum | 28 | 49 | 77 | | 182 // | Sum | 28 | 49 | 77 | |
97 | 183 |
98 scoped_ptr<Value> heap_dump = DumpAndReadBack(&writer); | 184 std::set<Entry> dump = writer.Dump(); |
99 | |
100 // The json heap dump should at least include this: | |
101 // { | |
102 // "entries": [ | |
103 // { "size": "4d" }, // 77 = 0x4d. | |
104 // { "size": "31", "bt": "id_of(Init <- RenMain)" }, // 49 = 0x31. | |
105 // { "size": "1c", "bt": "id_of(CrWidget <- BrMain)" }, // 28 = 0x1c. | |
106 // { "size": "30", "type": "id_of(bool)" }, // 48 = 0x30. | |
107 // { "size": "13", "type": "id_of(string)" }, // 19 = 0x13. | |
108 // { "size": "a", "type": "id_of(int)" } // 10 = 0xa. | |
109 // ] | |
110 // } | |
111 | 185 |
112 // Get the indices of the backtraces and types by adding them again to the | 186 // Get the indices of the backtraces and types by adding them again to the |
113 // deduplicator. Because they were added before, the same number is returned. | 187 // deduplicator. Because they were added before, the same number is returned. |
114 StackFrame bt0[] = {kRendererMain, kInitialize}; | 188 StackFrame bt0[] = {kRendererMain, kInitialize}; |
115 StackFrame bt1[] = {kBrowserMain, kCreateWidget}; | 189 StackFrame bt1[] = {kBrowserMain, kCreateWidget}; |
116 int bt_renderer_main_initialize = | 190 int bt_renderer_main = sf_deduplicator->Insert(bt0, bt0 + 1); |
117 sf_deduplicator->Insert(std::begin(bt0), std::end(bt0)); | 191 int bt_browser_main = sf_deduplicator->Insert(bt1, bt1 + 1); |
118 int bt_browser_main_create_widget = | 192 int bt_renderer_main_initialize = sf_deduplicator->Insert(bt0, bt0 + 2); |
119 sf_deduplicator->Insert(std::begin(bt1), std::end(bt1)); | 193 int bt_browser_main_create_widget = sf_deduplicator->Insert(bt1, bt1 + 2); |
120 int type_id_int = tn_deduplicator->Insert(kInt); | 194 int type_id_int = tn_deduplicator->Insert(kInt); |
121 int type_id_bool = tn_deduplicator->Insert(kBool); | 195 int type_id_bool = tn_deduplicator->Insert(kBool); |
122 int type_id_string = tn_deduplicator->Insert(kString); | 196 int type_id_string = tn_deduplicator->Insert(kString); |
123 | 197 |
124 const DictionaryValue* dictionary; | 198 // Full heap should have size 77. |
125 ASSERT_TRUE(heap_dump->GetAsDictionary(&dictionary)); | 199 AssertSizeEq(dump, -1, -1, 77); |
126 | 200 |
127 const ListValue* entries; | 201 // 49 bytes were allocated in RendererMain and children. Also check the type |
128 ASSERT_TRUE(dictionary->GetList("entries", &entries)); | 202 // breakdown. |
203 AssertSizeEq(dump, bt_renderer_main, -1, 49); | |
204 AssertSizeEq(dump, bt_renderer_main, type_id_bool, 30); | |
205 AssertSizeEq(dump, bt_renderer_main, type_id_string, 19); | |
129 | 206 |
130 // Keep counters to verify that every entry is present exactly once. | 207 // 28 bytes were allocated in BrowserMain and children. Also check the type |
131 int x4d_seen = 0; | 208 // breakdown. |
132 int x31_seen = 0; | 209 AssertSizeEq(dump, bt_browser_main, -1, 28); |
133 int x1c_seen = 0; | 210 AssertSizeEq(dump, bt_browser_main, type_id_int, 10); |
134 int x30_seen = 0; | 211 AssertSizeEq(dump, bt_browser_main, type_id_bool, 18); |
135 int x13_seen = 0; | |
136 int xa_seen = 0; | |
137 | 212 |
138 for (const Value* entry_as_value : *entries) { | 213 // In this test all bytes are allocated in leaf nodes, so check again one |
139 const DictionaryValue* entry; | 214 // level deeper. |
140 ASSERT_TRUE(entry_as_value->GetAsDictionary(&entry)); | 215 AssertSizeEq(dump, bt_renderer_main_initialize, -1, 49); |
216 AssertSizeEq(dump, bt_renderer_main_initialize, type_id_bool, 30); | |
217 AssertSizeEq(dump, bt_renderer_main_initialize, type_id_string, 19); | |
218 AssertSizeEq(dump, bt_browser_main_create_widget, -1, 28); | |
219 AssertSizeEq(dump, bt_browser_main_create_widget, type_id_int, 10); | |
220 AssertSizeEq(dump, bt_browser_main_create_widget, type_id_bool, 18); | |
141 | 221 |
142 // The "size" field, not to be confused with |entry->size()| which is the | 222 // The type breakdown of the entrie heap should have been dumped as well. |
143 // number of elements in the dictionary. | 223 AssertSizeEq(dump, -1, type_id_int, 10); |
144 std::string size; | 224 AssertSizeEq(dump, -1, type_id_bool, 48); |
145 ASSERT_TRUE(entry->GetString("size", &size)); | 225 AssertSizeEq(dump, -1, type_id_string, 19); |
146 | |
147 if (size == "4d") { | |
148 // Total size, should not include any other field. | |
149 ASSERT_EQ(1u, entry->size()); // Dictionary must have one element. | |
150 x4d_seen++; | |
151 } else if (size == "31") { | |
152 // Entry for backtrace "Initialize <- RendererMain". | |
153 ASSERT_EQ(2u, entry->size()); // Dictionary must have two elements. | |
154 AssertIntEq(entry, "bt", bt_renderer_main_initialize); | |
155 x31_seen++; | |
156 } else if (size == "1c") { | |
157 // Entry for backtrace "CreateWidget <- BrowserMain". | |
158 ASSERT_EQ(2u, entry->size()); // Dictionary must have two elements. | |
159 AssertIntEq(entry, "bt", bt_browser_main_create_widget); | |
160 x1c_seen++; | |
161 } else if (size == "30") { | |
162 // Entry for type bool. | |
163 ASSERT_EQ(2u, entry->size()); // Dictionary must have two elements. | |
164 AssertIntEq(entry, "type", type_id_bool); | |
165 x30_seen++; | |
166 } else if (size == "13") { | |
167 // Entry for type string. | |
168 ASSERT_EQ(2u, entry->size()); // Dictionary must have two elements. | |
169 AssertIntEq(entry, "type", type_id_string); | |
170 x13_seen++; | |
171 } else if (size == "a") { | |
172 // Entry for type int. | |
173 ASSERT_EQ(2u, entry->size()); // Dictionary must have two elements. | |
174 AssertIntEq(entry, "type", type_id_int); | |
175 xa_seen++; | |
176 } | |
177 } | |
178 | |
179 ASSERT_EQ(1, x4d_seen); | |
180 ASSERT_EQ(1, x31_seen); | |
181 ASSERT_EQ(1, x1c_seen); | |
182 ASSERT_EQ(1, x30_seen); | |
183 ASSERT_EQ(1, x13_seen); | |
184 ASSERT_EQ(1, xa_seen); | |
185 } | 226 } |
186 | 227 |
187 // Test that the entry for the empty backtrace ends up in the json with the | 228 // TODO(ruuda): Verify that cumulative sizes are computed correctly. |
188 // "bt" field set to the empty string. Also test that an entry for "unknown | 229 // TODO(ruuda): Verify that insignificant values are not dumped. |
189 // type" (nullptr type name) does not dereference the null pointer when writing | |
190 // the type names, and that the type ID is 0. | |
191 TEST(HeapDumpWriterTest, EmptyBacktraceIndexIsEmptyString) { | |
192 auto sf_deduplicator = make_scoped_refptr(new StackFrameDeduplicator); | |
193 auto tn_deduplicator = make_scoped_refptr(new TypeNameDeduplicator); | |
194 HeapDumpWriter writer(sf_deduplicator.get(), tn_deduplicator.get()); | |
195 | |
196 // A context with empty backtrace and unknown type (nullptr). | |
197 AllocationContext ctx = AllocationContext::Empty(); | |
198 | |
199 writer.InsertAllocation(ctx, 1); | |
200 | |
201 scoped_ptr<Value> heap_dump = DumpAndReadBack(&writer); | |
202 | |
203 const DictionaryValue* dictionary; | |
204 ASSERT_TRUE(heap_dump->GetAsDictionary(&dictionary)); | |
205 | |
206 const ListValue* entries; | |
207 ASSERT_TRUE(dictionary->GetList("entries", &entries)); | |
208 | |
209 int empty_backtrace_seen = 0; | |
210 int unknown_type_seen = 0; | |
211 | |
212 for (const Value* entry_as_value : *entries) { | |
213 const DictionaryValue* entry; | |
214 ASSERT_TRUE(entry_as_value->GetAsDictionary(&entry)); | |
215 | |
216 // Note that |entry->size()| is the number of elements in the dictionary. | |
217 if (entry->HasKey("bt") && entry->size() == 2) { | |
218 std::string backtrace; | |
219 ASSERT_TRUE(entry->GetString("bt", &backtrace)); | |
220 ASSERT_EQ("", backtrace); | |
221 empty_backtrace_seen++; | |
222 } | |
223 | |
224 if (entry->HasKey("type") && entry->size() == 2) { | |
225 std::string type_id; | |
226 ASSERT_TRUE(entry->GetString("type", &type_id)); | |
227 ASSERT_EQ("0", type_id); | |
228 unknown_type_seen++; | |
229 } | |
230 } | |
231 | |
232 ASSERT_EQ(1, unknown_type_seen); | |
233 ASSERT_EQ(1, empty_backtrace_seen); | |
234 } | |
235 | 230 |
236 } // namespace trace_event | 231 } // namespace trace_event |
237 } // namespace base | 232 } // namespace base |
OLD | NEW |