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