test-heap-profiler.cc revision 791712a13f1814dd3ab5d1a5ab8ff5dbc476f6d6
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 "snapshot.h" 10#include "string-stream.h" 11#include "cctest.h" 12#include "zone-inl.h" 13#include "../include/v8-profiler.h" 14 15namespace i = v8::internal; 16using i::ClustersCoarser; 17using i::JSObjectsCluster; 18using i::JSObjectsRetainerTree; 19using i::JSObjectsClusterTree; 20using i::RetainerHeapProfile; 21 22 23static void CompileAndRunScript(const char *src) { 24 v8::Script::Compile(v8::String::New(src))->Run(); 25} 26 27 28namespace { 29 30class ConstructorHeapProfileTestHelper : public i::ConstructorHeapProfile { 31 public: 32 ConstructorHeapProfileTestHelper() 33 : i::ConstructorHeapProfile(), 34 f_name_(i::Factory::NewStringFromAscii(i::CStrVector("F"))), 35 f_count_(0) { 36 } 37 38 void Call(const JSObjectsCluster& cluster, 39 const i::NumberAndSizeInfo& number_and_size) { 40 if (f_name_->Equals(cluster.constructor())) { 41 CHECK_EQ(f_count_, 0); 42 f_count_ = number_and_size.number(); 43 CHECK_GT(f_count_, 0); 44 } 45 } 46 47 int f_count() { return f_count_; } 48 49 private: 50 i::Handle<i::String> f_name_; 51 int f_count_; 52}; 53 54} // namespace 55 56 57TEST(ConstructorProfile) { 58 v8::HandleScope scope; 59 LocalContext env; 60 61 CompileAndRunScript( 62 "function F() {} // A constructor\n" 63 "var f1 = new F();\n" 64 "var f2 = new F();\n"); 65 66 ConstructorHeapProfileTestHelper cons_profile; 67 i::AssertNoAllocation no_alloc; 68 i::HeapIterator iterator; 69 for (i::HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next()) 70 cons_profile.CollectStats(obj); 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 LocalContext env; 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 LocalContext env; 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 LocalContext env; 215 216 i::ZoneScope zn_scope(i::DELETE_ON_EXIT); 217 218 JSObjectsRetainerTree tree; 219 220 // On the following graph: 221 // 222 // p 223 // <- o21 <- o11 <- 224 // q o 225 // <- o22 <- o12 <- 226 // r 227 // 228 // we expect that coarser will deduce equivalences: p ~ q ~ r, 229 // o21 ~ o22, and o11 ~ o12. 230 231 JSObjectsCluster o = 232 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x100); 233 JSObjectsCluster o11 = 234 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x110, &o); 235 JSObjectsCluster o12 = 236 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x120, &o); 237 JSObjectsCluster o21 = 238 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x210, &o11); 239 JSObjectsCluster o22 = 240 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x220, &o12); 241 JSObjectsCluster p = 242 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x300, &o21); 243 JSObjectsCluster q = 244 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x310, &o21, &o22); 245 JSObjectsCluster r = 246 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x320, &o22); 247 248 ClustersCoarser coarser; 249 coarser.Process(&tree); 250 251 CHECK_EQ(JSObjectsCluster(), coarser.GetCoarseEquivalent(o)); 252 CHECK_NE(JSObjectsCluster(), coarser.GetCoarseEquivalent(o11)); 253 CHECK_EQ(coarser.GetCoarseEquivalent(o11), coarser.GetCoarseEquivalent(o12)); 254 CHECK_EQ(coarser.GetCoarseEquivalent(o21), coarser.GetCoarseEquivalent(o22)); 255 CHECK_NE(coarser.GetCoarseEquivalent(o11), coarser.GetCoarseEquivalent(o21)); 256 CHECK_NE(JSObjectsCluster(), coarser.GetCoarseEquivalent(p)); 257 CHECK_EQ(coarser.GetCoarseEquivalent(p), coarser.GetCoarseEquivalent(q)); 258 CHECK_EQ(coarser.GetCoarseEquivalent(q), coarser.GetCoarseEquivalent(r)); 259 CHECK_NE(coarser.GetCoarseEquivalent(o11), coarser.GetCoarseEquivalent(p)); 260 CHECK_NE(coarser.GetCoarseEquivalent(o21), coarser.GetCoarseEquivalent(p)); 261} 262 263 264TEST(ClustersCoarserSelf) { 265 v8::HandleScope scope; 266 LocalContext env; 267 268 i::ZoneScope zn_scope(i::DELETE_ON_EXIT); 269 270 JSObjectsRetainerTree tree; 271 272 // On the following graph: 273 // 274 // p (self-referencing) 275 // <- o1 <- 276 // q (self-referencing) o 277 // <- o2 <- 278 // r (self-referencing) 279 // 280 // we expect that coarser will deduce equivalences: p ~ q ~ r, o1 ~ o2; 281 282 JSObjectsCluster o = 283 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x100); 284 JSObjectsCluster o1 = 285 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x110, &o); 286 JSObjectsCluster o2 = 287 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x120, &o); 288 JSObjectsCluster p = 289 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x300, &o1); 290 AddSelfReferenceToTree(&tree, &p); 291 JSObjectsCluster q = 292 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x310, &o1, &o2); 293 AddSelfReferenceToTree(&tree, &q); 294 JSObjectsCluster r = 295 AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x320, &o2); 296 AddSelfReferenceToTree(&tree, &r); 297 298 ClustersCoarser coarser; 299 coarser.Process(&tree); 300 301 CHECK_EQ(JSObjectsCluster(), coarser.GetCoarseEquivalent(o)); 302 CHECK_NE(JSObjectsCluster(), coarser.GetCoarseEquivalent(o1)); 303 CHECK_EQ(coarser.GetCoarseEquivalent(o1), coarser.GetCoarseEquivalent(o2)); 304 CHECK_NE(JSObjectsCluster(), coarser.GetCoarseEquivalent(p)); 305 CHECK_EQ(coarser.GetCoarseEquivalent(p), coarser.GetCoarseEquivalent(q)); 306 CHECK_EQ(coarser.GetCoarseEquivalent(q), coarser.GetCoarseEquivalent(r)); 307 CHECK_NE(coarser.GetCoarseEquivalent(o1), coarser.GetCoarseEquivalent(p)); 308} 309 310 311namespace { 312 313class RetainerProfilePrinter : public RetainerHeapProfile::Printer { 314 public: 315 RetainerProfilePrinter() : stream_(&allocator_), lines_(100) {} 316 317 void PrintRetainers(const JSObjectsCluster& cluster, 318 const i::StringStream& retainers) { 319 cluster.Print(&stream_); 320 stream_.Add("%s", *(retainers.ToCString())); 321 stream_.Put('\0'); 322 } 323 324 const char* GetRetainers(const char* constructor) { 325 FillLines(); 326 const size_t cons_len = strlen(constructor); 327 for (int i = 0; i < lines_.length(); ++i) { 328 if (strncmp(constructor, lines_[i], cons_len) == 0 && 329 lines_[i][cons_len] == ',') { 330 return lines_[i] + cons_len + 1; 331 } 332 } 333 return NULL; 334 } 335 336 private: 337 void FillLines() { 338 if (lines_.length() > 0) return; 339 stream_.Put('\0'); 340 stream_str_ = stream_.ToCString(); 341 const char* pos = *stream_str_; 342 while (pos != NULL && *pos != '\0') { 343 lines_.Add(pos); 344 pos = strchr(pos, '\0'); 345 if (pos != NULL) ++pos; 346 } 347 } 348 349 i::HeapStringAllocator allocator_; 350 i::StringStream stream_; 351 i::SmartPointer<const char> stream_str_; 352 i::List<const char*> lines_; 353}; 354 355} // namespace 356 357 358TEST(RetainerProfile) { 359 v8::HandleScope scope; 360 LocalContext env; 361 362 CompileAndRunScript( 363 "function A() {}\n" 364 "function B(x) { this.x = x; }\n" 365 "function C(x) { this.x1 = x; this.x2 = x; }\n" 366 "var a = new A();\n" 367 "var b1 = new B(a), b2 = new B(a);\n" 368 "var c = new C(a);"); 369 370 RetainerHeapProfile ret_profile; 371 i::AssertNoAllocation no_alloc; 372 i::HeapIterator iterator; 373 for (i::HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next()) 374 ret_profile.CollectStats(obj); 375 ret_profile.CoarseAndAggregate(); 376 RetainerProfilePrinter printer; 377 ret_profile.DebugPrintStats(&printer); 378 const char* retainers_of_a = printer.GetRetainers("A"); 379 // The order of retainers is unspecified, so we check string length, and 380 // verify each retainer separately. 381 CHECK_EQ(i::StrLength("(global property);1,B;2,C;2"), 382 i::StrLength(retainers_of_a)); 383 CHECK(strstr(retainers_of_a, "(global property);1") != NULL); 384 CHECK(strstr(retainers_of_a, "B;2") != NULL); 385 CHECK(strstr(retainers_of_a, "C;2") != NULL); 386 CHECK_EQ("(global property);2", printer.GetRetainers("B")); 387 CHECK_EQ("(global property);1", printer.GetRetainers("C")); 388} 389 390 391namespace { 392 393class NamedEntriesDetector { 394 public: 395 NamedEntriesDetector() 396 : has_A1(false), has_B1(false), has_C1(false), 397 has_A2(false), has_B2(false), has_C2(false) { 398 } 399 400 void Apply(i::HeapEntry** entry_ptr) { 401 if (IsReachableNodeWithName(*entry_ptr, "A1")) has_A1 = true; 402 if (IsReachableNodeWithName(*entry_ptr, "B1")) has_B1 = true; 403 if (IsReachableNodeWithName(*entry_ptr, "C1")) has_C1 = true; 404 if (IsReachableNodeWithName(*entry_ptr, "A2")) has_A2 = true; 405 if (IsReachableNodeWithName(*entry_ptr, "B2")) has_B2 = true; 406 if (IsReachableNodeWithName(*entry_ptr, "C2")) has_C2 = true; 407 } 408 409 static bool IsReachableNodeWithName(i::HeapEntry* entry, const char* name) { 410 return strcmp(name, entry->name()) == 0 && entry->painted_reachable(); 411 } 412 413 bool has_A1; 414 bool has_B1; 415 bool has_C1; 416 bool has_A2; 417 bool has_B2; 418 bool has_C2; 419}; 420 421} // namespace 422 423 424static const v8::HeapGraphNode* GetGlobalObject( 425 const v8::HeapSnapshot* snapshot) { 426 CHECK_EQ(1, snapshot->GetRoot()->GetChildrenCount()); 427 return snapshot->GetRoot()->GetChild(0)->GetToNode(); 428} 429 430 431static const v8::HeapGraphNode* GetProperty(const v8::HeapGraphNode* node, 432 v8::HeapGraphEdge::Type type, 433 const char* name) { 434 for (int i = 0, count = node->GetChildrenCount(); i < count; ++i) { 435 const v8::HeapGraphEdge* prop = node->GetChild(i); 436 v8::String::AsciiValue prop_name(prop->GetName()); 437 if (prop->GetType() == type && strcmp(name, *prop_name) == 0) 438 return prop->GetToNode(); 439 } 440 return NULL; 441} 442 443 444static bool IsNodeRetainedAs(const v8::HeapGraphNode* node, 445 v8::HeapGraphEdge::Type type, 446 const char* name) { 447 for (int i = 0, count = node->GetRetainersCount(); i < count; ++i) { 448 const v8::HeapGraphEdge* prop = node->GetRetainer(i); 449 v8::String::AsciiValue prop_name(prop->GetName()); 450 if (prop->GetType() == type && strcmp(name, *prop_name) == 0) 451 return true; 452 } 453 return false; 454} 455 456 457static bool HasString(const v8::HeapGraphNode* node, const char* contents) { 458 for (int i = 0, count = node->GetChildrenCount(); i < count; ++i) { 459 const v8::HeapGraphEdge* prop = node->GetChild(i); 460 const v8::HeapGraphNode* node = prop->GetToNode(); 461 if (node->GetType() == v8::HeapGraphNode::kString) { 462 v8::String::AsciiValue node_name(node->GetName()); 463 if (strcmp(contents, *node_name) == 0) return true; 464 } 465 } 466 return false; 467} 468 469 470TEST(HeapSnapshot) { 471 v8::HandleScope scope; 472 v8::Handle<v8::String> token1 = v8::String::New("token1"); 473 LocalContext env1; 474 env1->SetSecurityToken(token1); 475 476 CompileAndRunScript( 477 "function A1() {}\n" 478 "function B1(x) { this.x = x; }\n" 479 "function C1(x) { this.x1 = x; this.x2 = x; }\n" 480 "var a1 = new A1();\n" 481 "var b1_1 = new B1(a1), b1_2 = new B1(a1);\n" 482 "var c1 = new C1(a1);"); 483 484 v8::Handle<v8::String> token2 = v8::String::New("token2"); 485 LocalContext env2; 486 env2->SetSecurityToken(token2); 487 488 CompileAndRunScript( 489 "function A2() {}\n" 490 "function B2(x) { return function() { return typeof x; }; }\n" 491 "function C2(x) { this.x1 = x; this.x2 = x; this[1] = x; }\n" 492 "var a2 = new A2();\n" 493 "var b2_1 = new B2(a2), b2_2 = new B2(a2);\n" 494 "var c2 = new C2(a2);"); 495 const v8::HeapSnapshot* snapshot_env2 = 496 v8::HeapProfiler::TakeSnapshot(v8::String::New("env2")); 497 i::HeapSnapshot* i_snapshot_env2 = 498 const_cast<i::HeapSnapshot*>( 499 reinterpret_cast<const i::HeapSnapshot*>(snapshot_env2)); 500 const v8::HeapGraphNode* global_env2 = GetGlobalObject(snapshot_env2); 501 // Paint all nodes reachable from global object. 502 i_snapshot_env2->ClearPaint(); 503 const_cast<i::HeapEntry*>( 504 reinterpret_cast<const i::HeapEntry*>(global_env2))->PaintAllReachable(); 505 506 // Verify, that JS global object of env2 doesn't have '..1' 507 // properties, but has '..2' properties. 508 CHECK_EQ(NULL, GetProperty(global_env2, v8::HeapGraphEdge::kProperty, "a1")); 509 CHECK_EQ( 510 NULL, GetProperty(global_env2, v8::HeapGraphEdge::kProperty, "b1_1")); 511 CHECK_EQ( 512 NULL, GetProperty(global_env2, v8::HeapGraphEdge::kProperty, "b1_2")); 513 CHECK_EQ(NULL, GetProperty(global_env2, v8::HeapGraphEdge::kProperty, "c1")); 514 const v8::HeapGraphNode* a2_node = 515 GetProperty(global_env2, v8::HeapGraphEdge::kProperty, "a2"); 516 CHECK_NE(NULL, a2_node); 517 CHECK_NE( 518 NULL, GetProperty(global_env2, v8::HeapGraphEdge::kProperty, "b2_1")); 519 CHECK_NE( 520 NULL, GetProperty(global_env2, v8::HeapGraphEdge::kProperty, "b2_2")); 521 CHECK_NE(NULL, GetProperty(global_env2, v8::HeapGraphEdge::kProperty, "c2")); 522 523 // Verify that anything related to '[ABC]1' is not reachable. 524 NamedEntriesDetector det; 525 i_snapshot_env2->IterateEntries(&det); 526 CHECK(!det.has_A1); 527 CHECK(!det.has_B1); 528 CHECK(!det.has_C1); 529 CHECK(det.has_A2); 530 CHECK(det.has_B2); 531 CHECK(det.has_C2); 532 533 // Verify 'a2' object retainers. They are: 534 // - (global object).a2 535 // - c2.x1, c2.x2, c2[1] 536 // - b2_1 and b2_2 closures: via 'x' variable 537 CHECK_EQ(6, a2_node->GetRetainingPathsCount()); 538 bool has_global_obj_a2_ref = false; 539 bool has_c2_x1_ref = false, has_c2_x2_ref = false, has_c2_1_ref = false; 540 bool has_b2_1_x_ref = false, has_b2_2_x_ref = false; 541 for (int i = 0; i < a2_node->GetRetainingPathsCount(); ++i) { 542 const v8::HeapGraphPath* path = a2_node->GetRetainingPath(i); 543 const int edges_count = path->GetEdgesCount(); 544 CHECK_GT(edges_count, 0); 545 const v8::HeapGraphEdge* last_edge = path->GetEdge(edges_count - 1); 546 v8::String::AsciiValue last_edge_name(last_edge->GetName()); 547 if (strcmp("a2", *last_edge_name) == 0 548 && last_edge->GetType() == v8::HeapGraphEdge::kProperty) { 549 has_global_obj_a2_ref = true; 550 continue; 551 } 552 CHECK_GT(edges_count, 1); 553 const v8::HeapGraphEdge* prev_edge = path->GetEdge(edges_count - 2); 554 v8::String::AsciiValue prev_edge_name(prev_edge->GetName()); 555 if (strcmp("x1", *last_edge_name) == 0 556 && last_edge->GetType() == v8::HeapGraphEdge::kProperty 557 && strcmp("c2", *prev_edge_name) == 0) has_c2_x1_ref = true; 558 if (strcmp("x2", *last_edge_name) == 0 559 && last_edge->GetType() == v8::HeapGraphEdge::kProperty 560 && strcmp("c2", *prev_edge_name) == 0) has_c2_x2_ref = true; 561 if (strcmp("1", *last_edge_name) == 0 562 && last_edge->GetType() == v8::HeapGraphEdge::kElement 563 && strcmp("c2", *prev_edge_name) == 0) has_c2_1_ref = true; 564 if (strcmp("x", *last_edge_name) == 0 565 && last_edge->GetType() == v8::HeapGraphEdge::kContextVariable 566 && strcmp("b2_1", *prev_edge_name) == 0) has_b2_1_x_ref = true; 567 if (strcmp("x", *last_edge_name) == 0 568 && last_edge->GetType() == v8::HeapGraphEdge::kContextVariable 569 && strcmp("b2_2", *prev_edge_name) == 0) has_b2_2_x_ref = true; 570 } 571 CHECK(has_global_obj_a2_ref); 572 CHECK(has_c2_x1_ref); 573 CHECK(has_c2_x2_ref); 574 CHECK(has_c2_1_ref); 575 CHECK(has_b2_1_x_ref); 576 CHECK(has_b2_2_x_ref); 577} 578 579 580TEST(HeapSnapshotObjectSizes) { 581 v8::HandleScope scope; 582 LocalContext env; 583 584 // -a-> X1 --a 585 // x -b-> X2 <-| 586 CompileAndRunScript( 587 "function X(a, b) { this.a = a; this.b = b; }\n" 588 "x = new X(new X(), new X());\n" 589 "x.a.a = x.b;"); 590 const v8::HeapSnapshot* snapshot = 591 v8::HeapProfiler::TakeSnapshot(v8::String::New("sizes")); 592 const v8::HeapGraphNode* global = GetGlobalObject(snapshot); 593 const v8::HeapGraphNode* x = 594 GetProperty(global, v8::HeapGraphEdge::kProperty, "x"); 595 CHECK_NE(NULL, x); 596 const v8::HeapGraphNode* x_prototype = 597 GetProperty(x, v8::HeapGraphEdge::kProperty, "prototype"); 598 CHECK_NE(NULL, x_prototype); 599 const v8::HeapGraphNode* x1 = 600 GetProperty(x, v8::HeapGraphEdge::kProperty, "a"); 601 CHECK_NE(NULL, x1); 602 const v8::HeapGraphNode* x2 = 603 GetProperty(x, v8::HeapGraphEdge::kProperty, "b"); 604 CHECK_NE(NULL, x2); 605 CHECK_EQ( 606 x->GetSelfSize() * 3, 607 x->GetReachableSize() - x_prototype->GetReachableSize()); 608 CHECK_EQ( 609 x->GetSelfSize() * 3 + x_prototype->GetSelfSize(), x->GetRetainedSize()); 610 CHECK_EQ( 611 x1->GetSelfSize() * 2, 612 x1->GetReachableSize() - x_prototype->GetReachableSize()); 613 CHECK_EQ( 614 x1->GetSelfSize(), x1->GetRetainedSize()); 615 CHECK_EQ( 616 x2->GetSelfSize(), 617 x2->GetReachableSize() - x_prototype->GetReachableSize()); 618 CHECK_EQ( 619 x2->GetSelfSize(), x2->GetRetainedSize()); 620} 621 622 623TEST(HeapSnapshotEntryChildren) { 624 v8::HandleScope scope; 625 LocalContext env; 626 627 CompileAndRunScript( 628 "function A() { }\n" 629 "a = new A;"); 630 const v8::HeapSnapshot* snapshot = 631 v8::HeapProfiler::TakeSnapshot(v8::String::New("children")); 632 const v8::HeapGraphNode* global = GetGlobalObject(snapshot); 633 for (int i = 0, count = global->GetChildrenCount(); i < count; ++i) { 634 const v8::HeapGraphEdge* prop = global->GetChild(i); 635 CHECK_EQ(global, prop->GetFromNode()); 636 } 637 const v8::HeapGraphNode* a = 638 GetProperty(global, v8::HeapGraphEdge::kProperty, "a"); 639 CHECK_NE(NULL, a); 640 for (int i = 0, count = a->GetChildrenCount(); i < count; ++i) { 641 const v8::HeapGraphEdge* prop = a->GetChild(i); 642 CHECK_EQ(a, prop->GetFromNode()); 643 } 644} 645 646 647TEST(HeapSnapshotCodeObjects) { 648 v8::HandleScope scope; 649 LocalContext env; 650 651 CompileAndRunScript( 652 "function lazy(x) { return x - 1; }\n" 653 "function compiled(x) { return x + 1; }\n" 654 "var inferred = function(x) { return x; }\n" 655 "var anonymous = (function() { return function() { return 0; } })();\n" 656 "compiled(1)"); 657 const v8::HeapSnapshot* snapshot = 658 v8::HeapProfiler::TakeSnapshot(v8::String::New("code")); 659 660 const v8::HeapGraphNode* global = GetGlobalObject(snapshot); 661 const v8::HeapGraphNode* compiled = 662 GetProperty(global, v8::HeapGraphEdge::kProperty, "compiled"); 663 CHECK_NE(NULL, compiled); 664 CHECK_EQ(v8::HeapGraphNode::kClosure, compiled->GetType()); 665 const v8::HeapGraphNode* lazy = 666 GetProperty(global, v8::HeapGraphEdge::kProperty, "lazy"); 667 CHECK_NE(NULL, lazy); 668 CHECK_EQ(v8::HeapGraphNode::kClosure, lazy->GetType()); 669 const v8::HeapGraphNode* inferred = 670 GetProperty(global, v8::HeapGraphEdge::kProperty, "inferred"); 671 CHECK_NE(NULL, inferred); 672 CHECK_EQ(v8::HeapGraphNode::kClosure, inferred->GetType()); 673 v8::String::AsciiValue inferred_name(inferred->GetName()); 674 CHECK_EQ("inferred", *inferred_name); 675 const v8::HeapGraphNode* anonymous = 676 GetProperty(global, v8::HeapGraphEdge::kProperty, "anonymous"); 677 CHECK_NE(NULL, anonymous); 678 CHECK_EQ(v8::HeapGraphNode::kClosure, anonymous->GetType()); 679 v8::String::AsciiValue anonymous_name(anonymous->GetName()); 680 CHECK_EQ("(anonymous function)", *anonymous_name); 681 682 // Find references to code. 683 const v8::HeapGraphNode* compiled_code = 684 GetProperty(compiled, v8::HeapGraphEdge::kInternal, "code"); 685 CHECK_NE(NULL, compiled_code); 686 const v8::HeapGraphNode* lazy_code = 687 GetProperty(lazy, v8::HeapGraphEdge::kInternal, "code"); 688 CHECK_NE(NULL, lazy_code); 689 690 // Verify that non-compiled code doesn't contain references to "x" 691 // literal, while compiled code does. The scope info is stored in FixedArray 692 // objects attached to the SharedFunctionInfo. 693 bool compiled_references_x = false, lazy_references_x = false; 694 for (int i = 0, count = compiled_code->GetChildrenCount(); i < count; ++i) { 695 const v8::HeapGraphEdge* prop = compiled_code->GetChild(i); 696 const v8::HeapGraphNode* node = prop->GetToNode(); 697 if (node->GetType() == v8::HeapGraphNode::kArray) { 698 if (HasString(node, "x")) { 699 compiled_references_x = true; 700 break; 701 } 702 } 703 } 704 for (int i = 0, count = lazy_code->GetChildrenCount(); i < count; ++i) { 705 const v8::HeapGraphEdge* prop = lazy_code->GetChild(i); 706 const v8::HeapGraphNode* node = prop->GetToNode(); 707 if (node->GetType() == v8::HeapGraphNode::kArray) { 708 if (HasString(node, "x")) { 709 lazy_references_x = true; 710 break; 711 } 712 } 713 } 714 CHECK(compiled_references_x); 715 CHECK(!lazy_references_x); 716} 717 718 719// Trying to introduce a check helper for uint64_t causes many 720// overloading ambiguities, so it seems easier just to cast 721// them to a signed type. 722#define CHECK_EQ_UINT64_T(a, b) \ 723 CHECK_EQ(static_cast<int64_t>(a), static_cast<int64_t>(b)) 724#define CHECK_NE_UINT64_T(a, b) \ 725 CHECK((a) != (b)) // NOLINT 726 727TEST(HeapEntryIdsAndGC) { 728 v8::HandleScope scope; 729 LocalContext env; 730 731 CompileAndRunScript( 732 "function A() {}\n" 733 "function B(x) { this.x = x; }\n" 734 "var a = new A();\n" 735 "var b = new B(a);"); 736 const v8::HeapSnapshot* snapshot1 = 737 v8::HeapProfiler::TakeSnapshot(v8::String::New("s1")); 738 739 i::Heap::CollectAllGarbage(true); // Enforce compaction. 740 741 const v8::HeapSnapshot* snapshot2 = 742 v8::HeapProfiler::TakeSnapshot(v8::String::New("s2")); 743 744 const v8::HeapGraphNode* global1 = GetGlobalObject(snapshot1); 745 const v8::HeapGraphNode* global2 = GetGlobalObject(snapshot2); 746 CHECK_NE_UINT64_T(0, global1->GetId()); 747 CHECK_EQ_UINT64_T(global1->GetId(), global2->GetId()); 748 const v8::HeapGraphNode* A1 = 749 GetProperty(global1, v8::HeapGraphEdge::kProperty, "A"); 750 CHECK_NE(NULL, A1); 751 const v8::HeapGraphNode* A2 = 752 GetProperty(global2, v8::HeapGraphEdge::kProperty, "A"); 753 CHECK_NE(NULL, A2); 754 CHECK_NE_UINT64_T(0, A1->GetId()); 755 CHECK_EQ_UINT64_T(A1->GetId(), A2->GetId()); 756 const v8::HeapGraphNode* B1 = 757 GetProperty(global1, v8::HeapGraphEdge::kProperty, "B"); 758 CHECK_NE(NULL, B1); 759 const v8::HeapGraphNode* B2 = 760 GetProperty(global2, v8::HeapGraphEdge::kProperty, "B"); 761 CHECK_NE(NULL, B2); 762 CHECK_NE_UINT64_T(0, B1->GetId()); 763 CHECK_EQ_UINT64_T(B1->GetId(), B2->GetId()); 764 const v8::HeapGraphNode* a1 = 765 GetProperty(global1, v8::HeapGraphEdge::kProperty, "a"); 766 CHECK_NE(NULL, a1); 767 const v8::HeapGraphNode* a2 = 768 GetProperty(global2, v8::HeapGraphEdge::kProperty, "a"); 769 CHECK_NE(NULL, a2); 770 CHECK_NE_UINT64_T(0, a1->GetId()); 771 CHECK_EQ_UINT64_T(a1->GetId(), a2->GetId()); 772 const v8::HeapGraphNode* b1 = 773 GetProperty(global1, v8::HeapGraphEdge::kProperty, "b"); 774 CHECK_NE(NULL, b1); 775 const v8::HeapGraphNode* b2 = 776 GetProperty(global2, v8::HeapGraphEdge::kProperty, "b"); 777 CHECK_NE(NULL, b2); 778 CHECK_NE_UINT64_T(0, b1->GetId()); 779 CHECK_EQ_UINT64_T(b1->GetId(), b2->GetId()); 780} 781 782 783TEST(HeapSnapshotsDiff) { 784 v8::HandleScope scope; 785 LocalContext env; 786 787 CompileAndRunScript( 788 "function A() {}\n" 789 "function B(x) { this.x = x; }\n" 790 "var a = new A();\n" 791 "var b = new B(a);"); 792 const v8::HeapSnapshot* snapshot1 = 793 v8::HeapProfiler::TakeSnapshot(v8::String::New("s1")); 794 795 CompileAndRunScript( 796 "delete a;\n" 797 "b.x = null;\n" 798 "var a = new A();\n" 799 "var b2 = new B(a);"); 800 const v8::HeapSnapshot* snapshot2 = 801 v8::HeapProfiler::TakeSnapshot(v8::String::New("s2")); 802 803 const v8::HeapSnapshotsDiff* diff = snapshot1->CompareWith(snapshot2); 804 805 // Verify additions: ensure that addition of A and B was detected. 806 const v8::HeapGraphNode* additions_root = diff->GetAdditionsRoot(); 807 bool found_A = false, found_B = false; 808 uint64_t s1_A_id = 0; 809 for (int i = 0, count = additions_root->GetChildrenCount(); i < count; ++i) { 810 const v8::HeapGraphEdge* prop = additions_root->GetChild(i); 811 const v8::HeapGraphNode* node = prop->GetToNode(); 812 if (node->GetType() == v8::HeapGraphNode::kObject) { 813 v8::String::AsciiValue node_name(node->GetName()); 814 if (strcmp(*node_name, "A") == 0) { 815 CHECK(IsNodeRetainedAs(node, v8::HeapGraphEdge::kProperty, "a")); 816 CHECK(!found_A); 817 found_A = true; 818 s1_A_id = node->GetId(); 819 } else if (strcmp(*node_name, "B") == 0) { 820 CHECK(IsNodeRetainedAs(node, v8::HeapGraphEdge::kProperty, "b2")); 821 CHECK(!found_B); 822 found_B = true; 823 } 824 } 825 } 826 CHECK(found_A); 827 CHECK(found_B); 828 829 // Verify deletions: ensure that deletion of A was detected. 830 const v8::HeapGraphNode* deletions_root = diff->GetDeletionsRoot(); 831 bool found_A_del = false; 832 uint64_t s2_A_id = 0; 833 for (int i = 0, count = deletions_root->GetChildrenCount(); i < count; ++i) { 834 const v8::HeapGraphEdge* prop = deletions_root->GetChild(i); 835 const v8::HeapGraphNode* node = prop->GetToNode(); 836 if (node->GetType() == v8::HeapGraphNode::kObject) { 837 v8::String::AsciiValue node_name(node->GetName()); 838 if (strcmp(*node_name, "A") == 0) { 839 CHECK(IsNodeRetainedAs(node, v8::HeapGraphEdge::kProperty, "a")); 840 CHECK(!found_A_del); 841 found_A_del = true; 842 s2_A_id = node->GetId(); 843 } 844 } 845 } 846 CHECK(found_A_del); 847 CHECK_NE_UINT64_T(0, s1_A_id); 848 CHECK(s1_A_id != s2_A_id); 849} 850 851 852namespace v8 { 853namespace internal { 854 855class HeapSnapshotTester { 856 public: 857 static int CalculateNetworkSize(JSObject* obj) { 858 return HeapSnapshot::CalculateNetworkSize(obj); 859 } 860}; 861 862} } // namespace v8::internal 863 864// http://code.google.com/p/v8/issues/detail?id=822 865// Trying to call CalculateNetworkSize on an object with elements set 866// to non-FixedArray may cause an assertion error in debug builds. 867TEST(Issue822) { 868 v8::HandleScope scope; 869 LocalContext context; 870 const int kElementCount = 260; 871 uint8_t* pixel_data = reinterpret_cast<uint8_t*>(malloc(kElementCount)); 872 i::Handle<i::PixelArray> pixels = i::Factory::NewPixelArray(kElementCount, 873 pixel_data); 874 v8::Handle<v8::Object> obj = v8::Object::New(); 875 // Set the elements to be the pixels. 876 obj->SetIndexedPropertiesToPixelData(pixel_data, kElementCount); 877 i::Handle<i::JSObject> jsobj = v8::Utils::OpenHandle(*obj); 878 // This call must not cause an assertion error in debug builds. 879 i::HeapSnapshotTester::CalculateNetworkSize(*jsobj); 880} 881 882 883static const v8::HeapGraphNode* GetChild( 884 const v8::HeapGraphNode* node, 885 v8::HeapGraphNode::Type type, 886 const char* name, 887 const v8::HeapGraphNode* after = NULL) { 888 bool ignore_child = after == NULL ? false : true; 889 for (int i = 0, count = node->GetChildrenCount(); i < count; ++i) { 890 const v8::HeapGraphEdge* prop = node->GetChild(i); 891 const v8::HeapGraphNode* child = prop->GetToNode(); 892 v8::String::AsciiValue child_name(child->GetName()); 893 if (!ignore_child 894 && child->GetType() == type 895 && strcmp(name, *child_name) == 0) 896 return child; 897 if (after != NULL && child == after) ignore_child = false; 898 } 899 return NULL; 900} 901 902static bool IsNodeRetainedAs(const v8::HeapGraphNode* node, 903 int element) { 904 for (int i = 0, count = node->GetRetainersCount(); i < count; ++i) { 905 const v8::HeapGraphEdge* prop = node->GetRetainer(i); 906 if (prop->GetType() == v8::HeapGraphEdge::kElement 907 && element == prop->GetName()->Int32Value()) 908 return true; 909 } 910 return false; 911} 912 913TEST(AggregatedHeapSnapshot) { 914 v8::HandleScope scope; 915 LocalContext env; 916 917 CompileAndRunScript( 918 "function A() {}\n" 919 "function B(x) { this.x = x; }\n" 920 "var a = new A();\n" 921 "var b = new B(a);"); 922 const v8::HeapSnapshot* snapshot = 923 v8::HeapProfiler::TakeSnapshot( 924 v8::String::New("agg"), v8::HeapSnapshot::kAggregated); 925 const v8::HeapGraphNode* strings = GetChild(snapshot->GetRoot(), 926 v8::HeapGraphNode::kInternal, 927 "STRING_TYPE"); 928 CHECK_NE(NULL, strings); 929 CHECK_NE(0, strings->GetSelfSize()); 930 CHECK_NE(0, strings->GetInstancesCount()); 931 const v8::HeapGraphNode* maps = GetChild(snapshot->GetRoot(), 932 v8::HeapGraphNode::kInternal, 933 "MAP_TYPE"); 934 CHECK_NE(NULL, maps); 935 CHECK_NE(0, maps->GetSelfSize()); 936 CHECK_NE(0, maps->GetInstancesCount()); 937 938 const v8::HeapGraphNode* a = GetChild(snapshot->GetRoot(), 939 v8::HeapGraphNode::kObject, 940 "A"); 941 CHECK_NE(NULL, a); 942 CHECK_NE(0, a->GetSelfSize()); 943 CHECK_EQ(1, a->GetInstancesCount()); 944 945 const v8::HeapGraphNode* b = GetChild(snapshot->GetRoot(), 946 v8::HeapGraphNode::kObject, 947 "B"); 948 CHECK_NE(NULL, b); 949 CHECK_NE(0, b->GetSelfSize()); 950 CHECK_EQ(1, b->GetInstancesCount()); 951 952 const v8::HeapGraphNode* glob_prop = GetChild(snapshot->GetRoot(), 953 v8::HeapGraphNode::kObject, 954 "(global property)", 955 b); 956 CHECK_NE(NULL, glob_prop); 957 CHECK_EQ(0, glob_prop->GetSelfSize()); 958 CHECK_EQ(0, glob_prop->GetInstancesCount()); 959 CHECK_NE(0, glob_prop->GetChildrenCount()); 960 961 const v8::HeapGraphNode* a_from_glob_prop = GetChild( 962 glob_prop, 963 v8::HeapGraphNode::kObject, 964 "A"); 965 CHECK_NE(NULL, a_from_glob_prop); 966 CHECK_EQ(0, a_from_glob_prop->GetSelfSize()); 967 CHECK_EQ(0, a_from_glob_prop->GetInstancesCount()); 968 CHECK_EQ(0, a_from_glob_prop->GetChildrenCount()); // Retains nothing. 969 CHECK(IsNodeRetainedAs(a_from_glob_prop, 1)); // (global propery) has 1 ref. 970 971 const v8::HeapGraphNode* b_with_children = GetChild( 972 snapshot->GetRoot(), 973 v8::HeapGraphNode::kObject, 974 "B", 975 b); 976 CHECK_NE(NULL, b_with_children); 977 CHECK_EQ(0, b_with_children->GetSelfSize()); 978 CHECK_EQ(0, b_with_children->GetInstancesCount()); 979 CHECK_NE(0, b_with_children->GetChildrenCount()); 980 981 const v8::HeapGraphNode* a_from_b = GetChild( 982 b_with_children, 983 v8::HeapGraphNode::kObject, 984 "A"); 985 CHECK_NE(NULL, a_from_b); 986 CHECK_EQ(0, a_from_b->GetSelfSize()); 987 CHECK_EQ(0, a_from_b->GetInstancesCount()); 988 CHECK_EQ(0, a_from_b->GetChildrenCount()); // Retains nothing. 989 CHECK(IsNodeRetainedAs(a_from_b, 1)); // B has 1 ref to A. 990} 991 992#endif // ENABLE_LOGGING_AND_PROFILING 993