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