Revision: 5078
Author: [email protected]
Date: Thu Jul 15 06:21:50 2010
Log: 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
http://code.google.com/p/v8/source/detail?r=5078

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

=======================================
--- /branches/bleeding_edge/include/v8-profiler.h       Thu Jun 17 05:56:55 2010
+++ /branches/bleeding_edge/include/v8-profiler.h       Thu Jul 15 06:21:50 2010
@@ -258,6 +258,12 @@
    */
   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 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 @@
   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;
 };


=======================================
--- /branches/bleeding_edge/src/api.cc  Wed Jul 14 01:23:35 2010
+++ /branches/bleeding_edge/src/api.cc  Thu Jul 15 06:21:50 2010
@@ -4559,6 +4559,12 @@
   return Handle<String>(ToApi<String>(i::Factory::LookupAsciiSymbol(
       reinterpret_cast<const i::HeapEntry*>(this)->name())));
 }
+
+
+uint64_t HeapGraphNode::GetId() const {
+  IsDeadCheck("v8::HeapGraphNode::GetId");
+  return reinterpret_cast<const i::HeapEntry*>(this)->id();
+}


 int HeapGraphNode::GetSelfSize() const {
@@ -4622,6 +4628,22 @@
           reinterpret_cast<const i::HeapEntry*>(
               this))->GetRetainingPaths()->at(index));
 }
+
+
+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 {
@@ -4639,12 +4661,24 @@
 }


-const HeapGraphNode* HeapSnapshot::GetHead() const {
+const HeapGraphNode* HeapSnapshot::GetRoot() const {
   IsDeadCheck("v8::HeapSnapshot::GetHead");
   const i::HeapSnapshot* snapshot =
       reinterpret_cast<const i::HeapSnapshot*>(this);
   return reinterpret_cast<const HeapGraphNode*>(snapshot->const_root());
 }
+
+
+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() {
=======================================
--- /branches/bleeding_edge/src/checks.h        Thu Jun 17 05:56:55 2010
+++ /branches/bleeding_edge/src/checks.h        Thu Jul 15 06:21:50 2010
@@ -101,6 +101,28 @@
              static_cast<uint32_t>(value));
   }
 }
+
+
+// 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
=======================================
--- /branches/bleeding_edge/src/heap-profiler.cc        Tue Jun 22 07:58:08 2010
+++ /branches/bleeding_edge/src/heap-profiler.cc        Thu Jul 15 06:21:50 2010
@@ -364,6 +364,7 @@
HeapSnapshot* result = snapshots_->NewSnapshot(name, next_snapshot_uid_++);
   HeapSnapshotGenerator generator(result);
   generator.GenerateSnapshot();
+  snapshots_->SnapshotGenerationFinished();
   return result;
 }

@@ -389,6 +390,12 @@
   ASSERT(singleton_ != NULL);
   return singleton_->snapshots_->GetSnapshot(uid);
 }
+
+
+void HeapProfiler::ObjectMoveEvent(Address from, Address to) {
+  ASSERT(singleton_ != NULL);
+  singleton_->snapshots_->ObjectMoveEvent(from, to);
+}


 const JSObjectsClusterTreeConfig::Key JSObjectsClusterTreeConfig::kNoKey;
=======================================
--- /branches/bleeding_edge/src/heap-profiler.h Wed Jun 16 12:53:24 2010
+++ /branches/bleeding_edge/src/heap-profiler.h Thu Jul 15 06:21:50 2010
@@ -38,7 +38,15 @@
 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.
@@ -53,6 +61,12 @@
   static int GetSnapshotsCount();
   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.
=======================================
--- /branches/bleeding_edge/src/heap.cc Wed Jul 14 04:18:09 2010
+++ /branches/bleeding_edge/src/heap.cc Thu Jul 15 06:21:50 2010
@@ -1107,6 +1107,7 @@
   // Update NewSpace stats if necessary.
   RecordCopiedObject(target);
 #endif
