blob: 295b0ee060a28c07e2a7dd56cfc5284d05d91972 [file] [log] [blame]
Steve Blocka7e24c12009-10-30 11:49:00 +00001// Copyright 2009 the V8 project authors. All rights reserved.
2//
3// Tests for heap profiler
4
5#ifdef ENABLE_LOGGING_AND_PROFILING
6
7#include "v8.h"
8#include "heap-profiler.h"
9#include "string-stream.h"
10#include "cctest.h"
11
12namespace i = v8::internal;
13using i::ClustersCoarser;
14using i::JSObjectsCluster;
15using i::JSObjectsRetainerTree;
16using i::JSObjectsClusterTree;
17using i::RetainerHeapProfile;
18
19
20static void CompileAndRunScript(const char *src) {
21 v8::Script::Compile(v8::String::New(src))->Run();
22}
23
24
25namespace {
26
27class ConstructorHeapProfileTestHelper : public i::ConstructorHeapProfile {
28 public:
29 ConstructorHeapProfileTestHelper()
30 : i::ConstructorHeapProfile(),
31 f_name_(i::Factory::NewStringFromAscii(i::CStrVector("F"))),
32 f_count_(0) {
33 }
34
35 void Call(const JSObjectsCluster& cluster,
36 const i::NumberAndSizeInfo& number_and_size) {
37 if (f_name_->Equals(cluster.constructor())) {
38 CHECK_EQ(f_count_, 0);
39 f_count_ = number_and_size.number();
40 CHECK_GT(f_count_, 0);
41 }
42 }
43
44 int f_count() { return f_count_; }
45
46 private:
47 i::Handle<i::String> f_name_;
48 int f_count_;
49};
50
51} // namespace
52
53
54TEST(ConstructorProfile) {
55 v8::HandleScope scope;
56 v8::Handle<v8::Context> env = v8::Context::New();
57 env->Enter();
58
59 CompileAndRunScript(
60 "function F() {} // A constructor\n"
61 "var f1 = new F();\n"
62 "var f2 = new F();\n");
63
64 ConstructorHeapProfileTestHelper cons_profile;
65 i::AssertNoAllocation no_alloc;
66 i::HeapIterator iterator;
Leon Clarked91b9f72010-01-27 17:25:45 +000067 for (i::HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next())
Steve Blocka7e24c12009-10-30 11:49:00 +000068 cons_profile.CollectStats(obj);
Steve Blocka7e24c12009-10-30 11:49:00 +000069 CHECK_EQ(0, cons_profile.f_count());
70 cons_profile.PrintStats();
71 CHECK_EQ(2, cons_profile.f_count());
72}
73
74
75static JSObjectsCluster AddHeapObjectToTree(JSObjectsRetainerTree* tree,
76 i::String* constructor,
77 int instance,
78 JSObjectsCluster* ref1 = NULL,
79 JSObjectsCluster* ref2 = NULL,
80 JSObjectsCluster* ref3 = NULL) {
81 JSObjectsCluster o(constructor, reinterpret_cast<i::Object*>(instance));
82 JSObjectsClusterTree* o_tree = new JSObjectsClusterTree();
83 JSObjectsClusterTree::Locator o_loc;
84 if (ref1 != NULL) o_tree->Insert(*ref1, &o_loc);
85 if (ref2 != NULL) o_tree->Insert(*ref2, &o_loc);
86 if (ref3 != NULL) o_tree->Insert(*ref3, &o_loc);
87 JSObjectsRetainerTree::Locator loc;
88 tree->Insert(o, &loc);
89 loc.set_value(o_tree);
90 return o;
91}
92
93
94static void AddSelfReferenceToTree(JSObjectsRetainerTree* tree,
95 JSObjectsCluster* self_ref) {
96 JSObjectsRetainerTree::Locator loc;
97 CHECK(tree->Find(*self_ref, &loc));
98 JSObjectsClusterTree::Locator o_loc;
99 CHECK_NE(NULL, loc.value());
100 loc.value()->Insert(*self_ref, &o_loc);
101}
102
103
104static inline void CheckEqualsHelper(const char* file, int line,
105 const char* expected_source,
106 const JSObjectsCluster& expected,
107 const char* value_source,
108 const JSObjectsCluster& value) {
109 if (JSObjectsCluster::Compare(expected, value) != 0) {
110 i::HeapStringAllocator allocator;
111 i::StringStream stream(&allocator);
112 stream.Add("# Expected: ");
113 expected.DebugPrint(&stream);
114 stream.Add("\n# Found: ");
115 value.DebugPrint(&stream);
116 V8_Fatal(file, line, "CHECK_EQ(%s, %s) failed\n%s",
117 expected_source, value_source,
118 *stream.ToCString());
119 }
120}
121
122
123static inline void CheckNonEqualsHelper(const char* file, int line,
124 const char* expected_source,
125 const JSObjectsCluster& expected,
126 const char* value_source,
127 const JSObjectsCluster& value) {
128 if (JSObjectsCluster::Compare(expected, value) == 0) {
129 i::HeapStringAllocator allocator;
130 i::StringStream stream(&allocator);
131 stream.Add("# !Expected: ");
132 expected.DebugPrint(&stream);
133 stream.Add("\n# Found: ");
134 value.DebugPrint(&stream);
135 V8_Fatal(file, line, "CHECK_NE(%s, %s) failed\n%s",
136 expected_source, value_source,
137 *stream.ToCString());
138 }
139}
140
141
142TEST(ClustersCoarserSimple) {
143 v8::HandleScope scope;
144 v8::Handle<v8::Context> env = v8::Context::New();
145 env->Enter();
146
147 i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
148
149 JSObjectsRetainerTree tree;
150 JSObjectsCluster function(i::Heap::function_class_symbol());
151 JSObjectsCluster a(*i::Factory::NewStringFromAscii(i::CStrVector("A")));
152 JSObjectsCluster b(*i::Factory::NewStringFromAscii(i::CStrVector("B")));
153
154 // o1 <- Function
155 JSObjectsCluster o1 =
156 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x100, &function);
157 // o2 <- Function
158 JSObjectsCluster o2 =
159 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x200, &function);
160 // o3 <- A, B
161 JSObjectsCluster o3 =
162 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x300, &a, &b);
163 // o4 <- B, A
164 JSObjectsCluster o4 =
165 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x400, &b, &a);
166 // o5 <- A, B, Function
167 JSObjectsCluster o5 =
168 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x500,
169 &a, &b, &function);
170
171 ClustersCoarser coarser;
172 coarser.Process(&tree);
173
174 CHECK_EQ(coarser.GetCoarseEquivalent(o1), coarser.GetCoarseEquivalent(o2));
175 CHECK_EQ(coarser.GetCoarseEquivalent(o3), coarser.GetCoarseEquivalent(o4));
176 CHECK_NE(coarser.GetCoarseEquivalent(o1), coarser.GetCoarseEquivalent(o3));
177 CHECK_EQ(JSObjectsCluster(), coarser.GetCoarseEquivalent(o5));
178}
179
180
181TEST(ClustersCoarserMultipleConstructors) {
182 v8::HandleScope scope;
183 v8::Handle<v8::Context> env = v8::Context::New();
184 env->Enter();
185
186 i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
187
188 JSObjectsRetainerTree tree;
189 JSObjectsCluster function(i::Heap::function_class_symbol());
190
191 // o1 <- Function
192 JSObjectsCluster o1 =
193 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x100, &function);
194 // a1 <- Function
195 JSObjectsCluster a1 =
196 AddHeapObjectToTree(&tree, i::Heap::Array_symbol(), 0x1000, &function);
197 // o2 <- Function
198 JSObjectsCluster o2 =
199 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x200, &function);
200 // a2 <- Function
201 JSObjectsCluster a2 =
202 AddHeapObjectToTree(&tree, i::Heap::Array_symbol(), 0x2000, &function);
203
204 ClustersCoarser coarser;
205 coarser.Process(&tree);
206
207 CHECK_EQ(coarser.GetCoarseEquivalent(o1), coarser.GetCoarseEquivalent(o2));
208 CHECK_EQ(coarser.GetCoarseEquivalent(a1), coarser.GetCoarseEquivalent(a2));
209}
210
211
212TEST(ClustersCoarserPathsTraversal) {
213 v8::HandleScope scope;
214 v8::Handle<v8::Context> env = v8::Context::New();
215 env->Enter();
216
217 i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
218
219 JSObjectsRetainerTree tree;
220
221 // On the following graph:
222 //
223 // p
224 // <- o21 <- o11 <-
225 // q o
226 // <- o22 <- o12 <-
227 // r
228 //
229 // we expect that coarser will deduce equivalences: p ~ q ~ r,
230 // o21 ~ o22, and o11 ~ o12.
231
232 JSObjectsCluster o =
233 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x100);
234 JSObjectsCluster o11 =
235 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x110, &o);
236 JSObjectsCluster o12 =
237 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x120, &o);
238 JSObjectsCluster o21 =
239 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x210, &o11);
240 JSObjectsCluster o22 =
241 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x220, &o12);
242 JSObjectsCluster p =
243 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x300, &o21);
244 JSObjectsCluster q =
245 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x310, &o21, &o22);
246 JSObjectsCluster r =
247 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x320, &o22);
248
249 ClustersCoarser coarser;
250 coarser.Process(&tree);
251
252 CHECK_EQ(JSObjectsCluster(), coarser.GetCoarseEquivalent(o));
253 CHECK_NE(JSObjectsCluster(), coarser.GetCoarseEquivalent(o11));
254 CHECK_EQ(coarser.GetCoarseEquivalent(o11), coarser.GetCoarseEquivalent(o12));
255 CHECK_EQ(coarser.GetCoarseEquivalent(o21), coarser.GetCoarseEquivalent(o22));
256 CHECK_NE(coarser.GetCoarseEquivalent(o11), coarser.GetCoarseEquivalent(o21));
257 CHECK_NE(JSObjectsCluster(), coarser.GetCoarseEquivalent(p));
258 CHECK_EQ(coarser.GetCoarseEquivalent(p), coarser.GetCoarseEquivalent(q));
259 CHECK_EQ(coarser.GetCoarseEquivalent(q), coarser.GetCoarseEquivalent(r));
260 CHECK_NE(coarser.GetCoarseEquivalent(o11), coarser.GetCoarseEquivalent(p));
261 CHECK_NE(coarser.GetCoarseEquivalent(o21), coarser.GetCoarseEquivalent(p));
262}
263
264
265TEST(ClustersCoarserSelf) {
266 v8::HandleScope scope;
267 v8::Handle<v8::Context> env = v8::Context::New();
268 env->Enter();
269
270 i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
271
272 JSObjectsRetainerTree tree;
273
274 // On the following graph:
275 //
276 // p (self-referencing)
277 // <- o1 <-
278 // q (self-referencing) o
279 // <- o2 <-
280 // r (self-referencing)
281 //
282 // we expect that coarser will deduce equivalences: p ~ q ~ r, o1 ~ o2;
283
284 JSObjectsCluster o =
285 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x100);
286 JSObjectsCluster o1 =
287 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x110, &o);
288 JSObjectsCluster o2 =
289 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x120, &o);
290 JSObjectsCluster p =
291 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x300, &o1);
292 AddSelfReferenceToTree(&tree, &p);
293 JSObjectsCluster q =
294 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x310, &o1, &o2);
295 AddSelfReferenceToTree(&tree, &q);
296 JSObjectsCluster r =
297 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x320, &o2);
298 AddSelfReferenceToTree(&tree, &r);
299
300 ClustersCoarser coarser;
301 coarser.Process(&tree);
302
303 CHECK_EQ(JSObjectsCluster(), coarser.GetCoarseEquivalent(o));
304 CHECK_NE(JSObjectsCluster(), coarser.GetCoarseEquivalent(o1));
305 CHECK_EQ(coarser.GetCoarseEquivalent(o1), coarser.GetCoarseEquivalent(o2));
306 CHECK_NE(JSObjectsCluster(), coarser.GetCoarseEquivalent(p));
307 CHECK_EQ(coarser.GetCoarseEquivalent(p), coarser.GetCoarseEquivalent(q));
308 CHECK_EQ(coarser.GetCoarseEquivalent(q), coarser.GetCoarseEquivalent(r));
309 CHECK_NE(coarser.GetCoarseEquivalent(o1), coarser.GetCoarseEquivalent(p));
310}
311
312
313namespace {
314
315class RetainerProfilePrinter : public RetainerHeapProfile::Printer {
316 public:
317 RetainerProfilePrinter() : stream_(&allocator_), lines_(100) {}
318
319 void PrintRetainers(const JSObjectsCluster& cluster,
320 const i::StringStream& retainers) {
321 cluster.Print(&stream_);
322 stream_.Add("%s", *(retainers.ToCString()));
323 stream_.Put('\0');
324 }
325
326 const char* GetRetainers(const char* constructor) {
327 FillLines();
328 const size_t cons_len = strlen(constructor);
329 for (int i = 0; i < lines_.length(); ++i) {
330 if (strncmp(constructor, lines_[i], cons_len) == 0 &&
331 lines_[i][cons_len] == ',') {
332 return lines_[i] + cons_len + 1;
333 }
334 }
335 return NULL;
336 }
337
338 private:
339 void FillLines() {
340 if (lines_.length() > 0) return;
341 stream_.Put('\0');
342 stream_str_ = stream_.ToCString();
343 const char* pos = *stream_str_;
344 while (pos != NULL && *pos != '\0') {
345 lines_.Add(pos);
346 pos = strchr(pos, '\0');
347 if (pos != NULL) ++pos;
348 }
349 }
350
351 i::HeapStringAllocator allocator_;
352 i::StringStream stream_;
353 i::SmartPointer<const char> stream_str_;
354 i::List<const char*> lines_;
355};
356
357} // namespace
358
359
360TEST(RetainerProfile) {
361 v8::HandleScope scope;
362 v8::Handle<v8::Context> env = v8::Context::New();
363 env->Enter();
364
365 CompileAndRunScript(
366 "function A() {}\n"
367 "function B(x) { this.x = x; }\n"
368 "function C(x) { this.x1 = x; this.x2 = x; }\n"
369 "var a = new A();\n"
370 "var b1 = new B(a), b2 = new B(a);\n"
371 "var c = new C(a);");
372
373 RetainerHeapProfile ret_profile;
374 i::AssertNoAllocation no_alloc;
375 i::HeapIterator iterator;
Leon Clarked91b9f72010-01-27 17:25:45 +0000376 for (i::HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next())
Steve Blocka7e24c12009-10-30 11:49:00 +0000377 ret_profile.CollectStats(obj);
Steve Blocka7e24c12009-10-30 11:49:00 +0000378 RetainerProfilePrinter printer;
379 ret_profile.DebugPrintStats(&printer);
380 const char* retainers_of_a = printer.GetRetainers("A");
381 // The order of retainers is unspecified, so we check string length, and
382 // verify each retainer separately.
Steve Blockd0582a62009-12-15 09:54:21 +0000383 CHECK_EQ(i::StrLength("(global property);1,B;2,C;2"),
384 i::StrLength(retainers_of_a));
Steve Blocka7e24c12009-10-30 11:49:00 +0000385 CHECK(strstr(retainers_of_a, "(global property);1") != NULL);
386 CHECK(strstr(retainers_of_a, "B;2") != NULL);
387 CHECK(strstr(retainers_of_a, "C;2") != NULL);
388 CHECK_EQ("(global property);2", printer.GetRetainers("B"));
389 CHECK_EQ("(global property);1", printer.GetRetainers("C"));
390}
391
392#endif // ENABLE_LOGGING_AND_PROFILING