Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(200)

Side by Side Diff: base/trace_event/heap_profiler_heap_dump_writer_unittest.cc

Issue 1494293005: [Tracing] Enable heap dumps with both type info and backtraces (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@sfdedup-iter
Patch Set: Rename _all_ occurrences Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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 {
37 namespace internal {
38 38
39 // Asserts that an integer stored in the json as a string has the correct value. 39 scoped_ptr<const Value> WriteAndReadBack(const std::set<Entry>& entries) {
40 void AssertIntEq(const DictionaryValue* entry, 40 scoped_refptr<TracedValue> traced_value = Serialize(entries);
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
48 scoped_ptr<Value> DumpAndReadBack(HeapDumpWriter* writer) {
49 scoped_refptr<TracedValue> traced_value = writer->WriteHeapDump();
50 std::string json; 41 std::string json;
51 traced_value->AppendAsTraceFormat(&json); 42 traced_value->AppendAsTraceFormat(&json);
52 return JSONReader::Read(json); 43 return JSONReader::Read(json);
53 } 44 }
54 45
46 scoped_ptr<const DictionaryValue> WriteAndReadBackEntry(Entry entry) {
47 std::set<Entry> input_entries;
48 input_entries.insert(entry);
49
50 scoped_ptr<const Value> json_dict = WriteAndReadBack(input_entries);
51
52 // Note: Ideally these should use |ASSERT_TRUE| instead of |EXPECT_TRUE|, but
53 // |ASSERT_TRUE| can only be used in void functions.
54 const DictionaryValue* dictionary;
55 EXPECT_TRUE(json_dict->GetAsDictionary(&dictionary));
56
57 const ListValue* json_entries;
58 EXPECT_TRUE(dictionary->GetList("entries", &json_entries));
59
60 const DictionaryValue* json_entry;
61 EXPECT_TRUE(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, BacktraceIndex) {
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 // For an empty backtrace, the "bt" key cannot reference a stack frame.
94 // Instead it should be set to the empty string.
95 std::string backtrace_index;
96 ASSERT_TRUE(json_entry->GetString("bt", &backtrace_index));
97 ASSERT_EQ("", backtrace_index);
98
99 // Also verify that a non-negative backtrace index is dumped properly.
100 entry.stack_frame_id = 2;
101 json_entry = WriteAndReadBackEntry(entry);
102 ASSERT_TRUE(json_entry->GetString("bt", &backtrace_index));
103 ASSERT_EQ("2", backtrace_index);
104 }
105
106 TEST(HeapDumpWriterTest, TypeId) {
107 Entry entry;
108 entry.type_id = -1; // -1 means sum over all types.
109 entry.stack_frame_id = 0;
110 entry.size = 1;
111
112 scoped_ptr<const DictionaryValue> json_entry = WriteAndReadBackEntry(entry);
113
114 // Entries for the cumulative size of all types should not have the "type"
115 // key set.
116 ASSERT_FALSE(json_entry->HasKey("type"));
117
118 // Also verify that a non-negative type ID is dumped properly.
119 entry.type_id = 2;
120 json_entry = WriteAndReadBackEntry(entry);
121 std::string type_id;
122 ASSERT_TRUE(json_entry->GetString("type", &type_id));
123 ASSERT_EQ("2", type_id);
124 }
125
126 TEST(HeapDumpWriterTest, SizeIsHexadecimalString) {
127 // Take a number between 2^63 and 2^64 (or between 2^31 and 2^32 if |size_t|
128 // is not 64 bits).
129 const size_t large_value =
130 sizeof(size_t) == 8 ? 0xffffffffffffffc5 : 0xffffff9d;
131 const char* large_value_str =
132 sizeof(size_t) == 8 ? "ffffffffffffffc5" : "ffffff9d";
133 Entry entry;
134 entry.type_id = 0;
135 entry.stack_frame_id = 0;
136 entry.size = large_value;
137
138 scoped_ptr<const DictionaryValue> json_entry = WriteAndReadBackEntry(entry);
139
140 std::string size;
141 ASSERT_TRUE(json_entry->GetString("size", &size));
142 ASSERT_EQ(large_value_str, size);
143 }
144
55 TEST(HeapDumpWriterTest, BacktraceTypeNameTable) { 145 TEST(HeapDumpWriterTest, BacktraceTypeNameTable) {
56 auto sf_deduplicator = make_scoped_refptr(new StackFrameDeduplicator); 146 hash_map<AllocationContext, size_t> bytes_by_context;
57 auto tn_deduplicator = make_scoped_refptr(new TypeNameDeduplicator);
58 HeapDumpWriter writer(sf_deduplicator.get(), tn_deduplicator.get());
59 147
60 AllocationContext ctx = AllocationContext::Empty(); 148 AllocationContext ctx = AllocationContext::Empty();
61 ctx.backtrace.frames[0] = kBrowserMain; 149 ctx.backtrace.frames[0] = kBrowserMain;
62 ctx.backtrace.frames[1] = kCreateWidget; 150 ctx.backtrace.frames[1] = kCreateWidget;
63 ctx.type_name = kInt; 151 ctx.type_name = kInt;
64 152
65 // 10 bytes with context { type: int, bt: [BrowserMain, CreateWidget] }. 153 // 10 bytes with context { type: int, bt: [BrowserMain, CreateWidget] }.
66 writer.InsertAllocation(ctx, 2); 154 bytes_by_context[ctx] = 10;
67 writer.InsertAllocation(ctx, 3);
68 writer.InsertAllocation(ctx, 5);
69 155
70 ctx.type_name = kBool; 156 ctx.type_name = kBool;
71 157
72 // 18 bytes with context { type: bool, bt: [BrowserMain, CreateWidget] }. 158 // 18 bytes with context { type: bool, bt: [BrowserMain, CreateWidget] }.
73 writer.InsertAllocation(ctx, 7); 159 bytes_by_context[ctx] = 18;
74 writer.InsertAllocation(ctx, 11);
75 160
76 ctx.backtrace.frames[0] = kRendererMain; 161 ctx.backtrace.frames[0] = kRendererMain;
77 ctx.backtrace.frames[1] = kInitialize; 162 ctx.backtrace.frames[1] = kInitialize;
78 163
79 // 30 bytes with context { type: bool, bt: [RendererMain, Initialize] }. 164 // 30 bytes with context { type: bool, bt: [RendererMain, Initialize] }.
80 writer.InsertAllocation(ctx, 13); 165 bytes_by_context[ctx] = 30;
81 writer.InsertAllocation(ctx, 17);
82 166
83 ctx.type_name = kString; 167 ctx.type_name = kString;
84 168
85 // 19 bytes with context { type: string, bt: [RendererMain, Initialize] }. 169 // 19 bytes with context { type: string, bt: [RendererMain, Initialize] }.
86 writer.InsertAllocation(ctx, 19); 170 bytes_by_context[ctx] = 19;
87 171
88 // At this point the heap looks like this: 172 // At this point the heap looks like this:
89 // 173 //
90 // | | CrWidget <- BrMain | Init <- RenMain | Sum | 174 // | | CrWidget <- BrMain | Init <- RenMain | Sum |
91 // +--------+--------------------+-----------------+-----+ 175 // +--------+--------------------+-----------------+-----+
92 // | int | 10 | 0 | 10 | 176 // | int | 10 | 0 | 10 |
93 // | bool | 18 | 30 | 48 | 177 // | bool | 18 | 30 | 48 |
94 // | string | 0 | 19 | 19 | 178 // | string | 0 | 19 | 19 |
95 // +--------+--------------------+-----------------+-----+ 179 // +--------+--------------------+-----------------+-----+
96 // | Sum | 28 | 49 | 77 | 180 // | Sum | 28 | 49 | 77 |
97 181
98 scoped_ptr<Value> heap_dump = DumpAndReadBack(&writer); 182 auto sf_deduplicator = make_scoped_refptr(new StackFrameDeduplicator);
99 183 auto tn_deduplicator = make_scoped_refptr(new TypeNameDeduplicator);
100 // The json heap dump should at least include this: 184 HeapDumpWriter writer(sf_deduplicator.get(), tn_deduplicator.get());
101 // { 185 const std::set<Entry>& dump = writer.Summarize(bytes_by_context);
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 186
112 // Get the indices of the backtraces and types by adding them again to the 187 // 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. 188 // deduplicator. Because they were added before, the same number is returned.
114 StackFrame bt0[] = {kRendererMain, kInitialize}; 189 StackFrame bt0[] = {kRendererMain, kInitialize};
115 StackFrame bt1[] = {kBrowserMain, kCreateWidget}; 190 StackFrame bt1[] = {kBrowserMain, kCreateWidget};
116 int bt_renderer_main_initialize = 191 int bt_renderer_main = sf_deduplicator->Insert(bt0, bt0 + 1);
117 sf_deduplicator->Insert(std::begin(bt0), std::end(bt0)); 192 int bt_browser_main = sf_deduplicator->Insert(bt1, bt1 + 1);
118 int bt_browser_main_create_widget = 193 int bt_renderer_main_initialize = sf_deduplicator->Insert(bt0, bt0 + 2);
119 sf_deduplicator->Insert(std::begin(bt1), std::end(bt1)); 194 int bt_browser_main_create_widget = sf_deduplicator->Insert(bt1, bt1 + 2);
120 int type_id_int = tn_deduplicator->Insert(kInt); 195 int type_id_int = tn_deduplicator->Insert(kInt);
121 int type_id_bool = tn_deduplicator->Insert(kBool); 196 int type_id_bool = tn_deduplicator->Insert(kBool);
122 int type_id_string = tn_deduplicator->Insert(kString); 197 int type_id_string = tn_deduplicator->Insert(kString);
123 198
124 const DictionaryValue* dictionary; 199 // Full heap should have size 77.
125 ASSERT_TRUE(heap_dump->GetAsDictionary(&dictionary)); 200 AssertSizeEq(dump, -1, -1, 77);
126 201
127 const ListValue* entries; 202 // 49 bytes were allocated in RendererMain and children. Also check the type
128 ASSERT_TRUE(dictionary->GetList("entries", &entries)); 203 // breakdown.
204 AssertSizeEq(dump, bt_renderer_main, -1, 49);
205 AssertSizeEq(dump, bt_renderer_main, type_id_bool, 30);
206 AssertSizeEq(dump, bt_renderer_main, type_id_string, 19);
129 207
130 // Keep counters to verify that every entry is present exactly once. 208 // 28 bytes were allocated in BrowserMain and children. Also check the type
131 int x4d_seen = 0; 209 // breakdown.
132 int x31_seen = 0; 210 AssertSizeEq(dump, bt_browser_main, -1, 28);
133 int x1c_seen = 0; 211 AssertSizeEq(dump, bt_browser_main, type_id_int, 10);
134 int x30_seen = 0; 212 AssertSizeEq(dump, bt_browser_main, type_id_bool, 18);
135 int x13_seen = 0;
136 int xa_seen = 0;
137 213
138 for (const Value* entry_as_value : *entries) { 214 // In this test all bytes are allocated in leaf nodes, so check again one
139 const DictionaryValue* entry; 215 // level deeper.
140 ASSERT_TRUE(entry_as_value->GetAsDictionary(&entry)); 216 AssertSizeEq(dump, bt_renderer_main_initialize, -1, 49);
217 AssertSizeEq(dump, bt_renderer_main_initialize, type_id_bool, 30);
218 AssertSizeEq(dump, bt_renderer_main_initialize, type_id_string, 19);
219 AssertSizeEq(dump, bt_browser_main_create_widget, -1, 28);
220 AssertSizeEq(dump, bt_browser_main_create_widget, type_id_int, 10);
221 AssertSizeEq(dump, bt_browser_main_create_widget, type_id_bool, 18);
141 222
142 // The "size" field, not to be confused with |entry->size()| which is the 223 // The type breakdown of the entrie heap should have been dumped as well.
143 // number of elements in the dictionary. 224 AssertSizeEq(dump, -1, type_id_int, 10);
144 std::string size; 225 AssertSizeEq(dump, -1, type_id_bool, 48);
145 ASSERT_TRUE(entry->GetString("size", &size)); 226 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 } 227 }
186 228
187 // Test that the entry for the empty backtrace ends up in the json with the 229 // TODO(ruuda): Verify that cumulative sizes are computed correctly.
188 // "bt" field set to the empty string. Also test that an entry for "unknown 230 // 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 231
196 // A context with empty backtrace and unknown type (nullptr). 232 } // namespace internal
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
236 } // namespace trace_event 233 } // namespace trace_event
237 } // namespace base 234 } // namespace base
OLDNEW
« no previous file with comments | « base/trace_event/heap_profiler_heap_dump_writer.cc ('k') | content/child/web_memory_dump_provider_adapter.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698