+  HEAP_PROFILE(ObjectMoveEvent(source->address(), target->address()));

   return target;
 }
=======================================
--- /branches/bleeding_edge/src/mark-compact.cc Tue Jul 13 01:05:10 2010
+++ /branches/bleeding_edge/src/mark-compact.cc Thu Jul 15 06:21:50 2010
@@ -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 @@
   if (copied_to->IsJSFunction()) {
     PROFILE(FunctionMoveEvent(old_addr, new_addr));
   }
+  HEAP_PROFILE(ObjectMoveEvent(old_addr, new_addr));

   return obj_size;
 }
@@ -2264,6 +2266,7 @@
     // 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 @@
   if (copied_to->IsJSFunction()) {
     PROFILE(FunctionMoveEvent(old_addr, new_addr));
   }
+  HEAP_PROFILE(ObjectMoveEvent(old_addr, new_addr));

   return obj_size;
 }
=======================================
--- /branches/bleeding_edge/src/profile-generator.cc Wed Jul 14 04:18:09 2010 +++ /branches/bleeding_edge/src/profile-generator.cc Thu Jul 15 06:21:50 2010
@@ -181,8 +181,6 @@
 }


-namespace {
-
 class DeleteNodesCallback {
  public:
   void BeforeTraversingChild(ProfileNode*, ProfileNode*) { }
@@ -194,8 +192,6 @@
   void AfterChildTraversed(ProfileNode*, ProfileNode*) { }
 };

-}  // namespace
-

 ProfileTree::ProfileTree()
     : root_entry_(Logger::FUNCTION_TAG,
@@ -240,8 +236,6 @@
 }


-namespace {
-
 struct NodesPair {
   NodesPair(ProfileNode* src, ProfileNode* dst)
       : src(src), dst(dst) { }
@@ -294,8 +288,6 @@
   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 @@
 }


-namespace {
-
 class Position {
  public:
   explicit Position(ProfileNode* node)
@@ -328,8 +318,6 @@
   int child_idx_;
 };

-}  // namespace
-

 // Non-recursive implementation of a depth-first post-order tree traversal.
 template <typename Callback>
@@ -355,8 +343,6 @@
 }


-namespace {
-
 class CalculateTotalTicksCallback {
  public:
   void BeforeTraversingChild(ProfileNode*, ProfileNode*) { }
@@ -370,8 +356,6 @@
   }
 };

-}  // namespace
-

 void ProfileTree::CalculateTotalTicks() {
   CalculateTotalTicksCallback cb;
@@ -875,6 +859,11 @@
 void HeapEntry::SetAutoIndexReference(HeapEntry* entry) {
   SetElementReference(next_auto_index_++, entry);
 }
+
+
+void HeapEntry::SetUnidirAutoIndexReference(HeapEntry* entry) {
+  children_.Add(new HeapGraphEdge(next_auto_index_++, this, entry));
+}


 int HeapEntry::TotalSize() {
@@ -888,12 +877,12 @@
 }


-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 @@
       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 @@
   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();
-      }
-    }
-  }
-
-  List<HeapEntry*> list2(10);
+  PaintAllReachable();
+
+  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 @@

   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::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 @@
     : collection_(collection),
       title_(title),
       uid_(uid),
-      root_(this) {
+      root_(this),
+      sorted_entries_(NULL) {
+}
+
+
+HeapSnapshot::~HeapSnapshot() {
+  delete sorted_entries_;
 }


@@ -1355,6 +1370,7 @@
   HeapEntry* entry = new HeapEntry(this,
                                    type,
                                    name,
+ collection_->GetObjectId(object->address()),
                                    GetObjectSize(object),
                                    GetObjectSecurityToken(object));
   entries_.Pair(object, entry);
@@ -1381,8 +1397,6 @@
 }


-namespace {
-
 class EdgesCutter {
  public:
   explicit EdgesCutter(int global_security_token)
@@ -1400,8 +1414,6 @@
   const int global_security_token_;
 };

-}  // namespace
-
 void HeapSnapshot::CutObjectsFromForeignSecurityContexts() {
   EdgesCutter cutter(GetGlobalSecurityToken());
   entries_.Apply(&cutter);
@@ -1452,15 +1464,127 @@
   }
   return size;
 }
