blob: 5e570f34de3dc97a3eb8974ae24932de36e3fc44 [file] [log] [blame]
Steve Blocka7e24c12009-10-30 11:49:00 +00001// 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"
Kristian Monsen9dcf7e22010-06-28 14:14:28 +01009#include "snapshot.h"
Steve Blocka7e24c12009-10-30 11:49:00 +000010#include "string-stream.h"
11#include "cctest.h"
Steve Block6ded16b2010-05-10 14:33:55 +010012#include "zone-inl.h"
Kristian Monsen9dcf7e22010-06-28 14:14:28 +010013#include "../include/v8-profiler.h"
Steve Blocka7e24c12009-10-30 11:49:00 +000014
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;
Ben Murdoch3bec4d22010-07-22 14:51:16 +010059 LocalContext env;
Steve Blocka7e24c12009-10-30 11:49:00 +000060
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;
Leon Clarked91b9f72010-01-27 17:25:45 +000069 for (i::HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next())
Steve Blocka7e24c12009-10-30 11:49:00 +000070 cons_profile.CollectStats(obj);
Steve Blocka7e24c12009-10-30 11:49:00 +000071 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;
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100146 LocalContext env;
Steve Blocka7e24c12009-10-30 11:49:00 +0000147
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;
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100184 LocalContext env;
Steve Blocka7e24c12009-10-30 11:49:00 +0000185
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;
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100214 LocalContext env;
Steve Blocka7e24c12009-10-30 11:49:00 +0000215
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;
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100266 LocalContext env;
Steve Blocka7e24c12009-10-30 11:49:00 +0000267
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;
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100360 LocalContext env;
Steve Blocka7e24c12009-10-30 11:49:00 +0000361
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;
Leon Clarked91b9f72010-01-27 17:25:45 +0000373 for (i::HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next())
Steve Blocka7e24c12009-10-30 11:49:00 +0000374 ret_profile.CollectStats(obj);
Steve Block791712a2010-08-27 10:21:07 +0100375 ret_profile.CoarseAndAggregate();
Steve Blocka7e24c12009-10-30 11:49:00 +0000376 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.
Steve Blockd0582a62009-12-15 09:54:21 +0000381 CHECK_EQ(i::StrLength("(global property);1,B;2,C;2"),
382 i::StrLength(retainers_of_a));
Steve Blocka7e24c12009-10-30 11:49:00 +0000383 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
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100390
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
Iain Merrick75681382010-08-19 15:07:18 +0100400 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();
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100411 }
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) {
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100426 CHECK_EQ(1, snapshot->GetRoot()->GetChildrenCount());
427 return snapshot->GetRoot()->GetChild(0)->GetToNode();
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100428}
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
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100444static 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
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100457static 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();
Iain Merrick75681382010-08-19 15:07:18 +0100461 if (node->GetType() == v8::HeapGraphNode::kString) {
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100462 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;
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100472 v8::Handle<v8::String> token1 = v8::String::New("token1");
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100473 LocalContext env1;
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100474 env1->SetSecurityToken(token1);
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100475
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");
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100485 LocalContext env2;
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100486 env2->SetSecurityToken(token2);
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100487
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"));
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100497 i::HeapSnapshot* i_snapshot_env2 =
498 const_cast<i::HeapSnapshot*>(
499 reinterpret_cast<const i::HeapSnapshot*>(snapshot_env2));
Iain Merrick75681382010-08-19 15:07:18 +0100500 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;
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100525 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
Iain Merrick75681382010-08-19 15:07:18 +0100548 && last_edge->GetType() == v8::HeapGraphEdge::kProperty) {
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100549 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
Iain Merrick75681382010-08-19 15:07:18 +0100556 && last_edge->GetType() == v8::HeapGraphEdge::kProperty
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100557 && strcmp("c2", *prev_edge_name) == 0) has_c2_x1_ref = true;
558 if (strcmp("x2", *last_edge_name) == 0
Iain Merrick75681382010-08-19 15:07:18 +0100559 && last_edge->GetType() == v8::HeapGraphEdge::kProperty
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100560 && strcmp("c2", *prev_edge_name) == 0) has_c2_x2_ref = true;
561 if (strcmp("1", *last_edge_name) == 0
Iain Merrick75681382010-08-19 15:07:18 +0100562 && last_edge->GetType() == v8::HeapGraphEdge::kElement
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100563 && strcmp("c2", *prev_edge_name) == 0) has_c2_1_ref = true;
564 if (strcmp("x", *last_edge_name) == 0
Iain Merrick75681382010-08-19 15:07:18 +0100565 && last_edge->GetType() == v8::HeapGraphEdge::kContextVariable
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100566 && strcmp("b2_1", *prev_edge_name) == 0) has_b2_1_x_ref = true;
567 if (strcmp("x", *last_edge_name) == 0
Iain Merrick75681382010-08-19 15:07:18 +0100568 && last_edge->GetType() == v8::HeapGraphEdge::kContextVariable
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100569 && 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
Iain Merrick75681382010-08-19 15:07:18 +0100580TEST(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
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100647TEST(HeapSnapshotCodeObjects) {
648 v8::HandleScope scope;
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100649 LocalContext env;
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100650
651 CompileAndRunScript(
652 "function lazy(x) { return x - 1; }\n"
653 "function compiled(x) { return x + 1; }\n"
Steve Block791712a2010-08-27 10:21:07 +0100654 "var inferred = function(x) { return x; }\n"
655 "var anonymous = (function() { return function() { return 0; } })();\n"
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100656 "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 =
Iain Merrick75681382010-08-19 15:07:18 +0100662 GetProperty(global, v8::HeapGraphEdge::kProperty, "compiled");
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100663 CHECK_NE(NULL, compiled);
Iain Merrick75681382010-08-19 15:07:18 +0100664 CHECK_EQ(v8::HeapGraphNode::kClosure, compiled->GetType());
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100665 const v8::HeapGraphNode* lazy =
Iain Merrick75681382010-08-19 15:07:18 +0100666 GetProperty(global, v8::HeapGraphEdge::kProperty, "lazy");
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100667 CHECK_NE(NULL, lazy);
Iain Merrick75681382010-08-19 15:07:18 +0100668 CHECK_EQ(v8::HeapGraphNode::kClosure, lazy->GetType());
Steve Block791712a2010-08-27 10:21:07 +0100669 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);
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100681
682 // Find references to code.
683 const v8::HeapGraphNode* compiled_code =
Iain Merrick75681382010-08-19 15:07:18 +0100684 GetProperty(compiled, v8::HeapGraphEdge::kInternal, "code");
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100685 CHECK_NE(NULL, compiled_code);
686 const v8::HeapGraphNode* lazy_code =
Iain Merrick75681382010-08-19 15:07:18 +0100687 GetProperty(lazy, v8::HeapGraphEdge::kInternal, "code");
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100688 CHECK_NE(NULL, lazy_code);
689
690 // Verify that non-compiled code doesn't contain references to "x"
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100691 // literal, while compiled code does. The scope info is stored in FixedArray
692 // objects attached to the SharedFunctionInfo.
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100693 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();
Iain Merrick75681382010-08-19 15:07:18 +0100697 if (node->GetType() == v8::HeapGraphNode::kArray) {
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100698 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();
Iain Merrick75681382010-08-19 15:07:18 +0100707 if (node->GetType() == v8::HeapGraphNode::kArray) {
Kristian Monsen9dcf7e22010-06-28 14:14:28 +0100708 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
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100718
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))
Iain Merrick75681382010-08-19 15:07:18 +0100724#define CHECK_NE_UINT64_T(a, b) \
725 CHECK((a) != (b)) // NOLINT
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100726
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 =
Iain Merrick75681382010-08-19 15:07:18 +0100749 GetProperty(global1, v8::HeapGraphEdge::kProperty, "A");
750 CHECK_NE(NULL, A1);
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100751 const v8::HeapGraphNode* A2 =
Iain Merrick75681382010-08-19 15:07:18 +0100752 GetProperty(global2, v8::HeapGraphEdge::kProperty, "A");
753 CHECK_NE(NULL, A2);
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100754 CHECK_NE_UINT64_T(0, A1->GetId());
755 CHECK_EQ_UINT64_T(A1->GetId(), A2->GetId());
756 const v8::HeapGraphNode* B1 =
Iain Merrick75681382010-08-19 15:07:18 +0100757 GetProperty(global1, v8::HeapGraphEdge::kProperty, "B");
758 CHECK_NE(NULL, B1);
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100759 const v8::HeapGraphNode* B2 =
Iain Merrick75681382010-08-19 15:07:18 +0100760 GetProperty(global2, v8::HeapGraphEdge::kProperty, "B");
761 CHECK_NE(NULL, B2);
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100762 CHECK_NE_UINT64_T(0, B1->GetId());
763 CHECK_EQ_UINT64_T(B1->GetId(), B2->GetId());
764 const v8::HeapGraphNode* a1 =
Iain Merrick75681382010-08-19 15:07:18 +0100765 GetProperty(global1, v8::HeapGraphEdge::kProperty, "a");
766 CHECK_NE(NULL, a1);
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100767 const v8::HeapGraphNode* a2 =
Iain Merrick75681382010-08-19 15:07:18 +0100768 GetProperty(global2, v8::HeapGraphEdge::kProperty, "a");
769 CHECK_NE(NULL, a2);
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100770 CHECK_NE_UINT64_T(0, a1->GetId());
771 CHECK_EQ_UINT64_T(a1->GetId(), a2->GetId());
772 const v8::HeapGraphNode* b1 =
Iain Merrick75681382010-08-19 15:07:18 +0100773 GetProperty(global1, v8::HeapGraphEdge::kProperty, "b");
774 CHECK_NE(NULL, b1);
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100775 const v8::HeapGraphNode* b2 =
Iain Merrick75681382010-08-19 15:07:18 +0100776 GetProperty(global2, v8::HeapGraphEdge::kProperty, "b");
777 CHECK_NE(NULL, b2);
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100778 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();
Iain Merrick75681382010-08-19 15:07:18 +0100812 if (node->GetType() == v8::HeapGraphNode::kObject) {
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100813 v8::String::AsciiValue node_name(node->GetName());
814 if (strcmp(*node_name, "A") == 0) {
Iain Merrick75681382010-08-19 15:07:18 +0100815 CHECK(IsNodeRetainedAs(node, v8::HeapGraphEdge::kProperty, "a"));
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100816 CHECK(!found_A);
817 found_A = true;
818 s1_A_id = node->GetId();
819 } else if (strcmp(*node_name, "B") == 0) {
Iain Merrick75681382010-08-19 15:07:18 +0100820 CHECK(IsNodeRetainedAs(node, v8::HeapGraphEdge::kProperty, "b2"));
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100821 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();
Iain Merrick75681382010-08-19 15:07:18 +0100836 if (node->GetType() == v8::HeapGraphNode::kObject) {
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100837 v8::String::AsciiValue node_name(node->GetName());
838 if (strcmp(*node_name, "A") == 0) {
Iain Merrick75681382010-08-19 15:07:18 +0100839 CHECK(IsNodeRetainedAs(node, v8::HeapGraphEdge::kProperty, "a"));
Ben Murdoch3bec4d22010-07-22 14:51:16 +0100840 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
Iain Merrick75681382010-08-19 15:07:18 +0100851
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
Steve Block791712a2010-08-27 10:21:07 +0100882
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
Kristian Monsen0d5e1162010-09-30 15:31:59 +0100992namespace {
993
994class TestJSONStream : public v8::OutputStream {
995 public:
996 TestJSONStream() : eos_signaled_(0), abort_countdown_(-1) {}
997 explicit TestJSONStream(int abort_countdown)
998 : eos_signaled_(0), abort_countdown_(abort_countdown) {}
999 virtual ~TestJSONStream() {}
1000 virtual void EndOfStream() { ++eos_signaled_; }
1001 virtual WriteResult WriteAsciiChunk(char* buffer, int chars_written) {
1002 if (abort_countdown_ > 0) --abort_countdown_;
1003 if (abort_countdown_ == 0) return kAbort;
1004 CHECK_GT(chars_written, 0);
1005 i::Vector<char> chunk = buffer_.AddBlock(chars_written, '\0');
1006 memcpy(chunk.start(), buffer, chars_written);
1007 return kContinue;
1008 }
1009 void WriteTo(i::Vector<char> dest) { buffer_.WriteTo(dest); }
1010 int eos_signaled() { return eos_signaled_; }
1011 int size() { return buffer_.size(); }
1012 private:
1013 i::Collector<char> buffer_;
1014 int eos_signaled_;
1015 int abort_countdown_;
1016};
1017
1018class AsciiResource: public v8::String::ExternalAsciiStringResource {
1019 public:
1020 explicit AsciiResource(i::Vector<char> string): data_(string.start()) {
1021 length_ = string.length();
1022 }
1023 virtual const char* data() const { return data_; }
1024 virtual size_t length() const { return length_; }
1025 private:
1026 const char* data_;
1027 size_t length_;
1028};
1029
1030} // namespace
1031
1032TEST(HeapSnapshotJSONSerialization) {
1033 v8::HandleScope scope;
1034 LocalContext env;
1035
1036#define STRING_LITERAL_FOR_TEST \
1037 "\"String \\n\\r\\u0008\\u0081\\u0101\\u0801\\u8001\""
1038 CompileAndRunScript(
1039 "function A(s) { this.s = s; }\n"
1040 "function B(x) { this.x = x; }\n"
1041 "var a = new A(" STRING_LITERAL_FOR_TEST ");\n"
1042 "var b = new B(a);");
1043 const v8::HeapSnapshot* snapshot =
1044 v8::HeapProfiler::TakeSnapshot(v8::String::New("json"));
1045 TestJSONStream stream;
1046 snapshot->Serialize(&stream, v8::HeapSnapshot::kJSON);
1047 CHECK_GT(stream.size(), 0);
1048 CHECK_EQ(1, stream.eos_signaled());
1049 i::ScopedVector<char> json(stream.size());
1050 stream.WriteTo(json);
1051
1052 // Verify that snapshot string is valid JSON.
1053 AsciiResource json_res(json);
1054 v8::Local<v8::String> json_string = v8::String::NewExternal(&json_res);
1055 env->Global()->Set(v8::String::New("json_snapshot"), json_string);
1056 v8::Local<v8::Value> snapshot_parse_result = CompileRun(
1057 "var parsed = JSON.parse(json_snapshot); true;");
1058 CHECK(!snapshot_parse_result.IsEmpty());
1059
1060 // Verify that snapshot object has required fields.
1061 v8::Local<v8::Object> parsed_snapshot =
1062 env->Global()->Get(v8::String::New("parsed"))->ToObject();
1063 CHECK(parsed_snapshot->Has(v8::String::New("snapshot")));
1064 CHECK(parsed_snapshot->Has(v8::String::New("nodes")));
1065 CHECK(parsed_snapshot->Has(v8::String::New("strings")));
1066
1067 // Verify that nodes meta-info is valid JSON.
1068 v8::Local<v8::Value> nodes_meta_parse_result = CompileRun(
1069 "var parsed_meta = JSON.parse(parsed.nodes[0]); true;");
1070 CHECK(!nodes_meta_parse_result.IsEmpty());
1071
1072 // Get node and edge "member" offsets.
1073 v8::Local<v8::Value> meta_analysis_result = CompileRun(
1074 "var children_count_offset ="
1075 " parsed_meta.fields.indexOf('children_count');\n"
1076 "var children_offset ="
1077 " parsed_meta.fields.indexOf('children');\n"
1078 "var children_meta ="
1079 " parsed_meta.types[children_offset];\n"
1080 "var child_fields_count = children_meta.fields.length;\n"
1081 "var child_type_offset ="
1082 " children_meta.fields.indexOf('type');\n"
1083 "var child_name_offset ="
1084 " children_meta.fields.indexOf('name_or_index');\n"
1085 "var child_to_node_offset ="
1086 " children_meta.fields.indexOf('to_node');\n"
1087 "var property_type ="
1088 " children_meta.types[child_type_offset].indexOf('property');");
1089 CHECK(!meta_analysis_result.IsEmpty());
1090
1091 // A helper function for processing encoded nodes.
1092 CompileRun(
1093 "function GetChildPosByProperty(pos, prop_name) {\n"
1094 " var nodes = parsed.nodes;\n"
1095 " var strings = parsed.strings;\n"
1096 " for (var i = 0,\n"
1097 " count = nodes[pos + children_count_offset] * child_fields_count;\n"
1098 " i < count; i += child_fields_count) {\n"
1099 " var child_pos = pos + children_offset + i;\n"
1100 " if (nodes[child_pos + child_type_offset] === property_type\n"
1101 " && strings[nodes[child_pos + child_name_offset]] === prop_name)\n"
1102 " return nodes[child_pos + child_to_node_offset];\n"
1103 " }\n"
1104 " return null;\n"
1105 "}\n");
1106 // Get the string index using the path: <root> -> <global>.b.x.s
1107 v8::Local<v8::Value> string_obj_pos_val = CompileRun(
1108 "GetChildPosByProperty(\n"
1109 " GetChildPosByProperty(\n"
1110 " GetChildPosByProperty("
1111 " parsed.nodes[1 + children_offset + child_to_node_offset],\"b\"),\n"
1112 " \"x\"),"
1113 " \"s\")");
1114 CHECK(!string_obj_pos_val.IsEmpty());
1115 int string_obj_pos =
1116 static_cast<int>(string_obj_pos_val->ToNumber()->Value());
1117 v8::Local<v8::Object> nodes_array =
1118 parsed_snapshot->Get(v8::String::New("nodes"))->ToObject();
1119 int string_index = static_cast<int>(
1120 nodes_array->Get(string_obj_pos + 1)->ToNumber()->Value());
1121 CHECK_GT(string_index, 0);
1122 v8::Local<v8::Object> strings_array =
1123 parsed_snapshot->Get(v8::String::New("strings"))->ToObject();
1124 v8::Local<v8::String> string = strings_array->Get(string_index)->ToString();
1125 v8::Local<v8::String> ref_string =
1126 CompileRun(STRING_LITERAL_FOR_TEST)->ToString();
1127#undef STRING_LITERAL_FOR_TEST
1128 CHECK_EQ(*v8::String::Utf8Value(ref_string),
1129 *v8::String::Utf8Value(string));
1130}
1131
1132
1133TEST(HeapSnapshotJSONSerializationAborting) {
1134 v8::HandleScope scope;
1135 LocalContext env;
1136 const v8::HeapSnapshot* snapshot =
1137 v8::HeapProfiler::TakeSnapshot(v8::String::New("abort"));
1138 TestJSONStream stream(5);
1139 snapshot->Serialize(&stream, v8::HeapSnapshot::kJSON);
1140 CHECK_GT(stream.size(), 0);
1141 CHECK_EQ(0, stream.eos_signaled());
1142}
1143
Steve Blocka7e24c12009-10-30 11:49:00 +00001144#endif // ENABLE_LOGGING_AND_PROFILING