*/
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;
};
+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.
*/
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;
};
}
+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();
}
+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();
}
-const HeapGraphNode* HeapSnapshot::GetHead() const {
+const HeapGraphNode* HeapSnapshot::GetRoot() const {
IsDeadCheck("v8::HeapSnapshot::GetHead");
const i::HeapSnapshot* snapshot =
reinterpret_cast<const i::HeapSnapshot*>(this);
}
+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();
}
+// 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,
HeapSnapshot* result = snapshots_->NewSnapshot(name, next_snapshot_uid_++);
HeapSnapshotGenerator generator(result);
generator.GenerateSnapshot();
+ snapshots_->SnapshotGenerationFinished();
return result;
}
}
+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;
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.
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();
// Update NewSpace stats if necessary.
RecordCopiedObject(target);
#endif
+ HEAP_PROFILE(ObjectMoveEvent(source->address(), target->address()));
return target;
}
#include "v8.h"
#include "execution.h"
+#include "heap-profiler.h"
#include "global-handles.h"
#include "ic-inl.h"
#include "mark-compact.h"
if (copied_to->IsJSFunction()) {
PROFILE(FunctionMoveEvent(old_addr, new_addr));
}
+ HEAP_PROFILE(ObjectMoveEvent(old_addr, new_addr));
return obj_size;
}
// Notify the logger that compiled code has moved.
PROFILE(CodeMoveEvent(old_addr, new_addr));
}
+ HEAP_PROFILE(ObjectMoveEvent(old_addr, new_addr));
return obj_size;
}
if (copied_to->IsJSFunction()) {
PROFILE(FunctionMoveEvent(old_addr, new_addr));
}
+ HEAP_PROFILE(ObjectMoveEvent(old_addr, new_addr));
return obj_size;
}
}
-namespace {
-
class DeleteNodesCallback {
public:
void BeforeTraversingChild(ProfileNode*, ProfileNode*) { }
void AfterChildTraversed(ProfileNode*, ProfileNode*) { }
};
-} // namespace
-
ProfileTree::ProfileTree()
: root_entry_(Logger::FUNCTION_TAG,
}
-namespace {
-
struct NodesPair {
NodesPair(ProfileNode* src, ProfileNode* dst)
: src(src), dst(dst) { }
int security_token_id_;
};
-} // namespace
-
void ProfileTree::FilteredClone(ProfileTree* src, int security_token_id) {
ms_to_ticks_scale_ = src->ms_to_ticks_scale_;
FilteredCloneCallback cb(root_, security_token_id);
}
-namespace {
-
class Position {
public:
explicit Position(ProfileNode* node)
int child_idx_;
};
-} // namespace
-
// Non-recursive implementation of a depth-first post-order tree traversal.
template <typename Callback>
}
-namespace {
-
class CalculateTotalTicksCallback {
public:
void BeforeTraversingChild(ProfileNode*, ProfileNode*) { }
}
};
-} // namespace
-
void ProfileTree::CalculateTotalTicks() {
CalculateTotalTicksCallback cb;
}
+void HeapEntry::SetUnidirAutoIndexReference(HeapEntry* entry) {
+ children_.Add(new HeapGraphEdge(next_auto_index_++, this, entry));
+}
+
+
int HeapEntry::TotalSize() {
return total_size_ != kUnknownSize ? total_size_ : CalculateTotalSize();
}
}
-int HeapEntry::CalculateTotalSize() {
- snapshot_->ClearPaint();
+template<class Visitor>
+void HeapEntry::ApplyAndPaintAllReachable(Visitor* visitor) {
List<HeapEntry*> list(10);
list.Add(this);
- total_size_ = self_size_;
this->PaintReachable();
+ visitor->Apply(this);
while (!list.is_empty()) {
HeapEntry* entry = list.RemoveLast();
const int children_count = entry->children_.length();
if (!child->painted_reachable()) {
list.Add(child);
child->PaintReachable();
- total_size_ += child->self_size_;
+ visitor->Apply(child);
}
}
}
- return total_size_;
}
-namespace {
+class NullClass {
+ public:
+ void Apply(HeapEntry* entry) { }
+};
+
+void HeapEntry::PaintAllReachable() {
+ NullClass null;
+ ApplyAndPaintAllReachable(&null);
+}
+
+
+class TotalSizeCalculator {
+ public:
+ TotalSizeCalculator()
+ : total_size_(0) {
+ }
+
+ int total_size() const { return total_size_; }
+
+ void Apply(HeapEntry* entry) {
+ total_size_ += entry->self_size();
+ }
+
+ private:
+ int total_size_;
+};
+
+int HeapEntry::CalculateTotalSize() {
+ snapshot_->ClearPaint();
+ TotalSizeCalculator calc;
+ ApplyAndPaintAllReachable(&calc);
+ total_size_ = calc.total_size();
+ return total_size_;
+}
+
class NonSharedSizeCalculator {
public:
int non_shared_total_size_;
};
-} // namespace
-
int HeapEntry::CalculateNonSharedTotalSize() {
// To calculate non-shared total size, first we paint all reachable
// nodes in one color, then we paint all nodes reachable from other
// nodes with a different color. Then we consider only nodes painted
- // with the first color for caclulating the total size.
+ // with the first color for calculating the total size.
snapshot_->ClearPaint();
- List<HeapEntry*> list(10);
- list.Add(this);
- this->PaintReachable();
- while (!list.is_empty()) {
- HeapEntry* entry = list.RemoveLast();
- const int children_count = entry->children_.length();
- for (int i = 0; i < children_count; ++i) {
- HeapEntry* child = entry->children_[i]->to();
- if (!child->painted_reachable()) {
- list.Add(child);
- child->PaintReachable();
- }
- }
- }
+ PaintAllReachable();
- List<HeapEntry*> list2(10);
+ List<HeapEntry*> list(10);
if (this != snapshot_->root()) {
- list2.Add(snapshot_->root());
+ list.Add(snapshot_->root());
snapshot_->root()->PaintReachableFromOthers();
}
- while (!list2.is_empty()) {
- HeapEntry* entry = list2.RemoveLast();
+ while (!list.is_empty()) {
+ HeapEntry* entry = list.RemoveLast();
const int children_count = entry->children_.length();
for (int i = 0; i < children_count; ++i) {
HeapEntry* child = entry->children_[i]->to();
if (child != this && child->not_painted_reachable_from_others()) {
- list2.Add(child);
+ list.Add(child);
child->PaintReachableFromOthers();
}
}
NonSharedSizeCalculator calculator;
snapshot_->IterateEntries(&calculator);
- return calculator.non_shared_total_size();
+ non_shared_total_size_ = calculator.non_shared_total_size();
+ return non_shared_total_size_;
}
void HeapEntry::Print(int max_depth, int indent) {
- OS::Print("%6d %6d %6d ", self_size_, TotalSize(), NonSharedTotalSize());
+ OS::Print("%6d %6d %6d [%ld] ",
+ self_size_, TotalSize(), NonSharedTotalSize(), id_);
if (type_ != STRING) {
OS::Print("%s %.40s\n", TypeAsString(), name_);
} else {
: collection_(collection),
title_(title),
uid_(uid),
- root_(this) {
+ root_(this),
+ sorted_entries_(NULL) {
+}
+
+
+HeapSnapshot::~HeapSnapshot() {
+ delete sorted_entries_;
}
HeapEntry* entry = new HeapEntry(this,
type,
name,
+ collection_->GetObjectId(object->address()),
GetObjectSize(object),
GetObjectSecurityToken(object));
entries_.Pair(object, entry);
}
-namespace {
-
class EdgesCutter {
public:
explicit EdgesCutter(int global_security_token)
const int global_security_token_;
};
-} // namespace
-
void HeapSnapshot::CutObjectsFromForeignSecurityContexts() {
EdgesCutter cutter(GetGlobalSecurityToken());
entries_.Apply(&cutter);
}
+class EntriesCollector {
+ public:
+ explicit EntriesCollector(List<HeapEntry*>* list) : list_(list) { }
+ void Apply(HeapEntry* entry) {
+ list_->Add(entry);
+ }
+ private:
+ List<HeapEntry*>* list_;
+};
+
+template<class T>
+static int SortByIds(const T* entry1_ptr,
+ const T* entry2_ptr) {
+ if ((*entry1_ptr)->id() == (*entry2_ptr)->id()) return 0;
+ return (*entry1_ptr)->id() < (*entry2_ptr)->id() ? -1 : 1;
+}
+
+List<HeapEntry*>* HeapSnapshot::GetSortedEntriesList() {
+ if (sorted_entries_ != NULL) return sorted_entries_;
+ sorted_entries_ = new List<HeapEntry*>(entries_.capacity());
+ EntriesCollector collector(sorted_entries_);
+ entries_.Apply(&collector);
+ sorted_entries_->Sort(SortByIds);
+ return sorted_entries_;
+}
+
+
+HeapSnapshotsDiff* HeapSnapshot::CompareWith(HeapSnapshot* snapshot) {
+ return collection_->CompareSnapshots(this, snapshot);
+}
+
+
void HeapSnapshot::Print(int max_depth) {
root_.Print(max_depth, 0);
}
+HeapObjectsMap::HeapObjectsMap()
+ : initial_fill_mode_(true),
+ next_id_(1),
+ entries_map_(AddressesMatch),
+ entries_(new List<EntryInfo>()) { }
+
+
+HeapObjectsMap::~HeapObjectsMap() {
+ delete entries_;
+}
+
+
+void HeapObjectsMap::SnapshotGenerationFinished() {
+ initial_fill_mode_ = false;
+ RemoveDeadEntries();
+}
+
+
+uint64_t HeapObjectsMap::FindObject(Address addr) {
+ if (!initial_fill_mode_) {
+ uint64_t existing = FindEntry(addr);
+ if (existing != 0) return existing;
+ }
+ uint64_t id = next_id_++;
+ AddEntry(addr, id);
+ return id;
+}
+
+
+void HeapObjectsMap::MoveObject(Address from, Address to) {
+ HashMap::Entry* entry = entries_map_.Lookup(from, AddressHash(from), false);
+ if (entry != NULL) {
+ void* value = entry->value;
+ entries_map_.Remove(from, AddressHash(from));
+ entry = entries_map_.Lookup(to, AddressHash(to), true);
+ ASSERT(entry->value == NULL);
+ entry->value = value;
+ }
+}
+
+
+void HeapObjectsMap::AddEntry(Address addr, uint64_t id) {
+ HashMap::Entry* entry = entries_map_.Lookup(addr, AddressHash(addr), true);
+ ASSERT(entry->value == NULL);
+ entry->value = reinterpret_cast<void*>(entries_->length());
+ entries_->Add(EntryInfo(id));
+}
+
+
+uint64_t HeapObjectsMap::FindEntry(Address addr) {
+ HashMap::Entry* entry = entries_map_.Lookup(addr, AddressHash(addr), false);
+ if (entry != NULL) {
+ int entry_index = reinterpret_cast<intptr_t>(entry->value);
+ EntryInfo& entry_info = entries_->at(entry_index);
+ entry_info.accessed = true;
+ return entry_info.id;
+ } else {
+ return 0;
+ }
+}
+
+
+void HeapObjectsMap::RemoveDeadEntries() {
+ List<EntryInfo>* new_entries = new List<EntryInfo>();
+ for (HashMap::Entry* entry = entries_map_.Start();
+ entry != NULL;
+ entry = entries_map_.Next(entry)) {
+ int entry_index = reinterpret_cast<intptr_t>(entry->value);
+ EntryInfo& entry_info = entries_->at(entry_index);
+ if (entry_info.accessed) {
+ entry->value = reinterpret_cast<void*>(new_entries->length());
+ new_entries->Add(EntryInfo(entry_info.id, false));
+ }
+ }
+ delete entries_;
+ entries_ = new_entries;
+}
+
+
HeapSnapshotsCollection::HeapSnapshotsCollection()
- : snapshots_uids_(HeapSnapshotsMatch),
+ : is_tracking_objects_(false),
+ snapshots_uids_(HeapSnapshotsMatch),
token_enumerator_(new TokenEnumerator()) {
}
HeapSnapshot* HeapSnapshotsCollection::NewSnapshot(const char* name,
unsigned uid) {
+ is_tracking_objects_ = true; // Start watching for heap objects moves.
HeapSnapshot* snapshot = new HeapSnapshot(this, name, uid);
snapshots_.Add(snapshot);
HashMap::Entry* entry =
}
+HeapSnapshotsDiff* HeapSnapshotsCollection::CompareSnapshots(
+ HeapSnapshot* snapshot1,
+ HeapSnapshot* snapshot2) {
+ return comparator_.Compare(snapshot1, snapshot2);
+}
+
+
HeapSnapshotGenerator::HeapSnapshotGenerator(HeapSnapshot* snapshot)
: snapshot_(snapshot) {
}
}
}
+
+static void DeleteHeapSnapshotsDiff(HeapSnapshotsDiff** diff_ptr) {
+ delete *diff_ptr;
+}
+
+HeapSnapshotsComparator::~HeapSnapshotsComparator() {
+ diffs_.Iterate(DeleteHeapSnapshotsDiff);
+}
+
+
+HeapSnapshotsDiff* HeapSnapshotsComparator::Compare(HeapSnapshot* snapshot1,
+ HeapSnapshot* snapshot2) {
+ HeapSnapshotsDiff* diff = new HeapSnapshotsDiff(snapshot1, snapshot2);
+ diffs_.Add(diff);
+ List<HeapEntry*>* entries1 = snapshot1->GetSortedEntriesList();
+ List<HeapEntry*>* entries2 = snapshot2->GetSortedEntriesList();
+ int i = 0, j = 0;
+ List<HeapEntry*> added_entries, deleted_entries;
+ while (i < entries1->length() && j < entries2->length()) {
+ uint64_t id1 = entries1->at(i)->id();
+ uint64_t id2 = entries2->at(j)->id();
+ if (id1 == id2) {
+ i++;
+ j++;
+ } else if (id1 < id2) {
+ HeapEntry* entry = entries1->at(i++);
+ deleted_entries.Add(entry);
+ } else {
+ HeapEntry* entry = entries2->at(j++);
+ added_entries.Add(entry);
+ }
+ }
+ while (i < entries1->length()) {
+ HeapEntry* entry = entries1->at(i++);
+ deleted_entries.Add(entry);
+ }
+ while (j < entries2->length()) {
+ HeapEntry* entry = entries2->at(j++);
+ added_entries.Add(entry);
+ }
+
+ snapshot1->ClearPaint();
+ snapshot1->root()->PaintAllReachable();
+ for (int i = 0; i < deleted_entries.length(); ++i) {
+ HeapEntry* entry = deleted_entries[i];
+ if (entry->painted_reachable())
+ diff->AddDeletedEntry(entry);
+ }
+ snapshot2->ClearPaint();
+ snapshot2->root()->PaintAllReachable();
+ for (int i = 0; i < added_entries.length(); ++i) {
+ HeapEntry* entry = added_entries[i];
+ if (entry->painted_reachable())
+ diff->AddAddedEntry(entry);
+ }
+ return diff;
+}
+
} } // namespace v8::internal
#endif // ENABLE_LOGGING_AND_PROFILING
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);
CodeEntry* entry_;
unsigned total_ticks_;
unsigned self_ticks_;
- // CodeEntry* -> ProfileNode*
+ // Mapping from CodeEntry* to ProfileNode*
HashMap children_;
List<ProfileNode*> children_list_;
}
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.
visited_(false),
type_(INTERNAL),
name_(""),
+ id_(0),
next_auto_index_(0),
self_size_(0),
security_token_id_(TokenEnumerator::kNoSecurityToken),
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),
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; }
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;
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();
bool visited_;
Type type_;
const char* name_;
+ uint64_t id_;
int next_auto_index_;
int self_size_;
int security_token_id_;
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));
class HeapSnapshotsCollection;
+class HeapSnapshotsDiff;
// HeapSnapshot represents a single heap snapshot. It is stored in
// HeapSnapshotsCollection, which is also a factory for
HeapSnapshot(HeapSnapshotsCollection* collection,
const char* title,
unsigned uid);
+ ~HeapSnapshot();
void ClearPaint();
void CutObjectsFromForeignSecurityContexts();
HeapEntry* GetEntry(Object* object);
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);
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);
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);
};
TEST(ConstructorProfile) {
v8::HandleScope scope;
- v8::Handle<v8::Context> env = v8::Context::New();
- env->Enter();
+ LocalContext env;
CompileAndRunScript(
"function F() {} // A constructor\n"
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);
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);
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);
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);
TEST(RetainerProfile) {
v8::HandleScope scope;
- v8::Handle<v8::Context> env = v8::Context::New();
- env->Enter();
+ LocalContext env;
CompileAndRunScript(
"function A() {}\n"
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();
}
}
+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);
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"
"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"
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"
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