From 9b4c950963b74c7ea799dd06ab7705529e25efff Mon Sep 17 00:00:00 2001 From: "mikhail.naganov@gmail.com" Date: Fri, 18 Sep 2009 12:05:18 +0000 Subject: [PATCH] Heap profiler: count the number of back references for objects. 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 | 318 ++++++++++++++++-------------- src/heap-profiler.h | 113 ++++++----- src/log-utils.cc | 12 ++ src/log-utils.h | 9 +- src/log.cc | 41 +++- src/log.h | 3 +- test/cctest/test-heap-profiler.cc | 37 ++-- 7 files changed, 309 insertions(+), 224 deletions(-) diff --git a/src/heap-profiler.cc b/src/heap-profiler.cc index 46006fe626..5e945b49a0 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 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); } } diff --git a/src/heap-profiler.h b/src/heap-profiler.h index 23d9502c13..adc3da2b86 100644 --- a/src/heap-profiler.h +++ b/src/heap-profiler.h @@ -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 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 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 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 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); }; diff --git a/src/log-utils.cc b/src/log-utils.cc index b31864be46..f280bfbe28 100644 --- a/src/log-utils.cc +++ b/src/log-utils.cc @@ -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(Log::message_buffer_, pos_)); } diff --git a/src/log-utils.h b/src/log-utils.h index ad669d53d7..117f098cc8 100644 --- a/src/log-utils.h +++ b/src/log-utils.h @@ -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); diff --git a/src/log.cc b/src/log.cc index 2b5ecb447b..d225c3b495 100644 --- a/src/log.cc +++ b/src/log.cc @@ -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 } diff --git a/src/log.h b/src/log.h index 17f7e00651..07a0429ace 100644 --- 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); diff --git a/test/cctest/test-heap-profiler.cc b/test/cctest/test-heap-profiler.cc index a34c6093af..f8e5a6b755 100644 --- a/test/cctest/test-heap-profiler.cc +++ b/test/cctest/test-heap-profiler.cc @@ -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(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