Heap profiler: implement diffing of snapshots.
authormikhail.naganov@gmail.com <mikhail.naganov@gmail.com@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 15 Jul 2010 13:21:50 +0000 (13:21 +0000)
committermikhail.naganov@gmail.com <mikhail.naganov@gmail.com@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 15 Jul 2010 13:21:50 +0000 (13:21 +0000)
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

include/v8-profiler.h
src/api.cc
src/checks.h
src/heap-profiler.cc
src/heap-profiler.h
src/heap.cc
src/mark-compact.cc
src/profile-generator.cc
src/profile-generator.h
test/cctest/test-heap-profiler.cc

index 3e1952c..c99eb0d 100644 (file)
@@ -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;
 };
 
 
index b3c77d8..48c64b3 100644 (file)
@@ -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();
index 13374d8..05db574 100644 (file)
@@ -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,
index 73b9748..92ded7b 100644 (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;
 
index b593b99..dac488e 100644 (file)
@@ -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();
index 511d390..dc41027 100644 (file)
@@ -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;
 }
index e2dd2a3..d9b0222 100644 (file)
@@ -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;
 }
index 538a9ce..4e0cb71 100644 (file)
@@ -181,8 +181,6 @@ void ProfileNode::Print(int indent) {
 }
 
 
-namespace {
-
 class DeleteNodesCallback {
  public:
   void BeforeTraversingChild(ProfileNode*, ProfileNode*) { }
@@ -194,8 +192,6 @@ class DeleteNodesCallback {
   void AfterChildTraversed(ProfileNode*, ProfileNode*) { }
 };
 
-}  // namespace
-
 
 ProfileTree::ProfileTree()
     : root_entry_(Logger::FUNCTION_TAG,
@@ -240,8 +236,6 @@ void ProfileTree::AddPathFromStart(const Vector<CodeEntry*>& path) {
 }
 
 
-namespace {
-
 struct NodesPair {
   NodesPair(ProfileNode* src, ProfileNode* dst)
       : src(src), dst(dst) { }
@@ -294,8 +288,6 @@ class FilteredCloneCallback {
   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);
@@ -309,8 +301,6 @@ void ProfileTree::SetTickRatePerMs(double ticks_per_ms) {
 }
 
 
-namespace {
-
 class Position {
  public:
   explicit Position(ProfileNode* node)
@@ -328,8 +318,6 @@ class Position {
   int child_idx_;
 };
 
-}  // namespace
-
 
 // Non-recursive implementation of a depth-first post-order tree traversal.
 template <typename Callback>
@@ -355,8 +343,6 @@ void ProfileTree::TraverseDepthFirst(Callback* callback) {
 }
 
 
-namespace {
-
 class CalculateTotalTicksCallback {
  public:
   void BeforeTraversingChild(ProfileNode*, ProfileNode*) { }
@@ -370,8 +356,6 @@ class CalculateTotalTicksCallback {
   }
 };
 
-}  // namespace
-
 
 void ProfileTree::CalculateTotalTicks() {
   CalculateTotalTicksCallback cb;
@@ -877,6 +861,11 @@ void HeapEntry::SetAutoIndexReference(HeapEntry* entry) {
 }
 
 
+void HeapEntry::SetUnidirAutoIndexReference(HeapEntry* entry) {
+  children_.Add(new HeapGraphEdge(next_auto_index_++, this, entry));
+}
+
+
 int HeapEntry::TotalSize() {
   return total_size_ != kUnknownSize ? total_size_ : CalculateTotalSize();
 }
@@ -888,12 +877,12 @@ int HeapEntry::NonSharedTotalSize() {
 }
 
 
-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();
@@ -902,15 +891,48 @@ int HeapEntry::CalculateTotalSize() {
       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:
@@ -930,41 +952,26 @@ class NonSharedSizeCalculator {
   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();
       }
     }
@@ -972,7 +979,8 @@ int HeapEntry::CalculateNonSharedTotalSize() {
 
   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_;
 }
 
 
@@ -1078,7 +1086,8 @@ void HeapEntry::CutEdges() {
 
 
 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 {
@@ -1244,7 +1253,13 @@ HeapSnapshot::HeapSnapshot(HeapSnapshotsCollection* collection,
     : collection_(collection),
       title_(title),
       uid_(uid),
-      root_(this) {
+      root_(this),
+      sorted_entries_(NULL) {
+}
+
+
+HeapSnapshot::~HeapSnapshot() {
+  delete sorted_entries_;
 }
 
 
@@ -1355,6 +1370,7 @@ HeapEntry* HeapSnapshot::AddEntry(HeapObject* object,
   HeapEntry* entry = new HeapEntry(this,
                                    type,
                                    name,
+                                   collection_->GetObjectId(object->address()),
                                    GetObjectSize(object),
                                    GetObjectSecurityToken(object));
   entries_.Pair(object, entry);
@@ -1381,8 +1397,6 @@ HeapEntry* HeapSnapshot::AddEntry(HeapObject* object,
 }
 
 
-namespace {
-
 class EdgesCutter {
  public:
   explicit EdgesCutter(int global_security_token)
@@ -1400,8 +1414,6 @@ class EdgesCutter {
   const int global_security_token_;
 };
 
-}  // namespace
-
 void HeapSnapshot::CutObjectsFromForeignSecurityContexts() {
   EdgesCutter cutter(GetGlobalSecurityToken());
   entries_.Apply(&cutter);
@@ -1454,13 +1466,125 @@ int HeapSnapshot::CalculateNetworkSize(JSObject* obj) {
 }
 
 
+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()) {
 }
 
@@ -1478,6 +1602,7 @@ HeapSnapshotsCollection::~HeapSnapshotsCollection() {
 
 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 =
@@ -1498,6 +1623,13 @@ HeapSnapshot* HeapSnapshotsCollection::GetSnapshot(unsigned uid) {
 }
 
 
+HeapSnapshotsDiff* HeapSnapshotsCollection::CompareSnapshots(
+    HeapSnapshot* snapshot1,
+    HeapSnapshot* snapshot2) {
+  return comparator_.Compare(snapshot1, snapshot2);
+}
+
+
 HeapSnapshotGenerator::HeapSnapshotGenerator(HeapSnapshot* snapshot)
     : snapshot_(snapshot) {
 }
@@ -1630,6 +1762,64 @@ void HeapSnapshotGenerator::ExtractElementReferences(JSObject* js_obj,
   }
 }
 
+
+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
index 4e423c8..2054580 100644 (file)
@@ -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);
 };
index a4fcee1..462816d 100644 (file)
@@ -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