+
+
+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 @@

 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 =
@@ -1496,6 +1621,13 @@
                                                  false);
return entry != NULL ? reinterpret_cast<HeapSnapshot*>(entry->value) : NULL;
 }
+
+
+HeapSnapshotsDiff* HeapSnapshotsCollection::CompareSnapshots(
+    HeapSnapshot* snapshot1,
+    HeapSnapshot* snapshot2) {
+  return comparator_.Compare(snapshot1, snapshot2);
+}


 HeapSnapshotGenerator::HeapSnapshotGenerator(HeapSnapshot* snapshot)
@@ -1629,6 +1761,64 @@
     }
   }
 }
+
+
+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

=======================================
--- /branches/bleeding_edge/src/profile-generator.h     Thu Jun 17 05:56:55 2010
+++ /branches/bleeding_edge/src/profile-generator.h     Thu Jul 15 06:21:50 2010
@@ -74,7 +74,7 @@
                   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 @@
   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 @@
   }

   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 @@
         visited_(false),
         type_(INTERNAL),
         name_(""),
+        id_(0),
         next_auto_index_(0),
         self_size_(0),
         security_token_id_(TokenEnumerator::kNoSecurityToken),
@@ -494,12 +496,14 @@
   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 @@
   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 @@
   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 @@
   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 @@
   bool visited_;
   Type type_;
   const char* name_;
+  uint64_t id_;
   int next_auto_index_;
   int self_size_;
   int security_token_id_;
@@ -606,6 +617,8 @@
   void Apply(Visitor* visitor);
   HeapEntry* Map(HeapObject* object);
   void Pair(HeapObject* object, HeapEntry* entry);
+
+  uint32_t capacity() { return entries_.capacity(); }

  private:
   INLINE(uint32_t Hash(HeapObject* object)) {
@@ -627,6 +640,7 @@


 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 @@
   HeapSnapshot(HeapSnapshotsCollection* collection,
                const char* title,
                unsigned uid);
+  ~HeapSnapshot();
   void ClearPaint();
   void CutObjectsFromForeignSecurityContexts();
   HeapEntry* GetEntry(Object* object);
@@ -655,6 +670,8 @@
   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,36 +696,135 @@
   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);

   const char* GetName(String* name) { return names_.GetName(name); }

   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);
 };
=======================================
--- /branches/bleeding_edge/test/cctest/test-heap-profiler.cc Tue Jul 13 06:06:33 2010 +++ /branches/bleeding_edge/test/cctest/test-heap-profiler.cc Thu Jul 15 06:21:50 2010
@@ -56,8 +56,7 @@

 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 @@

 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(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(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(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 @@

 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 @@

 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();
 }


@@ -447,6 +441,19 @@
   }
   return NULL;
 }
+
+
+static bool IsNodeRetainedAs(const v8::HeapGraphNode* node,
+                             v8::HeapGraphEdge::Type type,
+                             const char* name) {
+  for (int i = 0, count = node->GetRetainersCount(); i < count; ++i) {
+    const v8::HeapGraphEdge* prop = node->GetRetainer(i);
+    v8::String::AsciiValue prop_name(prop->GetName());
+    if (prop->GetType() == type && strcmp(name, *prop_name) == 0)
+      return true;
+  }
+  return false;
+}


static bool HasString(const v8::HeapGraphNode* node, const char* contents) {
@@ -464,11 +471,9 @@

 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 @@
       "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(HeapSnapshotCodeObjects) {
   v8::HandleScope scope;
-  v8::Handle<v8::Context> env = v8::Context::New();
-  env->Enter();
+  LocalContext env;

   CompileAndRunScript(
       "function lazy(x) { return x - 1; }\n"
@@ -624,5 +627,122 @@
   CHECK(compiled_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

--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev

Reply via email to