5a30072502
during snapshot size optimization. Sorry, now I figured out that the diff implementation itself was also incorrect. Reachable nodes must be filtered from the beginning, otherwise, an object that is already disconnected, but not discarded yet, will not appear as a deleted (thankfully, this bug for some reason had appeared on the x64 port.) BUG=868 TEST=HeapSnapshotRootPreservedAfterSorting Review URL: http://codereview.chromium.org/3531005 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@5570 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
1159 lines
40 KiB
C++
1159 lines
40 KiB
C++
// Copyright 2009 the V8 project authors. All rights reserved.
|
|
//
|
|
// Tests for heap profiler
|
|
|
|
#ifdef ENABLE_LOGGING_AND_PROFILING
|
|
|
|
#include "v8.h"
|
|
#include "heap-profiler.h"
|
|
#include "snapshot.h"
|
|
#include "string-stream.h"
|
|
#include "cctest.h"
|
|
#include "zone-inl.h"
|
|
#include "../include/v8-profiler.h"
|
|
|
|
namespace i = v8::internal;
|
|
using i::ClustersCoarser;
|
|
using i::JSObjectsCluster;
|
|
using i::JSObjectsRetainerTree;
|
|
using i::JSObjectsClusterTree;
|
|
using i::RetainerHeapProfile;
|
|
|
|
|
|
static void CompileAndRunScript(const char *src) {
|
|
v8::Script::Compile(v8::String::New(src))->Run();
|
|
}
|
|
|
|
|
|
namespace {
|
|
|
|
class ConstructorHeapProfileTestHelper : public i::ConstructorHeapProfile {
|
|
public:
|
|
ConstructorHeapProfileTestHelper()
|
|
: i::ConstructorHeapProfile(),
|
|
f_name_(i::Factory::NewStringFromAscii(i::CStrVector("F"))),
|
|
f_count_(0) {
|
|
}
|
|
|
|
void Call(const JSObjectsCluster& cluster,
|
|
const i::NumberAndSizeInfo& number_and_size) {
|
|
if (f_name_->Equals(cluster.constructor())) {
|
|
CHECK_EQ(f_count_, 0);
|
|
f_count_ = number_and_size.number();
|
|
CHECK_GT(f_count_, 0);
|
|
}
|
|
}
|
|
|
|
int f_count() { return f_count_; }
|
|
|
|
private:
|
|
i::Handle<i::String> f_name_;
|
|
int f_count_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
|
|
TEST(ConstructorProfile) {
|
|
v8::HandleScope scope;
|
|
LocalContext env;
|
|
|
|
CompileAndRunScript(
|
|
"function F() {} // A constructor\n"
|
|
"var f1 = new F();\n"
|
|
"var f2 = new F();\n");
|
|
|
|
ConstructorHeapProfileTestHelper cons_profile;
|
|
i::AssertNoAllocation no_alloc;
|
|
i::HeapIterator iterator;
|
|
for (i::HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next())
|
|
cons_profile.CollectStats(obj);
|
|
CHECK_EQ(0, cons_profile.f_count());
|
|
cons_profile.PrintStats();
|
|
CHECK_EQ(2, cons_profile.f_count());
|
|
}
|
|
|
|
|
|
static JSObjectsCluster AddHeapObjectToTree(JSObjectsRetainerTree* tree,
|
|
i::String* constructor,
|
|
int instance,
|
|
JSObjectsCluster* ref1 = NULL,
|
|
JSObjectsCluster* ref2 = NULL,
|
|
JSObjectsCluster* ref3 = NULL) {
|
|
JSObjectsCluster o(constructor, reinterpret_cast<i::Object*>(instance));
|
|
JSObjectsClusterTree* o_tree = new JSObjectsClusterTree();
|
|
JSObjectsClusterTree::Locator o_loc;
|
|
if (ref1 != NULL) o_tree->Insert(*ref1, &o_loc);
|
|
if (ref2 != NULL) o_tree->Insert(*ref2, &o_loc);
|
|
if (ref3 != NULL) o_tree->Insert(*ref3, &o_loc);
|
|
JSObjectsRetainerTree::Locator loc;
|
|
tree->Insert(o, &loc);
|
|
loc.set_value(o_tree);
|
|
return o;
|
|
}
|
|
|
|
|
|
static void AddSelfReferenceToTree(JSObjectsRetainerTree* tree,
|
|
JSObjectsCluster* self_ref) {
|
|
JSObjectsRetainerTree::Locator loc;
|
|
CHECK(tree->Find(*self_ref, &loc));
|
|
JSObjectsClusterTree::Locator o_loc;
|
|
CHECK_NE(NULL, loc.value());
|
|
loc.value()->Insert(*self_ref, &o_loc);
|
|
}
|
|
|
|
|
|
static inline void CheckEqualsHelper(const char* file, int line,
|
|
const char* expected_source,
|
|
const JSObjectsCluster& expected,
|
|
const char* value_source,
|
|
const JSObjectsCluster& value) {
|
|
if (JSObjectsCluster::Compare(expected, value) != 0) {
|
|
i::HeapStringAllocator allocator;
|
|
i::StringStream stream(&allocator);
|
|
stream.Add("# Expected: ");
|
|
expected.DebugPrint(&stream);
|
|
stream.Add("\n# Found: ");
|
|
value.DebugPrint(&stream);
|
|
V8_Fatal(file, line, "CHECK_EQ(%s, %s) failed\n%s",
|
|
expected_source, value_source,
|
|
*stream.ToCString());
|
|
}
|
|
}
|
|
|
|
|
|
static inline void CheckNonEqualsHelper(const char* file, int line,
|
|
const char* expected_source,
|
|
const JSObjectsCluster& expected,
|
|
const char* value_source,
|
|
const JSObjectsCluster& value) {
|
|
if (JSObjectsCluster::Compare(expected, value) == 0) {
|
|
i::HeapStringAllocator allocator;
|
|
i::StringStream stream(&allocator);
|
|
stream.Add("# !Expected: ");
|
|
expected.DebugPrint(&stream);
|
|
stream.Add("\n# Found: ");
|
|
value.DebugPrint(&stream);
|
|
V8_Fatal(file, line, "CHECK_NE(%s, %s) failed\n%s",
|
|
expected_source, value_source,
|
|
*stream.ToCString());
|
|
}
|
|
}
|
|
|
|
|
|
TEST(ClustersCoarserSimple) {
|
|
v8::HandleScope scope;
|
|
LocalContext env;
|
|
|
|
i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
|
|
|
|
JSObjectsRetainerTree tree;
|
|
JSObjectsCluster function(i::Heap::function_class_symbol());
|
|
JSObjectsCluster a(*i::Factory::NewStringFromAscii(i::CStrVector("A")));
|
|
JSObjectsCluster b(*i::Factory::NewStringFromAscii(i::CStrVector("B")));
|
|
|
|
// o1 <- Function
|
|
JSObjectsCluster o1 =
|
|
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x100, &function);
|
|
// o2 <- Function
|
|
JSObjectsCluster o2 =
|
|
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x200, &function);
|
|
// o3 <- A, B
|
|
JSObjectsCluster o3 =
|
|
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x300, &a, &b);
|
|
// o4 <- B, A
|
|
JSObjectsCluster o4 =
|
|
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x400, &b, &a);
|
|
// o5 <- A, B, Function
|
|
JSObjectsCluster o5 =
|
|
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x500,
|
|
&a, &b, &function);
|
|
|
|
ClustersCoarser coarser;
|
|
coarser.Process(&tree);
|
|
|
|
CHECK_EQ(coarser.GetCoarseEquivalent(o1), coarser.GetCoarseEquivalent(o2));
|
|
CHECK_EQ(coarser.GetCoarseEquivalent(o3), coarser.GetCoarseEquivalent(o4));
|
|
CHECK_NE(coarser.GetCoarseEquivalent(o1), coarser.GetCoarseEquivalent(o3));
|
|
CHECK_EQ(JSObjectsCluster(), coarser.GetCoarseEquivalent(o5));
|
|
}
|
|
|
|
|
|
TEST(ClustersCoarserMultipleConstructors) {
|
|
v8::HandleScope scope;
|
|
LocalContext env;
|
|
|
|
i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
|
|
|
|
JSObjectsRetainerTree tree;
|
|
JSObjectsCluster function(i::Heap::function_class_symbol());
|
|
|
|
// o1 <- Function
|
|
JSObjectsCluster o1 =
|
|
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x100, &function);
|
|
// a1 <- Function
|
|
JSObjectsCluster a1 =
|
|
AddHeapObjectToTree(&tree, i::Heap::Array_symbol(), 0x1000, &function);
|
|
// o2 <- Function
|
|
JSObjectsCluster o2 =
|
|
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x200, &function);
|
|
// a2 <- Function
|
|
JSObjectsCluster a2 =
|
|
AddHeapObjectToTree(&tree, i::Heap::Array_symbol(), 0x2000, &function);
|
|
|
|
ClustersCoarser coarser;
|
|
coarser.Process(&tree);
|
|
|
|
CHECK_EQ(coarser.GetCoarseEquivalent(o1), coarser.GetCoarseEquivalent(o2));
|
|
CHECK_EQ(coarser.GetCoarseEquivalent(a1), coarser.GetCoarseEquivalent(a2));
|
|
}
|
|
|
|
|
|
TEST(ClustersCoarserPathsTraversal) {
|
|
v8::HandleScope scope;
|
|
LocalContext env;
|
|
|
|
i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
|
|
|
|
JSObjectsRetainerTree tree;
|
|
|
|
// On the following graph:
|
|
//
|
|
// p
|
|
// <- o21 <- o11 <-
|
|
// q o
|
|
// <- o22 <- o12 <-
|
|
// r
|
|
//
|
|
// we expect that coarser will deduce equivalences: p ~ q ~ r,
|
|
// o21 ~ o22, and o11 ~ o12.
|
|
|
|
JSObjectsCluster o =
|
|
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x100);
|
|
JSObjectsCluster o11 =
|
|
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x110, &o);
|
|
JSObjectsCluster o12 =
|
|
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x120, &o);
|
|
JSObjectsCluster o21 =
|
|
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x210, &o11);
|
|
JSObjectsCluster o22 =
|
|
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x220, &o12);
|
|
JSObjectsCluster p =
|
|
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x300, &o21);
|
|
JSObjectsCluster q =
|
|
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x310, &o21, &o22);
|
|
JSObjectsCluster r =
|
|
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x320, &o22);
|
|
|
|
ClustersCoarser coarser;
|
|
coarser.Process(&tree);
|
|
|
|
CHECK_EQ(JSObjectsCluster(), coarser.GetCoarseEquivalent(o));
|
|
CHECK_NE(JSObjectsCluster(), coarser.GetCoarseEquivalent(o11));
|
|
CHECK_EQ(coarser.GetCoarseEquivalent(o11), coarser.GetCoarseEquivalent(o12));
|
|
CHECK_EQ(coarser.GetCoarseEquivalent(o21), coarser.GetCoarseEquivalent(o22));
|
|
CHECK_NE(coarser.GetCoarseEquivalent(o11), coarser.GetCoarseEquivalent(o21));
|
|
CHECK_NE(JSObjectsCluster(), coarser.GetCoarseEquivalent(p));
|
|
CHECK_EQ(coarser.GetCoarseEquivalent(p), coarser.GetCoarseEquivalent(q));
|
|
CHECK_EQ(coarser.GetCoarseEquivalent(q), coarser.GetCoarseEquivalent(r));
|
|
CHECK_NE(coarser.GetCoarseEquivalent(o11), coarser.GetCoarseEquivalent(p));
|
|
CHECK_NE(coarser.GetCoarseEquivalent(o21), coarser.GetCoarseEquivalent(p));
|
|
}
|
|
|
|
|
|
TEST(ClustersCoarserSelf) {
|
|
v8::HandleScope scope;
|
|
LocalContext env;
|
|
|
|
i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
|
|
|
|
JSObjectsRetainerTree tree;
|
|
|
|
// On the following graph:
|
|
//
|
|
// p (self-referencing)
|
|
// <- o1 <-
|
|
// q (self-referencing) o
|
|
// <- o2 <-
|
|
// r (self-referencing)
|
|
//
|
|
// we expect that coarser will deduce equivalences: p ~ q ~ r, o1 ~ o2;
|
|
|
|
JSObjectsCluster o =
|
|
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x100);
|
|
JSObjectsCluster o1 =
|
|
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x110, &o);
|
|
JSObjectsCluster o2 =
|
|
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x120, &o);
|
|
JSObjectsCluster p =
|
|
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x300, &o1);
|
|
AddSelfReferenceToTree(&tree, &p);
|
|
JSObjectsCluster q =
|
|
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x310, &o1, &o2);
|
|
AddSelfReferenceToTree(&tree, &q);
|
|
JSObjectsCluster r =
|
|
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x320, &o2);
|
|
AddSelfReferenceToTree(&tree, &r);
|
|
|
|
ClustersCoarser coarser;
|
|
coarser.Process(&tree);
|
|
|
|
CHECK_EQ(JSObjectsCluster(), coarser.GetCoarseEquivalent(o));
|
|
CHECK_NE(JSObjectsCluster(), coarser.GetCoarseEquivalent(o1));
|
|
CHECK_EQ(coarser.GetCoarseEquivalent(o1), coarser.GetCoarseEquivalent(o2));
|
|
CHECK_NE(JSObjectsCluster(), coarser.GetCoarseEquivalent(p));
|
|
CHECK_EQ(coarser.GetCoarseEquivalent(p), coarser.GetCoarseEquivalent(q));
|
|
CHECK_EQ(coarser.GetCoarseEquivalent(q), coarser.GetCoarseEquivalent(r));
|
|
CHECK_NE(coarser.GetCoarseEquivalent(o1), coarser.GetCoarseEquivalent(p));
|
|
}
|
|
|
|
|
|
namespace {
|
|
|
|
class RetainerProfilePrinter : public RetainerHeapProfile::Printer {
|
|
public:
|
|
RetainerProfilePrinter() : stream_(&allocator_), lines_(100) {}
|
|
|
|
void PrintRetainers(const JSObjectsCluster& cluster,
|
|
const i::StringStream& retainers) {
|
|
cluster.Print(&stream_);
|
|
stream_.Add("%s", *(retainers.ToCString()));
|
|
stream_.Put('\0');
|
|
}
|
|
|
|
const char* GetRetainers(const char* constructor) {
|
|
FillLines();
|
|
const size_t cons_len = strlen(constructor);
|
|
for (int i = 0; i < lines_.length(); ++i) {
|
|
if (strncmp(constructor, lines_[i], cons_len) == 0 &&
|
|
lines_[i][cons_len] == ',') {
|
|
return lines_[i] + cons_len + 1;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
private:
|
|
void FillLines() {
|
|
if (lines_.length() > 0) return;
|
|
stream_.Put('\0');
|
|
stream_str_ = stream_.ToCString();
|
|
const char* pos = *stream_str_;
|
|
while (pos != NULL && *pos != '\0') {
|
|
lines_.Add(pos);
|
|
pos = strchr(pos, '\0');
|
|
if (pos != NULL) ++pos;
|
|
}
|
|
}
|
|
|
|
i::HeapStringAllocator allocator_;
|
|
i::StringStream stream_;
|
|
i::SmartPointer<const char> stream_str_;
|
|
i::List<const char*> lines_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
|
|
TEST(RetainerProfile) {
|
|
v8::HandleScope scope;
|
|
LocalContext env;
|
|
|
|
CompileAndRunScript(
|
|
"function A() {}\n"
|
|
"function B(x) { this.x = x; }\n"
|
|
"function C(x) { this.x1 = x; this.x2 = x; }\n"
|
|
"var a = new A();\n"
|
|
"var b1 = new B(a), b2 = new B(a);\n"
|
|
"var c = new C(a);");
|
|
|
|
RetainerHeapProfile ret_profile;
|
|
i::AssertNoAllocation no_alloc;
|
|
i::HeapIterator iterator;
|
|
for (i::HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next())
|
|
ret_profile.CollectStats(obj);
|
|
ret_profile.CoarseAndAggregate();
|
|
RetainerProfilePrinter printer;
|
|
ret_profile.DebugPrintStats(&printer);
|
|
const char* retainers_of_a = printer.GetRetainers("A");
|
|
// The order of retainers is unspecified, so we check string length, and
|
|
// verify each retainer separately.
|
|
CHECK_EQ(i::StrLength("(global property);1,B;2,C;2"),
|
|
i::StrLength(retainers_of_a));
|
|
CHECK(strstr(retainers_of_a, "(global property);1") != NULL);
|
|
CHECK(strstr(retainers_of_a, "B;2") != NULL);
|
|
CHECK(strstr(retainers_of_a, "C;2") != NULL);
|
|
CHECK_EQ("(global property);2", printer.GetRetainers("B"));
|
|
CHECK_EQ("(global property);1", printer.GetRetainers("C"));
|
|
}
|
|
|
|
|
|
namespace {
|
|
|
|
class NamedEntriesDetector {
|
|
public:
|
|
NamedEntriesDetector()
|
|
: has_A1(false), has_B1(false), has_C1(false),
|
|
has_A2(false), has_B2(false), has_C2(false) {
|
|
}
|
|
|
|
void Apply(i::HeapEntry** entry_ptr) {
|
|
if (IsReachableNodeWithName(*entry_ptr, "A1")) has_A1 = true;
|
|
if (IsReachableNodeWithName(*entry_ptr, "B1")) has_B1 = true;
|
|
if (IsReachableNodeWithName(*entry_ptr, "C1")) has_C1 = true;
|
|
if (IsReachableNodeWithName(*entry_ptr, "A2")) has_A2 = true;
|
|
if (IsReachableNodeWithName(*entry_ptr, "B2")) has_B2 = true;
|
|
if (IsReachableNodeWithName(*entry_ptr, "C2")) has_C2 = true;
|
|
}
|
|
|
|
static bool IsReachableNodeWithName(i::HeapEntry* entry, const char* name) {
|
|
return strcmp(name, entry->name()) == 0 && entry->painted_reachable();
|
|
}
|
|
|
|
bool has_A1;
|
|
bool has_B1;
|
|
bool has_C1;
|
|
bool has_A2;
|
|
bool has_B2;
|
|
bool has_C2;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
|
|
static const v8::HeapGraphNode* GetGlobalObject(
|
|
const v8::HeapSnapshot* snapshot) {
|
|
CHECK_EQ(1, snapshot->GetRoot()->GetChildrenCount());
|
|
return snapshot->GetRoot()->GetChild(0)->GetToNode();
|
|
}
|
|
|
|
|
|
static const v8::HeapGraphNode* GetProperty(const v8::HeapGraphNode* node,
|
|
v8::HeapGraphEdge::Type type,
|
|
const char* name) {
|
|
for (int i = 0, count = node->GetChildrenCount(); i < count; ++i) {
|
|
const v8::HeapGraphEdge* prop = node->GetChild(i);
|
|
v8::String::AsciiValue prop_name(prop->GetName());
|
|
if (prop->GetType() == type && strcmp(name, *prop_name) == 0)
|
|
return prop->GetToNode();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static bool IsNodeRetainedAs(const v8::HeapGraphNode* node,
|
|
v8::HeapGraphEdge::Type type,
|
|
const char* name) {
|
|
for (int i = 0, count = node->GetRetainersCount(); i < count; ++i) {
|
|
const v8::HeapGraphEdge* prop = node->GetRetainer(i);
|
|
v8::String::AsciiValue prop_name(prop->GetName());
|
|
if (prop->GetType() == type && strcmp(name, *prop_name) == 0)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
static bool HasString(const v8::HeapGraphNode* node, const char* contents) {
|
|
for (int i = 0, count = node->GetChildrenCount(); i < count; ++i) {
|
|
const v8::HeapGraphEdge* prop = node->GetChild(i);
|
|
const v8::HeapGraphNode* node = prop->GetToNode();
|
|
if (node->GetType() == v8::HeapGraphNode::kString) {
|
|
v8::String::AsciiValue node_name(node->GetName());
|
|
if (strcmp(contents, *node_name) == 0) return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
TEST(HeapSnapshot) {
|
|
v8::HandleScope scope;
|
|
v8::Handle<v8::String> token1 = v8::String::New("token1");
|
|
LocalContext env1;
|
|
env1->SetSecurityToken(token1);
|
|
|
|
CompileAndRunScript(
|
|
"function A1() {}\n"
|
|
"function B1(x) { this.x = x; }\n"
|
|
"function C1(x) { this.x1 = x; this.x2 = x; }\n"
|
|
"var a1 = new A1();\n"
|
|
"var b1_1 = new B1(a1), b1_2 = new B1(a1);\n"
|
|
"var c1 = new C1(a1);");
|
|
|
|
v8::Handle<v8::String> token2 = v8::String::New("token2");
|
|
LocalContext env2;
|
|
env2->SetSecurityToken(token2);
|
|
|
|
CompileAndRunScript(
|
|
"function A2() {}\n"
|
|
"function B2(x) { return function() { return typeof x; }; }\n"
|
|
"function C2(x) { this.x1 = x; this.x2 = x; this[1] = x; }\n"
|
|
"var a2 = new A2();\n"
|
|
"var b2_1 = new B2(a2), b2_2 = new B2(a2);\n"
|
|
"var c2 = new C2(a2);");
|
|
const v8::HeapSnapshot* snapshot_env2 =
|
|
v8::HeapProfiler::TakeSnapshot(v8::String::New("env2"));
|
|
i::HeapSnapshot* i_snapshot_env2 =
|
|
const_cast<i::HeapSnapshot*>(
|
|
reinterpret_cast<const i::HeapSnapshot*>(snapshot_env2));
|
|
const v8::HeapGraphNode* global_env2 = GetGlobalObject(snapshot_env2);
|
|
// Paint all nodes reachable from global object.
|
|
i_snapshot_env2->ClearPaint();
|
|
const_cast<i::HeapEntry*>(
|
|
reinterpret_cast<const i::HeapEntry*>(global_env2))->PaintAllReachable();
|
|
|
|
// Verify, that JS global object of env2 doesn't have '..1'
|
|
// properties, but has '..2' properties.
|
|
CHECK_EQ(NULL, GetProperty(global_env2, v8::HeapGraphEdge::kProperty, "a1"));
|
|
CHECK_EQ(
|
|
NULL, GetProperty(global_env2, v8::HeapGraphEdge::kProperty, "b1_1"));
|
|
CHECK_EQ(
|
|
NULL, GetProperty(global_env2, v8::HeapGraphEdge::kProperty, "b1_2"));
|
|
CHECK_EQ(NULL, GetProperty(global_env2, v8::HeapGraphEdge::kProperty, "c1"));
|
|
const v8::HeapGraphNode* a2_node =
|
|
GetProperty(global_env2, v8::HeapGraphEdge::kProperty, "a2");
|
|
CHECK_NE(NULL, a2_node);
|
|
CHECK_NE(
|
|
NULL, GetProperty(global_env2, v8::HeapGraphEdge::kProperty, "b2_1"));
|
|
CHECK_NE(
|
|
NULL, GetProperty(global_env2, v8::HeapGraphEdge::kProperty, "b2_2"));
|
|
CHECK_NE(NULL, GetProperty(global_env2, v8::HeapGraphEdge::kProperty, "c2"));
|
|
|
|
// Verify that anything related to '[ABC]1' is not reachable.
|
|
NamedEntriesDetector det;
|
|
i_snapshot_env2->IterateEntries(&det);
|
|
CHECK(!det.has_A1);
|
|
CHECK(!det.has_B1);
|
|
CHECK(!det.has_C1);
|
|
CHECK(det.has_A2);
|
|
CHECK(det.has_B2);
|
|
CHECK(det.has_C2);
|
|
|
|
// Verify 'a2' object retainers. They are:
|
|
// - (global object).a2
|
|
// - c2.x1, c2.x2, c2[1]
|
|
// - b2_1 and b2_2 closures: via 'x' variable
|
|
CHECK_EQ(6, a2_node->GetRetainingPathsCount());
|
|
bool has_global_obj_a2_ref = false;
|
|
bool has_c2_x1_ref = false, has_c2_x2_ref = false, has_c2_1_ref = false;
|
|
bool has_b2_1_x_ref = false, has_b2_2_x_ref = false;
|
|
for (int i = 0; i < a2_node->GetRetainingPathsCount(); ++i) {
|
|
const v8::HeapGraphPath* path = a2_node->GetRetainingPath(i);
|
|
const int edges_count = path->GetEdgesCount();
|
|
CHECK_GT(edges_count, 0);
|
|
const v8::HeapGraphEdge* last_edge = path->GetEdge(edges_count - 1);
|
|
v8::String::AsciiValue last_edge_name(last_edge->GetName());
|
|
if (strcmp("a2", *last_edge_name) == 0
|
|
&& last_edge->GetType() == v8::HeapGraphEdge::kProperty) {
|
|
has_global_obj_a2_ref = true;
|
|
continue;
|
|
}
|
|
CHECK_GT(edges_count, 1);
|
|
const v8::HeapGraphEdge* prev_edge = path->GetEdge(edges_count - 2);
|
|
v8::String::AsciiValue prev_edge_name(prev_edge->GetName());
|
|
if (strcmp("x1", *last_edge_name) == 0
|
|
&& last_edge->GetType() == v8::HeapGraphEdge::kProperty
|
|
&& strcmp("c2", *prev_edge_name) == 0) has_c2_x1_ref = true;
|
|
if (strcmp("x2", *last_edge_name) == 0
|
|
&& last_edge->GetType() == v8::HeapGraphEdge::kProperty
|
|
&& strcmp("c2", *prev_edge_name) == 0) has_c2_x2_ref = true;
|
|
if (strcmp("1", *last_edge_name) == 0
|
|
&& last_edge->GetType() == v8::HeapGraphEdge::kElement
|
|
&& strcmp("c2", *prev_edge_name) == 0) has_c2_1_ref = true;
|
|
if (strcmp("x", *last_edge_name) == 0
|
|
&& last_edge->GetType() == v8::HeapGraphEdge::kContextVariable
|
|
&& strcmp("b2_1", *prev_edge_name) == 0) has_b2_1_x_ref = true;
|
|
if (strcmp("x", *last_edge_name) == 0
|
|
&& last_edge->GetType() == v8::HeapGraphEdge::kContextVariable
|
|
&& strcmp("b2_2", *prev_edge_name) == 0) has_b2_2_x_ref = true;
|
|
}
|
|
CHECK(has_global_obj_a2_ref);
|
|
CHECK(has_c2_x1_ref);
|
|
CHECK(has_c2_x2_ref);
|
|
CHECK(has_c2_1_ref);
|
|
CHECK(has_b2_1_x_ref);
|
|
CHECK(has_b2_2_x_ref);
|
|
}
|
|
|
|
|
|
TEST(HeapSnapshotObjectSizes) {
|
|
v8::HandleScope scope;
|
|
LocalContext env;
|
|
|
|
// -a-> X1 --a
|
|
// x -b-> X2 <-|
|
|
CompileAndRunScript(
|
|
"function X(a, b) { this.a = a; this.b = b; }\n"
|
|
"x = new X(new X(), new X());\n"
|
|
"x.a.a = x.b;");
|
|
const v8::HeapSnapshot* snapshot =
|
|
v8::HeapProfiler::TakeSnapshot(v8::String::New("sizes"));
|
|
const v8::HeapGraphNode* global = GetGlobalObject(snapshot);
|
|
const v8::HeapGraphNode* x =
|
|
GetProperty(global, v8::HeapGraphEdge::kProperty, "x");
|
|
CHECK_NE(NULL, x);
|
|
const v8::HeapGraphNode* x_prototype =
|
|
GetProperty(x, v8::HeapGraphEdge::kProperty, "prototype");
|
|
CHECK_NE(NULL, x_prototype);
|
|
const v8::HeapGraphNode* x1 =
|
|
GetProperty(x, v8::HeapGraphEdge::kProperty, "a");
|
|
CHECK_NE(NULL, x1);
|
|
const v8::HeapGraphNode* x2 =
|
|
GetProperty(x, v8::HeapGraphEdge::kProperty, "b");
|
|
CHECK_NE(NULL, x2);
|
|
CHECK_EQ(
|
|
x->GetSelfSize() * 3,
|
|
x->GetReachableSize() - x_prototype->GetReachableSize());
|
|
CHECK_EQ(
|
|
x->GetSelfSize() * 3 + x_prototype->GetSelfSize(), x->GetRetainedSize());
|
|
CHECK_EQ(
|
|
x1->GetSelfSize() * 2,
|
|
x1->GetReachableSize() - x_prototype->GetReachableSize());
|
|
CHECK_EQ(
|
|
x1->GetSelfSize(), x1->GetRetainedSize());
|
|
CHECK_EQ(
|
|
x2->GetSelfSize(),
|
|
x2->GetReachableSize() - x_prototype->GetReachableSize());
|
|
CHECK_EQ(
|
|
x2->GetSelfSize(), x2->GetRetainedSize());
|
|
}
|
|
|
|
|
|
TEST(HeapSnapshotEntryChildren) {
|
|
v8::HandleScope scope;
|
|
LocalContext env;
|
|
|
|
CompileAndRunScript(
|
|
"function A() { }\n"
|
|
"a = new A;");
|
|
const v8::HeapSnapshot* snapshot =
|
|
v8::HeapProfiler::TakeSnapshot(v8::String::New("children"));
|
|
const v8::HeapGraphNode* global = GetGlobalObject(snapshot);
|
|
for (int i = 0, count = global->GetChildrenCount(); i < count; ++i) {
|
|
const v8::HeapGraphEdge* prop = global->GetChild(i);
|
|
CHECK_EQ(global, prop->GetFromNode());
|
|
}
|
|
const v8::HeapGraphNode* a =
|
|
GetProperty(global, v8::HeapGraphEdge::kProperty, "a");
|
|
CHECK_NE(NULL, a);
|
|
for (int i = 0, count = a->GetChildrenCount(); i < count; ++i) {
|
|
const v8::HeapGraphEdge* prop = a->GetChild(i);
|
|
CHECK_EQ(a, prop->GetFromNode());
|
|
}
|
|
}
|
|
|
|
|
|
TEST(HeapSnapshotCodeObjects) {
|
|
v8::HandleScope scope;
|
|
LocalContext env;
|
|
|
|
CompileAndRunScript(
|
|
"function lazy(x) { return x - 1; }\n"
|
|
"function compiled(x) { return x + 1; }\n"
|
|
"var inferred = function(x) { return x; }\n"
|
|
"var anonymous = (function() { return function() { return 0; } })();\n"
|
|
"compiled(1)");
|
|
const v8::HeapSnapshot* snapshot =
|
|
v8::HeapProfiler::TakeSnapshot(v8::String::New("code"));
|
|
|
|
const v8::HeapGraphNode* global = GetGlobalObject(snapshot);
|
|
const v8::HeapGraphNode* compiled =
|
|
GetProperty(global, v8::HeapGraphEdge::kProperty, "compiled");
|
|
CHECK_NE(NULL, compiled);
|
|
CHECK_EQ(v8::HeapGraphNode::kClosure, compiled->GetType());
|
|
const v8::HeapGraphNode* lazy =
|
|
GetProperty(global, v8::HeapGraphEdge::kProperty, "lazy");
|
|
CHECK_NE(NULL, lazy);
|
|
CHECK_EQ(v8::HeapGraphNode::kClosure, lazy->GetType());
|
|
const v8::HeapGraphNode* inferred =
|
|
GetProperty(global, v8::HeapGraphEdge::kProperty, "inferred");
|
|
CHECK_NE(NULL, inferred);
|
|
CHECK_EQ(v8::HeapGraphNode::kClosure, inferred->GetType());
|
|
v8::String::AsciiValue inferred_name(inferred->GetName());
|
|
CHECK_EQ("inferred", *inferred_name);
|
|
const v8::HeapGraphNode* anonymous =
|
|
GetProperty(global, v8::HeapGraphEdge::kProperty, "anonymous");
|
|
CHECK_NE(NULL, anonymous);
|
|
CHECK_EQ(v8::HeapGraphNode::kClosure, anonymous->GetType());
|
|
v8::String::AsciiValue anonymous_name(anonymous->GetName());
|
|
CHECK_EQ("(anonymous function)", *anonymous_name);
|
|
|
|
// Find references to code.
|
|
const v8::HeapGraphNode* compiled_code =
|
|
GetProperty(compiled, v8::HeapGraphEdge::kInternal, "code");
|
|
CHECK_NE(NULL, compiled_code);
|
|
const v8::HeapGraphNode* lazy_code =
|
|
GetProperty(lazy, v8::HeapGraphEdge::kInternal, "code");
|
|
CHECK_NE(NULL, lazy_code);
|
|
|
|
// Verify that non-compiled code doesn't contain references to "x"
|
|
// literal, while compiled code does. The scope info is stored in FixedArray
|
|
// objects attached to the SharedFunctionInfo.
|
|
bool compiled_references_x = false, lazy_references_x = false;
|
|
for (int i = 0, count = compiled_code->GetChildrenCount(); i < count; ++i) {
|
|
const v8::HeapGraphEdge* prop = compiled_code->GetChild(i);
|
|
const v8::HeapGraphNode* node = prop->GetToNode();
|
|
if (node->GetType() == v8::HeapGraphNode::kArray) {
|
|
if (HasString(node, "x")) {
|
|
compiled_references_x = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
for (int i = 0, count = lazy_code->GetChildrenCount(); i < count; ++i) {
|
|
const v8::HeapGraphEdge* prop = lazy_code->GetChild(i);
|
|
const v8::HeapGraphNode* node = prop->GetToNode();
|
|
if (node->GetType() == v8::HeapGraphNode::kArray) {
|
|
if (HasString(node, "x")) {
|
|
lazy_references_x = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
CHECK(compiled_references_x);
|
|
CHECK(!lazy_references_x);
|
|
}
|
|
|
|
|
|
// Trying to introduce a check helper for uint64_t causes many
|
|
// overloading ambiguities, so it seems easier just to cast
|
|
// them to a signed type.
|
|
#define CHECK_EQ_UINT64_T(a, b) \
|
|
CHECK_EQ(static_cast<int64_t>(a), static_cast<int64_t>(b))
|
|
#define CHECK_NE_UINT64_T(a, b) \
|
|
CHECK((a) != (b)) // NOLINT
|
|
|
|
TEST(HeapEntryIdsAndGC) {
|
|
v8::HandleScope scope;
|
|
LocalContext env;
|
|
|
|
CompileAndRunScript(
|
|
"function A() {}\n"
|
|
"function B(x) { this.x = x; }\n"
|
|
"var a = new A();\n"
|
|
"var b = new B(a);");
|
|
const v8::HeapSnapshot* snapshot1 =
|
|
v8::HeapProfiler::TakeSnapshot(v8::String::New("s1"));
|
|
|
|
i::Heap::CollectAllGarbage(true); // Enforce compaction.
|
|
|
|
const v8::HeapSnapshot* snapshot2 =
|
|
v8::HeapProfiler::TakeSnapshot(v8::String::New("s2"));
|
|
|
|
const v8::HeapGraphNode* global1 = GetGlobalObject(snapshot1);
|
|
const v8::HeapGraphNode* global2 = GetGlobalObject(snapshot2);
|
|
CHECK_NE_UINT64_T(0, global1->GetId());
|
|
CHECK_EQ_UINT64_T(global1->GetId(), global2->GetId());
|
|
const v8::HeapGraphNode* A1 =
|
|
GetProperty(global1, v8::HeapGraphEdge::kProperty, "A");
|
|
CHECK_NE(NULL, A1);
|
|
const v8::HeapGraphNode* A2 =
|
|
GetProperty(global2, v8::HeapGraphEdge::kProperty, "A");
|
|
CHECK_NE(NULL, A2);
|
|
CHECK_NE_UINT64_T(0, A1->GetId());
|
|
CHECK_EQ_UINT64_T(A1->GetId(), A2->GetId());
|
|
const v8::HeapGraphNode* B1 =
|
|
GetProperty(global1, v8::HeapGraphEdge::kProperty, "B");
|
|
CHECK_NE(NULL, B1);
|
|
const v8::HeapGraphNode* B2 =
|
|
GetProperty(global2, v8::HeapGraphEdge::kProperty, "B");
|
|
CHECK_NE(NULL, B2);
|
|
CHECK_NE_UINT64_T(0, B1->GetId());
|
|
CHECK_EQ_UINT64_T(B1->GetId(), B2->GetId());
|
|
const v8::HeapGraphNode* a1 =
|
|
GetProperty(global1, v8::HeapGraphEdge::kProperty, "a");
|
|
CHECK_NE(NULL, a1);
|
|
const v8::HeapGraphNode* a2 =
|
|
GetProperty(global2, v8::HeapGraphEdge::kProperty, "a");
|
|
CHECK_NE(NULL, a2);
|
|
CHECK_NE_UINT64_T(0, a1->GetId());
|
|
CHECK_EQ_UINT64_T(a1->GetId(), a2->GetId());
|
|
const v8::HeapGraphNode* b1 =
|
|
GetProperty(global1, v8::HeapGraphEdge::kProperty, "b");
|
|
CHECK_NE(NULL, b1);
|
|
const v8::HeapGraphNode* b2 =
|
|
GetProperty(global2, v8::HeapGraphEdge::kProperty, "b");
|
|
CHECK_NE(NULL, b2);
|
|
CHECK_NE_UINT64_T(0, b1->GetId());
|
|
CHECK_EQ_UINT64_T(b1->GetId(), b2->GetId());
|
|
}
|
|
|
|
|
|
TEST(HeapSnapshotsDiff) {
|
|
v8::HandleScope scope;
|
|
LocalContext env;
|
|
|
|
CompileAndRunScript(
|
|
"function A() {}\n"
|
|
"function B(x) { this.x = x; }\n"
|
|
"function A2(a) { for (var i = 0; i < a; ++i) this[i] = i; }\n"
|
|
"var a = new A();\n"
|
|
"var b = new B(a);");
|
|
const v8::HeapSnapshot* snapshot1 =
|
|
v8::HeapProfiler::TakeSnapshot(v8::String::New("s1"));
|
|
|
|
CompileAndRunScript(
|
|
"delete a;\n"
|
|
"b.x = null;\n"
|
|
"var a = new A2(20);\n"
|
|
"var b2 = new B(a);");
|
|
const v8::HeapSnapshot* snapshot2 =
|
|
v8::HeapProfiler::TakeSnapshot(v8::String::New("s2"));
|
|
|
|
const v8::HeapSnapshotsDiff* diff = snapshot1->CompareWith(snapshot2);
|
|
|
|
// Verify additions: ensure that addition of A and B was detected.
|
|
const v8::HeapGraphNode* additions_root = diff->GetAdditionsRoot();
|
|
bool found_A = false, found_B = false;
|
|
uint64_t s1_A_id = 0;
|
|
for (int i = 0, count = additions_root->GetChildrenCount(); i < count; ++i) {
|
|
const v8::HeapGraphEdge* prop = additions_root->GetChild(i);
|
|
const v8::HeapGraphNode* node = prop->GetToNode();
|
|
if (node->GetType() == v8::HeapGraphNode::kObject) {
|
|
v8::String::AsciiValue node_name(node->GetName());
|
|
if (strcmp(*node_name, "A2") == 0) {
|
|
CHECK(IsNodeRetainedAs(node, v8::HeapGraphEdge::kProperty, "a"));
|
|
CHECK(!found_A);
|
|
found_A = true;
|
|
s1_A_id = node->GetId();
|
|
} else if (strcmp(*node_name, "B") == 0) {
|
|
CHECK(IsNodeRetainedAs(node, v8::HeapGraphEdge::kProperty, "b2"));
|
|
CHECK(!found_B);
|
|
found_B = true;
|
|
}
|
|
}
|
|
}
|
|
CHECK(found_A);
|
|
CHECK(found_B);
|
|
|
|
// Verify deletions: ensure that deletion of A was detected.
|
|
const v8::HeapGraphNode* deletions_root = diff->GetDeletionsRoot();
|
|
bool found_A_del = false;
|
|
uint64_t s2_A_id = 0;
|
|
for (int i = 0, count = deletions_root->GetChildrenCount(); i < count; ++i) {
|
|
const v8::HeapGraphEdge* prop = deletions_root->GetChild(i);
|
|
const v8::HeapGraphNode* node = prop->GetToNode();
|
|
if (node->GetType() == v8::HeapGraphNode::kObject) {
|
|
v8::String::AsciiValue node_name(node->GetName());
|
|
if (strcmp(*node_name, "A") == 0) {
|
|
CHECK(IsNodeRetainedAs(node, v8::HeapGraphEdge::kProperty, "a"));
|
|
CHECK(!found_A_del);
|
|
found_A_del = true;
|
|
s2_A_id = node->GetId();
|
|
}
|
|
}
|
|
}
|
|
CHECK(found_A_del);
|
|
CHECK_NE_UINT64_T(0, s1_A_id);
|
|
CHECK(s1_A_id != s2_A_id);
|
|
}
|
|
|
|
|
|
TEST(HeapSnapshotRootPreservedAfterSorting) {
|
|
v8::HandleScope scope;
|
|
LocalContext env;
|
|
const v8::HeapSnapshot* snapshot =
|
|
v8::HeapProfiler::TakeSnapshot(v8::String::New("s"));
|
|
const v8::HeapGraphNode* root1 = snapshot->GetRoot();
|
|
const_cast<i::HeapSnapshot*>(reinterpret_cast<const i::HeapSnapshot*>(
|
|
snapshot))->GetSortedEntriesList();
|
|
const v8::HeapGraphNode* root2 = snapshot->GetRoot();
|
|
CHECK_EQ(root1, root2);
|
|
}
|
|
|
|
|
|
namespace v8 {
|
|
namespace internal {
|
|
|
|
class HeapSnapshotTester {
|
|
public:
|
|
static int CalculateNetworkSize(JSObject* obj) {
|
|
return HeapSnapshot::CalculateNetworkSize(obj);
|
|
}
|
|
};
|
|
|
|
} } // namespace v8::internal
|
|
|
|
// http://code.google.com/p/v8/issues/detail?id=822
|
|
// Trying to call CalculateNetworkSize on an object with elements set
|
|
// to non-FixedArray may cause an assertion error in debug builds.
|
|
TEST(Issue822) {
|
|
v8::HandleScope scope;
|
|
LocalContext context;
|
|
const int kElementCount = 260;
|
|
uint8_t* pixel_data = reinterpret_cast<uint8_t*>(malloc(kElementCount));
|
|
i::Handle<i::PixelArray> pixels = i::Factory::NewPixelArray(kElementCount,
|
|
pixel_data);
|
|
v8::Handle<v8::Object> obj = v8::Object::New();
|
|
// Set the elements to be the pixels.
|
|
obj->SetIndexedPropertiesToPixelData(pixel_data, kElementCount);
|
|
i::Handle<i::JSObject> jsobj = v8::Utils::OpenHandle(*obj);
|
|
// This call must not cause an assertion error in debug builds.
|
|
i::HeapSnapshotTester::CalculateNetworkSize(*jsobj);
|
|
}
|
|
|
|
|
|
static const v8::HeapGraphNode* GetChild(
|
|
const v8::HeapGraphNode* node,
|
|
v8::HeapGraphNode::Type type,
|
|
const char* name,
|
|
const v8::HeapGraphNode* after = NULL) {
|
|
bool ignore_child = after == NULL ? false : true;
|
|
for (int i = 0, count = node->GetChildrenCount(); i < count; ++i) {
|
|
const v8::HeapGraphEdge* prop = node->GetChild(i);
|
|
const v8::HeapGraphNode* child = prop->GetToNode();
|
|
v8::String::AsciiValue child_name(child->GetName());
|
|
if (!ignore_child
|
|
&& child->GetType() == type
|
|
&& strcmp(name, *child_name) == 0)
|
|
return child;
|
|
if (after != NULL && child == after) ignore_child = false;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static bool IsNodeRetainedAs(const v8::HeapGraphNode* node,
|
|
int element) {
|
|
for (int i = 0, count = node->GetRetainersCount(); i < count; ++i) {
|
|
const v8::HeapGraphEdge* prop = node->GetRetainer(i);
|
|
if (prop->GetType() == v8::HeapGraphEdge::kElement
|
|
&& element == prop->GetName()->Int32Value())
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
TEST(AggregatedHeapSnapshot) {
|
|
v8::HandleScope scope;
|
|
LocalContext env;
|
|
|
|
CompileAndRunScript(
|
|
"function A() {}\n"
|
|
"function B(x) { this.x = x; }\n"
|
|
"var a = new A();\n"
|
|
"var b = new B(a);");
|
|
const v8::HeapSnapshot* snapshot =
|
|
v8::HeapProfiler::TakeSnapshot(
|
|
v8::String::New("agg"), v8::HeapSnapshot::kAggregated);
|
|
const v8::HeapGraphNode* strings = GetChild(snapshot->GetRoot(),
|
|
v8::HeapGraphNode::kInternal,
|
|
"STRING_TYPE");
|
|
CHECK_NE(NULL, strings);
|
|
CHECK_NE(0, strings->GetSelfSize());
|
|
CHECK_NE(0, strings->GetInstancesCount());
|
|
const v8::HeapGraphNode* maps = GetChild(snapshot->GetRoot(),
|
|
v8::HeapGraphNode::kInternal,
|
|
"MAP_TYPE");
|
|
CHECK_NE(NULL, maps);
|
|
CHECK_NE(0, maps->GetSelfSize());
|
|
CHECK_NE(0, maps->GetInstancesCount());
|
|
|
|
const v8::HeapGraphNode* a = GetChild(snapshot->GetRoot(),
|
|
v8::HeapGraphNode::kObject,
|
|
"A");
|
|
CHECK_NE(NULL, a);
|
|
CHECK_NE(0, a->GetSelfSize());
|
|
CHECK_EQ(1, a->GetInstancesCount());
|
|
|
|
const v8::HeapGraphNode* b = GetChild(snapshot->GetRoot(),
|
|
v8::HeapGraphNode::kObject,
|
|
"B");
|
|
CHECK_NE(NULL, b);
|
|
CHECK_NE(0, b->GetSelfSize());
|
|
CHECK_EQ(1, b->GetInstancesCount());
|
|
|
|
const v8::HeapGraphNode* glob_prop = GetChild(snapshot->GetRoot(),
|
|
v8::HeapGraphNode::kObject,
|
|
"(global property)",
|
|
b);
|
|
CHECK_NE(NULL, glob_prop);
|
|
CHECK_EQ(0, glob_prop->GetSelfSize());
|
|
CHECK_EQ(0, glob_prop->GetInstancesCount());
|
|
CHECK_NE(0, glob_prop->GetChildrenCount());
|
|
|
|
const v8::HeapGraphNode* a_from_glob_prop = GetChild(
|
|
glob_prop,
|
|
v8::HeapGraphNode::kObject,
|
|
"A");
|
|
CHECK_NE(NULL, a_from_glob_prop);
|
|
CHECK_EQ(0, a_from_glob_prop->GetSelfSize());
|
|
CHECK_EQ(0, a_from_glob_prop->GetInstancesCount());
|
|
CHECK_EQ(0, a_from_glob_prop->GetChildrenCount()); // Retains nothing.
|
|
CHECK(IsNodeRetainedAs(a_from_glob_prop, 1)); // (global propery) has 1 ref.
|
|
|
|
const v8::HeapGraphNode* b_with_children = GetChild(
|
|
snapshot->GetRoot(),
|
|
v8::HeapGraphNode::kObject,
|
|
"B",
|
|
b);
|
|
CHECK_NE(NULL, b_with_children);
|
|
CHECK_EQ(0, b_with_children->GetSelfSize());
|
|
CHECK_EQ(0, b_with_children->GetInstancesCount());
|
|
CHECK_NE(0, b_with_children->GetChildrenCount());
|
|
|
|
const v8::HeapGraphNode* a_from_b = GetChild(
|
|
b_with_children,
|
|
v8::HeapGraphNode::kObject,
|
|
"A");
|
|
CHECK_NE(NULL, a_from_b);
|
|
CHECK_EQ(0, a_from_b->GetSelfSize());
|
|
CHECK_EQ(0, a_from_b->GetInstancesCount());
|
|
CHECK_EQ(0, a_from_b->GetChildrenCount()); // Retains nothing.
|
|
CHECK(IsNodeRetainedAs(a_from_b, 1)); // B has 1 ref to A.
|
|
}
|
|
|
|
namespace {
|
|
|
|
class TestJSONStream : public v8::OutputStream {
|
|
public:
|
|
TestJSONStream() : eos_signaled_(0), abort_countdown_(-1) {}
|
|
explicit TestJSONStream(int abort_countdown)
|
|
: eos_signaled_(0), abort_countdown_(abort_countdown) {}
|
|
virtual ~TestJSONStream() {}
|
|
virtual void EndOfStream() { ++eos_signaled_; }
|
|
virtual WriteResult WriteAsciiChunk(char* buffer, int chars_written) {
|
|
if (abort_countdown_ > 0) --abort_countdown_;
|
|
if (abort_countdown_ == 0) return kAbort;
|
|
CHECK_GT(chars_written, 0);
|
|
i::Vector<char> chunk = buffer_.AddBlock(chars_written, '\0');
|
|
memcpy(chunk.start(), buffer, chars_written);
|
|
return kContinue;
|
|
}
|
|
void WriteTo(i::Vector<char> dest) { buffer_.WriteTo(dest); }
|
|
int eos_signaled() { return eos_signaled_; }
|
|
int size() { return buffer_.size(); }
|
|
private:
|
|
i::Collector<char> buffer_;
|
|
int eos_signaled_;
|
|
int abort_countdown_;
|
|
};
|
|
|
|
class AsciiResource: public v8::String::ExternalAsciiStringResource {
|
|
public:
|
|
explicit AsciiResource(i::Vector<char> string): data_(string.start()) {
|
|
length_ = string.length();
|
|
}
|
|
virtual const char* data() const { return data_; }
|
|
virtual size_t length() const { return length_; }
|
|
private:
|
|
const char* data_;
|
|
size_t length_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST(HeapSnapshotJSONSerialization) {
|
|
v8::HandleScope scope;
|
|
LocalContext env;
|
|
|
|
#define STRING_LITERAL_FOR_TEST \
|
|
"\"String \\n\\r\\u0008\\u0081\\u0101\\u0801\\u8001\""
|
|
CompileAndRunScript(
|
|
"function A(s) { this.s = s; }\n"
|
|
"function B(x) { this.x = x; }\n"
|
|
"var a = new A(" STRING_LITERAL_FOR_TEST ");\n"
|
|
"var b = new B(a);");
|
|
const v8::HeapSnapshot* snapshot =
|
|
v8::HeapProfiler::TakeSnapshot(v8::String::New("json"));
|
|
TestJSONStream stream;
|
|
snapshot->Serialize(&stream, v8::HeapSnapshot::kJSON);
|
|
CHECK_GT(stream.size(), 0);
|
|
CHECK_EQ(1, stream.eos_signaled());
|
|
i::ScopedVector<char> json(stream.size());
|
|
stream.WriteTo(json);
|
|
|
|
// Verify that snapshot string is valid JSON.
|
|
AsciiResource json_res(json);
|
|
v8::Local<v8::String> json_string = v8::String::NewExternal(&json_res);
|
|
env->Global()->Set(v8::String::New("json_snapshot"), json_string);
|
|
v8::Local<v8::Value> snapshot_parse_result = CompileRun(
|
|
"var parsed = JSON.parse(json_snapshot); true;");
|
|
CHECK(!snapshot_parse_result.IsEmpty());
|
|
|
|
// Verify that snapshot object has required fields.
|
|
v8::Local<v8::Object> parsed_snapshot =
|
|
env->Global()->Get(v8::String::New("parsed"))->ToObject();
|
|
CHECK(parsed_snapshot->Has(v8::String::New("snapshot")));
|
|
CHECK(parsed_snapshot->Has(v8::String::New("nodes")));
|
|
CHECK(parsed_snapshot->Has(v8::String::New("strings")));
|
|
|
|
// Verify that nodes meta-info is valid JSON.
|
|
v8::Local<v8::Value> nodes_meta_parse_result = CompileRun(
|
|
"var parsed_meta = JSON.parse(parsed.nodes[0]); true;");
|
|
CHECK(!nodes_meta_parse_result.IsEmpty());
|
|
|
|
// Get node and edge "member" offsets.
|
|
v8::Local<v8::Value> meta_analysis_result = CompileRun(
|
|
"var children_count_offset ="
|
|
" parsed_meta.fields.indexOf('children_count');\n"
|
|
"var children_offset ="
|
|
" parsed_meta.fields.indexOf('children');\n"
|
|
"var children_meta ="
|
|
" parsed_meta.types[children_offset];\n"
|
|
"var child_fields_count = children_meta.fields.length;\n"
|
|
"var child_type_offset ="
|
|
" children_meta.fields.indexOf('type');\n"
|
|
"var child_name_offset ="
|
|
" children_meta.fields.indexOf('name_or_index');\n"
|
|
"var child_to_node_offset ="
|
|
" children_meta.fields.indexOf('to_node');\n"
|
|
"var property_type ="
|
|
" children_meta.types[child_type_offset].indexOf('property');");
|
|
CHECK(!meta_analysis_result.IsEmpty());
|
|
|
|
// A helper function for processing encoded nodes.
|
|
CompileRun(
|
|
"function GetChildPosByProperty(pos, prop_name) {\n"
|
|
" var nodes = parsed.nodes;\n"
|
|
" var strings = parsed.strings;\n"
|
|
" for (var i = 0,\n"
|
|
" count = nodes[pos + children_count_offset] * child_fields_count;\n"
|
|
" i < count; i += child_fields_count) {\n"
|
|
" var child_pos = pos + children_offset + i;\n"
|
|
" if (nodes[child_pos + child_type_offset] === property_type\n"
|
|
" && strings[nodes[child_pos + child_name_offset]] === prop_name)\n"
|
|
" return nodes[child_pos + child_to_node_offset];\n"
|
|
" }\n"
|
|
" return null;\n"
|
|
"}\n");
|
|
// Get the string index using the path: <root> -> <global>.b.x.s
|
|
v8::Local<v8::Value> string_obj_pos_val = CompileRun(
|
|
"GetChildPosByProperty(\n"
|
|
" GetChildPosByProperty(\n"
|
|
" GetChildPosByProperty("
|
|
" parsed.nodes[1 + children_offset + child_to_node_offset],\"b\"),\n"
|
|
" \"x\"),"
|
|
" \"s\")");
|
|
CHECK(!string_obj_pos_val.IsEmpty());
|
|
int string_obj_pos =
|
|
static_cast<int>(string_obj_pos_val->ToNumber()->Value());
|
|
v8::Local<v8::Object> nodes_array =
|
|
parsed_snapshot->Get(v8::String::New("nodes"))->ToObject();
|
|
int string_index = static_cast<int>(
|
|
nodes_array->Get(string_obj_pos + 1)->ToNumber()->Value());
|
|
CHECK_GT(string_index, 0);
|
|
v8::Local<v8::Object> strings_array =
|
|
parsed_snapshot->Get(v8::String::New("strings"))->ToObject();
|
|
v8::Local<v8::String> string = strings_array->Get(string_index)->ToString();
|
|
v8::Local<v8::String> ref_string =
|
|
CompileRun(STRING_LITERAL_FOR_TEST)->ToString();
|
|
#undef STRING_LITERAL_FOR_TEST
|
|
CHECK_EQ(*v8::String::Utf8Value(ref_string),
|
|
*v8::String::Utf8Value(string));
|
|
}
|
|
|
|
|
|
TEST(HeapSnapshotJSONSerializationAborting) {
|
|
v8::HandleScope scope;
|
|
LocalContext env;
|
|
const v8::HeapSnapshot* snapshot =
|
|
v8::HeapProfiler::TakeSnapshot(v8::String::New("abort"));
|
|
TestJSONStream stream(5);
|
|
snapshot->Serialize(&stream, v8::HeapSnapshot::kJSON);
|
|
CHECK_GT(stream.size(), 0);
|
|
CHECK_EQ(0, stream.eos_signaled());
|
|
}
|
|
|
|
#endif // ENABLE_LOGGING_AND_PROFILING
|