| Index: src/heap-profiler.cc | 
| diff --git a/src/heap-profiler.cc b/src/heap-profiler.cc | 
| index 46006fe6265bb31f3d6c3dabc88f80f97aff6339..5e945b49a0ca126c72eaba27ccaa746d00039d8b 100644 | 
| --- a/src/heap-profiler.cc | 
| +++ b/src/heap-profiler.cc | 
| @@ -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); | 
| } | 
| } | 
|  | 
|  |