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