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 15 namespace i = v8::internal; 16 using i::ClustersCoarser; 17 using i::JSObjectsCluster; 18 using i::JSObjectsRetainerTree; 19 using i::JSObjectsClusterTree; 20 using i::RetainerHeapProfile; 21 22 23 namespace { 24 25 class ConstructorHeapProfileTestHelper : public i::ConstructorHeapProfile { 26 public: 27 ConstructorHeapProfileTestHelper() 28 : i::ConstructorHeapProfile(), 29 f_name_(FACTORY->NewStringFromAscii(i::CStrVector("F"))), 30 f_count_(0) { 31 } 32 33 void Call(const JSObjectsCluster& cluster, 34 const i::NumberAndSizeInfo& number_and_size) { 35 if (f_name_->Equals(cluster.constructor())) { 36 CHECK_EQ(f_count_, 0); 37 f_count_ = number_and_size.number(); 38 CHECK_GT(f_count_, 0); 39 } 40 } 41 42 int f_count() { return f_count_; } 43 44 private: 45 i::Handle<i::String> f_name_; 46 int f_count_; 47 }; 48 49 } // namespace 50 51 52 TEST(ConstructorProfile) { 53 v8::HandleScope scope; 54 LocalContext env; 55 56 CompileRun( 57 "function F() {} // A constructor\n" 58 "var f1 = new F();\n" 59 "var f2 = new F();\n"); 60 61 ConstructorHeapProfileTestHelper cons_profile; 62 i::AssertNoAllocation no_alloc; 63 i::HeapIterator iterator; 64 for (i::HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next()) 65 cons_profile.CollectStats(obj); 66 CHECK_EQ(0, cons_profile.f_count()); 67 cons_profile.PrintStats(); 68 CHECK_EQ(2, cons_profile.f_count()); 69 } 70 71 72 static JSObjectsCluster AddHeapObjectToTree(JSObjectsRetainerTree* tree, 73 i::String* constructor, 74 int instance, 75 JSObjectsCluster* ref1 = NULL, 76 JSObjectsCluster* ref2 = NULL, 77 JSObjectsCluster* ref3 = NULL) { 78 JSObjectsCluster o(constructor, reinterpret_cast<i::Object*>(instance)); 79 JSObjectsClusterTree* o_tree = new JSObjectsClusterTree(); 80 JSObjectsClusterTree::Locator o_loc; 81 if (ref1 != NULL) o_tree->Insert(*ref1, &o_loc); 82 if (ref2 != NULL) o_tree->Insert(*ref2, &o_loc); 83 if (ref3 != NULL) o_tree->Insert(*ref3, &o_loc); 84 JSObjectsRetainerTree::Locator loc; 85 tree->Insert(o, &loc); 86 loc.set_value(o_tree); 87 return o; 88 } 89 90 91 static void AddSelfReferenceToTree(JSObjectsRetainerTree* tree, 92 JSObjectsCluster* self_ref) { 93 JSObjectsRetainerTree::Locator loc; 94 CHECK(tree->Find(*self_ref, &loc)); 95 JSObjectsClusterTree::Locator o_loc; 96 CHECK_NE(NULL, loc.value()); 97 loc.value()->Insert(*self_ref, &o_loc); 98 } 99 100 101 static inline void CheckEqualsHelper(const char* file, int line, 102 const char* expected_source, 103 const JSObjectsCluster& expected, 104 const char* value_source, 105 const JSObjectsCluster& value) { 106 if (JSObjectsCluster::Compare(expected, value) != 0) { 107 i::HeapStringAllocator allocator; 108 i::StringStream stream(&allocator); 109 stream.Add("# Expected: "); 110 expected.DebugPrint(&stream); 111 stream.Add("\n# Found: "); 112 value.DebugPrint(&stream); 113 V8_Fatal(file, line, "CHECK_EQ(%s, %s) failed\n%s", 114 expected_source, value_source, 115 *stream.ToCString()); 116 } 117 } 118 119 120 static inline void CheckNonEqualsHelper(const char* file, int line, 121 const char* expected_source, 122 const JSObjectsCluster& expected, 123 const char* value_source, 124 const JSObjectsCluster& value) { 125 if (JSObjectsCluster::Compare(expected, value) == 0) { 126 i::HeapStringAllocator allocator; 127 i::StringStream stream(&allocator); 128 stream.Add("# !Expected: "); 129 expected.DebugPrint(&stream); 130 stream.Add("\n# Found: "); 131 value.DebugPrint(&stream); 132 V8_Fatal(file, line, "CHECK_NE(%s, %s) failed\n%s", 133 expected_source, value_source, 134 *stream.ToCString()); 135 } 136 } 137 138 139 TEST(ClustersCoarserSimple) { 140 v8::HandleScope scope; 141 LocalContext env; 142 143 i::ZoneScope zn_scope(i::DELETE_ON_EXIT); 144 145 JSObjectsRetainerTree tree; 146 JSObjectsCluster function(HEAP->function_class_symbol()); 147 JSObjectsCluster a(*FACTORY->NewStringFromAscii(i::CStrVector("A"))); 148 JSObjectsCluster b(*FACTORY->NewStringFromAscii(i::CStrVector("B"))); 149 150 // o1 <- Function 151 JSObjectsCluster o1 = 152 AddHeapObjectToTree(&tree, HEAP->Object_symbol(), 0x100, &function); 153 // o2 <- Function 154 JSObjectsCluster o2 = 155 AddHeapObjectToTree(&tree, HEAP->Object_symbol(), 0x200, &function); 156 // o3 <- A, B 157 JSObjectsCluster o3 = 158 AddHeapObjectToTree(&tree, HEAP->Object_symbol(), 0x300, &a, &b); 159 // o4 <- B, A 160 JSObjectsCluster o4 = 161 AddHeapObjectToTree(&tree, HEAP->Object_symbol(), 0x400, &b, &a); 162 // o5 <- A, B, Function 163 JSObjectsCluster o5 = 164 AddHeapObjectToTree(&tree, HEAP->Object_symbol(), 0x500, 165 &a, &b, &function); 166 167 ClustersCoarser coarser; 168 coarser.Process(&tree); 169 170 CHECK_EQ(coarser.GetCoarseEquivalent(o1), coarser.GetCoarseEquivalent(o2)); 171 CHECK_EQ(coarser.GetCoarseEquivalent(o3), coarser.GetCoarseEquivalent(o4)); 172 CHECK_NE(coarser.GetCoarseEquivalent(o1), coarser.GetCoarseEquivalent(o3)); 173 CHECK_EQ(JSObjectsCluster(), coarser.GetCoarseEquivalent(o5)); 174 } 175 176 177 TEST(ClustersCoarserMultipleConstructors) { 178 v8::HandleScope scope; 179 LocalContext env; 180 181 i::ZoneScope zn_scope(i::DELETE_ON_EXIT); 182 183 JSObjectsRetainerTree tree; 184 JSObjectsCluster function(HEAP->function_class_symbol()); 185 186 // o1 <- Function 187 JSObjectsCluster o1 = 188 AddHeapObjectToTree(&tree, HEAP->Object_symbol(), 0x100, &function); 189 // a1 <- Function 190 JSObjectsCluster a1 = 191 AddHeapObjectToTree(&tree, HEAP->Array_symbol(), 0x1000, &function); 192 // o2 <- Function 193 JSObjectsCluster o2 = 194 AddHeapObjectToTree(&tree, HEAP->Object_symbol(), 0x200, &function); 195 // a2 <- Function 196 JSObjectsCluster a2 = 197 AddHeapObjectToTree(&tree, HEAP->Array_symbol(), 0x2000, &function); 198 199 ClustersCoarser coarser; 200 coarser.Process(&tree); 201 202 CHECK_EQ(coarser.GetCoarseEquivalent(o1), coarser.GetCoarseEquivalent(o2)); 203 CHECK_EQ(coarser.GetCoarseEquivalent(a1), coarser.GetCoarseEquivalent(a2)); 204 } 205 206 207 TEST(ClustersCoarserPathsTraversal) { 208 v8::HandleScope scope; 209 LocalContext env; 210 211 i::ZoneScope zn_scope(i::DELETE_ON_EXIT); 212 213 JSObjectsRetainerTree tree; 214 215 // On the following graph: 216 // 217 // p 218 // <- o21 <- o11 <- 219 // q o 220 // <- o22 <- o12 <- 221 // r 222 // 223 // we expect that coarser will deduce equivalences: p ~ q ~ r, 224 // o21 ~ o22, and o11 ~ o12. 225 226 JSObjectsCluster o = 227 AddHeapObjectToTree(&tree, HEAP->Object_symbol(), 0x100); 228 JSObjectsCluster o11 = 229 AddHeapObjectToTree(&tree, HEAP->Object_symbol(), 0x110, &o); 230 JSObjectsCluster o12 = 231 AddHeapObjectToTree(&tree, HEAP->Object_symbol(), 0x120, &o); 232 JSObjectsCluster o21 = 233 AddHeapObjectToTree(&tree, HEAP->Object_symbol(), 0x210, &o11); 234 JSObjectsCluster o22 = 235 AddHeapObjectToTree(&tree, HEAP->Object_symbol(), 0x220, &o12); 236 JSObjectsCluster p = 237 AddHeapObjectToTree(&tree, HEAP->Object_symbol(), 0x300, &o21); 238 JSObjectsCluster q = 239 AddHeapObjectToTree(&tree, HEAP->Object_symbol(), 0x310, &o21, &o22); 240 JSObjectsCluster r = 241 AddHeapObjectToTree(&tree, HEAP->Object_symbol(), 0x320, &o22); 242 243 ClustersCoarser coarser; 244 coarser.Process(&tree); 245 246 CHECK_EQ(JSObjectsCluster(), coarser.GetCoarseEquivalent(o)); 247 CHECK_NE(JSObjectsCluster(), coarser.GetCoarseEquivalent(o11)); 248 CHECK_EQ(coarser.GetCoarseEquivalent(o11), coarser.GetCoarseEquivalent(o12)); 249 CHECK_EQ(coarser.GetCoarseEquivalent(o21), coarser.GetCoarseEquivalent(o22)); 250 CHECK_NE(coarser.GetCoarseEquivalent(o11), coarser.GetCoarseEquivalent(o21)); 251 CHECK_NE(JSObjectsCluster(), coarser.GetCoarseEquivalent(p)); 252 CHECK_EQ(coarser.GetCoarseEquivalent(p), coarser.GetCoarseEquivalent(q)); 253 CHECK_EQ(coarser.GetCoarseEquivalent(q), coarser.GetCoarseEquivalent(r)); 254 CHECK_NE(coarser.GetCoarseEquivalent(o11), coarser.GetCoarseEquivalent(p)); 255 CHECK_NE(coarser.GetCoarseEquivalent(o21), coarser.GetCoarseEquivalent(p)); 256 } 257 258 259 TEST(ClustersCoarserSelf) { 260 v8::HandleScope scope; 261 LocalContext env; 262 263 i::ZoneScope zn_scope(i::DELETE_ON_EXIT); 264 265 JSObjectsRetainerTree tree; 266 267 // On the following graph: 268 // 269 // p (self-referencing) 270 // <- o1 <- 271 // q (self-referencing) o 272 // <- o2 <- 273 // r (self-referencing) 274 // 275 // we expect that coarser will deduce equivalences: p ~ q ~ r, o1 ~ o2; 276 277 JSObjectsCluster o = 278 AddHeapObjectToTree(&tree, HEAP->Object_symbol(), 0x100); 279 JSObjectsCluster o1 = 280 AddHeapObjectToTree(&tree, HEAP->Object_symbol(), 0x110, &o); 281 JSObjectsCluster o2 = 282 AddHeapObjectToTree(&tree, HEAP->Object_symbol(), 0x120, &o); 283 JSObjectsCluster p = 284 AddHeapObjectToTree(&tree, HEAP->Object_symbol(), 0x300, &o1); 285 AddSelfReferenceToTree(&tree, &p); 286 JSObjectsCluster q = 287 AddHeapObjectToTree(&tree, HEAP->Object_symbol(), 0x310, &o1, &o2); 288 AddSelfReferenceToTree(&tree, &q); 289 JSObjectsCluster r = 290 AddHeapObjectToTree(&tree, HEAP->Object_symbol(), 0x320, &o2); 291 AddSelfReferenceToTree(&tree, &r); 292 293 ClustersCoarser coarser; 294 coarser.Process(&tree); 295 296 CHECK_EQ(JSObjectsCluster(), coarser.GetCoarseEquivalent(o)); 297 CHECK_NE(JSObjectsCluster(), coarser.GetCoarseEquivalent(o1)); 298 CHECK_EQ(coarser.GetCoarseEquivalent(o1), coarser.GetCoarseEquivalent(o2)); 299 CHECK_NE(JSObjectsCluster(), coarser.GetCoarseEquivalent(p)); 300 CHECK_EQ(coarser.GetCoarseEquivalent(p), coarser.GetCoarseEquivalent(q)); 301 CHECK_EQ(coarser.GetCoarseEquivalent(q), coarser.GetCoarseEquivalent(r)); 302 CHECK_NE(coarser.GetCoarseEquivalent(o1), coarser.GetCoarseEquivalent(p)); 303 } 304 305 306 namespace { 307 308 class RetainerProfilePrinter : public RetainerHeapProfile::Printer { 309 public: 310 RetainerProfilePrinter() : stream_(&allocator_), lines_(100) {} 311 312 void PrintRetainers(const JSObjectsCluster& cluster, 313 const i::StringStream& retainers) { 314 cluster.Print(&stream_); 315 stream_.Add("%s", *(retainers.ToCString())); 316 stream_.Put('\0'); 317 } 318 319 const char* GetRetainers(const char* constructor) { 320 FillLines(); 321 const size_t cons_len = strlen(constructor); 322 for (int i = 0; i < lines_.length(); ++i) { 323 if (strncmp(constructor, lines_[i], cons_len) == 0 && 324 lines_[i][cons_len] == ',') { 325 return lines_[i] + cons_len + 1; 326 } 327 } 328 return NULL; 329 } 330 331 private: 332 void FillLines() { 333 if (lines_.length() > 0) return; 334 stream_.Put('\0'); 335 stream_str_ = stream_.ToCString(); 336 const char* pos = *stream_str_; 337 while (pos != NULL && *pos != '\0') { 338 lines_.Add(pos); 339 pos = strchr(pos, '\0'); 340 if (pos != NULL) ++pos; 341 } 342 } 343 344 i::HeapStringAllocator allocator_; 345 i::StringStream stream_; 346 i::SmartPointer<const char> stream_str_; 347 i::List<const char*> lines_; 348 }; 349 350 } // namespace 351 352 353 TEST(RetainerProfile) { 354 v8::HandleScope scope; 355 LocalContext env; 356 357 CompileRun( 358 "function A() {}\n" 359 "function B(x) { this.x = x; }\n" 360 "function C(x) { this.x1 = x; this.x2 = x; }\n" 361 "var a = new A();\n" 362 "var b1 = new B(a), b2 = new B(a);\n" 363 "var c = new C(a);"); 364 365 RetainerHeapProfile ret_profile; 366 i::AssertNoAllocation no_alloc; 367 i::HeapIterator iterator; 368 for (i::HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next()) 369 ret_profile.CollectStats(obj); 370 ret_profile.CoarseAndAggregate(); 371 RetainerProfilePrinter printer; 372 ret_profile.DebugPrintStats(&printer); 373 const char* retainers_of_a = printer.GetRetainers("A"); 374 // The order of retainers is unspecified, so we check string length, and 375 // verify each retainer separately. 376 CHECK_EQ(i::StrLength("(global property);1,B;2,C;2"), 377 i::StrLength(retainers_of_a)); 378 CHECK(strstr(retainers_of_a, "(global property);1") != NULL); 379 CHECK(strstr(retainers_of_a, "B;2") != NULL); 380 CHECK(strstr(retainers_of_a, "C;2") != NULL); 381 CHECK_EQ("(global property);2", printer.GetRetainers("B")); 382 CHECK_EQ("(global property);1", printer.GetRetainers("C")); 383 } 384 385 386 namespace { 387 388 class NamedEntriesDetector { 389 public: 390 NamedEntriesDetector() 391 : has_A2(false), has_B2(false), has_C2(false) { 392 } 393 394 void Apply(i::HeapEntry** entry_ptr) { 395 if (IsReachableNodeWithName(*entry_ptr, "A2")) has_A2 = true; 396 if (IsReachableNodeWithName(*entry_ptr, "B2")) has_B2 = true; 397 if (IsReachableNodeWithName(*entry_ptr, "C2")) has_C2 = true; 398 } 399 400 static bool IsReachableNodeWithName(i::HeapEntry* entry, const char* name) { 401 return strcmp(name, entry->name()) == 0 && entry->painted_reachable(); 402 } 403 404 bool has_A2; 405 bool has_B2; 406 bool has_C2; 407 }; 408 409 } // namespace 410 411 412 static const v8::HeapGraphNode* GetGlobalObject( 413 const v8::HeapSnapshot* snapshot) { 414 CHECK_EQ(2, snapshot->GetRoot()->GetChildrenCount()); 415 const v8::HeapGraphNode* global_obj = 416 snapshot->GetRoot()->GetChild(0)->GetToNode(); 417 CHECK_EQ("Object", const_cast<i::HeapEntry*>( 418 reinterpret_cast<const i::HeapEntry*>(global_obj))->name()); 419 return global_obj; 420 } 421 422 423 static const v8::HeapGraphNode* GetProperty(const v8::HeapGraphNode* node, 424 v8::HeapGraphEdge::Type type, 425 const char* name) { 426 for (int i = 0, count = node->GetChildrenCount(); i < count; ++i) { 427 const v8::HeapGraphEdge* prop = node->GetChild(i); 428 v8::String::AsciiValue prop_name(prop->GetName()); 429 if (prop->GetType() == type && strcmp(name, *prop_name) == 0) 430 return prop->GetToNode(); 431 } 432 return NULL; 433 } 434 435 436 static bool HasString(const v8::HeapGraphNode* node, const char* contents) { 437 for (int i = 0, count = node->GetChildrenCount(); i < count; ++i) { 438 const v8::HeapGraphEdge* prop = node->GetChild(i); 439 const v8::HeapGraphNode* node = prop->GetToNode(); 440 if (node->GetType() == v8::HeapGraphNode::kString) { 441 v8::String::AsciiValue node_name(node->GetName()); 442 if (strcmp(contents, *node_name) == 0) return true; 443 } 444 } 445 return false; 446 } 447 448 449 TEST(HeapSnapshot) { 450 v8::HandleScope scope; 451 LocalContext env2; 452 453 CompileRun( 454 "function A2() {}\n" 455 "function B2(x) { return function() { return typeof x; }; }\n" 456 "function C2(x) { this.x1 = x; this.x2 = x; this[1] = x; }\n" 457 "var a2 = new A2();\n" 458 "var b2_1 = new B2(a2), b2_2 = new B2(a2);\n" 459 "var c2 = new C2(a2);"); 460 const v8::HeapSnapshot* snapshot_env2 = 461 v8::HeapProfiler::TakeSnapshot(v8::String::New("env2")); 462 i::HeapSnapshot* i_snapshot_env2 = 463 const_cast<i::HeapSnapshot*>( 464 reinterpret_cast<const i::HeapSnapshot*>(snapshot_env2)); 465 const v8::HeapGraphNode* global_env2 = GetGlobalObject(snapshot_env2); 466 // Paint all nodes reachable from global object. 467 i_snapshot_env2->ClearPaint(); 468 const_cast<i::HeapEntry*>( 469 reinterpret_cast<const i::HeapEntry*>(global_env2))->PaintAllReachable(); 470 471 // Verify, that JS global object of env2 has '..2' properties. 472 const v8::HeapGraphNode* a2_node = 473 GetProperty(global_env2, v8::HeapGraphEdge::kShortcut, "a2"); 474 CHECK_NE(NULL, a2_node); 475 CHECK_NE( 476 NULL, GetProperty(global_env2, v8::HeapGraphEdge::kShortcut, "b2_1")); 477 CHECK_NE( 478 NULL, GetProperty(global_env2, v8::HeapGraphEdge::kShortcut, "b2_2")); 479 CHECK_NE(NULL, GetProperty(global_env2, v8::HeapGraphEdge::kShortcut, "c2")); 480 481 NamedEntriesDetector det; 482 i_snapshot_env2->IterateEntries(&det); 483 CHECK(det.has_A2); 484 CHECK(det.has_B2); 485 CHECK(det.has_C2); 486 } 487 488 489 TEST(HeapSnapshotObjectSizes) { 490 v8::HandleScope scope; 491 LocalContext env; 492 493 // -a-> X1 --a 494 // x -b-> X2 <-| 495 CompileRun( 496 "function X(a, b) { this.a = a; this.b = b; }\n" 497 "x = new X(new X(), new X());\n" 498 "(function() { x.a.a = x.b; })();"); 499 const v8::HeapSnapshot* snapshot = 500 v8::HeapProfiler::TakeSnapshot(v8::String::New("sizes")); 501 const v8::HeapGraphNode* global = GetGlobalObject(snapshot); 502 const v8::HeapGraphNode* x = 503 GetProperty(global, v8::HeapGraphEdge::kShortcut, "x"); 504 CHECK_NE(NULL, x); 505 const v8::HeapGraphNode* x1 = 506 GetProperty(x, v8::HeapGraphEdge::kProperty, "a"); 507 CHECK_NE(NULL, x1); 508 const v8::HeapGraphNode* x2 = 509 GetProperty(x, v8::HeapGraphEdge::kProperty, "b"); 510 CHECK_NE(NULL, x2); 511 512 // Test approximate sizes. 513 CHECK_EQ(x->GetSelfSize() * 3, x->GetRetainedSize(false)); 514 CHECK_EQ(x1->GetSelfSize(), x1->GetRetainedSize(false)); 515 CHECK_EQ(x2->GetSelfSize(), x2->GetRetainedSize(false)); 516 // Test exact sizes. 517 CHECK_EQ(x->GetSelfSize() * 3, x->GetRetainedSize(true)); 518 CHECK_EQ(x1->GetSelfSize(), x1->GetRetainedSize(true)); 519 CHECK_EQ(x2->GetSelfSize(), x2->GetRetainedSize(true)); 520 } 521 522 523 TEST(HeapSnapshotEntryChildren) { 524 v8::HandleScope scope; 525 LocalContext env; 526 527 CompileRun( 528 "function A() { }\n" 529 "a = new A;"); 530 const v8::HeapSnapshot* snapshot = 531 v8::HeapProfiler::TakeSnapshot(v8::String::New("children")); 532 const v8::HeapGraphNode* global = GetGlobalObject(snapshot); 533 for (int i = 0, count = global->GetChildrenCount(); i < count; ++i) { 534 const v8::HeapGraphEdge* prop = global->GetChild(i); 535 CHECK_EQ(global, prop->GetFromNode()); 536 } 537 const v8::HeapGraphNode* a = 538 GetProperty(global, v8::HeapGraphEdge::kProperty, "a"); 539 CHECK_NE(NULL, a); 540 for (int i = 0, count = a->GetChildrenCount(); i < count; ++i) { 541 const v8::HeapGraphEdge* prop = a->GetChild(i); 542 CHECK_EQ(a, prop->GetFromNode()); 543 } 544 } 545 546 547 TEST(HeapSnapshotCodeObjects) { 548 v8::HandleScope scope; 549 LocalContext env; 550 551 CompileRun( 552 "function lazy(x) { return x - 1; }\n" 553 "function compiled(x) { return x + 1; }\n" 554 "var anonymous = (function() { return function() { return 0; } })();\n" 555 "compiled(1)"); 556 const v8::HeapSnapshot* snapshot = 557 v8::HeapProfiler::TakeSnapshot(v8::String::New("code")); 558 559 const v8::HeapGraphNode* global = GetGlobalObject(snapshot); 560 const v8::HeapGraphNode* compiled = 561 GetProperty(global, v8::HeapGraphEdge::kShortcut, "compiled"); 562 CHECK_NE(NULL, compiled); 563 CHECK_EQ(v8::HeapGraphNode::kClosure, compiled->GetType()); 564 const v8::HeapGraphNode* lazy = 565 GetProperty(global, v8::HeapGraphEdge::kShortcut, "lazy"); 566 CHECK_NE(NULL, lazy); 567 CHECK_EQ(v8::HeapGraphNode::kClosure, lazy->GetType()); 568 const v8::HeapGraphNode* anonymous = 569 GetProperty(global, v8::HeapGraphEdge::kShortcut, "anonymous"); 570 CHECK_NE(NULL, anonymous); 571 CHECK_EQ(v8::HeapGraphNode::kClosure, anonymous->GetType()); 572 v8::String::AsciiValue anonymous_name(anonymous->GetName()); 573 CHECK_EQ("", *anonymous_name); 574 575 // Find references to code. 576 const v8::HeapGraphNode* compiled_code = 577 GetProperty(compiled, v8::HeapGraphEdge::kInternal, "shared"); 578 CHECK_NE(NULL, compiled_code); 579 const v8::HeapGraphNode* lazy_code = 580 GetProperty(lazy, v8::HeapGraphEdge::kInternal, "shared"); 581 CHECK_NE(NULL, lazy_code); 582 583 // Verify that non-compiled code doesn't contain references to "x" 584 // literal, while compiled code does. The scope info is stored in FixedArray 585 // objects attached to the SharedFunctionInfo. 586 bool compiled_references_x = false, lazy_references_x = false; 587 for (int i = 0, count = compiled_code->GetChildrenCount(); i < count; ++i) { 588 const v8::HeapGraphEdge* prop = compiled_code->GetChild(i); 589 const v8::HeapGraphNode* node = prop->GetToNode(); 590 if (node->GetType() == v8::HeapGraphNode::kArray) { 591 if (HasString(node, "x")) { 592 compiled_references_x = true; 593 break; 594 } 595 } 596 } 597 for (int i = 0, count = lazy_code->GetChildrenCount(); i < count; ++i) { 598 const v8::HeapGraphEdge* prop = lazy_code->GetChild(i); 599 const v8::HeapGraphNode* node = prop->GetToNode(); 600 if (node->GetType() == v8::HeapGraphNode::kArray) { 601 if (HasString(node, "x")) { 602 lazy_references_x = true; 603 break; 604 } 605 } 606 } 607 CHECK(compiled_references_x); 608 CHECK(!lazy_references_x); 609 } 610 611 612 TEST(HeapSnapshotHeapNumbers) { 613 v8::HandleScope scope; 614 LocalContext env; 615 CompileRun( 616 "a = 1; // a is Smi\n" 617 "b = 2.5; // b is HeapNumber"); 618 const v8::HeapSnapshot* snapshot = 619 v8::HeapProfiler::TakeSnapshot(v8::String::New("numbers")); 620 const v8::HeapGraphNode* global = GetGlobalObject(snapshot); 621 CHECK_EQ(NULL, GetProperty(global, v8::HeapGraphEdge::kShortcut, "a")); 622 const v8::HeapGraphNode* b = 623 GetProperty(global, v8::HeapGraphEdge::kShortcut, "b"); 624 CHECK_NE(NULL, b); 625 CHECK_EQ(v8::HeapGraphNode::kHeapNumber, b->GetType()); 626 } 627 628 629 TEST(HeapSnapshotInternalReferences) { 630 v8::HandleScope scope; 631 v8::Local<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New(); 632 global_template->SetInternalFieldCount(2); 633 LocalContext env(NULL, global_template); 634 v8::Handle<v8::Object> global_proxy = env->Global(); 635 v8::Handle<v8::Object> global = global_proxy->GetPrototype().As<v8::Object>(); 636 CHECK_EQ(2, global->InternalFieldCount()); 637 v8::Local<v8::Object> obj = v8::Object::New(); 638 global->SetInternalField(0, v8_num(17)); 639 global->SetInternalField(1, obj); 640 const v8::HeapSnapshot* snapshot = 641 v8::HeapProfiler::TakeSnapshot(v8::String::New("internals")); 642 const v8::HeapGraphNode* global_node = GetGlobalObject(snapshot); 643 // The first reference will not present, because it's a Smi. 644 CHECK_EQ(NULL, GetProperty(global_node, v8::HeapGraphEdge::kInternal, "0")); 645 // The second reference is to an object. 646 CHECK_NE(NULL, GetProperty(global_node, v8::HeapGraphEdge::kInternal, "1")); 647 } 648 649 650 // Trying to introduce a check helper for uint64_t causes many 651 // overloading ambiguities, so it seems easier just to cast 652 // them to a signed type. 653 #define CHECK_EQ_UINT64_T(a, b) \ 654 CHECK_EQ(static_cast<int64_t>(a), static_cast<int64_t>(b)) 655 #define CHECK_NE_UINT64_T(a, b) \ 656 CHECK((a) != (b)) // NOLINT 657 658 TEST(HeapEntryIdsAndGC) { 659 v8::HandleScope scope; 660 LocalContext env; 661 662 CompileRun( 663 "function A() {}\n" 664 "function B(x) { this.x = x; }\n" 665 "var a = new A();\n" 666 "var b = new B(a);"); 667 const v8::HeapSnapshot* snapshot1 = 668 v8::HeapProfiler::TakeSnapshot(v8::String::New("s1")); 669 670 HEAP->CollectAllGarbage(true); // Enforce compaction. 671 672 const v8::HeapSnapshot* snapshot2 = 673 v8::HeapProfiler::TakeSnapshot(v8::String::New("s2")); 674 675 const v8::HeapGraphNode* global1 = GetGlobalObject(snapshot1); 676 const v8::HeapGraphNode* global2 = GetGlobalObject(snapshot2); 677 CHECK_NE_UINT64_T(0, global1->GetId()); 678 CHECK_EQ_UINT64_T(global1->GetId(), global2->GetId()); 679 const v8::HeapGraphNode* A1 = 680 GetProperty(global1, v8::HeapGraphEdge::kProperty, "A"); 681 CHECK_NE(NULL, A1); 682 const v8::HeapGraphNode* A2 = 683 GetProperty(global2, v8::HeapGraphEdge::kProperty, "A"); 684 CHECK_NE(NULL, A2); 685 CHECK_NE_UINT64_T(0, A1->GetId()); 686 CHECK_EQ_UINT64_T(A1->GetId(), A2->GetId()); 687 const v8::HeapGraphNode* B1 = 688 GetProperty(global1, v8::HeapGraphEdge::kProperty, "B"); 689 CHECK_NE(NULL, B1); 690 const v8::HeapGraphNode* B2 = 691 GetProperty(global2, v8::HeapGraphEdge::kProperty, "B"); 692 CHECK_NE(NULL, B2); 693 CHECK_NE_UINT64_T(0, B1->GetId()); 694 CHECK_EQ_UINT64_T(B1->GetId(), B2->GetId()); 695 const v8::HeapGraphNode* a1 = 696 GetProperty(global1, v8::HeapGraphEdge::kProperty, "a"); 697 CHECK_NE(NULL, a1); 698 const v8::HeapGraphNode* a2 = 699 GetProperty(global2, v8::HeapGraphEdge::kProperty, "a"); 700 CHECK_NE(NULL, a2); 701 CHECK_NE_UINT64_T(0, a1->GetId()); 702 CHECK_EQ_UINT64_T(a1->GetId(), a2->GetId()); 703 const v8::HeapGraphNode* b1 = 704 GetProperty(global1, v8::HeapGraphEdge::kProperty, "b"); 705 CHECK_NE(NULL, b1); 706 const v8::HeapGraphNode* b2 = 707 GetProperty(global2, v8::HeapGraphEdge::kProperty, "b"); 708 CHECK_NE(NULL, b2); 709 CHECK_NE_UINT64_T(0, b1->GetId()); 710 CHECK_EQ_UINT64_T(b1->GetId(), b2->GetId()); 711 } 712 713 714 TEST(HeapSnapshotRootPreservedAfterSorting) { 715 v8::HandleScope scope; 716 LocalContext env; 717 const v8::HeapSnapshot* snapshot = 718 v8::HeapProfiler::TakeSnapshot(v8::String::New("s")); 719 const v8::HeapGraphNode* root1 = snapshot->GetRoot(); 720 const_cast<i::HeapSnapshot*>(reinterpret_cast<const i::HeapSnapshot*>( 721 snapshot))->GetSortedEntriesList(); 722 const v8::HeapGraphNode* root2 = snapshot->GetRoot(); 723 CHECK_EQ(root1, root2); 724 } 725 726 727 static const v8::HeapGraphNode* GetChild( 728 const v8::HeapGraphNode* node, 729 v8::HeapGraphNode::Type type, 730 const char* name, 731 const v8::HeapGraphNode* after = NULL) { 732 bool ignore_child = after == NULL ? false : true; 733 for (int i = 0, count = node->GetChildrenCount(); i < count; ++i) { 734 const v8::HeapGraphEdge* prop = node->GetChild(i); 735 const v8::HeapGraphNode* child = prop->GetToNode(); 736 v8::String::AsciiValue child_name(child->GetName()); 737 if (!ignore_child 738 && child->GetType() == type 739 && strcmp(name, *child_name) == 0) 740 return child; 741 if (after != NULL && child == after) ignore_child = false; 742 } 743 return NULL; 744 } 745 746 static bool IsNodeRetainedAs(const v8::HeapGraphNode* node, 747 int element) { 748 for (int i = 0, count = node->GetRetainersCount(); i < count; ++i) { 749 const v8::HeapGraphEdge* prop = node->GetRetainer(i); 750 if (prop->GetType() == v8::HeapGraphEdge::kElement 751 && element == prop->GetName()->Int32Value()) 752 return true; 753 } 754 return false; 755 } 756 757 TEST(AggregatedHeapSnapshot) { 758 v8::HandleScope scope; 759 LocalContext env; 760 761 CompileRun( 762 "function A() {}\n" 763 "function B(x) { this.x = x; }\n" 764 "var a = new A();\n" 765 "var b = new B(a);"); 766 const v8::HeapSnapshot* snapshot = 767 v8::HeapProfiler::TakeSnapshot( 768 v8::String::New("agg"), v8::HeapSnapshot::kAggregated); 769 const v8::HeapGraphNode* strings = GetChild(snapshot->GetRoot(), 770 v8::HeapGraphNode::kHidden, 771 "STRING_TYPE"); 772 CHECK_NE(NULL, strings); 773 CHECK_NE(0, strings->GetSelfSize()); 774 CHECK_NE(0, strings->GetInstancesCount()); 775 const v8::HeapGraphNode* maps = GetChild(snapshot->GetRoot(), 776 v8::HeapGraphNode::kHidden, 777 "MAP_TYPE"); 778 CHECK_NE(NULL, maps); 779 CHECK_NE(0, maps->GetSelfSize()); 780 CHECK_NE(0, maps->GetInstancesCount()); 781 782 const v8::HeapGraphNode* a = GetChild(snapshot->GetRoot(), 783 v8::HeapGraphNode::kObject, 784 "A"); 785 CHECK_NE(NULL, a); 786 CHECK_NE(0, a->GetSelfSize()); 787 CHECK_EQ(1, a->GetInstancesCount()); 788 789 const v8::HeapGraphNode* b = GetChild(snapshot->GetRoot(), 790 v8::HeapGraphNode::kObject, 791 "B"); 792 CHECK_NE(NULL, b); 793 CHECK_NE(0, b->GetSelfSize()); 794 CHECK_EQ(1, b->GetInstancesCount()); 795 796 const v8::HeapGraphNode* glob_prop = GetChild(snapshot->GetRoot(), 797 v8::HeapGraphNode::kObject, 798 "(global property)", 799 b); 800 CHECK_NE(NULL, glob_prop); 801 CHECK_EQ(0, glob_prop->GetSelfSize()); 802 CHECK_EQ(0, glob_prop->GetInstancesCount()); 803 CHECK_NE(0, glob_prop->GetChildrenCount()); 804 805 const v8::HeapGraphNode* a_from_glob_prop = GetChild( 806 glob_prop, 807 v8::HeapGraphNode::kObject, 808 "A"); 809 CHECK_NE(NULL, a_from_glob_prop); 810 CHECK_EQ(0, a_from_glob_prop->GetSelfSize()); 811 CHECK_EQ(0, a_from_glob_prop->GetInstancesCount()); 812 CHECK_EQ(0, a_from_glob_prop->GetChildrenCount()); // Retains nothing. 813 CHECK(IsNodeRetainedAs(a_from_glob_prop, 1)); // (global propery) has 1 ref. 814 815 const v8::HeapGraphNode* b_with_children = GetChild( 816 snapshot->GetRoot(), 817 v8::HeapGraphNode::kObject, 818 "B", 819 b); 820 CHECK_NE(NULL, b_with_children); 821 CHECK_EQ(0, b_with_children->GetSelfSize()); 822 CHECK_EQ(0, b_with_children->GetInstancesCount()); 823 CHECK_NE(0, b_with_children->GetChildrenCount()); 824 825 const v8::HeapGraphNode* a_from_b = GetChild( 826 b_with_children, 827 v8::HeapGraphNode::kObject, 828 "A"); 829 CHECK_NE(NULL, a_from_b); 830 CHECK_EQ(0, a_from_b->GetSelfSize()); 831 CHECK_EQ(0, a_from_b->GetInstancesCount()); 832 CHECK_EQ(0, a_from_b->GetChildrenCount()); // Retains nothing. 833 CHECK(IsNodeRetainedAs(a_from_b, 1)); // B has 1 ref to A. 834 } 835 836 837 TEST(HeapEntryDominator) { 838 // The graph looks like this: 839 // 840 // -> node1 841 // a |^ 842 // -> node5 ba 843 // a v| 844 // node6 -> node2 845 // b a |^ 846 // -> node4 ba 847 // b v| 848 // -> node3 849 // 850 // The dominator for all nodes is node6. 851 852 v8::HandleScope scope; 853 LocalContext env; 854 855 CompileRun( 856 "function X(a, b) { this.a = a; this.b = b; }\n" 857 "node6 = new X(new X(new X()), new X(new X(),new X()));\n" 858 "(function(){\n" 859 "node6.a.a.b = node6.b.a; // node1 -> node2\n" 860 "node6.b.a.a = node6.a.a; // node2 -> node1\n" 861 "node6.b.a.b = node6.b.b; // node2 -> node3\n" 862 "node6.b.b.a = node6.b.a; // node3 -> node2\n" 863 "})();"); 864 865 const v8::HeapSnapshot* snapshot = 866 v8::HeapProfiler::TakeSnapshot(v8::String::New("dominators")); 867 868 const v8::HeapGraphNode* global = GetGlobalObject(snapshot); 869 CHECK_NE(NULL, global); 870 const v8::HeapGraphNode* node6 = 871 GetProperty(global, v8::HeapGraphEdge::kShortcut, "node6"); 872 CHECK_NE(NULL, node6); 873 const v8::HeapGraphNode* node5 = 874 GetProperty(node6, v8::HeapGraphEdge::kProperty, "a"); 875 CHECK_NE(NULL, node5); 876 const v8::HeapGraphNode* node4 = 877 GetProperty(node6, v8::HeapGraphEdge::kProperty, "b"); 878 CHECK_NE(NULL, node4); 879 const v8::HeapGraphNode* node3 = 880 GetProperty(node4, v8::HeapGraphEdge::kProperty, "b"); 881 CHECK_NE(NULL, node3); 882 const v8::HeapGraphNode* node2 = 883 GetProperty(node4, v8::HeapGraphEdge::kProperty, "a"); 884 CHECK_NE(NULL, node2); 885 const v8::HeapGraphNode* node1 = 886 GetProperty(node5, v8::HeapGraphEdge::kProperty, "a"); 887 CHECK_NE(NULL, node1); 888 889 CHECK_EQ(node6, node1->GetDominatorNode()); 890 CHECK_EQ(node6, node2->GetDominatorNode()); 891 CHECK_EQ(node6, node3->GetDominatorNode()); 892 CHECK_EQ(node6, node4->GetDominatorNode()); 893 CHECK_EQ(node6, node5->GetDominatorNode()); 894 } 895 896 897 namespace { 898 899 class TestJSONStream : public v8::OutputStream { 900 public: 901 TestJSONStream() : eos_signaled_(0), abort_countdown_(-1) {} 902 explicit TestJSONStream(int abort_countdown) 903 : eos_signaled_(0), abort_countdown_(abort_countdown) {} 904 virtual ~TestJSONStream() {} 905 virtual void EndOfStream() { ++eos_signaled_; } 906 virtual WriteResult WriteAsciiChunk(char* buffer, int chars_written) { 907 if (abort_countdown_ > 0) --abort_countdown_; 908 if (abort_countdown_ == 0) return kAbort; 909 CHECK_GT(chars_written, 0); 910 i::Vector<char> chunk = buffer_.AddBlock(chars_written, '\0'); 911 memcpy(chunk.start(), buffer, chars_written); 912 return kContinue; 913 } 914 void WriteTo(i::Vector<char> dest) { buffer_.WriteTo(dest); } 915 int eos_signaled() { return eos_signaled_; } 916 int size() { return buffer_.size(); } 917 private: 918 i::Collector<char> buffer_; 919 int eos_signaled_; 920 int abort_countdown_; 921 }; 922 923 class AsciiResource: public v8::String::ExternalAsciiStringResource { 924 public: 925 explicit AsciiResource(i::Vector<char> string): data_(string.start()) { 926 length_ = string.length(); 927 } 928 virtual const char* data() const { return data_; } 929 virtual size_t length() const { return length_; } 930 private: 931 const char* data_; 932 size_t length_; 933 }; 934 935 } // namespace 936 937 TEST(HeapSnapshotJSONSerialization) { 938 v8::HandleScope scope; 939 LocalContext env; 940 941 #define STRING_LITERAL_FOR_TEST \ 942 "\"String \\n\\r\\u0008\\u0081\\u0101\\u0801\\u8001\"" 943 CompileRun( 944 "function A(s) { this.s = s; }\n" 945 "function B(x) { this.x = x; }\n" 946 "var a = new A(" STRING_LITERAL_FOR_TEST ");\n" 947 "var b = new B(a);"); 948 const v8::HeapSnapshot* snapshot = 949 v8::HeapProfiler::TakeSnapshot(v8::String::New("json")); 950 TestJSONStream stream; 951 snapshot->Serialize(&stream, v8::HeapSnapshot::kJSON); 952 CHECK_GT(stream.size(), 0); 953 CHECK_EQ(1, stream.eos_signaled()); 954 i::ScopedVector<char> json(stream.size()); 955 stream.WriteTo(json); 956 957 // Verify that snapshot string is valid JSON. 958 AsciiResource json_res(json); 959 v8::Local<v8::String> json_string = v8::String::NewExternal(&json_res); 960 env->Global()->Set(v8::String::New("json_snapshot"), json_string); 961 v8::Local<v8::Value> snapshot_parse_result = CompileRun( 962 "var parsed = JSON.parse(json_snapshot); true;"); 963 CHECK(!snapshot_parse_result.IsEmpty()); 964 965 // Verify that snapshot object has required fields. 966 v8::Local<v8::Object> parsed_snapshot = 967 env->Global()->Get(v8::String::New("parsed"))->ToObject(); 968 CHECK(parsed_snapshot->Has(v8::String::New("snapshot"))); 969 CHECK(parsed_snapshot->Has(v8::String::New("nodes"))); 970 CHECK(parsed_snapshot->Has(v8::String::New("strings"))); 971 972 // Get node and edge "member" offsets. 973 v8::Local<v8::Value> meta_analysis_result = CompileRun( 974 "var parsed_meta = parsed.nodes[0];\n" 975 "var children_count_offset =" 976 " parsed_meta.fields.indexOf('children_count');\n" 977 "var children_offset =" 978 " parsed_meta.fields.indexOf('children');\n" 979 "var children_meta =" 980 " parsed_meta.types[children_offset];\n" 981 "var child_fields_count = children_meta.fields.length;\n" 982 "var child_type_offset =" 983 " children_meta.fields.indexOf('type');\n" 984 "var child_name_offset =" 985 " children_meta.fields.indexOf('name_or_index');\n" 986 "var child_to_node_offset =" 987 " children_meta.fields.indexOf('to_node');\n" 988 "var property_type =" 989 " children_meta.types[child_type_offset].indexOf('property');\n" 990 "var shortcut_type =" 991 " children_meta.types[child_type_offset].indexOf('shortcut');"); 992 CHECK(!meta_analysis_result.IsEmpty()); 993 994 // A helper function for processing encoded nodes. 995 CompileRun( 996 "function GetChildPosByProperty(pos, prop_name, prop_type) {\n" 997 " var nodes = parsed.nodes;\n" 998 " var strings = parsed.strings;\n" 999 " for (var i = 0,\n" 1000 " count = nodes[pos + children_count_offset] * child_fields_count;\n" 1001 " i < count; i += child_fields_count) {\n" 1002 " var child_pos = pos + children_offset + i;\n" 1003 " if (nodes[child_pos + child_type_offset] === prop_type\n" 1004 " && strings[nodes[child_pos + child_name_offset]] === prop_name)\n" 1005 " return nodes[child_pos + child_to_node_offset];\n" 1006 " }\n" 1007 " return null;\n" 1008 "}\n"); 1009 // Get the string index using the path: <root> -> <global>.b.x.s 1010 v8::Local<v8::Value> string_obj_pos_val = CompileRun( 1011 "GetChildPosByProperty(\n" 1012 " GetChildPosByProperty(\n" 1013 " GetChildPosByProperty(" 1014 " parsed.nodes[1 + children_offset + child_to_node_offset]," 1015 " \"b\",shortcut_type),\n" 1016 " \"x\", property_type)," 1017 " \"s\", property_type)"); 1018 CHECK(!string_obj_pos_val.IsEmpty()); 1019 int string_obj_pos = 1020 static_cast<int>(string_obj_pos_val->ToNumber()->Value()); 1021 v8::Local<v8::Object> nodes_array = 1022 parsed_snapshot->Get(v8::String::New("nodes"))->ToObject(); 1023 int string_index = static_cast<int>( 1024 nodes_array->Get(string_obj_pos + 1)->ToNumber()->Value()); 1025 CHECK_GT(string_index, 0); 1026 v8::Local<v8::Object> strings_array = 1027 parsed_snapshot->Get(v8::String::New("strings"))->ToObject(); 1028 v8::Local<v8::String> string = strings_array->Get(string_index)->ToString(); 1029 v8::Local<v8::String> ref_string = 1030 CompileRun(STRING_LITERAL_FOR_TEST)->ToString(); 1031 #undef STRING_LITERAL_FOR_TEST 1032 CHECK_EQ(*v8::String::Utf8Value(ref_string), 1033 *v8::String::Utf8Value(string)); 1034 } 1035 1036 1037 TEST(HeapSnapshotJSONSerializationAborting) { 1038 v8::HandleScope scope; 1039 LocalContext env; 1040 const v8::HeapSnapshot* snapshot = 1041 v8::HeapProfiler::TakeSnapshot(v8::String::New("abort")); 1042 TestJSONStream stream(5); 1043 snapshot->Serialize(&stream, v8::HeapSnapshot::kJSON); 1044 CHECK_GT(stream.size(), 0); 1045 CHECK_EQ(0, stream.eos_signaled()); 1046 } 1047 1048 1049 // Must not crash in debug mode. 1050 TEST(AggregatedHeapSnapshotJSONSerialization) { 1051 v8::HandleScope scope; 1052 LocalContext env; 1053 1054 const v8::HeapSnapshot* snapshot = 1055 v8::HeapProfiler::TakeSnapshot( 1056 v8::String::New("agg"), v8::HeapSnapshot::kAggregated); 1057 TestJSONStream stream; 1058 snapshot->Serialize(&stream, v8::HeapSnapshot::kJSON); 1059 CHECK_GT(stream.size(), 0); 1060 CHECK_EQ(1, stream.eos_signaled()); 1061 } 1062 1063 1064 TEST(HeapSnapshotGetNodeById) { 1065 v8::HandleScope scope; 1066 LocalContext env; 1067 1068 const v8::HeapSnapshot* snapshot = 1069 v8::HeapProfiler::TakeSnapshot(v8::String::New("id")); 1070 const v8::HeapGraphNode* root = snapshot->GetRoot(); 1071 CHECK_EQ(root, snapshot->GetNodeById(root->GetId())); 1072 for (int i = 0, count = root->GetChildrenCount(); i < count; ++i) { 1073 const v8::HeapGraphEdge* prop = root->GetChild(i); 1074 CHECK_EQ( 1075 prop->GetToNode(), snapshot->GetNodeById(prop->GetToNode()->GetId())); 1076 } 1077 // Check a big id, which should not exist yet. 1078 CHECK_EQ(NULL, snapshot->GetNodeById(0x1000000UL)); 1079 } 1080 1081 1082 namespace { 1083 1084 class TestActivityControl : public v8::ActivityControl { 1085 public: 1086 explicit TestActivityControl(int abort_count) 1087 : done_(0), total_(0), abort_count_(abort_count) {} 1088 ControlOption ReportProgressValue(int done, int total) { 1089 done_ = done; 1090 total_ = total; 1091 return --abort_count_ != 0 ? kContinue : kAbort; 1092 } 1093 int done() { return done_; } 1094 int total() { return total_; } 1095 1096 private: 1097 int done_; 1098 int total_; 1099 int abort_count_; 1100 }; 1101 } 1102 1103 TEST(TakeHeapSnapshotAborting) { 1104 v8::HandleScope scope; 1105 LocalContext env; 1106 1107 const int snapshots_count = v8::HeapProfiler::GetSnapshotsCount(); 1108 TestActivityControl aborting_control(3); 1109 const v8::HeapSnapshot* no_snapshot = 1110 v8::HeapProfiler::TakeSnapshot(v8::String::New("abort"), 1111 v8::HeapSnapshot::kFull, 1112 &aborting_control); 1113 CHECK_EQ(NULL, no_snapshot); 1114 CHECK_EQ(snapshots_count, v8::HeapProfiler::GetSnapshotsCount()); 1115 CHECK_GT(aborting_control.total(), aborting_control.done()); 1116 1117 TestActivityControl control(-1); // Don't abort. 1118 const v8::HeapSnapshot* snapshot = 1119 v8::HeapProfiler::TakeSnapshot(v8::String::New("full"), 1120 v8::HeapSnapshot::kFull, 1121 &control); 1122 CHECK_NE(NULL, snapshot); 1123 CHECK_EQ(snapshots_count + 1, v8::HeapProfiler::GetSnapshotsCount()); 1124 CHECK_EQ(control.total(), control.done()); 1125 CHECK_GT(control.total(), 0); 1126 } 1127 1128 1129 namespace { 1130 1131 class TestRetainedObjectInfo : public v8::RetainedObjectInfo { 1132 public: 1133 TestRetainedObjectInfo(int hash, 1134 const char* label, 1135 intptr_t element_count = -1, 1136 intptr_t size = -1) 1137 : disposed_(false), 1138 hash_(hash), 1139 label_(label), 1140 element_count_(element_count), 1141 size_(size) { 1142 instances.Add(this); 1143 } 1144 virtual ~TestRetainedObjectInfo() {} 1145 virtual void Dispose() { 1146 CHECK(!disposed_); 1147 disposed_ = true; 1148 } 1149 virtual bool IsEquivalent(RetainedObjectInfo* other) { 1150 return GetHash() == other->GetHash(); 1151 } 1152 virtual intptr_t GetHash() { return hash_; } 1153 virtual const char* GetLabel() { return label_; } 1154 virtual intptr_t GetElementCount() { return element_count_; } 1155 virtual intptr_t GetSizeInBytes() { return size_; } 1156 bool disposed() { return disposed_; } 1157 1158 static v8::RetainedObjectInfo* WrapperInfoCallback( 1159 uint16_t class_id, v8::Handle<v8::Value> wrapper) { 1160 if (class_id == 1) { 1161 if (wrapper->IsString()) { 1162 v8::String::AsciiValue ascii(wrapper); 1163 if (strcmp(*ascii, "AAA") == 0) 1164 return new TestRetainedObjectInfo(1, "aaa", 100); 1165 else if (strcmp(*ascii, "BBB") == 0) 1166 return new TestRetainedObjectInfo(1, "aaa", 100); 1167 } 1168 } else if (class_id == 2) { 1169 if (wrapper->IsString()) { 1170 v8::String::AsciiValue ascii(wrapper); 1171 if (strcmp(*ascii, "CCC") == 0) 1172 return new TestRetainedObjectInfo(2, "ccc"); 1173 } 1174 } 1175 CHECK(false); 1176 return NULL; 1177 } 1178 1179 static i::List<TestRetainedObjectInfo*> instances; 1180 1181 private: 1182 bool disposed_; 1183 int category_; 1184 int hash_; 1185 const char* label_; 1186 intptr_t element_count_; 1187 intptr_t size_; 1188 }; 1189 1190 1191 i::List<TestRetainedObjectInfo*> TestRetainedObjectInfo::instances; 1192 } 1193 1194 1195 static const v8::HeapGraphNode* GetNode(const v8::HeapGraphNode* parent, 1196 v8::HeapGraphNode::Type type, 1197 const char* name) { 1198 for (int i = 0, count = parent->GetChildrenCount(); i < count; ++i) { 1199 const v8::HeapGraphNode* node = parent->GetChild(i)->GetToNode(); 1200 if (node->GetType() == type && strcmp(name, 1201 const_cast<i::HeapEntry*>( 1202 reinterpret_cast<const i::HeapEntry*>(node))->name()) == 0) { 1203 return node; 1204 } 1205 } 1206 return NULL; 1207 } 1208 1209 1210 TEST(HeapSnapshotRetainedObjectInfo) { 1211 v8::HandleScope scope; 1212 LocalContext env; 1213 1214 v8::HeapProfiler::DefineWrapperClass( 1215 1, TestRetainedObjectInfo::WrapperInfoCallback); 1216 v8::HeapProfiler::DefineWrapperClass( 1217 2, TestRetainedObjectInfo::WrapperInfoCallback); 1218 v8::Persistent<v8::String> p_AAA = 1219 v8::Persistent<v8::String>::New(v8_str("AAA")); 1220 p_AAA.SetWrapperClassId(1); 1221 v8::Persistent<v8::String> p_BBB = 1222 v8::Persistent<v8::String>::New(v8_str("BBB")); 1223 p_BBB.SetWrapperClassId(1); 1224 v8::Persistent<v8::String> p_CCC = 1225 v8::Persistent<v8::String>::New(v8_str("CCC")); 1226 p_CCC.SetWrapperClassId(2); 1227 CHECK_EQ(0, TestRetainedObjectInfo::instances.length()); 1228 const v8::HeapSnapshot* snapshot = 1229 v8::HeapProfiler::TakeSnapshot(v8::String::New("retained")); 1230 1231 CHECK_EQ(3, TestRetainedObjectInfo::instances.length()); 1232 for (int i = 0; i < TestRetainedObjectInfo::instances.length(); ++i) { 1233 CHECK(TestRetainedObjectInfo::instances[i]->disposed()); 1234 delete TestRetainedObjectInfo::instances[i]; 1235 } 1236 1237 const v8::HeapGraphNode* natives = GetNode( 1238 snapshot->GetRoot(), v8::HeapGraphNode::kObject, "(Native objects)"); 1239 CHECK_NE(NULL, natives); 1240 CHECK_EQ(2, natives->GetChildrenCount()); 1241 const v8::HeapGraphNode* aaa = GetNode( 1242 natives, v8::HeapGraphNode::kNative, "aaa / 100 entries"); 1243 CHECK_NE(NULL, aaa); 1244 const v8::HeapGraphNode* ccc = GetNode( 1245 natives, v8::HeapGraphNode::kNative, "ccc"); 1246 CHECK_NE(NULL, ccc); 1247 1248 CHECK_EQ(2, aaa->GetChildrenCount()); 1249 const v8::HeapGraphNode* n_AAA = GetNode( 1250 aaa, v8::HeapGraphNode::kString, "AAA"); 1251 CHECK_NE(NULL, n_AAA); 1252 const v8::HeapGraphNode* n_BBB = GetNode( 1253 aaa, v8::HeapGraphNode::kString, "BBB"); 1254 CHECK_NE(NULL, n_BBB); 1255 CHECK_EQ(1, ccc->GetChildrenCount()); 1256 const v8::HeapGraphNode* n_CCC = GetNode( 1257 ccc, v8::HeapGraphNode::kString, "CCC"); 1258 CHECK_NE(NULL, n_CCC); 1259 1260 CHECK_EQ(aaa, GetProperty(n_AAA, v8::HeapGraphEdge::kInternal, "native")); 1261 CHECK_EQ(aaa, GetProperty(n_BBB, v8::HeapGraphEdge::kInternal, "native")); 1262 CHECK_EQ(ccc, GetProperty(n_CCC, v8::HeapGraphEdge::kInternal, "native")); 1263 } 1264 1265 1266 TEST(DeleteAllHeapSnapshots) { 1267 v8::HandleScope scope; 1268 LocalContext env; 1269 1270 CHECK_EQ(0, v8::HeapProfiler::GetSnapshotsCount()); 1271 v8::HeapProfiler::DeleteAllSnapshots(); 1272 CHECK_EQ(0, v8::HeapProfiler::GetSnapshotsCount()); 1273 CHECK_NE(NULL, v8::HeapProfiler::TakeSnapshot(v8::String::New("1"))); 1274 CHECK_EQ(1, v8::HeapProfiler::GetSnapshotsCount()); 1275 v8::HeapProfiler::DeleteAllSnapshots(); 1276 CHECK_EQ(0, v8::HeapProfiler::GetSnapshotsCount()); 1277 CHECK_NE(NULL, v8::HeapProfiler::TakeSnapshot(v8::String::New("1"))); 1278 CHECK_NE(NULL, v8::HeapProfiler::TakeSnapshot(v8::String::New("2"))); 1279 CHECK_EQ(2, v8::HeapProfiler::GetSnapshotsCount()); 1280 v8::HeapProfiler::DeleteAllSnapshots(); 1281 CHECK_EQ(0, v8::HeapProfiler::GetSnapshotsCount()); 1282 } 1283 1284 1285 TEST(DeleteHeapSnapshot) { 1286 v8::HandleScope scope; 1287 LocalContext env; 1288 1289 CHECK_EQ(0, v8::HeapProfiler::GetSnapshotsCount()); 1290 const v8::HeapSnapshot* s1 = 1291 v8::HeapProfiler::TakeSnapshot(v8::String::New("1")); 1292 CHECK_NE(NULL, s1); 1293 CHECK_EQ(1, v8::HeapProfiler::GetSnapshotsCount()); 1294 unsigned uid1 = s1->GetUid(); 1295 CHECK_EQ(s1, v8::HeapProfiler::FindSnapshot(uid1)); 1296 const_cast<v8::HeapSnapshot*>(s1)->Delete(); 1297 CHECK_EQ(0, v8::HeapProfiler::GetSnapshotsCount()); 1298 CHECK_EQ(NULL, v8::HeapProfiler::FindSnapshot(uid1)); 1299 1300 const v8::HeapSnapshot* s2 = 1301 v8::HeapProfiler::TakeSnapshot(v8::String::New("2")); 1302 CHECK_NE(NULL, s2); 1303 CHECK_EQ(1, v8::HeapProfiler::GetSnapshotsCount()); 1304 unsigned uid2 = s2->GetUid(); 1305 CHECK_NE(static_cast<int>(uid1), static_cast<int>(uid2)); 1306 CHECK_EQ(s2, v8::HeapProfiler::FindSnapshot(uid2)); 1307 const v8::HeapSnapshot* s3 = 1308 v8::HeapProfiler::TakeSnapshot(v8::String::New("3")); 1309 CHECK_NE(NULL, s3); 1310 CHECK_EQ(2, v8::HeapProfiler::GetSnapshotsCount()); 1311 unsigned uid3 = s3->GetUid(); 1312 CHECK_NE(static_cast<int>(uid1), static_cast<int>(uid3)); 1313 CHECK_EQ(s3, v8::HeapProfiler::FindSnapshot(uid3)); 1314 const_cast<v8::HeapSnapshot*>(s2)->Delete(); 1315 CHECK_EQ(1, v8::HeapProfiler::GetSnapshotsCount()); 1316 CHECK_EQ(NULL, v8::HeapProfiler::FindSnapshot(uid2)); 1317 CHECK_EQ(s3, v8::HeapProfiler::FindSnapshot(uid3)); 1318 const_cast<v8::HeapSnapshot*>(s3)->Delete(); 1319 CHECK_EQ(0, v8::HeapProfiler::GetSnapshotsCount()); 1320 CHECK_EQ(NULL, v8::HeapProfiler::FindSnapshot(uid3)); 1321 } 1322 1323 #endif // ENABLE_LOGGING_AND_PROFILING 1324