From e839a1ca58d7b44a89c3893eaa140b5331b2fc00 Mon Sep 17 00:00:00 2001 From: "mikhail.naganov@gmail.com" Date: Wed, 18 Aug 2010 08:19:29 +0000 Subject: [PATCH] Heap profiler: allow returning aggregated snapshots via the new API. This is intended for smoother migration to the new API in Chromium. Also, aggregated heap snapshots can be used for cheaply obtaining heap statistics, e.g. in tests. Review URL: http://codereview.chromium.org/3124024 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@5297 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- include/v8-profiler.h | 32 ++- src/api.cc | 30 ++- src/heap-profiler.cc | 444 ++++++++++++++++++++++++++++++++------ src/heap-profiler.h | 64 +++++- src/profile-generator-inl.h | 32 ++- src/profile-generator.cc | 91 ++++---- src/profile-generator.h | 38 +++- test/cctest/test-heap-profiler.cc | 125 +++++++++++ 8 files changed, 717 insertions(+), 139 deletions(-) diff --git a/include/v8-profiler.h b/include/v8-profiler.h index 9e3cb87..dd1b8ca 100644 --- a/include/v8-profiler.h +++ b/include/v8-profiler.h @@ -260,10 +260,17 @@ class V8EXPORT HeapGraphNode { /** * Returns node id. For the same heap object, the id remains the same - * across all snapshots. + * across all snapshots. Not applicable to aggregated heap snapshots + * as they only contain aggregated instances. */ uint64_t GetId() const; + /** + * Returns the number of instances. Only applicable to aggregated + * heap snapshots. + */ + int GetInstancesCount() const; + /** Returns node's own size, in bytes. */ int GetSelfSize() const; @@ -313,6 +320,15 @@ class V8EXPORT HeapSnapshotsDiff { */ class V8EXPORT HeapSnapshot { public: + enum Type { + kFull = 0, // Heap snapshot with all instances and references. + kAggregated = 1 // Snapshot doesn't contain individual heap entries, + //instead they are grouped by constructor name. + }; + + /** Returns heap snapshot type. */ + Type GetType() const; + /** Returns heap snapshot UID (assigned by the profiler.) */ unsigned GetUid() const; @@ -322,7 +338,10 @@ class V8EXPORT HeapSnapshot { /** Returns the root node of the heap graph. */ const HeapGraphNode* GetRoot() const; - /** Returns a diff between this snapshot and another one. */ + /** + * Returns a diff between this snapshot and another one. Only snapshots + * of the same type can be compared. + */ const HeapSnapshotsDiff* CompareWith(const HeapSnapshot* snapshot) const; }; @@ -341,8 +360,13 @@ class V8EXPORT HeapProfiler { /** Returns a profile by uid. */ static const HeapSnapshot* FindSnapshot(unsigned uid); - /** Takes a heap snapshot and returns it. Title may be an empty string. */ - static const HeapSnapshot* TakeSnapshot(Handle title); + /** + * Takes a heap snapshot and returns it. Title may be an empty string. + * See HeapSnapshot::Type for types description. + */ + static const HeapSnapshot* TakeSnapshot( + Handle title, + HeapSnapshot::Type type = HeapSnapshot::kFull); }; diff --git a/src/api.cc b/src/api.cc index 7a967db..e7a9e5c 100644 --- a/src/api.cc +++ b/src/api.cc @@ -4592,10 +4592,18 @@ Handle HeapGraphNode::GetName() const { uint64_t HeapGraphNode::GetId() const { IsDeadCheck("v8::HeapGraphNode::GetId"); + ASSERT(ToInternal(this)->snapshot()->type() != i::HeapSnapshot::kAggregated); return ToInternal(this)->id(); } +int HeapGraphNode::GetInstancesCount() const { + IsDeadCheck("v8::HeapGraphNode::GetInstancesCount"); + ASSERT(ToInternal(this)->snapshot()->type() == i::HeapSnapshot::kAggregated); + return static_cast(ToInternal(this)->id()); +} + + int HeapGraphNode::GetSelfSize() const { IsDeadCheck("v8::HeapGraphNode::GetSelfSize"); return ToInternal(this)->self_size(); @@ -4677,6 +4685,12 @@ static i::HeapSnapshot* ToInternal(const HeapSnapshot* snapshot) { } +HeapSnapshot::Type HeapSnapshot::GetType() const { + IsDeadCheck("v8::HeapSnapshot::GetType"); + return static_cast(ToInternal(this)->type()); +} + + unsigned HeapSnapshot::GetUid() const { IsDeadCheck("v8::HeapSnapshot::GetUid"); return ToInternal(this)->uid(); @@ -4724,10 +4738,22 @@ const HeapSnapshot* HeapProfiler::FindSnapshot(unsigned uid) { } -const HeapSnapshot* HeapProfiler::TakeSnapshot(Handle title) { +const HeapSnapshot* HeapProfiler::TakeSnapshot(Handle title, + HeapSnapshot::Type type) { IsDeadCheck("v8::HeapProfiler::TakeSnapshot"); + i::HeapSnapshot::Type internal_type = i::HeapSnapshot::kFull; + switch (type) { + case HeapSnapshot::kFull: + internal_type = i::HeapSnapshot::kFull; + break; + case HeapSnapshot::kAggregated: + internal_type = i::HeapSnapshot::kAggregated; + break; + default: + UNREACHABLE(); + } return reinterpret_cast( - i::HeapProfiler::TakeSnapshot(*Utils::OpenHandle(*title))); + i::HeapProfiler::TakeSnapshot(*Utils::OpenHandle(*title), internal_type)); } #endif // ENABLE_LOGGING_AND_PROFILING diff --git a/src/heap-profiler.cc b/src/heap-profiler.cc index 7668bbc..e47d66f 100644 --- a/src/heap-profiler.cc +++ b/src/heap-profiler.cc @@ -280,10 +280,12 @@ void AggregatingRetainerTreePrinter::Call(const JSObjectsCluster& cluster, printer_->PrintRetainers(cluster, stream); } +} // namespace + // A helper class for building a retainers tree, that aggregates // all equivalent clusters. -class RetainerTreeAggregator BASE_EMBEDDED { +class RetainerTreeAggregator { public: explicit RetainerTreeAggregator(ClustersCoarser* coarser) : coarser_(coarser) {} @@ -311,8 +313,6 @@ void RetainerTreeAggregator::Call(const JSObjectsCluster& cluster, tree->ForEach(&retainers_aggregator); } -} // namespace - HeapProfiler* HeapProfiler::singleton_ = NULL; @@ -347,30 +347,46 @@ void HeapProfiler::TearDown() { #ifdef ENABLE_LOGGING_AND_PROFILING -HeapSnapshot* HeapProfiler::TakeSnapshot(const char* name) { +HeapSnapshot* HeapProfiler::TakeSnapshot(const char* name, int type) { ASSERT(singleton_ != NULL); - return singleton_->TakeSnapshotImpl(name); + return singleton_->TakeSnapshotImpl(name, type); } -HeapSnapshot* HeapProfiler::TakeSnapshot(String* name) { +HeapSnapshot* HeapProfiler::TakeSnapshot(String* name, int type) { ASSERT(singleton_ != NULL); - return singleton_->TakeSnapshotImpl(name); + return singleton_->TakeSnapshotImpl(name, type); } -HeapSnapshot* HeapProfiler::TakeSnapshotImpl(const char* name) { +HeapSnapshot* HeapProfiler::TakeSnapshotImpl(const char* name, int type) { Heap::CollectAllGarbage(true); - HeapSnapshot* result = snapshots_->NewSnapshot(name, next_snapshot_uid_++); - HeapSnapshotGenerator generator(result); - generator.GenerateSnapshot(); + HeapSnapshot::Type s_type = static_cast(type); + HeapSnapshot* result = + snapshots_->NewSnapshot(s_type, name, next_snapshot_uid_++); + switch (s_type) { + case HeapSnapshot::kFull: { + HeapSnapshotGenerator generator(result); + generator.GenerateSnapshot(); + break; + } + case HeapSnapshot::kAggregated: { + AggregatedHeapSnapshot agg_snapshot; + AggregatedHeapSnapshotGenerator generator(&agg_snapshot); + generator.GenerateSnapshot(); + generator.FillHeapSnapshot(result); + break; + } + default: + UNREACHABLE(); + } snapshots_->SnapshotGenerationFinished(); return result; } -HeapSnapshot* HeapProfiler::TakeSnapshotImpl(String* name) { - return TakeSnapshotImpl(snapshots_->GetName(name)); +HeapSnapshot* HeapProfiler::TakeSnapshotImpl(String* name, int type) { + return TakeSnapshotImpl(snapshots_->GetName(name), type); } @@ -433,16 +449,25 @@ static const char* GetConstructorName(const char* name) { } -void JSObjectsCluster::Print(StringStream* accumulator) const { - ASSERT(!is_null()); +const char* JSObjectsCluster::GetSpecialCaseName() const { if (constructor_ == FromSpecialCase(ROOTS)) { - accumulator->Add("(roots)"); + return "(roots)"; } else if (constructor_ == FromSpecialCase(GLOBAL_PROPERTY)) { - accumulator->Add("(global property)"); + return "(global property)"; } else if (constructor_ == FromSpecialCase(CODE)) { - accumulator->Add("(code)"); + return "(code)"; } else if (constructor_ == FromSpecialCase(SELF)) { - accumulator->Add("(self)"); + return "(self)"; + } + return NULL; +} + + +void JSObjectsCluster::Print(StringStream* accumulator) const { + ASSERT(!is_null()); + const char* special_case_name = GetSpecialCaseName(); + if (special_case_name != NULL) { + accumulator->Add(special_case_name); } else { SmartPointer s_name( constructor_->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL)); @@ -618,13 +643,19 @@ const JSObjectsRetainerTreeConfig::Value JSObjectsRetainerTreeConfig::kNoValue = RetainerHeapProfile::RetainerHeapProfile() - : zscope_(DELETE_ON_EXIT) { + : zscope_(DELETE_ON_EXIT), + aggregator_(NULL) { JSObjectsCluster roots(JSObjectsCluster::ROOTS); ReferencesExtractor extractor(roots, this); Heap::IterateRoots(&extractor, VISIT_ONLY_STRONG); } +RetainerHeapProfile::~RetainerHeapProfile() { + delete aggregator_; +} + + void RetainerHeapProfile::StoreReference(const JSObjectsCluster& cluster, HeapObject* ref) { JSObjectsCluster ref_cluster = Clusterizer::Clusterize(ref); @@ -646,18 +677,22 @@ void RetainerHeapProfile::CollectStats(HeapObject* obj) { } +void RetainerHeapProfile::CoarseAndAggregate() { + coarser_.Process(&retainers_tree_); + ASSERT(aggregator_ == NULL); + aggregator_ = new RetainerTreeAggregator(&coarser_); + aggregator_->Process(&retainers_tree_); +} + + void RetainerHeapProfile::DebugPrintStats( RetainerHeapProfile::Printer* printer) { - coarser_.Process(&retainers_tree_); // Print clusters that have no equivalents, aggregating their retainers. AggregatingRetainerTreePrinter agg_printer(&coarser_, printer); retainers_tree_.ForEach(&agg_printer); - // Now aggregate clusters that have equivalents... - RetainerTreeAggregator aggregator(&coarser_); - aggregator.Process(&retainers_tree_); - // ...and print them. + // Print clusters that have equivalents. SimpleRetainerTreePrinter s_printer(printer); - aggregator.output_tree().ForEach(&s_printer); + aggregator_->output_tree().ForEach(&s_printer); } @@ -670,16 +705,6 @@ void RetainerHeapProfile::PrintStats() { // // HeapProfiler class implementation. // -void HeapProfiler::CollectStats(HeapObject* obj, HistogramInfo* info) { - InstanceType type = obj->map()->instance_type(); - ASSERT(0 <= type && type <= LAST_TYPE); - if (!FreeListNode::IsFreeListNode(obj)) { - info[type].increment_number(1); - info[type].increment_bytes(obj->Size()); - } -} - - static void StackWeakReferenceCallback(Persistent object, void* trace) { DeleteArray(static_cast(trace)); @@ -702,46 +727,339 @@ void HeapProfiler::WriteSample() { LOG(HeapSampleStats( "Heap", "allocated", Heap::CommittedMemory(), Heap::SizeOfObjects())); - HistogramInfo info[LAST_TYPE+1]; -#define DEF_TYPE_NAME(name) info[name].set_name(#name); - INSTANCE_TYPE_LIST(DEF_TYPE_NAME) -#undef DEF_TYPE_NAME + AggregatedHeapSnapshot snapshot; + AggregatedHeapSnapshotGenerator generator(&snapshot); + generator.GenerateSnapshot(); - ConstructorHeapProfile js_cons_profile; - RetainerHeapProfile js_retainer_profile; - HeapIterator iterator; - for (HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next()) { - CollectStats(obj, info); - js_cons_profile.CollectStats(obj); - js_retainer_profile.CollectStats(obj); + HistogramInfo* info = snapshot.info(); + for (int i = FIRST_NONSTRING_TYPE; + i <= AggregatedHeapSnapshotGenerator::kAllStringsType; + ++i) { + if (info[i].bytes() > 0) { + LOG(HeapSampleItemEvent(info[i].name(), info[i].number(), + info[i].bytes())); + } } + snapshot.js_cons_profile()->PrintStats(); + snapshot.js_retainer_profile()->PrintStats(); + + GlobalHandles::IterateWeakRoots(PrintProducerStackTrace, + StackWeakReferenceCallback); + + LOG(HeapSampleEndEvent("Heap", "allocated")); +} + + +AggregatedHeapSnapshot::AggregatedHeapSnapshot() + : info_(NewArray( + AggregatedHeapSnapshotGenerator::kAllStringsType + 1)) { +#define DEF_TYPE_NAME(name) info_[name].set_name(#name); + INSTANCE_TYPE_LIST(DEF_TYPE_NAME); +#undef DEF_TYPE_NAME + info_[AggregatedHeapSnapshotGenerator::kAllStringsType].set_name( + "STRING_TYPE"); +} + + +AggregatedHeapSnapshot::~AggregatedHeapSnapshot() { + DeleteArray(info_); +} + + +AggregatedHeapSnapshotGenerator::AggregatedHeapSnapshotGenerator( + AggregatedHeapSnapshot* agg_snapshot) + : agg_snapshot_(agg_snapshot) { +} + + +void AggregatedHeapSnapshotGenerator::CalculateStringsStats() { + HistogramInfo* info = agg_snapshot_->info(); + HistogramInfo& strings = info[kAllStringsType]; // Lump all the string types together. - int string_number = 0; - int string_bytes = 0; #define INCREMENT_SIZE(type, size, name, camel_name) \ - string_number += info[type].number(); \ - string_bytes += info[type].bytes(); - STRING_TYPE_LIST(INCREMENT_SIZE) + strings.increment_number(info[type].number()); \ + strings.increment_bytes(info[type].bytes()); + STRING_TYPE_LIST(INCREMENT_SIZE); #undef INCREMENT_SIZE - if (string_bytes > 0) { - LOG(HeapSampleItemEvent("STRING_TYPE", string_number, string_bytes)); +} + + +void AggregatedHeapSnapshotGenerator::CollectStats(HeapObject* obj) { + InstanceType type = obj->map()->instance_type(); + ASSERT(0 <= type && type <= LAST_TYPE); + if (!FreeListNode::IsFreeListNode(obj)) { + agg_snapshot_->info()[type].increment_number(1); + agg_snapshot_->info()[type].increment_bytes(obj->Size()); } +} - for (int i = FIRST_NONSTRING_TYPE; i <= LAST_TYPE; ++i) { - if (info[i].bytes() > 0) { - LOG(HeapSampleItemEvent(info[i].name(), info[i].number(), - info[i].bytes())); + +void AggregatedHeapSnapshotGenerator::GenerateSnapshot() { + HeapIterator iterator; + for (HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next()) { + CollectStats(obj); + agg_snapshot_->js_cons_profile()->CollectStats(obj); + agg_snapshot_->js_retainer_profile()->CollectStats(obj); + } + CalculateStringsStats(); + agg_snapshot_->js_retainer_profile()->CoarseAndAggregate(); +} + + +class CountingConstructorHeapProfileIterator { + public: + CountingConstructorHeapProfileIterator() + : entities_count_(0), children_count_(0) { + } + + void Call(const JSObjectsCluster& cluster, + const NumberAndSizeInfo& number_and_size) { + ++entities_count_; + children_count_ += number_and_size.number(); + } + + int entities_count() { return entities_count_; } + int children_count() { return children_count_; } + + private: + int entities_count_; + int children_count_; +}; + + +static HeapEntry* AddEntryFromAggregatedSnapshot(HeapSnapshot* snapshot, + int* root_child_index, + HeapEntry::Type type, + const char* name, + int count, + int size, + int children_count, + int retainers_count) { + HeapEntry* entry = snapshot->AddEntry( + type, name, count, size, children_count, retainers_count); + ASSERT(entry != NULL); + snapshot->root()->SetUnidirElementReference(*root_child_index, + *root_child_index + 1, + entry); + *root_child_index = *root_child_index + 1; + return entry; +} + + +class AllocatingConstructorHeapProfileIterator { + public: + AllocatingConstructorHeapProfileIterator(HeapSnapshot* snapshot, + int* root_child_index) + : snapshot_(snapshot), + root_child_index_(root_child_index) { + } + + void Call(const JSObjectsCluster& cluster, + const NumberAndSizeInfo& number_and_size) { + const char* name = cluster.GetSpecialCaseName(); + if (name == NULL) { + name = snapshot_->collection()->GetFunctionName(cluster.constructor()); } + AddEntryFromAggregatedSnapshot(snapshot_, + root_child_index_, + HeapEntry::kObject, + name, + number_and_size.number(), + number_and_size.bytes(), + 0, + 0); } - js_cons_profile.PrintStats(); - js_retainer_profile.PrintStats(); + private: + HeapSnapshot* snapshot_; + int* root_child_index_; +}; - GlobalHandles::IterateWeakRoots(PrintProducerStackTrace, - StackWeakReferenceCallback); - LOG(HeapSampleEndEvent("Heap", "allocated")); +static HeapObject* ClusterAsHeapObject(const JSObjectsCluster& cluster) { + return cluster.can_be_coarsed() ? + reinterpret_cast(cluster.instance()) : cluster.constructor(); +} + + +static JSObjectsCluster HeapObjectAsCluster(HeapObject* object) { + if (object->IsString()) { + return JSObjectsCluster(String::cast(object)); + } else { + JSObject* js_obj = JSObject::cast(object); + String* constructor = JSObject::cast(js_obj)->constructor_name(); + return JSObjectsCluster(constructor, object); + } +} + + +class CountingRetainersIterator { + public: + CountingRetainersIterator(const JSObjectsCluster& child_cluster, + HeapEntriesMap* map) + : child_(ClusterAsHeapObject(child_cluster)), map_(map) { + if (map_->Map(child_) == NULL) + map_->Pair(child_, HeapEntriesMap::kHeapEntryPlaceholder); + } + + void Call(const JSObjectsCluster& cluster, + const NumberAndSizeInfo& number_and_size) { + if (map_->Map(ClusterAsHeapObject(cluster)) == NULL) + map_->Pair(ClusterAsHeapObject(cluster), + HeapEntriesMap::kHeapEntryPlaceholder); + map_->CountReference(ClusterAsHeapObject(cluster), child_); + } + + private: + HeapObject* child_; + HeapEntriesMap* map_; +}; + + +class AllocatingRetainersIterator { + public: + AllocatingRetainersIterator(const JSObjectsCluster& child_cluster, + HeapEntriesMap* map) + : child_(ClusterAsHeapObject(child_cluster)), map_(map) { + child_entry_ = map_->Map(child_); + ASSERT(child_entry_ != NULL); + } + + void Call(const JSObjectsCluster& cluster, + const NumberAndSizeInfo& number_and_size) { + int child_index, retainer_index; + map_->CountReference(ClusterAsHeapObject(cluster), child_, + &child_index, &retainer_index); + map_->Map(ClusterAsHeapObject(cluster))->SetElementReference( + child_index, number_and_size.number(), child_entry_, retainer_index); + } + + private: + HeapObject* child_; + HeapEntriesMap* map_; + HeapEntry* child_entry_; +}; + + +template +class AggregatingRetainerTreeIterator { + public: + explicit AggregatingRetainerTreeIterator(ClustersCoarser* coarser, + HeapEntriesMap* map) + : coarser_(coarser), map_(map) { + } + + void Call(const JSObjectsCluster& cluster, JSObjectsClusterTree* tree) { + if (coarser_ != NULL && + !coarser_->GetCoarseEquivalent(cluster).is_null()) return; + JSObjectsClusterTree* tree_to_iterate = tree; + ZoneScope zs(DELETE_ON_EXIT); + JSObjectsClusterTree dest_tree_; + if (coarser_ != NULL) { + RetainersAggregator retainers_aggregator(coarser_, &dest_tree_); + tree->ForEach(&retainers_aggregator); + tree_to_iterate = &dest_tree_; + } + RetainersIterator iterator(cluster, map_); + tree_to_iterate->ForEach(&iterator); + } + + private: + ClustersCoarser* coarser_; + HeapEntriesMap* map_; +}; + + +class AggregatedRetainerTreeAllocator { + public: + AggregatedRetainerTreeAllocator(HeapSnapshot* snapshot, + int* root_child_index) + : snapshot_(snapshot), root_child_index_(root_child_index) { + } + + HeapEntry* GetEntry( + HeapObject* obj, int children_count, int retainers_count) { + JSObjectsCluster cluster = HeapObjectAsCluster(obj); + const char* name = cluster.GetSpecialCaseName(); + if (name == NULL) { + name = snapshot_->collection()->GetFunctionName(cluster.constructor()); + } + return AddEntryFromAggregatedSnapshot( + snapshot_, root_child_index_, HeapEntry::kObject, name, + 0, 0, children_count, retainers_count); + } + + private: + HeapSnapshot* snapshot_; + int* root_child_index_; +}; + + +template +void AggregatedHeapSnapshotGenerator::IterateRetainers( + HeapEntriesMap* entries_map) { + RetainerHeapProfile* p = agg_snapshot_->js_retainer_profile(); + AggregatingRetainerTreeIterator agg_ret_iter_1( + p->coarser(), entries_map); + p->retainers_tree()->ForEach(&agg_ret_iter_1); + AggregatingRetainerTreeIterator agg_ret_iter_2(NULL, entries_map); + p->aggregator()->output_tree().ForEach(&agg_ret_iter_2); +} + + +void AggregatedHeapSnapshotGenerator::FillHeapSnapshot(HeapSnapshot* snapshot) { + // Count the number of entities. + int histogram_entities_count = 0; + int histogram_children_count = 0; + int histogram_retainers_count = 0; + for (int i = FIRST_NONSTRING_TYPE; i <= kAllStringsType; ++i) { + if (agg_snapshot_->info()[i].bytes() > 0) { + ++histogram_entities_count; + } + } + CountingConstructorHeapProfileIterator counting_cons_iter; + agg_snapshot_->js_cons_profile()->ForEach(&counting_cons_iter); + histogram_entities_count += counting_cons_iter.entities_count(); + HeapEntriesMap entries_map; + IterateRetainers(&entries_map); + histogram_entities_count += entries_map.entries_count(); + histogram_children_count += entries_map.total_children_count(); + histogram_retainers_count += entries_map.total_retainers_count(); + + // Root entry references all other entries. + histogram_children_count += histogram_entities_count; + int root_children_count = histogram_entities_count; + ++histogram_entities_count; + + // Allocate and fill entries in the snapshot, allocate references. + snapshot->AllocateEntries(histogram_entities_count, + histogram_children_count, + histogram_retainers_count); + snapshot->AddEntry(HeapSnapshot::kInternalRootObject, + root_children_count, + 0); + int root_child_index = 0; + for (int i = FIRST_NONSTRING_TYPE; i <= kAllStringsType; ++i) { + if (agg_snapshot_->info()[i].bytes() > 0) { + AddEntryFromAggregatedSnapshot(snapshot, + &root_child_index, + HeapEntry::kInternal, + agg_snapshot_->info()[i].name(), + agg_snapshot_->info()[i].number(), + agg_snapshot_->info()[i].bytes(), + 0, + 0); + } + } + AllocatingConstructorHeapProfileIterator alloc_cons_iter( + snapshot, &root_child_index); + agg_snapshot_->js_cons_profile()->ForEach(&alloc_cons_iter); + AggregatedRetainerTreeAllocator allocator(snapshot, &root_child_index); + entries_map.UpdateEntries(&allocator); + + // Fill up references. + IterateRetainers(&entries_map); } diff --git a/src/heap-profiler.h b/src/heap-profiler.h index dac488e..2ef081e 100644 --- a/src/heap-profiler.h +++ b/src/heap-profiler.h @@ -56,8 +56,8 @@ class HeapProfiler { static void TearDown(); #ifdef ENABLE_LOGGING_AND_PROFILING - static HeapSnapshot* TakeSnapshot(const char* name); - static HeapSnapshot* TakeSnapshot(String* name); + static HeapSnapshot* TakeSnapshot(const char* name, int type); + static HeapSnapshot* TakeSnapshot(String* name, int type); static int GetSnapshotsCount(); static HeapSnapshot* GetSnapshot(int index); static HeapSnapshot* FindSnapshot(unsigned uid); @@ -75,12 +75,8 @@ class HeapProfiler { private: HeapProfiler(); ~HeapProfiler(); - HeapSnapshot* TakeSnapshotImpl(const char* name); - HeapSnapshot* TakeSnapshotImpl(String* name); - - // Obsolete interface. - // Update the array info with stats from obj. - static void CollectStats(HeapObject* obj, HistogramInfo* info); + HeapSnapshot* TakeSnapshotImpl(const char* name, int type); + HeapSnapshot* TakeSnapshotImpl(String* name, int type); HeapSnapshotsCollection* snapshots_; unsigned next_snapshot_uid_; @@ -132,7 +128,9 @@ class JSObjectsCluster BASE_EMBEDDED { bool is_null() const { return constructor_ == NULL; } bool can_be_coarsed() const { return instance_ != NULL; } String* constructor() const { return constructor_; } + Object* instance() const { return instance_; } + const char* GetSpecialCaseName() const; void Print(StringStream* accumulator) const; // Allows null clusters to be printed. void DebugPrint(StringStream* accumulator) const; @@ -179,6 +177,9 @@ class ConstructorHeapProfile BASE_EMBEDDED { virtual ~ConstructorHeapProfile() {} void CollectStats(HeapObject* obj); void PrintStats(); + + template + void ForEach(Callback* callback) { js_objects_info_tree_.ForEach(callback); } // Used by ZoneSplayTree::ForEach. Made virtual to allow overriding in tests. virtual void Call(const JSObjectsCluster& cluster, const NumberAndSizeInfo& number_and_size); @@ -282,6 +283,8 @@ class ClustersCoarser BASE_EMBEDDED { // "retainer profile" of JS objects allocated on heap. // It is run during garbage collection cycle, thus it doesn't need // to use handles. +class RetainerTreeAggregator; + class RetainerHeapProfile BASE_EMBEDDED { public: class Printer { @@ -292,7 +295,14 @@ class RetainerHeapProfile BASE_EMBEDDED { }; RetainerHeapProfile(); + ~RetainerHeapProfile(); + + RetainerTreeAggregator* aggregator() { return aggregator_; } + ClustersCoarser* coarser() { return &coarser_; } + JSObjectsRetainerTree* retainers_tree() { return &retainers_tree_; } + void CollectStats(HeapObject* obj); + void CoarseAndAggregate(); void PrintStats(); void DebugPrintStats(Printer* printer); void StoreReference(const JSObjectsCluster& cluster, HeapObject* ref); @@ -301,6 +311,44 @@ class RetainerHeapProfile BASE_EMBEDDED { ZoneScope zscope_; JSObjectsRetainerTree retainers_tree_; ClustersCoarser coarser_; + RetainerTreeAggregator* aggregator_; +}; + + +class AggregatedHeapSnapshot { + public: + AggregatedHeapSnapshot(); + ~AggregatedHeapSnapshot(); + + HistogramInfo* info() { return info_; } + ConstructorHeapProfile* js_cons_profile() { return &js_cons_profile_; } + RetainerHeapProfile* js_retainer_profile() { return &js_retainer_profile_; } + + private: + HistogramInfo* info_; + ConstructorHeapProfile js_cons_profile_; + RetainerHeapProfile js_retainer_profile_; +}; + + +class HeapEntriesMap; +class HeapSnapshot; + +class AggregatedHeapSnapshotGenerator { + public: + explicit AggregatedHeapSnapshotGenerator(AggregatedHeapSnapshot* snapshot); + void GenerateSnapshot(); + void FillHeapSnapshot(HeapSnapshot* snapshot); + + static const int kAllStringsType = LAST_TYPE + 1; + + private: + void CalculateStringsStats(); + void CollectStats(HeapObject* obj); + template + void IterateRetainers(HeapEntriesMap* entries_map); + + AggregatedHeapSnapshot* agg_snapshot_; }; diff --git a/src/profile-generator-inl.h b/src/profile-generator-inl.h index 0c50581..cef825d 100644 --- a/src/profile-generator-inl.h +++ b/src/profile-generator-inl.h @@ -35,6 +35,16 @@ namespace v8 { namespace internal { +const char* StringsStorage::GetFunctionName(String* name) { + return GetFunctionName(GetName(name)); +} + + +const char* StringsStorage::GetFunctionName(const char* name) { + return strlen(name) > 0 ? name : ProfileGenerator::kAnonymousFunctionName; +} + + CodeEntry::CodeEntry(int security_token_id) : call_uid_(0), tag_(Logger::FUNCTION_TAG), @@ -97,13 +107,21 @@ void CodeMap::DeleteCode(Address addr) { } -const char* CpuProfilesCollection::GetFunctionName(String* name) { - return GetFunctionName(GetName(name)); -} - - -const char* CpuProfilesCollection::GetFunctionName(const char* name) { - return strlen(name) > 0 ? name : ProfileGenerator::kAnonymousFunctionName; +template +void HeapEntriesMap::UpdateEntries(Visitor* visitor) { + for (HashMap::Entry* p = entries_.Start(); + p != NULL; + p = entries_.Next(p)) { + if (!IsAlias(p->value)) { + EntryInfo* entry_info = reinterpret_cast(p->value); + entry_info->entry = visitor->GetEntry( + reinterpret_cast(p->key), + entry_info->children_count, + entry_info->retainers_count); + entry_info->children_count = 0; + entry_info->retainers_count = 0; + } + } } diff --git a/src/profile-generator.cc b/src/profile-generator.cc index cd46bad..1c6c902 100644 --- a/src/profile-generator.cc +++ b/src/profile-generator.cc @@ -824,13 +824,6 @@ HeapEntry* HeapGraphEdge::From() { void HeapEntry::Init(HeapSnapshot* snapshot, - int children_count, - int retainers_count) { - Init(snapshot, kInternal, "", 0, 0, children_count, retainers_count); -} - - -void HeapEntry::Init(HeapSnapshot* snapshot, Type type, const char* name, uint64_t id, @@ -1210,9 +1203,11 @@ template <> struct SnapshotSizeConstants<8> { } // namespace HeapSnapshot::HeapSnapshot(HeapSnapshotsCollection* collection, + HeapSnapshot::Type type, const char* title, unsigned uid) : collection_(collection), + type_(type), title_(title), uid_(uid), root_entry_index_(-1), @@ -1243,6 +1238,10 @@ void HeapSnapshot::AllocateEntries(int entries_count, ASSERT(raw_entries_ == NULL); raw_entries_ = NewArray( HeapEntry::EntriesSize(entries_count, children_count, retainers_count)); +#ifdef DEBUG + raw_entries_size_ = + HeapEntry::EntriesSize(entries_count, children_count, retainers_count); +#endif } @@ -1252,9 +1251,9 @@ HeapEntry* HeapSnapshot::AddEntry(HeapObject* object, if (object == kInternalRootObject) { ASSERT(root_entry_index_ == -1); root_entry_index_ = entries_.length(); - HeapEntry* entry = GetNextEntryToInit(); - entry->Init(this, children_count, retainers_count); - return entry; + ASSERT(retainers_count == 0); + return AddEntry( + HeapEntry::kInternal, "", 0, 0, children_count, retainers_count); } else if (object->IsJSFunction()) { JSFunction* func = JSFunction::cast(object); SharedFunctionInfo* shared = func->shared(); @@ -1262,7 +1261,7 @@ HeapEntry* HeapSnapshot::AddEntry(HeapObject* object, String::cast(shared->name()) : shared->inferred_name(); return AddEntry(object, HeapEntry::kClosure, - collection_->GetName(name), + collection_->GetFunctionName(name), children_count, retainers_count); } else if (object->IsJSObject()) { @@ -1290,7 +1289,7 @@ HeapEntry* HeapSnapshot::AddEntry(HeapObject* object, String::cast(shared->name()) : shared->inferred_name(); return AddEntry(object, HeapEntry::kCode, - collection_->GetName(name), + collection_->GetFunctionName(name), children_count, retainers_count); } else if (object->IsScript()) { @@ -1345,14 +1344,23 @@ HeapEntry* HeapSnapshot::AddEntry(HeapObject* object, const char* name, int children_count, int retainers_count) { + return AddEntry(type, + name, + collection_->GetObjectId(object->address()), + GetObjectSize(object), + children_count, + retainers_count); +} + + +HeapEntry* HeapSnapshot::AddEntry(HeapEntry::Type type, + const char* name, + uint64_t id, + int size, + int children_count, + int retainers_count) { HeapEntry* entry = GetNextEntryToInit(); - entry->Init(this, - type, - name, - collection_->GetObjectId(object->address()), - GetObjectSize(object), - children_count, - retainers_count); + entry->Init(this, type, name, id, size, children_count, retainers_count); return entry; } @@ -1365,6 +1373,8 @@ HeapEntry* HeapSnapshot::GetNextEntryToInit() { } else { entries_.Add(reinterpret_cast(raw_entries_)); } + ASSERT(reinterpret_cast(entries_.last()) < + (raw_entries_ + raw_entries_size_)); return entries_.last(); } @@ -1534,10 +1544,11 @@ HeapSnapshotsCollection::~HeapSnapshotsCollection() { } -HeapSnapshot* HeapSnapshotsCollection::NewSnapshot(const char* name, +HeapSnapshot* HeapSnapshotsCollection::NewSnapshot(HeapSnapshot::Type type, + const char* name, unsigned uid) { is_tracking_objects_ = true; // Start watching for heap objects moves. - HeapSnapshot* snapshot = new HeapSnapshot(this, name, uid); + HeapSnapshot* snapshot = new HeapSnapshot(this, type, name, uid); snapshots_.Add(snapshot); HashMap::Entry* entry = snapshots_uids_.Lookup(reinterpret_cast(snapshot->uid()), @@ -1564,6 +1575,9 @@ HeapSnapshotsDiff* HeapSnapshotsCollection::CompareSnapshots( } +HeapEntry *const HeapEntriesMap::kHeapEntryPlaceholder = + reinterpret_cast(1); + HeapEntriesMap::HeapEntriesMap() : entries_(HeapObjectsMatch), entries_count_(0), @@ -1612,7 +1626,7 @@ void HeapEntriesMap::Pair(HeapObject* object, HeapEntry* entry) { void HeapEntriesMap::CountReference(HeapObject* from, HeapObject* to, int* prev_children_count, int* prev_retainers_count) { - HashMap::Entry* from_cache_entry = entries_.Lookup(from, Hash(from), true); + HashMap::Entry* from_cache_entry = entries_.Lookup(from, Hash(from), false); HashMap::Entry* to_cache_entry = entries_.Lookup(to, Hash(to), false); ASSERT(from_cache_entry != NULL); ASSERT(to_cache_entry != NULL); @@ -1631,42 +1645,19 @@ void HeapEntriesMap::CountReference(HeapObject* from, HeapObject* to, } -template -void HeapEntriesMap::UpdateEntries(Visitor* visitor) { - for (HashMap::Entry* p = entries_.Start(); - p != NULL; - p = entries_.Next(p)) { - if (!IsAlias(p->value)) { - EntryInfo* entry_info = reinterpret_cast(p->value); - entry_info->entry = visitor->GetEntry( - reinterpret_cast(p->key), - entry_info->children_count, - entry_info->retainers_count); - entry_info->children_count = 0; - entry_info->retainers_count = 0; - } - } -} - - HeapSnapshotGenerator::HeapSnapshotGenerator(HeapSnapshot* snapshot) : snapshot_(snapshot), collection_(snapshot->collection()), filler_(NULL) { } - -HeapEntry *const -HeapSnapshotGenerator::SnapshotFillerInterface::kHeapEntryPlaceholder = - reinterpret_cast(1); - class SnapshotCounter : public HeapSnapshotGenerator::SnapshotFillerInterface { public: explicit SnapshotCounter(HeapEntriesMap* entries) : entries_(entries) { } HeapEntry* AddEntry(HeapObject* obj) { - entries_->Pair(obj, kHeapEntryPlaceholder); - return kHeapEntryPlaceholder; + entries_->Pair(obj, HeapEntriesMap::kHeapEntryPlaceholder); + return HeapEntriesMap::kHeapEntryPlaceholder; } void SetElementReference(HeapObject* parent_obj, HeapEntry*, @@ -2057,10 +2048,12 @@ void HeapSnapshotGenerator::SetRootReference(Object* child_obj) { void HeapSnapshotsDiff::CreateRoots(int additions_count, int deletions_count) { raw_additions_root_ = NewArray(HeapEntry::EntriesSize(1, additions_count, 0)); - additions_root()->Init(snapshot2_, additions_count, 0); + additions_root()->Init( + snapshot2_, HeapEntry::kInternal, "", 0, 0, additions_count, 0); raw_deletions_root_ = NewArray(HeapEntry::EntriesSize(1, deletions_count, 0)); - deletions_root()->Init(snapshot1_, deletions_count, 0); + deletions_root()->Init( + snapshot1_, HeapEntry::kInternal, "", 0, 0, deletions_count, 0); } diff --git a/src/profile-generator.h b/src/profile-generator.h index bebf40a..5611b6f 100644 --- a/src/profile-generator.h +++ b/src/profile-generator.h @@ -67,6 +67,8 @@ class StringsStorage { ~StringsStorage(); const char* GetName(String* name); + inline const char* GetFunctionName(String* name); + inline const char* GetFunctionName(const char* name); private: INLINE(static bool StringsMatch(void* key1, void* key2)) { @@ -298,9 +300,13 @@ class CpuProfilesCollection { void AddPathToCurrentProfiles(const Vector& path); private: - INLINE(const char* GetFunctionName(String* name)); - INLINE(const char* GetFunctionName(const char* name)); const char* GetName(int args_count); + const char* GetFunctionName(String* name) { + return function_and_resource_names_.GetFunctionName(name); + } + const char* GetFunctionName(const char* name) { + return function_and_resource_names_.GetFunctionName(name); + } List* GetProfilesList(int security_token_id); int TokenToIndex(int security_token_id); @@ -498,7 +504,6 @@ class HeapEntry BASE_EMBEDDED { }; HeapEntry() { } - void Init(HeapSnapshot* snapshot, int children_count, int retainers_count); void Init(HeapSnapshot* snapshot, Type type, const char* name, @@ -640,12 +645,19 @@ class HeapSnapshotsDiff; // HeapSnapshotGenerator fills in a HeapSnapshot. class HeapSnapshot { public: + enum Type { + kFull = v8::HeapSnapshot::kFull, + kAggregated = v8::HeapSnapshot::kAggregated + }; + HeapSnapshot(HeapSnapshotsCollection* collection, + Type type, const char* title, unsigned uid); ~HeapSnapshot(); HeapSnapshotsCollection* collection() { return collection_; } + Type type() { return type_; } const char* title() { return title_; } unsigned uid() { return uid_; } HeapEntry* root() { return entries_[root_entry_index_]; } @@ -655,6 +667,12 @@ class HeapSnapshot { HeapEntry* AddEntry( HeapObject* object, int children_count, int retainers_count); bool WillAddEntry(HeapObject* object); + HeapEntry* AddEntry(HeapEntry::Type type, + const char* name, + uint64_t id, + int size, + int children_count, + int retainers_count); int AddCalculatedData(); HeapEntryCalculatedData& GetCalculatedData(int index) { return calculated_data_[index]; @@ -681,6 +699,7 @@ class HeapSnapshot { static int CalculateNetworkSize(JSObject* obj); HeapSnapshotsCollection* collection_; + Type type_; const char* title_; unsigned uid_; int root_entry_index_; @@ -688,6 +707,9 @@ class HeapSnapshot { List entries_; bool entries_sorted_; List calculated_data_; +#ifdef DEBUG + int raw_entries_size_; +#endif friend class HeapSnapshotTester; @@ -792,12 +814,16 @@ class HeapSnapshotsCollection { bool is_tracking_objects() { return is_tracking_objects_; } - HeapSnapshot* NewSnapshot(const char* name, unsigned uid); + HeapSnapshot* NewSnapshot( + HeapSnapshot::Type type, const char* name, unsigned uid); void SnapshotGenerationFinished() { ids_.SnapshotGenerationFinished(); } List* snapshots() { return &snapshots_; } HeapSnapshot* GetSnapshot(unsigned uid); const char* GetName(String* name) { return names_.GetName(name); } + const char* GetFunctionName(String* name) { + return names_.GetFunctionName(name); + } TokenEnumerator* token_enumerator() { return token_enumerator_; } @@ -848,6 +874,8 @@ class HeapEntriesMap { int total_children_count() { return total_children_count_; } int total_retainers_count() { return total_retainers_count_; } + static HeapEntry *const kHeapEntryPlaceholder; + private: struct EntryInfo { explicit EntryInfo(HeapEntry* entry) @@ -903,8 +931,6 @@ class HeapSnapshotGenerator { HeapEntry* child_entry) = 0; virtual void SetRootReference(Object* child_obj, HeapEntry* child_entry) = 0; - - static HeapEntry *const kHeapEntryPlaceholder; }; explicit HeapSnapshotGenerator(HeapSnapshot* snapshot); diff --git a/test/cctest/test-heap-profiler.cc b/test/cctest/test-heap-profiler.cc index 92ad0a4..6dc49c0 100644 --- a/test/cctest/test-heap-profiler.cc +++ b/test/cctest/test-heap-profiler.cc @@ -372,6 +372,7 @@ TEST(RetainerProfile) { i::HeapIterator iterator; for (i::HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next()) ret_profile.CollectStats(obj); + ret_profile.CoarseAndAggregate(); RetainerProfilePrinter printer; ret_profile.DebugPrintStats(&printer); const char* retainers_of_a = printer.GetRetainers("A"); @@ -650,6 +651,8 @@ TEST(HeapSnapshotCodeObjects) { CompileAndRunScript( "function lazy(x) { return x - 1; }\n" "function compiled(x) { return x + 1; }\n" + "var inferred = function(x) { return x; }\n" + "var anonymous = (function() { return function() { return 0; } })();\n" "compiled(1)"); const v8::HeapSnapshot* snapshot = v8::HeapProfiler::TakeSnapshot(v8::String::New("code")); @@ -663,6 +666,18 @@ TEST(HeapSnapshotCodeObjects) { GetProperty(global, v8::HeapGraphEdge::kProperty, "lazy"); CHECK_NE(NULL, lazy); CHECK_EQ(v8::HeapGraphNode::kClosure, lazy->GetType()); + const v8::HeapGraphNode* inferred = + GetProperty(global, v8::HeapGraphEdge::kProperty, "inferred"); + CHECK_NE(NULL, inferred); + CHECK_EQ(v8::HeapGraphNode::kClosure, inferred->GetType()); + v8::String::AsciiValue inferred_name(inferred->GetName()); + CHECK_EQ("inferred", *inferred_name); + const v8::HeapGraphNode* anonymous = + GetProperty(global, v8::HeapGraphEdge::kProperty, "anonymous"); + CHECK_NE(NULL, anonymous); + CHECK_EQ(v8::HeapGraphNode::kClosure, anonymous->GetType()); + v8::String::AsciiValue anonymous_name(anonymous->GetName()); + CHECK_EQ("(anonymous function)", *anonymous_name); // Find references to code. const v8::HeapGraphNode* compiled_code = @@ -864,4 +879,114 @@ TEST(Issue822) { i::HeapSnapshotTester::CalculateNetworkSize(*jsobj); } + +static const v8::HeapGraphNode* GetChild( + const v8::HeapGraphNode* node, + v8::HeapGraphNode::Type type, + const char* name, + const v8::HeapGraphNode* after = NULL) { + bool ignore_child = after == NULL ? false : true; + for (int i = 0, count = node->GetChildrenCount(); i < count; ++i) { + const v8::HeapGraphEdge* prop = node->GetChild(i); + const v8::HeapGraphNode* child = prop->GetToNode(); + v8::String::AsciiValue child_name(child->GetName()); + if (!ignore_child + && child->GetType() == type + && strcmp(name, *child_name) == 0) + return child; + if (after != NULL && child == after) ignore_child = false; + } + return NULL; +} + +static bool IsNodeRetainedAs(const v8::HeapGraphNode* node, + int element) { + for (int i = 0, count = node->GetRetainersCount(); i < count; ++i) { + const v8::HeapGraphEdge* prop = node->GetRetainer(i); + if (prop->GetType() == v8::HeapGraphEdge::kElement + && element == prop->GetName()->Int32Value()) + return true; + } + return false; +} + +TEST(AggregatedHeapSnapshot) { + 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* snapshot = + v8::HeapProfiler::TakeSnapshot( + v8::String::New("agg"), v8::HeapSnapshot::kAggregated); + const v8::HeapGraphNode* strings = GetChild(snapshot->GetRoot(), + v8::HeapGraphNode::kInternal, + "STRING_TYPE"); + CHECK_NE(NULL, strings); + CHECK_NE(0, strings->GetSelfSize()); + CHECK_NE(0, strings->GetInstancesCount()); + const v8::HeapGraphNode* maps = GetChild(snapshot->GetRoot(), + v8::HeapGraphNode::kInternal, + "MAP_TYPE"); + CHECK_NE(NULL, maps); + CHECK_NE(0, maps->GetSelfSize()); + CHECK_NE(0, maps->GetInstancesCount()); + + const v8::HeapGraphNode* a = GetChild(snapshot->GetRoot(), + v8::HeapGraphNode::kObject, + "A"); + CHECK_NE(NULL, a); + CHECK_NE(0, a->GetSelfSize()); + CHECK_EQ(1, a->GetInstancesCount()); + + const v8::HeapGraphNode* b = GetChild(snapshot->GetRoot(), + v8::HeapGraphNode::kObject, + "B"); + CHECK_NE(NULL, b); + CHECK_NE(0, b->GetSelfSize()); + CHECK_EQ(1, b->GetInstancesCount()); + + const v8::HeapGraphNode* glob_prop = GetChild(snapshot->GetRoot(), + v8::HeapGraphNode::kObject, + "(global property)", + b); + CHECK_NE(NULL, glob_prop); + CHECK_EQ(0, glob_prop->GetSelfSize()); + CHECK_EQ(0, glob_prop->GetInstancesCount()); + CHECK_NE(0, glob_prop->GetChildrenCount()); + + const v8::HeapGraphNode* a_from_glob_prop = GetChild( + glob_prop, + v8::HeapGraphNode::kObject, + "A"); + CHECK_NE(NULL, a_from_glob_prop); + CHECK_EQ(0, a_from_glob_prop->GetSelfSize()); + CHECK_EQ(0, a_from_glob_prop->GetInstancesCount()); + CHECK_EQ(0, a_from_glob_prop->GetChildrenCount()); // Retains nothing. + CHECK(IsNodeRetainedAs(a_from_glob_prop, 1)); // (global propery) has 1 ref. + + const v8::HeapGraphNode* b_with_children = GetChild( + snapshot->GetRoot(), + v8::HeapGraphNode::kObject, + "B", + b); + CHECK_NE(NULL, b_with_children); + CHECK_EQ(0, b_with_children->GetSelfSize()); + CHECK_EQ(0, b_with_children->GetInstancesCount()); + CHECK_NE(0, b_with_children->GetChildrenCount()); + + const v8::HeapGraphNode* a_from_b = GetChild( + b_with_children, + v8::HeapGraphNode::kObject, + "A"); + CHECK_NE(NULL, a_from_b); + CHECK_EQ(0, a_from_b->GetSelfSize()); + CHECK_EQ(0, a_from_b->GetInstancesCount()); + CHECK_EQ(0, a_from_b->GetChildrenCount()); // Retains nothing. + CHECK(IsNodeRetainedAs(a_from_b, 1)); // B has 1 ref to A. +} + #endif // ENABLE_LOGGING_AND_PROFILING -- 2.7.4