Heap profiler: implement diffing of snapshots.

To trace objects between snapshots, an external map of object tags is
maintained. After the first heap snapshot has been taken, the map is
updated by reporting object moves from the GC. If no snapshots were
taken, there is no overhead (except for flag checking).

I considered graph comparison algorithms that doesn't require using
object tags, but they are all of a high computational complexity, and
will still fail to detect object moves properly, even for trivial
cases, so using tags looks like unavoidable.

Review URL: http://codereview.chromium.org/3020002

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@5078 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent dd06f4f1
......@@ -258,6 +258,12 @@ class V8EXPORT HeapGraphNode {
*/
Handle<String> GetName() const;
/**
* Returns node id. For the same heap object, the id remains the same
* across all snapshots.
*/
uint64_t GetId() const;
/** Returns node's own size, in bytes. */
int GetSelfSize() const;
......@@ -290,6 +296,16 @@ class V8EXPORT HeapGraphNode {
};
class V8EXPORT HeapSnapshotsDiff {
public:
/** Returns the root node for added nodes. */
const HeapGraphNode* GetAdditionsRoot() const;
/** Returns the root node for deleted nodes. */
const HeapGraphNode* GetDeletionsRoot() const;
};
/**
* HeapSnapshots record the state of the JS heap at some moment.
*/
......@@ -302,7 +318,10 @@ class V8EXPORT HeapSnapshot {
Handle<String> GetTitle() const;
/** Returns the root node of the heap graph. */
const HeapGraphNode* GetHead() const;
const HeapGraphNode* GetRoot() const;
/** Returns a diff between this snapshot and another one. */
const HeapSnapshotsDiff* CompareWith(const HeapSnapshot* snapshot) const;
};
......
......@@ -4561,6 +4561,12 @@ Handle<String> HeapGraphNode::GetName() const {
}
uint64_t HeapGraphNode::GetId() const {
IsDeadCheck("v8::HeapGraphNode::GetId");
return reinterpret_cast<const i::HeapEntry*>(this)->id();
}
int HeapGraphNode::GetSelfSize() const {
IsDeadCheck("v8::HeapGraphNode::GetSelfSize");
return reinterpret_cast<const i::HeapEntry*>(this)->self_size();
......@@ -4624,6 +4630,22 @@ const HeapGraphPath* HeapGraphNode::GetRetainingPath(int index) const {
}
const HeapGraphNode* HeapSnapshotsDiff::GetAdditionsRoot() const {
IsDeadCheck("v8::HeapSnapshotsDiff::GetAdditionsRoot");
const i::HeapSnapshotsDiff* diff =
reinterpret_cast<const i::HeapSnapshotsDiff*>(this);
return reinterpret_cast<const HeapGraphNode*>(diff->additions_root());
}
const HeapGraphNode* HeapSnapshotsDiff::GetDeletionsRoot() const {
IsDeadCheck("v8::HeapSnapshotsDiff::GetDeletionsRoot");
const i::HeapSnapshotsDiff* diff =
reinterpret_cast<const i::HeapSnapshotsDiff*>(this);
return reinterpret_cast<const HeapGraphNode*>(diff->deletions_root());
}
unsigned HeapSnapshot::GetUid() const {
IsDeadCheck("v8::HeapSnapshot::GetUid");
return reinterpret_cast<const i::HeapSnapshot*>(this)->uid();
......@@ -4639,7 +4661,7 @@ Handle<String> HeapSnapshot::GetTitle() const {
}
const HeapGraphNode* HeapSnapshot::GetHead() const {
const HeapGraphNode* HeapSnapshot::GetRoot() const {
IsDeadCheck("v8::HeapSnapshot::GetHead");
const i::HeapSnapshot* snapshot =
reinterpret_cast<const i::HeapSnapshot*>(this);
......@@ -4647,6 +4669,18 @@ const HeapGraphNode* HeapSnapshot::GetHead() const {
}
const HeapSnapshotsDiff* HeapSnapshot::CompareWith(
const HeapSnapshot* snapshot) const {
IsDeadCheck("v8::HeapSnapshot::CompareWith");
i::HeapSnapshot* snapshot1 = const_cast<i::HeapSnapshot*>(
reinterpret_cast<const i::HeapSnapshot*>(this));
i::HeapSnapshot* snapshot2 = const_cast<i::HeapSnapshot*>(
reinterpret_cast<const i::HeapSnapshot*>(snapshot));
return reinterpret_cast<const HeapSnapshotsDiff*>(
snapshot1->CompareWith(snapshot2));
}
int HeapProfiler::GetSnapshotsCount() {
IsDeadCheck("v8::HeapProfiler::GetSnapshotsCount");
return i::HeapProfiler::GetSnapshotsCount();
......
......@@ -103,6 +103,28 @@ static inline void CheckEqualsHelper(const char* file, int line,
}
// Helper function used by the CHECK_EQ function when given uint64_t
// arguments. Should not be called directly.
static inline void CheckEqualsHelper(const char* file, int line,
const char* expected_source,
uint64_t expected,
const char* value_source,
uint64_t value) {
if (expected != value) {
// Print uint64_t values in hex, as two int32s,
// to avoid platform-dependencies.
V8_Fatal(file, line,
"CHECK_EQ(%s, %s) failed\n#"
" Expected: 0x%08x%08x\n# Found: 0x%08x%08x",
expected_source, value_source,
static_cast<uint32_t>(expected >> 32),
static_cast<uint32_t>(expected),
static_cast<uint32_t>(value >> 32),
static_cast<uint32_t>(value));
}
}
// Helper function used by the CHECK_NE function when given int
// arguments. Should not be called directly.
static inline void CheckNonEqualsHelper(const char* file,
......
......@@ -364,6 +364,7 @@ HeapSnapshot* HeapProfiler::TakeSnapshotImpl(const char* name) {
HeapSnapshot* result = snapshots_->NewSnapshot(name, next_snapshot_uid_++);
HeapSnapshotGenerator generator(result);
generator.GenerateSnapshot();
snapshots_->SnapshotGenerationFinished();
return result;
}
......@@ -391,6 +392,12 @@ HeapSnapshot* HeapProfiler::FindSnapshot(unsigned uid) {
}
void HeapProfiler::ObjectMoveEvent(Address from, Address to) {
ASSERT(singleton_ != NULL);
singleton_->snapshots_->ObjectMoveEvent(from, to);
}
const JSObjectsClusterTreeConfig::Key JSObjectsClusterTreeConfig::kNoKey;
const JSObjectsClusterTreeConfig::Value JSObjectsClusterTreeConfig::kNoValue;
......
......@@ -38,7 +38,15 @@ namespace internal {
class HeapSnapshot;
class HeapSnapshotsCollection;
#endif
#define HEAP_PROFILE(Call) \
do { \
if (v8::internal::HeapProfiler::is_profiling()) { \
v8::internal::HeapProfiler::Call; \
} \
} while (false)
#else
#define HEAP_PROFILE(Call) ((void) 0)
#endif // ENABLE_LOGGING_AND_PROFILING
// The HeapProfiler writes data to the log files, which can be postprocessed
// to generate .hp files for use by the GHC/Valgrind tool hp2ps.
......@@ -54,6 +62,12 @@ class HeapProfiler {
static HeapSnapshot* GetSnapshot(int index);
static HeapSnapshot* FindSnapshot(unsigned uid);
static void ObjectMoveEvent(Address from, Address to);
static INLINE(bool is_profiling()) {
return singleton_ != NULL && singleton_->snapshots_->is_tracking_objects();
}
// Obsolete interface.
// Write a single heap sample to the log file.
static void WriteSample();
......
......@@ -1107,6 +1107,7 @@ inline static HeapObject* MigrateObject(HeapObject* source,
// Update NewSpace stats if necessary.
RecordCopiedObject(target);
#endif
HEAP_PROFILE(ObjectMoveEvent(source->address(), target->address()));
return target;
}
......
......@@ -28,6 +28,7 @@
#include "v8.h"
#include "execution.h"
#include "heap-profiler.h"
#include "global-handles.h"
#include "ic-inl.h"
#include "mark-compact.h"
......@@ -2218,6 +2219,7 @@ int MarkCompactCollector::RelocateOldNonCodeObject(HeapObject* obj,
if (copied_to->IsJSFunction()) {
PROFILE(FunctionMoveEvent(old_addr, new_addr));
}
HEAP_PROFILE(ObjectMoveEvent(old_addr, new_addr));
return obj_size;
}
......@@ -2264,6 +2266,7 @@ int MarkCompactCollector::RelocateCodeObject(HeapObject* obj) {
// Notify the logger that compiled code has moved.
PROFILE(CodeMoveEvent(old_addr, new_addr));
}
HEAP_PROFILE(ObjectMoveEvent(old_addr, new_addr));
return obj_size;
}
......@@ -2308,6 +2311,7 @@ int MarkCompactCollector::RelocateNewObject(HeapObject* obj) {
if (copied_to->IsJSFunction()) {
PROFILE(FunctionMoveEvent(old_addr, new_addr));
}
HEAP_PROFILE(ObjectMoveEvent(old_addr, new_addr));
return obj_size;
}
......
This diff is collapsed.
......@@ -74,7 +74,7 @@ class StringsStorage {
reinterpret_cast<char*>(key2)) == 0;
}
// String::Hash -> const char*
// Mapping of strings by String::Hash to const char* strings.
HashMap names_;
DISALLOW_COPY_AND_ASSIGN(StringsStorage);
......@@ -156,7 +156,7 @@ class ProfileNode {
CodeEntry* entry_;
unsigned total_ticks_;
unsigned self_ticks_;
// CodeEntry* -> ProfileNode*
// Mapping from CodeEntry* to ProfileNode*
HashMap children_;
List<ProfileNode*> children_list_;
......@@ -312,11 +312,12 @@ class CpuProfilesCollection {
}
StringsStorage function_and_resource_names_;
// args_count -> char*
// Mapping from args_count (int) to char* strings.
List<char*> args_count_names_;
List<CodeEntry*> code_entries_;
List<List<CpuProfile*>* > profiles_by_token_;
// uid -> index
// Mapping from profiles' uids to indexes in the second nested list
// of profiles_by_token_.
HashMap profiles_uids_;
// Accessed by VM thread and profile generator thread.
......@@ -482,6 +483,7 @@ class HeapEntry {
visited_(false),
type_(INTERNAL),
name_(""),
id_(0),
next_auto_index_(0),
self_size_(0),
security_token_id_(TokenEnumerator::kNoSecurityToken),
......@@ -494,12 +496,14 @@ class HeapEntry {
HeapEntry(HeapSnapshot* snapshot,
Type type,
const char* name,
int id,
int self_size,
int security_token_id)
: snapshot_(snapshot),
visited_(false),
type_(type),
name_(name),
id_(id),
next_auto_index_(1),
self_size_(self_size),
security_token_id_(security_token_id),
......@@ -514,6 +518,7 @@ class HeapEntry {
bool visited() const { return visited_; }
Type type() const { return type_; }
const char* name() const { return name_; }
uint64_t id() const { return id_; }
int self_size() const { return self_size_; }
int security_token_id() const { return security_token_id_; }
bool painted_reachable() { return painted_ == kPaintReachable; }
......@@ -524,9 +529,13 @@ class HeapEntry {
const List<HeapGraphEdge*>* retainers() const { return &retainers_; }
const List<HeapGraphPath*>* GetRetainingPaths();
template<class Visitor>
void ApplyAndPaintAllReachable(Visitor* visitor);
void ClearPaint() { painted_ = kUnpainted; }
void CutEdges();
void MarkAsVisited() { visited_ = true; }
void PaintAllReachable();
void PaintReachable() {
ASSERT(painted_ == kUnpainted);
painted_ = kPaintReachable;
......@@ -537,6 +546,7 @@ class HeapEntry {
void SetInternalReference(const char* name, HeapEntry* entry);
void SetPropertyReference(const char* name, HeapEntry* entry);
void SetAutoIndexReference(HeapEntry* entry);
void SetUnidirAutoIndexReference(HeapEntry* entry);
int TotalSize();
int NonSharedTotalSize();
......@@ -557,6 +567,7 @@ class HeapEntry {
bool visited_;
Type type_;
const char* name_;
uint64_t id_;
int next_auto_index_;
int self_size_;
int security_token_id_;
......@@ -607,6 +618,8 @@ class HeapEntriesMap {
HeapEntry* Map(HeapObject* object);
void Pair(HeapObject* object, HeapEntry* entry);
uint32_t capacity() { return entries_.capacity(); }
private:
INLINE(uint32_t Hash(HeapObject* object)) {
return static_cast<uint32_t>(reinterpret_cast<intptr_t>(object));
......@@ -627,6 +640,7 @@ class HeapEntriesMap {
class HeapSnapshotsCollection;
class HeapSnapshotsDiff;
// HeapSnapshot represents a single heap snapshot. It is stored in
// HeapSnapshotsCollection, which is also a factory for
......@@ -638,6 +652,7 @@ class HeapSnapshot {
HeapSnapshot(HeapSnapshotsCollection* collection,
const char* title,
unsigned uid);
~HeapSnapshot();
void ClearPaint();
void CutObjectsFromForeignSecurityContexts();
HeapEntry* GetEntry(Object* object);
......@@ -655,6 +670,8 @@ class HeapSnapshot {
HeapEntry* root() { return &root_; }
template<class Visitor>
void IterateEntries(Visitor* visitor) { entries_.Apply(visitor); }
List<HeapEntry*>* GetSortedEntriesList();
HeapSnapshotsDiff* CompareWith(HeapSnapshot* snapshot);
void Print(int max_depth);
......@@ -679,19 +696,108 @@ class HeapSnapshot {
const char* title_;
unsigned uid_;
HeapEntry root_;
// HeapObject* -> HeapEntry*
// Mapping from HeapObject* pointers to HeapEntry* pointers.
HeapEntriesMap entries_;
// Entries sorted by id.
List<HeapEntry*>* sorted_entries_;
DISALLOW_COPY_AND_ASSIGN(HeapSnapshot);
};
class HeapObjectsMap {
public:
HeapObjectsMap();
~HeapObjectsMap();
void SnapshotGenerationFinished();
uint64_t FindObject(Address addr);
void MoveObject(Address from, Address to);
private:
struct EntryInfo {
explicit EntryInfo(uint64_t id) : id(id), accessed(true) { }
EntryInfo(uint64_t id, bool accessed) : id(id), accessed(accessed) { }
uint64_t id;
bool accessed;
};
void AddEntry(Address addr, uint64_t id);
uint64_t FindEntry(Address addr);
void RemoveDeadEntries();
static bool AddressesMatch(void* key1, void* key2) {
return key1 == key2;
}
static uint32_t AddressHash(Address addr) {
return static_cast<int32_t>(reinterpret_cast<intptr_t>(addr));
}
bool initial_fill_mode_;
uint64_t next_id_;
HashMap entries_map_;
List<EntryInfo>* entries_;
DISALLOW_COPY_AND_ASSIGN(HeapObjectsMap);
};
class HeapSnapshotsDiff {
public:
HeapSnapshotsDiff(HeapSnapshot* snapshot1, HeapSnapshot* snapshot2)
: snapshot1_(snapshot1),
snapshot2_(snapshot2),
additions_root_(new HeapEntry(snapshot2)),
deletions_root_(new HeapEntry(snapshot1)) { }
~HeapSnapshotsDiff() {
delete deletions_root_;
delete additions_root_;
}
void AddAddedEntry(HeapEntry* entry) {
additions_root_->SetUnidirAutoIndexReference(entry);
}
void AddDeletedEntry(HeapEntry* entry) {
deletions_root_->SetUnidirAutoIndexReference(entry);
}
const HeapEntry* additions_root() const { return additions_root_; }
const HeapEntry* deletions_root() const { return deletions_root_; }
private:
HeapSnapshot* snapshot1_;
HeapSnapshot* snapshot2_;
HeapEntry* additions_root_;
HeapEntry* deletions_root_;
DISALLOW_COPY_AND_ASSIGN(HeapSnapshotsDiff);
};
class HeapSnapshotsComparator {
public:
HeapSnapshotsComparator() { }
~HeapSnapshotsComparator();
HeapSnapshotsDiff* Compare(HeapSnapshot* snapshot1, HeapSnapshot* snapshot2);
private:
List<HeapSnapshotsDiff*> diffs_;
DISALLOW_COPY_AND_ASSIGN(HeapSnapshotsComparator);
};
class HeapSnapshotsCollection {
public:
HeapSnapshotsCollection();
~HeapSnapshotsCollection();
bool is_tracking_objects() { return is_tracking_objects_; }
HeapSnapshot* NewSnapshot(const char* name, unsigned uid);
void SnapshotGenerationFinished() { ids_.SnapshotGenerationFinished(); }
List<HeapSnapshot*>* snapshots() { return &snapshots_; }
HeapSnapshot* GetSnapshot(unsigned uid);
......@@ -699,16 +805,26 @@ class HeapSnapshotsCollection {
TokenEnumerator* token_enumerator() { return token_enumerator_; }
uint64_t GetObjectId(Address addr) { return ids_.FindObject(addr); }
void ObjectMoveEvent(Address from, Address to) { ids_.MoveObject(from, to); }
HeapSnapshotsDiff* CompareSnapshots(HeapSnapshot* snapshot1,
HeapSnapshot* snapshot2);
private:
INLINE(static bool HeapSnapshotsMatch(void* key1, void* key2)) {
return key1 == key2;
}
bool is_tracking_objects_; // Whether tracking object moves is needed.
List<HeapSnapshot*> snapshots_;
// uid -> HeapSnapshot*
// Mapping from snapshots' uids to HeapSnapshot* pointers.
HashMap snapshots_uids_;
StringsStorage names_;
TokenEnumerator* token_enumerator_;
// Mapping from HeapObject addresses to objects' uids.
HeapObjectsMap ids_;
HeapSnapshotsComparator comparator_;
DISALLOW_COPY_AND_ASSIGN(HeapSnapshotsCollection);
};
......
......@@ -56,8 +56,7 @@ class ConstructorHeapProfileTestHelper : public i::ConstructorHeapProfile {
TEST(ConstructorProfile) {
v8::HandleScope scope;
v8::Handle<v8::Context> env = v8::Context::New();
env->Enter();
LocalContext env;
CompileAndRunScript(
"function F() {} // A constructor\n"
......@@ -144,8 +143,7 @@ static inline void CheckNonEqualsHelper(const char* file, int line,
TEST(ClustersCoarserSimple) {
v8::HandleScope scope;
v8::Handle<v8::Context> env = v8::Context::New();
env->Enter();
LocalContext env;
i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
......@@ -183,8 +181,7 @@ TEST(ClustersCoarserSimple) {
TEST(ClustersCoarserMultipleConstructors) {
v8::HandleScope scope;
v8::Handle<v8::Context> env = v8::Context::New();
env->Enter();
LocalContext env;
i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
......@@ -214,8 +211,7 @@ TEST(ClustersCoarserMultipleConstructors) {
TEST(ClustersCoarserPathsTraversal) {
v8::HandleScope scope;
v8::Handle<v8::Context> env = v8::Context::New();
env->Enter();
LocalContext env;
i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
......@@ -267,8 +263,7 @@ TEST(ClustersCoarserPathsTraversal) {
TEST(ClustersCoarserSelf) {
v8::HandleScope scope;
v8::Handle<v8::Context> env = v8::Context::New();
env->Enter();
LocalContext env;
i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
......@@ -362,8 +357,7 @@ class RetainerProfilePrinter : public RetainerHeapProfile::Printer {
TEST(RetainerProfile) {
v8::HandleScope scope;
v8::Handle<v8::Context> env = v8::Context::New();
env->Enter();
LocalContext env;
CompileAndRunScript(
"function A() {}\n"
......@@ -431,8 +425,8 @@ class NamedEntriesDetector {
static const v8::HeapGraphNode* GetGlobalObject(
const v8::HeapSnapshot* snapshot) {
CHECK_EQ(1, snapshot->GetHead()->GetChildrenCount());
return snapshot->GetHead()->GetChild(0)->GetToNode();
CHECK_EQ(1, snapshot->GetRoot()->GetChildrenCount());
return snapshot->GetRoot()->GetChild(0)->GetToNode();
}
......@@ -449,6 +443,19 @@ static const v8::HeapGraphNode* GetProperty(const v8::HeapGraphNode* node,
}
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);
......@@ -464,11 +471,9 @@ static bool HasString(const v8::HeapGraphNode* node, const char* contents) {
TEST(HeapSnapshot) {
v8::HandleScope scope;
v8::Handle<v8::String> token1 = v8::String::New("token1");
v8::Handle<v8::Context> env1 = v8::Context::New();
LocalContext env1;
env1->SetSecurityToken(token1);
env1->Enter();
CompileAndRunScript(
"function A1() {}\n"
......@@ -479,9 +484,8 @@ TEST(HeapSnapshot) {
"var c1 = new C1(a1);");
v8::Handle<v8::String> token2 = v8::String::New("token2");
v8::Handle<v8::Context> env2 = v8::Context::New();
LocalContext env2;
env2->SetSecurityToken(token2);
env2->Enter();
CompileAndRunScript(
"function A2() {}\n"
......@@ -569,8 +573,7 @@ TEST(HeapSnapshot) {
TEST(HeapSnapshotCodeObjects) {
v8::HandleScope scope;
v8::Handle<v8::Context> env = v8::Context::New();
env->Enter();
LocalContext env;
CompileAndRunScript(
"function lazy(x) { return x - 1; }\n"
......@@ -625,4 +628,121 @@ TEST(HeapSnapshotCodeObjects) {
CHECK(!lazy_references_x);
}
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(0, global1->GetId());
CHECK_EQ(global1->GetId(), global2->GetId());
const v8::HeapGraphNode* A1 =
GetProperty(global1, v8::HeapGraphEdge::PROPERTY, "A");
const v8::HeapGraphNode* A2 =
GetProperty(global2, v8::HeapGraphEdge::PROPERTY, "A");
CHECK_NE(0, A1->GetId());
CHECK_EQ(A1->GetId(), A2->GetId());
const v8::HeapGraphNode* B1 =
GetProperty(global1, v8::HeapGraphEdge::PROPERTY, "B");
const v8::HeapGraphNode* B2 =
GetProperty(global2, v8::HeapGraphEdge::PROPERTY, "B");
CHECK_NE(0, B1->GetId());
CHECK_EQ(B1->GetId(), B2->GetId());
const v8::HeapGraphNode* a1 =
GetProperty(global1, v8::HeapGraphEdge::PROPERTY, "a");
const v8::HeapGraphNode* a2 =
GetProperty(global2, v8::HeapGraphEdge::PROPERTY, "a");
CHECK_NE(0, a1->GetId());
CHECK_EQ(a1->GetId(), a2->GetId());
const v8::HeapGraphNode* b1 =
GetProperty(global1, v8::HeapGraphEdge::PROPERTY, "b");
const v8::HeapGraphNode* b2 =
GetProperty(global2, v8::HeapGraphEdge::PROPERTY, "b");
CHECK_NE(0, b1->GetId());
CHECK_EQ(b1->GetId(), b2->GetId());
}
TEST(HeapSnapshotsDiff) {
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"));
CompileAndRunScript(
"delete a;\n"
"b.x = null;\n"
"var a = new A();\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::OBJECT) {
v8::String::AsciiValue node_name(node->GetName());
if (strcmp(*node_name, "A") == 0) {
CHECK(IsNodeRetainedAs(node, v8::HeapGraphEdge::PROPERTY, "a"));
CHECK(!found_A);
found_A = true;
s1_A_id = node->GetId();
} else if (strcmp(*node_name, "B") == 0) {
CHECK(IsNodeRetainedAs(node, v8::HeapGraphEdge::PROPERTY, "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::OBJECT) {
v8::String::AsciiValue node_name(node->GetName());
if (strcmp(*node_name, "A") == 0) {
CHECK(IsNodeRetainedAs(node, v8::HeapGraphEdge::PROPERTY, "a"));
CHECK(!found_A_del);
found_A_del = true;
s2_A_id = node->GetId();
}
}
}
CHECK(found_A_del);
CHECK_NE(0, s1_A_id);
CHECK(s1_A_id != s2_A_id);
}
#endif // ENABLE_LOGGING_AND_PROFILING
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment