Heap profiler: count the number of back references for objects.
authormikhail.naganov@gmail.com <mikhail.naganov@gmail.com@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 18 Sep 2009 12:05:18 +0000 (12:05 +0000)
committermikhail.naganov@gmail.com <mikhail.naganov@gmail.com@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 18 Sep 2009 12:05:18 +0000 (12:05 +0000)
Also, perform some refactoring to reuse common code between constructor and retainer profiles.

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

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@2936 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/heap-profiler.cc
src/heap-profiler.h
src/log-utils.cc
src/log-utils.h
src/log.cc
src/log.h
test/cctest/test-heap-profiler.cc

index 46006fe6265bb31f3d6c3dabc88f80f97aff6339..5e945b49a0ca126c72eaba27ccaa746d00039d8b 100644 (file)
@@ -37,18 +37,70 @@ namespace internal {
 #ifdef ENABLE_LOGGING_AND_PROFILING
 namespace {
 
-// JSStatsHelper provides service functions for examining
-// JS objects allocated on heap. It is run during garbage
-// collection cycle, thus it doesn't need to use handles.
-class JSStatsHelper {
+// Clusterizer is a set of helper functions for converting
+// object references into clusters.
+class Clusterizer : public AllStatic {
  public:
-  static int CalculateNetworkSize(JSObject* obj);
+  static JSObjectsCluster Clusterize(HeapObject* obj) {
+    return Clusterize(obj, true);
+  }
+  static void InsertIntoTree(JSObjectsClusterTree* tree,
+                             HeapObject* obj, bool fine_grain);
+  static void InsertReferenceIntoTree(JSObjectsClusterTree* tree,
+                                      const JSObjectsCluster& cluster) {
+    InsertIntoTree(tree, cluster, 0);
+  }
+
  private:
-  DISALLOW_IMPLICIT_CONSTRUCTORS(JSStatsHelper);
+  static JSObjectsCluster Clusterize(HeapObject* obj, bool fine_grain);
+  static int CalculateNetworkSize(JSObject* obj);
+  static int GetObjectSize(HeapObject* obj) {
+    return obj->IsJSObject() ?
+        CalculateNetworkSize(JSObject::cast(obj)) : obj->Size();
+  }
+  static void InsertIntoTree(JSObjectsClusterTree* tree,
+                             const JSObjectsCluster& cluster, int size);
 };
 
 
-int JSStatsHelper::CalculateNetworkSize(JSObject* obj) {
+JSObjectsCluster Clusterizer::Clusterize(HeapObject* obj, bool fine_grain) {
+  if (obj->IsJSObject()) {
+    JSObject* js_obj = JSObject::cast(obj);
+    String* constructor = JSObject::cast(js_obj)->constructor_name();
+    // Differentiate Object and Array instances.
+    if (fine_grain && (constructor == Heap::Object_symbol() ||
+                       constructor == Heap::Array_symbol())) {
+      return JSObjectsCluster(constructor, obj);
+    } else {
+      return JSObjectsCluster(constructor);
+    }
+  } else if (obj->IsString()) {
+    return JSObjectsCluster(Heap::String_symbol());
+  }
+  return JSObjectsCluster();
+}
+
+
+void Clusterizer::InsertIntoTree(JSObjectsClusterTree* tree,
+                                 HeapObject* obj, bool fine_grain) {
+  JSObjectsCluster cluster = Clusterize(obj, fine_grain);
+  if (cluster.is_null()) return;
+  InsertIntoTree(tree, cluster, GetObjectSize(obj));
+}
+
+
+void Clusterizer::InsertIntoTree(JSObjectsClusterTree* tree,
+                                 const JSObjectsCluster& cluster, int size) {
+  JSObjectsClusterTree::Locator loc;
+  tree->Insert(cluster, &loc);
+  NumberAndSizeInfo number_and_size = loc.value();
+  number_and_size.increment_number(1);
+  number_and_size.increment_bytes(size);
+  loc.set_value(number_and_size);
+}
+
+
+int Clusterizer::CalculateNetworkSize(JSObject* obj) {
   int size = obj->Size();
   // If 'properties' and 'elements' are non-empty (thus, non-shared),
   // take their size into account.
@@ -65,8 +117,8 @@ int JSStatsHelper::CalculateNetworkSize(JSObject* obj) {
 // A helper class for recording back references.
 class ReferencesExtractor : public ObjectVisitor {
  public:
-  ReferencesExtractor(
-      const JSObjectsCluster& cluster, RetainerHeapProfile* profile)
+  ReferencesExtractor(const JSObjectsCluster& cluster,
+                      RetainerHeapProfile* profile)
       : cluster_(cluster),
         profile_(profile),
         inside_array_(false) {
@@ -74,7 +126,7 @@ class ReferencesExtractor : public ObjectVisitor {
 
   void VisitPointer(Object** o) {
     if ((*o)->IsJSObject() || (*o)->IsString()) {
-      profile_->StoreReference(cluster_, *o);
+      profile_->StoreReference(cluster_, HeapObject::cast(*o));
     } else if ((*o)->IsFixedArray() && !inside_array_) {
       // Traverse one level deep for data members that are fixed arrays.
       // This covers the case of 'elements' and 'properties' of JSObject,
@@ -99,18 +151,47 @@ class ReferencesExtractor : public ObjectVisitor {
 // A printer interface implementation for the Retainers profile.
 class RetainersPrinter : public RetainerHeapProfile::Printer {
  public:
-  void PrintRetainers(const StringStream& retainers) {
-    LOG(HeapSampleJSRetainersEvent(*(retainers.ToCString())));
+  void PrintRetainers(const JSObjectsCluster& cluster,
+                      const StringStream& retainers) {
+    HeapStringAllocator allocator;
+    StringStream stream(&allocator);
+    cluster.Print(&stream);
+    LOG(HeapSampleJSRetainersEvent(
+        *(stream.ToCString()), *(retainers.ToCString())));
+  }
+};
+
+
+class RetainerTreePrinter BASE_EMBEDDED {
+ public:
+  explicit RetainerTreePrinter(StringStream* stream) : stream_(stream) {}
+  void Call(const JSObjectsCluster& cluster,
+            const NumberAndSizeInfo& number_and_size) {
+    Print(stream_, cluster, number_and_size);
   }
+  static void Print(StringStream* stream,
+                    const JSObjectsCluster& cluster,
+                    const NumberAndSizeInfo& numNNber_and_size);
+
+ private:
+  StringStream* stream_;
 };
 
+
+void RetainerTreePrinter::Print(StringStream* stream,
+                                const JSObjectsCluster& cluster,
+                                const NumberAndSizeInfo& number_and_size) {
+  stream->Put(',');
+  cluster.Print(stream);
+  stream->Add(";%d", number_and_size.number());
+}
+
+
 }  // namespace
 
 
-const ConstructorHeapProfile::TreeConfig::Key
-    ConstructorHeapProfile::TreeConfig::kNoKey = NULL;
-const ConstructorHeapProfile::TreeConfig::Value
-    ConstructorHeapProfile::TreeConfig::kNoValue;
+const JSObjectsClusterTreeConfig::Key JSObjectsClusterTreeConfig::kNoKey;
+const JSObjectsClusterTreeConfig::Value JSObjectsClusterTreeConfig::kNoValue;
 
 
 ConstructorHeapProfile::ConstructorHeapProfile()
@@ -118,39 +199,19 @@ ConstructorHeapProfile::ConstructorHeapProfile()
 }
 
 
-void ConstructorHeapProfile::Call(String* name,
-                                const NumberAndSizeInfo& number_and_size) {
-  ASSERT(name != NULL);
-  SmartPointer<char> s_name(
-      name->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL));
-  LOG(HeapSampleJSConstructorEvent(*s_name,
+void ConstructorHeapProfile::Call(const JSObjectsCluster& cluster,
+                                  const NumberAndSizeInfo& number_and_size) {
+  HeapStringAllocator allocator;
+  StringStream stream(&allocator);
+  cluster.Print(&stream);
+  LOG(HeapSampleJSConstructorEvent(*(stream.ToCString()),
                                    number_and_size.number(),
                                    number_and_size.bytes()));
 }
 
 
 void ConstructorHeapProfile::CollectStats(HeapObject* obj) {
-  String* constructor = NULL;
-  int size;
-  if (obj->IsString()) {
-    constructor = Heap::String_symbol();
-    size = obj->Size();
-  } else if (obj->IsJSObject()) {
-    JSObject* js_obj = JSObject::cast(obj);
-    constructor = js_obj->constructor_name();
-    size = JSStatsHelper::CalculateNetworkSize(js_obj);
-  } else {
-    return;
-  }
-
-  JSObjectsInfoTree::Locator loc;
-  if (!js_objects_info_tree_.Find(constructor, &loc)) {
-    js_objects_info_tree_.Insert(constructor, &loc);
-  }
-  NumberAndSizeInfo number_and_size = loc.value();
-  number_and_size.increment_number(1);
-  number_and_size.increment_bytes(size);
-  loc.set_value(number_and_size);
+  Clusterizer::InsertIntoTree(&js_objects_info_tree_, obj, false);
 }
 
 
@@ -231,37 +292,37 @@ ClustersCoarser::ClustersCoarser()
 }
 
 
-void ClustersCoarser::Call(
-    const JSObjectsCluster& cluster, JSObjectsClusterTree* tree) {
-  if (tree != NULL) {
-    // First level of retainer graph.
-    if (!cluster.can_be_coarsed()) return;
-    ClusterBackRefs pair(cluster);
-    ASSERT(current_pair_ == NULL);
-    current_pair_ = &pair;
-    current_set_ = new JSObjectsClusterTree();
-    tree->ForEach(this);
-    sim_list_.Add(pair);
-    current_pair_ = NULL;
-    current_set_ = NULL;
+void ClustersCoarser::Call(const JSObjectsCluster& cluster,
+                           JSObjectsClusterTree* tree) {
+  if (!cluster.can_be_coarsed()) return;
+  ClusterBackRefs pair(cluster);
+  ASSERT(current_pair_ == NULL);
+  current_pair_ = &pair;
+  current_set_ = new JSObjectsRetainerTree();
+  tree->ForEach(this);
+  sim_list_.Add(pair);
+  current_pair_ = NULL;
+  current_set_ = NULL;
+}
+
+
+void ClustersCoarser::Call(const JSObjectsCluster& cluster,
+                           const NumberAndSizeInfo& number_and_size) {
+  ASSERT(current_pair_ != NULL);
+  ASSERT(current_set_ != NULL);
+  JSObjectsCluster eq = GetCoarseEquivalent(cluster);
+  JSObjectsRetainerTree::Locator loc;
+  if (!eq.is_null()) {
+    if (current_set_->Find(eq, &loc)) return;
+    current_pair_->refs.Add(eq);
+    current_set_->Insert(eq, &loc);
   } else {
-    // Second level of retainer graph.
-    ASSERT(current_pair_ != NULL);
-    ASSERT(current_set_ != NULL);
-    JSObjectsCluster eq = GetCoarseEquivalent(cluster);
-    JSObjectsClusterTree::Locator loc;
-    if (!eq.is_null()) {
-      if (current_set_->Find(eq, &loc)) return;
-      current_pair_->refs.Add(eq);
-      current_set_->Insert(eq, &loc);
-    } else {
-      current_pair_->refs.Add(cluster);
-    }
+    current_pair_->refs.Add(cluster);
   }
 }
 
 
-void ClustersCoarser::Process(JSObjectsClusterTree* tree) {
+void ClustersCoarser::Process(JSObjectsRetainerTree* tree) {
   int last_eq_clusters = -1;
   for (int i = 0; i < kMaxPassesCount; ++i) {
     sim_list_.Clear();
@@ -273,7 +334,7 @@ void ClustersCoarser::Process(JSObjectsClusterTree* tree) {
 }
 
 
-int ClustersCoarser::DoProcess(JSObjectsClusterTree* tree) {
+int ClustersCoarser::DoProcess(JSObjectsRetainerTree* tree) {
   tree->ForEach(this);
   // To sort similarity list properly, references list of a cluster is
   // required to be sorted, thus 'O1 <- A, B' and 'O2 <- B, A' would
@@ -328,60 +389,37 @@ int ClustersCoarser::FillEqualityTree() {
 
 const JSObjectsCluster ClustersCoarser::ClusterEqualityConfig::kNoKey;
 const JSObjectsCluster ClustersCoarser::ClusterEqualityConfig::kNoValue;
-const JSObjectsClusterTreeConfig::Key JSObjectsClusterTreeConfig::kNoKey;
-const JSObjectsClusterTreeConfig::Value JSObjectsClusterTreeConfig::kNoValue =
+const JSObjectsRetainerTreeConfig::Key JSObjectsRetainerTreeConfig::kNoKey;
+const JSObjectsRetainerTreeConfig::Value JSObjectsRetainerTreeConfig::kNoValue =
     NULL;
 
 
 RetainerHeapProfile::RetainerHeapProfile()
     : zscope_(DELETE_ON_EXIT),
       coarse_cluster_tree_(NULL),
-      retainers_printed_(0),
       current_printer_(NULL),
       current_stream_(NULL) {
   JSObjectsCluster roots(JSObjectsCluster::ROOTS);
-  ReferencesExtractor extractor(
-      roots, this);
+  ReferencesExtractor extractor(roots, this);
   Heap::IterateRoots(&extractor);
 }
 
 
-JSObjectsCluster RetainerHeapProfile::Clusterize(Object* obj) {
-  if (obj->IsJSObject()) {
-    String* constructor = JSObject::cast(obj)->constructor_name();
-    // Differentiate Object and Array instances.
-    if (constructor == Heap::Object_symbol() ||
-        constructor == Heap::Array_symbol()) {
-      return JSObjectsCluster(constructor, obj);
-    } else {
-      return JSObjectsCluster(constructor);
-    }
-  } else if (obj->IsString()) {
-    return JSObjectsCluster(Heap::String_symbol());
-  } else {
-    UNREACHABLE();
-    return JSObjectsCluster();
-  }
-}
-
-
-void RetainerHeapProfile::StoreReference(
-    const JSObjectsCluster& cluster,
-    Object* ref) {
-  JSObjectsCluster ref_cluster = Clusterize(ref);
-  JSObjectsClusterTree::Locator ref_loc;
+void RetainerHeapProfile::StoreReference(const JSObjectsCluster& cluster,
+                                         HeapObject* ref) {
+  JSObjectsCluster ref_cluster = Clusterizer::Clusterize(ref);
+  JSObjectsRetainerTree::Locator ref_loc;
   if (retainers_tree_.Insert(ref_cluster, &ref_loc)) {
     ref_loc.set_value(new JSObjectsClusterTree());
   }
   JSObjectsClusterTree* referenced_by = ref_loc.value();
-  JSObjectsClusterTree::Locator obj_loc;
-  referenced_by->Insert(cluster, &obj_loc);
+  Clusterizer::InsertReferenceIntoTree(referenced_by, cluster);
 }
 
 
 void RetainerHeapProfile::CollectStats(HeapObject* obj) {
   if (obj->IsJSObject()) {
-    const JSObjectsCluster cluster = Clusterize(JSObject::cast(obj));
+    const JSObjectsCluster cluster = Clusterizer::Clusterize(obj);
     ReferencesExtractor extractor(cluster, this);
     obj->Iterate(&extractor);
   } else if (obj->IsJSGlobalPropertyCell()) {
@@ -408,50 +446,40 @@ void RetainerHeapProfile::PrintStats() {
 }
 
 
-void RetainerHeapProfile::Call(
-    const JSObjectsCluster& cluster,
-    JSObjectsClusterTree* tree) {
-  ASSERT(current_printer_ != NULL);
-  if (tree != NULL) {
-    // First level of retainer graph.
-    if (coarser_.HasAnEquivalent(cluster)) return;
-    ASSERT(current_stream_ == NULL);
-    HeapStringAllocator allocator;
-    StringStream stream(&allocator);
-    current_stream_ = &stream;
-    cluster.Print(current_stream_);
-    ASSERT(coarse_cluster_tree_ == NULL);
-    coarse_cluster_tree_ = new JSObjectsClusterTree();
-    retainers_printed_ = 0;
-    tree->ForEach(this);
-    coarse_cluster_tree_ = NULL;
-    current_printer_->PrintRetainers(stream);
-    current_stream_ = NULL;
+void RetainerHeapProfile::Call(const JSObjectsCluster& cluster,
+                               JSObjectsClusterTree* tree) {
+  // First level of retainer graph.
+  if (coarser_.HasAnEquivalent(cluster)) return;
+  ASSERT(current_stream_ == NULL);
+  HeapStringAllocator allocator;
+  StringStream stream(&allocator);
+  current_stream_ = &stream;
+  ASSERT(coarse_cluster_tree_ == NULL);
+  coarse_cluster_tree_ = new JSObjectsClusterTree();
+  tree->ForEach(this);
+  // Print aggregated counts and sizes.
+  RetainerTreePrinter printer(current_stream_);
+  coarse_cluster_tree_->ForEach(&printer);
+  coarse_cluster_tree_ = NULL;
+  current_printer_->PrintRetainers(cluster, stream);
+  current_stream_ = NULL;
+}
+
+
+void RetainerHeapProfile::Call(const JSObjectsCluster& cluster,
+                               const NumberAndSizeInfo& number_and_size) {
+  ASSERT(coarse_cluster_tree_ != NULL);
+  ASSERT(current_stream_ != NULL);
+  JSObjectsCluster eq = coarser_.GetCoarseEquivalent(cluster);
+  if (eq.is_null()) {
+    RetainerTreePrinter::Print(current_stream_, cluster, number_and_size);
   } else {
-    // Second level of retainer graph.
-    ASSERT(coarse_cluster_tree_ != NULL);
-    ASSERT(current_stream_ != NULL);
-    if (retainers_printed_ >= kMaxRetainersToPrint) {
-      if (retainers_printed_ == kMaxRetainersToPrint) {
-        // TODO(mnaganov): Print the exact count.
-        current_stream_->Add(",...");
-        ++retainers_printed_;  // avoid printing ellipsis next time.
-      }
-      return;
-    }
-    JSObjectsCluster eq = coarser_.GetCoarseEquivalent(cluster);
-    if (eq.is_null()) {
-      current_stream_->Put(',');
-      cluster.Print(current_stream_);
-      ++retainers_printed_;
-    } else {
-      JSObjectsClusterTree::Locator loc;
-      if (coarse_cluster_tree_->Insert(eq, &loc)) {
-        current_stream_->Put(',');
-        eq.Print(current_stream_);
-        ++retainers_printed_;
-      }
-    }
+    // Aggregate counts and sizes for equivalent clusters.
+    JSObjectsClusterTree::Locator loc;
+    coarse_cluster_tree_->Insert(eq, &loc);
+    NumberAndSizeInfo eq_number_and_size = loc.value();
+    eq_number_and_size.increment_number(number_and_size.number());
+    loc.set_value(eq_number_and_size);
   }
 }
 
index 23d9502c136ac5813f7c0480265e2caaece2fa35..adc3da2b86b02f054af4d2f807193ec209797563 100644 (file)
@@ -46,41 +46,11 @@ class HeapProfiler {
 };
 
 
-// ConstructorHeapProfile is responsible for gathering and logging
-// "constructor profile" of JS objects allocated on heap.
-// It is run during garbage collection cycle, thus it doesn't need
-// to use handles.
-class ConstructorHeapProfile BASE_EMBEDDED {
- public:
-  ConstructorHeapProfile();
-  virtual ~ConstructorHeapProfile() {}
-  void CollectStats(HeapObject* obj);
-  void PrintStats();
-  // Used by ZoneSplayTree::ForEach. Made virtual to allow overriding in tests.
-  virtual void Call(String* name, const NumberAndSizeInfo& number_and_size);
-
- private:
-  struct TreeConfig {
-    typedef String* Key;
-    typedef NumberAndSizeInfo Value;
-    static const Key kNoKey;
-    static const Value kNoValue;
-    static int Compare(const Key& a, const Key& b) {
-      // Strings are unique, so it is sufficient to compare their pointers.
-      return a == b ? 0 : (a < b ? -1 : 1);
-    }
-  };
-  typedef ZoneSplayTree<TreeConfig> JSObjectsInfoTree;
-
-  ZoneScope zscope_;
-  JSObjectsInfoTree js_objects_info_tree_;
-};
-
-
 // JSObjectsCluster describes a group of JS objects that are
-// considered equivalent in terms of retainer profile.
+// considered equivalent in terms of a particular profile.
 class JSObjectsCluster BASE_EMBEDDED {
  public:
+  // These special cases are used in retainer profile.
   enum SpecialCase {
     ROOTS = 1,
     GLOBAL_PROPERTY = 2
@@ -94,8 +64,8 @@ class JSObjectsCluster BASE_EMBEDDED {
   JSObjectsCluster(String* constructor, Object* instance)
       : constructor_(constructor), instance_(instance) {}
 
-  static int CompareConstructors(
-      const JSObjectsCluster& a, const JSObjectsCluster& b) {
+  static int CompareConstructors(const JSObjectsCluster& a,
+                                 const JSObjectsCluster& b) {
     // Strings are unique, so it is sufficient to compare their pointers.
     return a.constructor_ == b.constructor_ ? 0
         : (a.constructor_ < b.constructor_ ? -1 : 1);
@@ -110,6 +80,7 @@ class JSObjectsCluster BASE_EMBEDDED {
 
   bool is_null() const { return constructor_ == NULL; }
   bool can_be_coarsed() const { return instance_ != NULL; }
+  String* constructor() const { return constructor_; }
 
   void Print(StringStream* accumulator) const;
   // Allows null clusters to be printed.
@@ -133,14 +104,47 @@ class JSObjectsCluster BASE_EMBEDDED {
 };
 
 
-struct JSObjectsClusterTreeConfig;
+struct JSObjectsClusterTreeConfig {
+  typedef JSObjectsCluster Key;
+  typedef NumberAndSizeInfo Value;
+  static const Key kNoKey;
+  static const Value kNoValue;
+  static int Compare(const Key& a, const Key& b) {
+    return Key::Compare(a, b);
+  }
+};
 typedef ZoneSplayTree<JSObjectsClusterTreeConfig> JSObjectsClusterTree;
 
-// JSObjectsClusterTree is used to represent retainer graphs using
-// adjacency list form. That is, the first level maps JS object
-// clusters to adjacency lists, which in their turn are degenerate
-// JSObjectsClusterTrees (their values are NULLs.)
-struct JSObjectsClusterTreeConfig {
+
+// ConstructorHeapProfile is responsible for gathering and logging
+// "constructor profile" of JS objects allocated on heap.
+// It is run during garbage collection cycle, thus it doesn't need
+// to use handles.
+class ConstructorHeapProfile BASE_EMBEDDED {
+ public:
+  ConstructorHeapProfile();
+  virtual ~ConstructorHeapProfile() {}
+  void CollectStats(HeapObject* obj);
+  void PrintStats();
+  // Used by ZoneSplayTree::ForEach. Made virtual to allow overriding in tests.
+  virtual void Call(const JSObjectsCluster& cluster,
+                    const NumberAndSizeInfo& number_and_size);
+
+ private:
+  ZoneScope zscope_;
+  JSObjectsClusterTree js_objects_info_tree_;
+};
+
+
+// JSObjectsRetainerTree is used to represent retainer graphs using
+// adjacency list form:
+//
+//   Cluster -> (Cluster -> NumberAndSizeInfo)
+//
+// Subordinate splay trees are stored by pointer. They are zone-allocated,
+// so it isn't needed to manage their lifetime.
+//
+struct JSObjectsRetainerTreeConfig {
   typedef JSObjectsCluster Key;
   typedef JSObjectsClusterTree* Value;
   static const Key kNoKey;
@@ -149,6 +153,7 @@ struct JSObjectsClusterTreeConfig {
     return Key::Compare(a, b);
   }
 };
+typedef ZoneSplayTree<JSObjectsRetainerTreeConfig> JSObjectsRetainerTree;
 
 
 class ClustersCoarser BASE_EMBEDDED {
@@ -156,7 +161,7 @@ class ClustersCoarser BASE_EMBEDDED {
   ClustersCoarser();
 
   // Processes a given retainer graph.
-  void Process(JSObjectsClusterTree* tree);
+  void Process(JSObjectsRetainerTree* tree);
 
   // Returns an equivalent cluster (can be the cluster itself).
   // If the given cluster doesn't have an equivalent, returns null cluster.
@@ -165,8 +170,10 @@ class ClustersCoarser BASE_EMBEDDED {
   // skipped in some cases.
   bool HasAnEquivalent(const JSObjectsCluster& cluster);
 
-  // Used by ZoneSplayTree::ForEach.
+  // Used by JSObjectsRetainerTree::ForEach.
   void Call(const JSObjectsCluster& cluster, JSObjectsClusterTree* tree);
+  void Call(const JSObjectsCluster& cluster,
+            const NumberAndSizeInfo& number_and_size);
 
  private:
   // Stores a list of back references for a cluster.
@@ -194,11 +201,11 @@ class ClustersCoarser BASE_EMBEDDED {
   };
   typedef ZoneSplayTree<ClusterEqualityConfig> EqualityTree;
 
-  static int ClusterBackRefsCmp(
-      const ClusterBackRefs* a, const ClusterBackRefs* b) {
+  static int ClusterBackRefsCmp(const ClusterBackRefs* a,
+                                const ClusterBackRefs* b) {
     return ClusterBackRefs::Compare(*a, *b);
   }
-  int DoProcess(JSObjectsClusterTree* tree);
+  int DoProcess(JSObjectsRetainerTree* tree);
   int FillEqualityTree();
 
   static const int kInitialBackrefsListCapacity = 2;
@@ -211,7 +218,7 @@ class ClustersCoarser BASE_EMBEDDED {
   SimilarityList sim_list_;
   EqualityTree eq_tree_;
   ClusterBackRefs* current_pair_;
-  JSObjectsClusterTree* current_set_;
+  JSObjectsRetainerTree* current_set_;
 };
 
 
@@ -224,31 +231,31 @@ class RetainerHeapProfile BASE_EMBEDDED {
   class Printer {
    public:
     virtual ~Printer() {}
-    virtual void PrintRetainers(const StringStream& retainers) = 0;
+    virtual void PrintRetainers(const JSObjectsCluster& cluster,
+                                const StringStream& retainers) = 0;
   };
 
   RetainerHeapProfile();
   void CollectStats(HeapObject* obj);
   void PrintStats();
   void DebugPrintStats(Printer* printer);
-  void StoreReference(const JSObjectsCluster& cluster, Object* ref);
+  void StoreReference(const JSObjectsCluster& cluster, HeapObject* ref);
 
  private:
-  JSObjectsCluster Clusterize(Object* obj);
-
   // Limit on the number of retainers to be printed per cluster.
   static const int kMaxRetainersToPrint = 50;
   ZoneScope zscope_;
-  JSObjectsClusterTree retainers_tree_;
+  JSObjectsRetainerTree retainers_tree_;
   ClustersCoarser coarser_;
   // TODO(mnaganov): Use some helper class to hold these state variables.
   JSObjectsClusterTree* coarse_cluster_tree_;
-  int retainers_printed_;
   Printer* current_printer_;
   StringStream* current_stream_;
  public:
-  // Used by JSObjectsClusterTree::ForEach.
+  // Used by JSObjectsRetainerTree::ForEach.
   void Call(const JSObjectsCluster& cluster, JSObjectsClusterTree* tree);
+  void Call(const JSObjectsCluster& cluster,
+            const NumberAndSizeInfo& number_and_size);
 };
 
 
index b31864be4695c752ebe210ad1ac77282c377a855..f280bfbe28a2a3ecc853cf5620b1a42d39fca5a0 100644 (file)
@@ -310,6 +310,18 @@ void LogMessageBuilder::AppendDetailed(String* str, bool show_impl_info) {
 }
 
 
+void LogMessageBuilder::AppendStringPart(const char* str, int len) {
+  if (pos_ + len > Log::kMessageBufferSize) {
+    len = Log::kMessageBufferSize - pos_;
+    ASSERT(len >= 0);
+    if (len == 0) return;
+  }
+  strncpy(Log::message_buffer_ + pos_, str, len);
+  pos_ += len;
+  ASSERT(pos_ <= Log::kMessageBufferSize);
+}
+
+
 bool LogMessageBuilder::StoreInCompressor(LogRecordCompressor* compressor) {
   return compressor->Store(Vector<const char>(Log::message_buffer_, pos_));
 }
index ad669d53d76360bbf63874334734fb8149bf5d97..117f098cc8eed9b4b53c55b1f3810590acdaa597 100644 (file)
@@ -114,6 +114,9 @@ class Log : public AllStatic {
     return !is_stopped_ && (output_handle_ != NULL || output_buffer_ != NULL);
   }
 
+  // Size of buffer used for formatting log messages.
+  static const int kMessageBufferSize = 2048;
+
  private:
   typedef int (*WritePtr)(const char* msg, int length);
 
@@ -162,9 +165,6 @@ class Log : public AllStatic {
   // access to the formatting buffer and the log file or log memory buffer.
   static Mutex* mutex_;
 
-  // Size of buffer used for formatting log messages.
-  static const int kMessageBufferSize = 2048;
-
   // Buffer used for formatting log messages. This is a singleton buffer and
   // mutex_ should be acquired before using it.
   static char* message_buffer_;
@@ -247,6 +247,9 @@ class LogMessageBuilder BASE_EMBEDDED {
 
   void AppendDetailed(String* str, bool show_impl_info);
 
+  // Append a portion of a string.
+  void AppendStringPart(const char* str, int len);
+
   // Stores log message into compressor, returns true if the message
   // was stored (i.e. doesn't repeat the previous one).
   bool StoreInCompressor(LogRecordCompressor* compressor);
index 2b5ecb447b60d82694a7f6dadb92b53e10fe70ab..d225c3b49515a4df9d3574fe6246d92bcc95d62d 100644 (file)
@@ -889,20 +889,47 @@ void Logger::HeapSampleJSConstructorEvent(const char* constructor,
 #ifdef ENABLE_LOGGING_AND_PROFILING
   if (!Log::IsEnabled() || !FLAG_log_gc) return;
   LogMessageBuilder msg;
-  msg.Append("heap-js-cons-item,%s,%d,%d\n",
-             constructor[0] != '\0' ? constructor : "(anonymous)",
-             number, bytes);
+  msg.Append("heap-js-cons-item,%s,%d,%d\n", constructor, number, bytes);
   msg.WriteToLogFile();
 #endif
 }
 
 
-void Logger::HeapSampleJSRetainersEvent(const char* event) {
+void Logger::HeapSampleJSRetainersEvent(
+    const char* constructor, const char* event) {
 #ifdef ENABLE_LOGGING_AND_PROFILING
   if (!Log::IsEnabled() || !FLAG_log_gc) return;
-  LogMessageBuilder msg;
-  msg.Append("heap-js-ret-item,%s\n", event);
-  msg.WriteToLogFile();
+  // Event starts with comma, so we don't have it in the format string.
+  static const char* event_text = "heap-js-ret-item,%s";
+  // We take placeholder strings into account, but it's OK to be conservative.
+  static const int event_text_len = strlen(event_text);
+  const int cons_len = strlen(constructor), event_len = strlen(event);
+  int pos = 0;
+  // Retainer lists can be long. We may need to split them into multiple events.
+  do {
+    LogMessageBuilder msg;
+    msg.Append(event_text, constructor);
+    int to_write = event_len - pos;
+    if (to_write > Log::kMessageBufferSize - (cons_len + event_text_len)) {
+      int cut_pos = pos + Log::kMessageBufferSize - (cons_len + event_text_len);
+      ASSERT(cut_pos < event_len);
+      while (cut_pos > pos && event[cut_pos] != ',') --cut_pos;
+      if (event[cut_pos] != ',') {
+        // Crash in debug mode, skip in release mode.
+        ASSERT(false);
+        return;
+      }
+      // Append a piece of event that fits, without trailing comma.
+      msg.AppendStringPart(event + pos, cut_pos - pos);
+      // Start next piece with comma.
+      pos = cut_pos;
+    } else {
+      msg.Append("%s", event + pos);
+      pos += event_len;
+    }
+    msg.Append('\n');
+    msg.WriteToLogFile();
+  } while (pos < event_len);
 #endif
 }
 
index 17f7e00651b05ae498a7020b6164a8bd4cb102c0..07a0429ace19895a167a9d54f86ab8332720e546 100644 (file)
--- a/src/log.h
+++ b/src/log.h
@@ -221,7 +221,8 @@ class Logger {
   static void HeapSampleItemEvent(const char* type, int number, int bytes);
   static void HeapSampleJSConstructorEvent(const char* constructor,
                                            int number, int bytes);
-  static void HeapSampleJSRetainersEvent(const char* event);
+  static void HeapSampleJSRetainersEvent(const char* constructor,
+                                         const char* event);
   static void HeapSampleStats(const char* space, const char* kind,
                               int capacity, int used);
 
index a34c6093afa1da74c383a44ba03158efdf5dee9a..f8e5a6b755ff423a9202e31b5673e8cba64db2dd 100644 (file)
@@ -12,6 +12,7 @@
 namespace i = v8::internal;
 using i::ClustersCoarser;
 using i::JSObjectsCluster;
+using i::JSObjectsRetainerTree;
 using i::JSObjectsClusterTree;
 using i::RetainerHeapProfile;
 
@@ -31,9 +32,9 @@ class ConstructorHeapProfileTestHelper : public i::ConstructorHeapProfile {
       f_count_(0) {
   }
 
-  void Call(i::String* name, const i::NumberAndSizeInfo& number_and_size) {
-    CHECK(name != NULL);
-    if (f_name_->Equals(name)) {
+  void Call(const JSObjectsCluster& cluster,
+            const i::NumberAndSizeInfo& number_and_size) {
+    if (f_name_->Equals(cluster.constructor())) {
       CHECK_EQ(f_count_, 0);
       f_count_ = number_and_size.number();
       CHECK_GT(f_count_, 0);
@@ -74,7 +75,7 @@ TEST(ConstructorProfile) {
 
 
 static JSObjectsCluster AddHeapObjectToTree(
-    JSObjectsClusterTree* tree,
+    JSObjectsRetainerTree* tree,
     i::String* constructor,
     int instance,
     JSObjectsCluster* ref1 = NULL,
@@ -82,10 +83,11 @@ static JSObjectsCluster AddHeapObjectToTree(
     JSObjectsCluster* ref3 = NULL) {
   JSObjectsCluster o(constructor, reinterpret_cast<i::Object*>(instance));
   JSObjectsClusterTree* o_tree = new JSObjectsClusterTree();
-  JSObjectsClusterTree::Locator loc;
-  if (ref1 != NULL) o_tree->Insert(*ref1, &loc);
-  if (ref2 != NULL) o_tree->Insert(*ref2, &loc);
-  if (ref3 != NULL) o_tree->Insert(*ref3, &loc);
+  JSObjectsClusterTree::Locator o_loc;
+  if (ref1 != NULL) o_tree->Insert(*ref1, &o_loc);
+  if (ref2 != NULL) o_tree->Insert(*ref2, &o_loc);
+  if (ref3 != NULL) o_tree->Insert(*ref3, &o_loc);
+  JSObjectsRetainerTree::Locator loc;
   tree->Insert(o, &loc);
   loc.set_value(o_tree);
   return o;
@@ -137,7 +139,7 @@ TEST(ClustersCoarserSimple) {
 
   i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
 
-  JSObjectsClusterTree tree;
+  JSObjectsRetainerTree tree;
   JSObjectsCluster function(i::Heap::function_class_symbol());
   JSObjectsCluster a(*i::Factory::NewStringFromAscii(i::CStrVector("A")));
   JSObjectsCluster b(*i::Factory::NewStringFromAscii(i::CStrVector("B")));
@@ -176,7 +178,7 @@ TEST(ClustersCoarserMultipleConstructors) {
 
   i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
 
-  JSObjectsClusterTree tree;
+  JSObjectsRetainerTree tree;
   JSObjectsCluster function(i::Heap::function_class_symbol());
 
   // o1 <- Function
@@ -207,7 +209,7 @@ TEST(ClustersCoarserPathsTraversal) {
 
   i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
 
-  JSObjectsClusterTree tree;
+  JSObjectsRetainerTree tree;
 
   // On the following graph:
   //
@@ -257,7 +259,9 @@ class RetainerProfilePrinter : public RetainerHeapProfile::Printer {
  public:
   RetainerProfilePrinter() : stream_(&allocator_), lines_(100) {}
 
-  void PrintRetainers(const i::StringStream& retainers) {
+  void PrintRetainers(const JSObjectsCluster& cluster,
+                      const i::StringStream& retainers) {
+    cluster.Print(&stream_);
     stream_.Add("%s", *(retainers.ToCString()));
     stream_.Put('\0');
   }
@@ -304,8 +308,10 @@ TEST(RetainerProfile) {
   CompileAndRunScript(
       "function A() {}\n"
       "function B(x) { this.x = x; }\n"
+      "function C(x) { this.x1 = x; this.x2 = x; }\n"
       "var a = new A();\n"
-      "var b = new B(a);\n");
+      "var b1 = new B(a), b2 = new B(a);\n"
+      "var c = new C(a);");
 
   RetainerHeapProfile ret_profile;
   i::AssertNoAllocation no_alloc;
@@ -316,8 +322,9 @@ TEST(RetainerProfile) {
   }
   RetainerProfilePrinter printer;
   ret_profile.DebugPrintStats(&printer);
-  CHECK_EQ("(global property),B", printer.GetRetainers("A"));
-  CHECK_EQ("(global property)", printer.GetRetainers("B"));
+  CHECK_EQ("(global property);1,B;2,C;2", printer.GetRetainers("A"));
+  CHECK_EQ("(global property);2", printer.GetRetainers("B"));
+  CHECK_EQ("(global property);1", printer.GetRetainers("C"));
 }
 
 #endif  // ENABLE_LOGGING_AND_PROFILING