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