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 { ...@@ -258,6 +258,12 @@ class V8EXPORT HeapGraphNode {
*/ */
Handle<String> GetName() const; 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. */ /** Returns node's own size, in bytes. */
int GetSelfSize() const; int GetSelfSize() const;
...@@ -290,6 +296,16 @@ class V8EXPORT HeapGraphNode { ...@@ -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. * HeapSnapshots record the state of the JS heap at some moment.
*/ */
...@@ -302,7 +318,10 @@ class V8EXPORT HeapSnapshot { ...@@ -302,7 +318,10 @@ class V8EXPORT HeapSnapshot {
Handle<String> GetTitle() const; Handle<String> GetTitle() const;
/** Returns the root node of the heap graph. */ /** 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 { ...@@ -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 { int HeapGraphNode::GetSelfSize() const {
IsDeadCheck("v8::HeapGraphNode::GetSelfSize"); IsDeadCheck("v8::HeapGraphNode::GetSelfSize");
return reinterpret_cast<const i::HeapEntry*>(this)->self_size(); return reinterpret_cast<const i::HeapEntry*>(this)->self_size();
...@@ -4624,6 +4630,22 @@ const HeapGraphPath* HeapGraphNode::GetRetainingPath(int index) const { ...@@ -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 { unsigned HeapSnapshot::GetUid() const {
IsDeadCheck("v8::HeapSnapshot::GetUid"); IsDeadCheck("v8::HeapSnapshot::GetUid");
return reinterpret_cast<const i::HeapSnapshot*>(this)->uid(); return reinterpret_cast<const i::HeapSnapshot*>(this)->uid();
...@@ -4639,7 +4661,7 @@ Handle<String> HeapSnapshot::GetTitle() const { ...@@ -4639,7 +4661,7 @@ Handle<String> HeapSnapshot::GetTitle() const {
} }
const HeapGraphNode* HeapSnapshot::GetHead() const { const HeapGraphNode* HeapSnapshot::GetRoot() const {
IsDeadCheck("v8::HeapSnapshot::GetHead"); IsDeadCheck("v8::HeapSnapshot::GetHead");
const i::HeapSnapshot* snapshot = const i::HeapSnapshot* snapshot =
reinterpret_cast<const i::HeapSnapshot*>(this); reinterpret_cast<const i::HeapSnapshot*>(this);
...@@ -4647,6 +4669,18 @@ const HeapGraphNode* HeapSnapshot::GetHead() const { ...@@ -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() { int HeapProfiler::GetSnapshotsCount() {
IsDeadCheck("v8::HeapProfiler::GetSnapshotsCount"); IsDeadCheck("v8::HeapProfiler::GetSnapshotsCount");
return i::HeapProfiler::GetSnapshotsCount(); return i::HeapProfiler::GetSnapshotsCount();
......
...@@ -103,6 +103,28 @@ static inline void CheckEqualsHelper(const char* file, int line, ...@@ -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 // Helper function used by the CHECK_NE function when given int
// arguments. Should not be called directly. // arguments. Should not be called directly.
static inline void CheckNonEqualsHelper(const char* file, static inline void CheckNonEqualsHelper(const char* file,
......
...@@ -364,6 +364,7 @@ HeapSnapshot* HeapProfiler::TakeSnapshotImpl(const char* name) { ...@@ -364,6 +364,7 @@ HeapSnapshot* HeapProfiler::TakeSnapshotImpl(const char* name) {
HeapSnapshot* result = snapshots_->NewSnapshot(name, next_snapshot_uid_++); HeapSnapshot* result = snapshots_->NewSnapshot(name, next_snapshot_uid_++);
HeapSnapshotGenerator generator(result); HeapSnapshotGenerator generator(result);
generator.GenerateSnapshot(); generator.GenerateSnapshot();
snapshots_->SnapshotGenerationFinished();
return result; return result;
} }
...@@ -391,6 +392,12 @@ HeapSnapshot* HeapProfiler::FindSnapshot(unsigned uid) { ...@@ -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::Key JSObjectsClusterTreeConfig::kNoKey;
const JSObjectsClusterTreeConfig::Value JSObjectsClusterTreeConfig::kNoValue; const JSObjectsClusterTreeConfig::Value JSObjectsClusterTreeConfig::kNoValue;
......
...@@ -38,7 +38,15 @@ namespace internal { ...@@ -38,7 +38,15 @@ namespace internal {
class HeapSnapshot; class HeapSnapshot;
class HeapSnapshotsCollection; 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 // The HeapProfiler writes data to the log files, which can be postprocessed
// to generate .hp files for use by the GHC/Valgrind tool hp2ps. // to generate .hp files for use by the GHC/Valgrind tool hp2ps.
...@@ -54,6 +62,12 @@ class HeapProfiler { ...@@ -54,6 +62,12 @@ class HeapProfiler {
static HeapSnapshot* GetSnapshot(int index); static HeapSnapshot* GetSnapshot(int index);
static HeapSnapshot* FindSnapshot(unsigned uid); 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. // Obsolete interface.
// Write a single heap sample to the log file. // Write a single heap sample to the log file.
static void WriteSample(); static void WriteSample();
......
...@@ -1107,6 +1107,7 @@ inline static HeapObject* MigrateObject(HeapObject* source, ...@@ -1107,6 +1107,7 @@ inline static HeapObject* MigrateObject(HeapObject* source,
// Update NewSpace stats if necessary. // Update NewSpace stats if necessary.
RecordCopiedObject(target); RecordCopiedObject(target);
#endif #endif
HEAP_PROFILE(ObjectMoveEvent(source->address(), target->address()));
return target; return target;
} }
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
#include "v8.h" #include "v8.h"
#include "execution.h" #include "execution.h"
#include "heap-profiler.h"
#include "global-handles.h" #include "global-handles.h"
#include "ic-inl.h" #include "ic-inl.h"
#include "mark-compact.h" #include "mark-compact.h"
...@@ -2218,6 +2219,7 @@ int MarkCompactCollector::RelocateOldNonCodeObject(HeapObject* obj, ...@@ -2218,6 +2219,7 @@ int MarkCompactCollector::RelocateOldNonCodeObject(HeapObject* obj,
if (copied_to->IsJSFunction()) { if (copied_to->IsJSFunction()) {
PROFILE(FunctionMoveEvent(old_addr, new_addr)); PROFILE(FunctionMoveEvent(old_addr, new_addr));
} }
HEAP_PROFILE(ObjectMoveEvent(old_addr, new_addr));
return obj_size; return obj_size;
} }
...@@ -2264,6 +2266,7 @@ int MarkCompactCollector::RelocateCodeObject(HeapObject* obj) { ...@@ -2264,6 +2266,7 @@ int MarkCompactCollector::RelocateCodeObject(HeapObject* obj) {
// Notify the logger that compiled code has moved. // Notify the logger that compiled code has moved.
PROFILE(CodeMoveEvent(old_addr, new_addr)); PROFILE(CodeMoveEvent(old_addr, new_addr));
} }
HEAP_PROFILE(ObjectMoveEvent(old_addr, new_addr));
return obj_size; return obj_size;
} }
...@@ -2308,6 +2311,7 @@ int MarkCompactCollector::RelocateNewObject(HeapObject* obj) { ...@@ -2308,6 +2311,7 @@ int MarkCompactCollector::RelocateNewObject(HeapObject* obj) {
if (copied_to->IsJSFunction()) { if (copied_to->IsJSFunction()) {
PROFILE(FunctionMoveEvent(old_addr, new_addr)); PROFILE(FunctionMoveEvent(old_addr, new_addr));
} }
HEAP_PROFILE(ObjectMoveEvent(old_addr, new_addr));
return obj_size; return obj_size;
} }
......
This diff is collapsed.
...@@ -74,7 +74,7 @@ class StringsStorage { ...@@ -74,7 +74,7 @@ class StringsStorage {
reinterpret_cast<char*>(key2)) == 0; reinterpret_cast<char*>(key2)) == 0;
} }
// String::Hash -> const char* // Mapping of strings by String::Hash to const char* strings.
HashMap names_; HashMap names_;
DISALLOW_COPY_AND_ASSIGN(StringsStorage); DISALLOW_COPY_AND_ASSIGN(StringsStorage);
...@@ -156,7 +156,7 @@ class ProfileNode { ...@@ -156,7 +156,7 @@ class ProfileNode {
CodeEntry* entry_; CodeEntry* entry_;
unsigned total_ticks_; unsigned total_ticks_;
unsigned self_ticks_; unsigned self_ticks_;
// CodeEntry* -> ProfileNode* // Mapping from CodeEntry* to ProfileNode*
HashMap children_; HashMap children_;
List<ProfileNode*> children_list_; List<ProfileNode*> children_list_;
...@@ -312,11 +312,12 @@ class CpuProfilesCollection { ...@@ -312,11 +312,12 @@ class CpuProfilesCollection {
} }
StringsStorage function_and_resource_names_; StringsStorage function_and_resource_names_;
// args_count -> char* // Mapping from args_count (int) to char* strings.
List<char*> args_count_names_; List<char*> args_count_names_;
List<CodeEntry*> code_entries_; List<CodeEntry*> code_entries_;
List<List<CpuProfile*>* > profiles_by_token_; 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_; HashMap profiles_uids_;
// Accessed by VM thread and profile generator thread. // Accessed by VM thread and profile generator thread.
...@@ -482,6 +483,7 @@ class HeapEntry { ...@@ -482,6 +483,7 @@ class HeapEntry {
visited_(false), visited_(false),
type_(INTERNAL), type_(INTERNAL),
name_(""), name_(""),
id_(0),
next_auto_index_(0), next_auto_index_(0),
self_size_(0), self_size_(0),
security_token_id_(TokenEnumerator::kNoSecurityToken), security_token_id_(TokenEnumerator::kNoSecurityToken),
...@@ -494,12 +496,14 @@ class HeapEntry { ...@@ -494,12 +496,14 @@ class HeapEntry {
HeapEntry(HeapSnapshot* snapshot, HeapEntry(HeapSnapshot* snapshot,
Type type, Type type,
const char* name, const char* name,
int id,
int self_size, int self_size,
int security_token_id) int security_token_id)
: snapshot_(snapshot), : snapshot_(snapshot),
visited_(false), visited_(false),
type_(type), type_(type),
name_(name), name_(name),
id_(id),
next_auto_index_(1), next_auto_index_(1),
self_size_(self_size), self_size_(self_size),
security_token_id_(security_token_id), security_token_id_(security_token_id),
...@@ -514,6 +518,7 @@ class HeapEntry { ...@@ -514,6 +518,7 @@ class HeapEntry {
bool visited() const { return visited_; } bool visited() const { return visited_; }
Type type() const { return type_; } Type type() const { return type_; }
const char* name() const { return name_; } const char* name() const { return name_; }
uint64_t id() const { return id_; }
int self_size() const { return self_size_; } int self_size() const { return self_size_; }
int security_token_id() const { return security_token_id_; } int security_token_id() const { return security_token_id_; }
bool painted_reachable() { return painted_ == kPaintReachable; } bool painted_reachable() { return painted_ == kPaintReachable; }
...@@ -524,9 +529,13 @@ class HeapEntry { ...@@ -524,9 +529,13 @@ class HeapEntry {
const List<HeapGraphEdge*>* retainers() const { return &retainers_; } const List<HeapGraphEdge*>* retainers() const { return &retainers_; }
const List<HeapGraphPath*>* GetRetainingPaths(); const List<HeapGraphPath*>* GetRetainingPaths();
template<class Visitor>
void ApplyAndPaintAllReachable(Visitor* visitor);
void ClearPaint() { painted_ = kUnpainted; } void ClearPaint() { painted_ = kUnpainted; }
void CutEdges(); void CutEdges();
void MarkAsVisited() { visited_ = true; } void MarkAsVisited() { visited_ = true; }
void PaintAllReachable();
void PaintReachable() { void PaintReachable() {
ASSERT(painted_ == kUnpainted); ASSERT(painted_ == kUnpainted);
painted_ = kPaintReachable; painted_ = kPaintReachable;
...@@ -537,6 +546,7 @@ class HeapEntry { ...@@ -537,6 +546,7 @@ class HeapEntry {
void SetInternalReference(const char* name, HeapEntry* entry); void SetInternalReference(const char* name, HeapEntry* entry);
void SetPropertyReference(const char* name, HeapEntry* entry); void SetPropertyReference(const char* name, HeapEntry* entry);
void SetAutoIndexReference(HeapEntry* entry); void SetAutoIndexReference(HeapEntry* entry);
void SetUnidirAutoIndexReference(HeapEntry* entry);
int TotalSize(); int TotalSize();
int NonSharedTotalSize(); int NonSharedTotalSize();
...@@ -557,6 +567,7 @@ class HeapEntry { ...@@ -557,6 +567,7 @@ class HeapEntry {
bool visited_; bool visited_;
Type type_; Type type_;
const char* name_; const char* name_;
uint64_t id_;
int next_auto_index_; int next_auto_index_;
int self_size_; int self_size_;
int security_token_id_; int security_token_id_;
...@@ -607,6 +618,8 @@ class HeapEntriesMap { ...@@ -607,6 +618,8 @@ class HeapEntriesMap {
HeapEntry* Map(HeapObject* object); HeapEntry* Map(HeapObject* object);
void Pair(HeapObject* object, HeapEntry* entry); void Pair(HeapObject* object, HeapEntry* entry);
uint32_t capacity() { return entries_.capacity(); }
private: private:
INLINE(uint32_t Hash(HeapObject* object)) { INLINE(uint32_t Hash(HeapObject* object)) {
return static_cast<uint32_t>(reinterpret_cast<intptr_t>(object)); return static_cast<uint32_t>(reinterpret_cast<intptr_t>(object));
...@@ -627,6 +640,7 @@ class HeapEntriesMap { ...@@ -627,6 +640,7 @@ class HeapEntriesMap {
class HeapSnapshotsCollection; class HeapSnapshotsCollection;
class HeapSnapshotsDiff;
// HeapSnapshot represents a single heap snapshot. It is stored in // HeapSnapshot represents a single heap snapshot. It is stored in
// HeapSnapshotsCollection, which is also a factory for // HeapSnapshotsCollection, which is also a factory for
...@@ -638,6 +652,7 @@ class HeapSnapshot { ...@@ -638,6 +652,7 @@ class HeapSnapshot {
HeapSnapshot(HeapSnapshotsCollection* collection, HeapSnapshot(HeapSnapshotsCollection* collection,
const char* title, const char* title,
unsigned uid); unsigned uid);
~HeapSnapshot();
void ClearPaint(); void ClearPaint();
void CutObjectsFromForeignSecurityContexts(); void CutObjectsFromForeignSecurityContexts();
HeapEntry* GetEntry(Object* object); HeapEntry* GetEntry(Object* object);
...@@ -655,6 +670,8 @@ class HeapSnapshot { ...@@ -655,6 +670,8 @@ class HeapSnapshot {
HeapEntry* root() { return &root_; } HeapEntry* root() { return &root_; }
template<class Visitor> template<class Visitor>
void IterateEntries(Visitor* visitor) { entries_.Apply(visitor); } void IterateEntries(Visitor* visitor) { entries_.Apply(visitor); }
List<HeapEntry*>* GetSortedEntriesList();
HeapSnapshotsDiff* CompareWith(HeapSnapshot* snapshot);
void Print(int max_depth); void Print(int max_depth);
...@@ -679,19 +696,108 @@ class HeapSnapshot { ...@@ -679,19 +696,108 @@ class HeapSnapshot {
const char* title_; const char* title_;
unsigned uid_; unsigned uid_;
HeapEntry root_; HeapEntry root_;
// HeapObject* -> HeapEntry* // Mapping from HeapObject* pointers to HeapEntry* pointers.
HeapEntriesMap entries_; HeapEntriesMap entries_;
// Entries sorted by id.
List<HeapEntry*>* sorted_entries_;
DISALLOW_COPY_AND_ASSIGN(HeapSnapshot); 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 { class HeapSnapshotsCollection {
public: public:
HeapSnapshotsCollection(); HeapSnapshotsCollection();
~HeapSnapshotsCollection(); ~HeapSnapshotsCollection();
bool is_tracking_objects() { return is_tracking_objects_; }
HeapSnapshot* NewSnapshot(const char* name, unsigned uid); HeapSnapshot* NewSnapshot(const char* name, unsigned uid);
void SnapshotGenerationFinished() { ids_.SnapshotGenerationFinished(); }
List<HeapSnapshot*>* snapshots() { return &snapshots_; } List<HeapSnapshot*>* snapshots() { return &snapshots_; }
HeapSnapshot* GetSnapshot(unsigned uid); HeapSnapshot* GetSnapshot(unsigned uid);
...@@ -699,16 +805,26 @@ class HeapSnapshotsCollection { ...@@ -699,16 +805,26 @@ class HeapSnapshotsCollection {
TokenEnumerator* token_enumerator() { return token_enumerator_; } 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: private:
INLINE(static bool HeapSnapshotsMatch(void* key1, void* key2)) { INLINE(static bool HeapSnapshotsMatch(void* key1, void* key2)) {
return key1 == key2; return key1 == key2;
} }
bool is_tracking_objects_; // Whether tracking object moves is needed.
List<HeapSnapshot*> snapshots_; List<HeapSnapshot*> snapshots_;
// uid -> HeapSnapshot* // Mapping from snapshots' uids to HeapSnapshot* pointers.
HashMap snapshots_uids_; HashMap snapshots_uids_;
StringsStorage names_; StringsStorage names_;
TokenEnumerator* token_enumerator_; TokenEnumerator* token_enumerator_;
// Mapping from HeapObject addresses to objects' uids.
HeapObjectsMap ids_;
HeapSnapshotsComparator comparator_;
DISALLOW_COPY_AND_ASSIGN(HeapSnapshotsCollection); DISALLOW_COPY_AND_ASSIGN(HeapSnapshotsCollection);
}; };
......
...@@ -56,8 +56,7 @@ class ConstructorHeapProfileTestHelper : public i::ConstructorHeapProfile { ...@@ -56,8 +56,7 @@ class ConstructorHeapProfileTestHelper : public i::ConstructorHeapProfile {
TEST(ConstructorProfile) { TEST(ConstructorProfile) {
v8::HandleScope scope; v8::HandleScope scope;
v8::Handle<v8::Context> env = v8::Context::New(); LocalContext env;
env->Enter();
CompileAndRunScript( CompileAndRunScript(
"function F() {} // A constructor\n" "function F() {} // A constructor\n"
...@@ -144,8 +143,7 @@ static inline void CheckNonEqualsHelper(const char* file, int line, ...@@ -144,8 +143,7 @@ static inline void CheckNonEqualsHelper(const char* file, int line,
TEST(ClustersCoarserSimple) { TEST(ClustersCoarserSimple) {
v8::HandleScope scope; v8::HandleScope scope;
v8::Handle<v8::Context> env = v8::Context::New(); LocalContext env;
env->Enter();
i::ZoneScope zn_scope(i::DELETE_ON_EXIT); i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
...@@ -183,8 +181,7 @@ TEST(ClustersCoarserSimple) { ...@@ -183,8 +181,7 @@ TEST(ClustersCoarserSimple) {
TEST(ClustersCoarserMultipleConstructors) { TEST(ClustersCoarserMultipleConstructors) {
v8::HandleScope scope; v8::HandleScope scope;
v8::Handle<v8::Context> env = v8::Context::New(); LocalContext env;
env->Enter();
i::ZoneScope zn_scope(i::DELETE_ON_EXIT); i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
...@@ -214,8 +211,7 @@ TEST(ClustersCoarserMultipleConstructors) { ...@@ -214,8 +211,7 @@ TEST(ClustersCoarserMultipleConstructors) {
TEST(ClustersCoarserPathsTraversal) { TEST(ClustersCoarserPathsTraversal) {
v8::HandleScope scope; v8::HandleScope scope;
v8::Handle<v8::Context> env = v8::Context::New(); LocalContext env;
env->Enter();
i::ZoneScope zn_scope(i::DELETE_ON_EXIT); i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
...@@ -267,8 +263,7 @@ TEST(ClustersCoarserPathsTraversal) { ...@@ -267,8 +263,7 @@ TEST(ClustersCoarserPathsTraversal) {
TEST(ClustersCoarserSelf) { TEST(ClustersCoarserSelf) {
v8::HandleScope scope; v8::HandleScope scope;
v8::Handle<v8::Context> env = v8::Context::New(); LocalContext env;
env->Enter();
i::ZoneScope zn_scope(i::DELETE_ON_EXIT); i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
...@@ -362,8 +357,7 @@ class RetainerProfilePrinter : public RetainerHeapProfile::Printer { ...@@ -362,8 +357,7 @@ class RetainerProfilePrinter : public RetainerHeapProfile::Printer {
TEST(RetainerProfile) { TEST(RetainerProfile) {
v8::HandleScope scope; v8::HandleScope scope;
v8::Handle<v8::Context> env = v8::Context::New(); LocalContext env;
env->Enter();
CompileAndRunScript( CompileAndRunScript(
"function A() {}\n" "function A() {}\n"
...@@ -431,8 +425,8 @@ class NamedEntriesDetector { ...@@ -431,8 +425,8 @@ class NamedEntriesDetector {
static const v8::HeapGraphNode* GetGlobalObject( static const v8::HeapGraphNode* GetGlobalObject(
const v8::HeapSnapshot* snapshot) { const v8::HeapSnapshot* snapshot) {
CHECK_EQ(1, snapshot->GetHead()->GetChildrenCount()); CHECK_EQ(1, snapshot->GetRoot()->GetChildrenCount());
return snapshot->GetHead()->GetChild(0)->GetToNode(); return snapshot->GetRoot()->GetChild(0)->GetToNode();
} }
...@@ -449,6 +443,19 @@ static const v8::HeapGraphNode* GetProperty(const v8::HeapGraphNode* node, ...@@ -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) { static bool HasString(const v8::HeapGraphNode* node, const char* contents) {
for (int i = 0, count = node->GetChildrenCount(); i < count; ++i) { for (int i = 0, count = node->GetChildrenCount(); i < count; ++i) {
const v8::HeapGraphEdge* prop = node->GetChild(i); const v8::HeapGraphEdge* prop = node->GetChild(i);
...@@ -464,11 +471,9 @@ static bool HasString(const v8::HeapGraphNode* node, const char* contents) { ...@@ -464,11 +471,9 @@ static bool HasString(const v8::HeapGraphNode* node, const char* contents) {
TEST(HeapSnapshot) { TEST(HeapSnapshot) {
v8::HandleScope scope; v8::HandleScope scope;
v8::Handle<v8::String> token1 = v8::String::New("token1"); v8::Handle<v8::String> token1 = v8::String::New("token1");
v8::Handle<v8::Context> env1 = v8::Context::New(); LocalContext env1;
env1->SetSecurityToken(token1); env1->SetSecurityToken(token1);
env1->Enter();
CompileAndRunScript( CompileAndRunScript(
"function A1() {}\n" "function A1() {}\n"
...@@ -479,9 +484,8 @@ TEST(HeapSnapshot) { ...@@ -479,9 +484,8 @@ TEST(HeapSnapshot) {
"var c1 = new C1(a1);"); "var c1 = new C1(a1);");
v8::Handle<v8::String> token2 = v8::String::New("token2"); v8::Handle<v8::String> token2 = v8::String::New("token2");
v8::Handle<v8::Context> env2 = v8::Context::New(); LocalContext env2;
env2->SetSecurityToken(token2); env2->SetSecurityToken(token2);
env2->Enter();
CompileAndRunScript( CompileAndRunScript(
"function A2() {}\n" "function A2() {}\n"
...@@ -569,8 +573,7 @@ TEST(HeapSnapshot) { ...@@ -569,8 +573,7 @@ TEST(HeapSnapshot) {
TEST(HeapSnapshotCodeObjects) { TEST(HeapSnapshotCodeObjects) {
v8::HandleScope scope; v8::HandleScope scope;
v8::Handle<v8::Context> env = v8::Context::New(); LocalContext env;
env->Enter();
CompileAndRunScript( CompileAndRunScript(
"function lazy(x) { return x - 1; }\n" "function lazy(x) { return x - 1; }\n"
...@@ -625,4 +628,121 @@ TEST(HeapSnapshotCodeObjects) { ...@@ -625,4 +628,121 @@ TEST(HeapSnapshotCodeObjects) {
CHECK(!lazy_references_x); 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 #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