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