Home | History | Annotate | Download | only in cctest
      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 
     12 namespace i = v8::internal;
     13 using i::ClustersCoarser;
     14 using i::JSObjectsCluster;
     15 using i::JSObjectsRetainerTree;
     16 using i::JSObjectsClusterTree;
     17 using i::RetainerHeapProfile;
     18 
     19 
     20 static void CompileAndRunScript(const char *src) {
     21   v8::Script::Compile(v8::String::New(src))->Run();
     22 }
     23 
     24 
     25 namespace {
     26 
     27 class 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 
     54 TEST(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   for (i::HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next())
     68     cons_profile.CollectStats(obj);
     69   CHECK_EQ(0, cons_profile.f_count());
     70   cons_profile.PrintStats();
     71   CHECK_EQ(2, cons_profile.f_count());
     72 }
     73 
     74 
     75 static 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 
     94 static 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 
    104 static 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 
    123 static 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 
    142 TEST(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 
    181 TEST(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 
    212 TEST(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 
    265 TEST(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 
    313 namespace {
    314 
    315 class 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 
    360 TEST(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;
    376   for (i::HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next())
    377     ret_profile.CollectStats(obj);
    378   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.
    383   CHECK_EQ(i::StrLength("(global property);1,B;2,C;2"),
    384            i::StrLength(retainers_of_a));
    385   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
    393