2d9c65901b
Aggregated snapshots: don't report unreachable objects. Full snapshots: restore forcing GC prior to taking a snapshot. I played with the repro page provided for the bug and found that GC must be performed prior to taking a snapshot even if we only report reachable objects. GC allows weak handles to finalize. Now heap profiler produces aligned results for the repro page in both modes. BUG=crbug/70434 TEST=none Review URL: http://codereview.chromium.org/6410030 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@6598 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
1120 lines
35 KiB
C++
1120 lines
35 KiB
C++
// Copyright 2009-2010 the V8 project authors. All rights reserved.
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are
|
|
// met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above
|
|
// copyright notice, this list of conditions and the following
|
|
// disclaimer in the documentation and/or other materials provided
|
|
// with the distribution.
|
|
// * Neither the name of Google Inc. nor the names of its
|
|
// contributors may be used to endorse or promote products derived
|
|
// from this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
#include "v8.h"
|
|
|
|
#include "heap-profiler.h"
|
|
#include "frames-inl.h"
|
|
#include "global-handles.h"
|
|
#include "profile-generator.h"
|
|
#include "string-stream.h"
|
|
|
|
namespace v8 {
|
|
namespace internal {
|
|
|
|
|
|
#ifdef ENABLE_LOGGING_AND_PROFILING
|
|
namespace {
|
|
|
|
// Clusterizer is a set of helper functions for converting
|
|
// object references into clusters.
|
|
class Clusterizer : public AllStatic {
|
|
public:
|
|
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:
|
|
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);
|
|
};
|
|
|
|
|
|
JSObjectsCluster Clusterizer::Clusterize(HeapObject* obj, bool fine_grain) {
|
|
if (obj->IsJSObject()) {
|
|
JSObject* js_obj = JSObject::cast(obj);
|
|
String* constructor = GetConstructorNameForHeapProfile(
|
|
JSObject::cast(js_obj));
|
|
// 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());
|
|
} else if (obj->IsJSGlobalPropertyCell()) {
|
|
return JSObjectsCluster(JSObjectsCluster::GLOBAL_PROPERTY);
|
|
} else if (obj->IsCode() || obj->IsSharedFunctionInfo() || obj->IsScript()) {
|
|
return JSObjectsCluster(JSObjectsCluster::CODE);
|
|
}
|
|
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.
|
|
if (obj->properties() != Heap::empty_fixed_array()) {
|
|
size += obj->properties()->Size();
|
|
}
|
|
if (obj->elements() != Heap::empty_fixed_array()) {
|
|
size += obj->elements()->Size();
|
|
}
|
|
// For functions, also account non-empty context and literals sizes.
|
|
if (obj->IsJSFunction()) {
|
|
JSFunction* f = JSFunction::cast(obj);
|
|
if (f->unchecked_context()->IsContext()) {
|
|
size += f->context()->Size();
|
|
}
|
|
if (f->literals()->length() != 0) {
|
|
size += f->literals()->Size();
|
|
}
|
|
}
|
|
return size;
|
|
}
|
|
|
|
|
|
// A helper class for recording back references.
|
|
class ReferencesExtractor : public ObjectVisitor {
|
|
public:
|
|
ReferencesExtractor(const JSObjectsCluster& cluster,
|
|
RetainerHeapProfile* profile)
|
|
: cluster_(cluster),
|
|
profile_(profile),
|
|
inside_array_(false) {
|
|
}
|
|
|
|
void VisitPointer(Object** o) {
|
|
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,
|
|
// and function contexts.
|
|
inside_array_ = true;
|
|
FixedArray::cast(*o)->Iterate(this);
|
|
inside_array_ = false;
|
|
} else if ((*o)->IsHeapObject()) {
|
|
profile_->StoreReference(cluster_, HeapObject::cast(*o));
|
|
}
|
|
}
|
|
|
|
void VisitPointers(Object** start, Object** end) {
|
|
for (Object** p = start; p < end; p++) VisitPointer(p);
|
|
}
|
|
|
|
private:
|
|
const JSObjectsCluster& cluster_;
|
|
RetainerHeapProfile* profile_;
|
|
bool inside_array_;
|
|
};
|
|
|
|
|
|
// A printer interface implementation for the Retainers profile.
|
|
class RetainersPrinter : public RetainerHeapProfile::Printer {
|
|
public:
|
|
void PrintRetainers(const JSObjectsCluster& cluster,
|
|
const StringStream& retainers) {
|
|
HeapStringAllocator allocator;
|
|
StringStream stream(&allocator);
|
|
cluster.Print(&stream);
|
|
LOG(HeapSampleJSRetainersEvent(
|
|
*(stream.ToCString()), *(retainers.ToCString())));
|
|
}
|
|
};
|
|
|
|
|
|
// Visitor for printing a cluster tree.
|
|
class ClusterTreePrinter BASE_EMBEDDED {
|
|
public:
|
|
explicit ClusterTreePrinter(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& number_and_size);
|
|
|
|
private:
|
|
StringStream* stream_;
|
|
};
|
|
|
|
|
|
void ClusterTreePrinter::Print(StringStream* stream,
|
|
const JSObjectsCluster& cluster,
|
|
const NumberAndSizeInfo& number_and_size) {
|
|
stream->Put(',');
|
|
cluster.Print(stream);
|
|
stream->Add(";%d", number_and_size.number());
|
|
}
|
|
|
|
|
|
// Visitor for printing a retainer tree.
|
|
class SimpleRetainerTreePrinter BASE_EMBEDDED {
|
|
public:
|
|
explicit SimpleRetainerTreePrinter(RetainerHeapProfile::Printer* printer)
|
|
: printer_(printer) {}
|
|
void Call(const JSObjectsCluster& cluster, JSObjectsClusterTree* tree);
|
|
|
|
private:
|
|
RetainerHeapProfile::Printer* printer_;
|
|
};
|
|
|
|
|
|
void SimpleRetainerTreePrinter::Call(const JSObjectsCluster& cluster,
|
|
JSObjectsClusterTree* tree) {
|
|
HeapStringAllocator allocator;
|
|
StringStream stream(&allocator);
|
|
ClusterTreePrinter retainers_printer(&stream);
|
|
tree->ForEach(&retainers_printer);
|
|
printer_->PrintRetainers(cluster, stream);
|
|
}
|
|
|
|
|
|
// Visitor for aggregating references count of equivalent clusters.
|
|
class RetainersAggregator BASE_EMBEDDED {
|
|
public:
|
|
RetainersAggregator(ClustersCoarser* coarser, JSObjectsClusterTree* dest_tree)
|
|
: coarser_(coarser), dest_tree_(dest_tree) {}
|
|
void Call(const JSObjectsCluster& cluster,
|
|
const NumberAndSizeInfo& number_and_size);
|
|
|
|
private:
|
|
ClustersCoarser* coarser_;
|
|
JSObjectsClusterTree* dest_tree_;
|
|
};
|
|
|
|
|
|
void RetainersAggregator::Call(const JSObjectsCluster& cluster,
|
|
const NumberAndSizeInfo& number_and_size) {
|
|
JSObjectsCluster eq = coarser_->GetCoarseEquivalent(cluster);
|
|
if (eq.is_null()) eq = cluster;
|
|
JSObjectsClusterTree::Locator loc;
|
|
dest_tree_->Insert(eq, &loc);
|
|
NumberAndSizeInfo aggregated_number = loc.value();
|
|
aggregated_number.increment_number(number_and_size.number());
|
|
loc.set_value(aggregated_number);
|
|
}
|
|
|
|
|
|
// Visitor for printing retainers tree. Aggregates equivalent retainer clusters.
|
|
class AggregatingRetainerTreePrinter BASE_EMBEDDED {
|
|
public:
|
|
AggregatingRetainerTreePrinter(ClustersCoarser* coarser,
|
|
RetainerHeapProfile::Printer* printer)
|
|
: coarser_(coarser), printer_(printer) {}
|
|
void Call(const JSObjectsCluster& cluster, JSObjectsClusterTree* tree);
|
|
|
|
private:
|
|
ClustersCoarser* coarser_;
|
|
RetainerHeapProfile::Printer* printer_;
|
|
};
|
|
|
|
|
|
void AggregatingRetainerTreePrinter::Call(const JSObjectsCluster& cluster,
|
|
JSObjectsClusterTree* tree) {
|
|
if (!coarser_->GetCoarseEquivalent(cluster).is_null()) return;
|
|
JSObjectsClusterTree dest_tree_;
|
|
RetainersAggregator retainers_aggregator(coarser_, &dest_tree_);
|
|
tree->ForEach(&retainers_aggregator);
|
|
HeapStringAllocator allocator;
|
|
StringStream stream(&allocator);
|
|
ClusterTreePrinter retainers_printer(&stream);
|
|
dest_tree_.ForEach(&retainers_printer);
|
|
printer_->PrintRetainers(cluster, stream);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
// A helper class for building a retainers tree, that aggregates
|
|
// all equivalent clusters.
|
|
class RetainerTreeAggregator {
|
|
public:
|
|
explicit RetainerTreeAggregator(ClustersCoarser* coarser)
|
|
: coarser_(coarser) {}
|
|
void Process(JSObjectsRetainerTree* input_tree) {
|
|
input_tree->ForEach(this);
|
|
}
|
|
void Call(const JSObjectsCluster& cluster, JSObjectsClusterTree* tree);
|
|
JSObjectsRetainerTree& output_tree() { return output_tree_; }
|
|
|
|
private:
|
|
ClustersCoarser* coarser_;
|
|
JSObjectsRetainerTree output_tree_;
|
|
};
|
|
|
|
|
|
void RetainerTreeAggregator::Call(const JSObjectsCluster& cluster,
|
|
JSObjectsClusterTree* tree) {
|
|
JSObjectsCluster eq = coarser_->GetCoarseEquivalent(cluster);
|
|
if (eq.is_null()) return;
|
|
JSObjectsRetainerTree::Locator loc;
|
|
if (output_tree_.Insert(eq, &loc)) {
|
|
loc.set_value(new JSObjectsClusterTree());
|
|
}
|
|
RetainersAggregator retainers_aggregator(coarser_, loc.value());
|
|
tree->ForEach(&retainers_aggregator);
|
|
}
|
|
|
|
|
|
HeapProfiler* HeapProfiler::singleton_ = NULL;
|
|
|
|
HeapProfiler::HeapProfiler()
|
|
: snapshots_(new HeapSnapshotsCollection()),
|
|
next_snapshot_uid_(1) {
|
|
}
|
|
|
|
|
|
HeapProfiler::~HeapProfiler() {
|
|
delete snapshots_;
|
|
}
|
|
|
|
#endif // ENABLE_LOGGING_AND_PROFILING
|
|
|
|
void HeapProfiler::Setup() {
|
|
#ifdef ENABLE_LOGGING_AND_PROFILING
|
|
if (singleton_ == NULL) {
|
|
singleton_ = new HeapProfiler();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
void HeapProfiler::TearDown() {
|
|
#ifdef ENABLE_LOGGING_AND_PROFILING
|
|
delete singleton_;
|
|
singleton_ = NULL;
|
|
#endif
|
|
}
|
|
|
|
|
|
#ifdef ENABLE_LOGGING_AND_PROFILING
|
|
|
|
HeapSnapshot* HeapProfiler::TakeSnapshot(const char* name,
|
|
int type,
|
|
v8::ActivityControl* control) {
|
|
ASSERT(singleton_ != NULL);
|
|
return singleton_->TakeSnapshotImpl(name, type, control);
|
|
}
|
|
|
|
|
|
HeapSnapshot* HeapProfiler::TakeSnapshot(String* name,
|
|
int type,
|
|
v8::ActivityControl* control) {
|
|
ASSERT(singleton_ != NULL);
|
|
return singleton_->TakeSnapshotImpl(name, type, control);
|
|
}
|
|
|
|
|
|
HeapSnapshot* HeapProfiler::TakeSnapshotImpl(const char* name,
|
|
int type,
|
|
v8::ActivityControl* control) {
|
|
HeapSnapshot::Type s_type = static_cast<HeapSnapshot::Type>(type);
|
|
HeapSnapshot* result =
|
|
snapshots_->NewSnapshot(s_type, name, next_snapshot_uid_++);
|
|
bool generation_completed = true;
|
|
switch (s_type) {
|
|
case HeapSnapshot::kFull: {
|
|
Heap::CollectAllGarbage(true);
|
|
HeapSnapshotGenerator generator(result, control);
|
|
generation_completed = generator.GenerateSnapshot();
|
|
break;
|
|
}
|
|
case HeapSnapshot::kAggregated: {
|
|
Heap::CollectAllGarbage(true);
|
|
AggregatedHeapSnapshot agg_snapshot;
|
|
AggregatedHeapSnapshotGenerator generator(&agg_snapshot);
|
|
generator.GenerateSnapshot();
|
|
generator.FillHeapSnapshot(result);
|
|
break;
|
|
}
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
if (!generation_completed) {
|
|
delete result;
|
|
result = NULL;
|
|
}
|
|
snapshots_->SnapshotGenerationFinished(result);
|
|
return result;
|
|
}
|
|
|
|
|
|
HeapSnapshot* HeapProfiler::TakeSnapshotImpl(String* name,
|
|
int type,
|
|
v8::ActivityControl* control) {
|
|
return TakeSnapshotImpl(snapshots_->GetName(name), type, control);
|
|
}
|
|
|
|
|
|
int HeapProfiler::GetSnapshotsCount() {
|
|
ASSERT(singleton_ != NULL);
|
|
return singleton_->snapshots_->snapshots()->length();
|
|
}
|
|
|
|
|
|
HeapSnapshot* HeapProfiler::GetSnapshot(int index) {
|
|
ASSERT(singleton_ != NULL);
|
|
return singleton_->snapshots_->snapshots()->at(index);
|
|
}
|
|
|
|
|
|
HeapSnapshot* HeapProfiler::FindSnapshot(unsigned uid) {
|
|
ASSERT(singleton_ != NULL);
|
|
return singleton_->snapshots_->GetSnapshot(uid);
|
|
}
|
|
|
|
|
|
void HeapProfiler::ObjectMoveEvent(Address from, Address to) {
|
|
ASSERT(singleton_ != NULL);
|
|
singleton_->snapshots_->ObjectMoveEvent(from, to);
|
|
}
|
|
|
|
|
|
const JSObjectsClusterTreeConfig::Key JSObjectsClusterTreeConfig::kNoKey;
|
|
const JSObjectsClusterTreeConfig::Value JSObjectsClusterTreeConfig::kNoValue;
|
|
|
|
|
|
ConstructorHeapProfile::ConstructorHeapProfile()
|
|
: zscope_(DELETE_ON_EXIT) {
|
|
}
|
|
|
|
|
|
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) {
|
|
Clusterizer::InsertIntoTree(&js_objects_info_tree_, obj, false);
|
|
}
|
|
|
|
|
|
void ConstructorHeapProfile::PrintStats() {
|
|
js_objects_info_tree_.ForEach(this);
|
|
}
|
|
|
|
|
|
static const char* GetConstructorName(const char* name) {
|
|
return name[0] != '\0' ? name : "(anonymous)";
|
|
}
|
|
|
|
|
|
const char* JSObjectsCluster::GetSpecialCaseName() const {
|
|
if (constructor_ == FromSpecialCase(ROOTS)) {
|
|
return "(roots)";
|
|
} else if (constructor_ == FromSpecialCase(GLOBAL_PROPERTY)) {
|
|
return "(global property)";
|
|
} else if (constructor_ == FromSpecialCase(CODE)) {
|
|
return "(code)";
|
|
} else if (constructor_ == FromSpecialCase(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<char> s_name(
|
|
constructor_->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL));
|
|
accumulator->Add("%s", GetConstructorName(*s_name));
|
|
if (instance_ != NULL) {
|
|
accumulator->Add(":%p", static_cast<void*>(instance_));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void JSObjectsCluster::DebugPrint(StringStream* accumulator) const {
|
|
if (!is_null()) {
|
|
Print(accumulator);
|
|
} else {
|
|
accumulator->Add("(null cluster)");
|
|
}
|
|
}
|
|
|
|
|
|
inline ClustersCoarser::ClusterBackRefs::ClusterBackRefs(
|
|
const JSObjectsCluster& cluster_)
|
|
: cluster(cluster_), refs(kInitialBackrefsListCapacity) {
|
|
}
|
|
|
|
|
|
inline ClustersCoarser::ClusterBackRefs::ClusterBackRefs(
|
|
const ClustersCoarser::ClusterBackRefs& src)
|
|
: cluster(src.cluster), refs(src.refs.capacity()) {
|
|
refs.AddAll(src.refs);
|
|
}
|
|
|
|
|
|
inline ClustersCoarser::ClusterBackRefs&
|
|
ClustersCoarser::ClusterBackRefs::operator=(
|
|
const ClustersCoarser::ClusterBackRefs& src) {
|
|
if (this == &src) return *this;
|
|
cluster = src.cluster;
|
|
refs.Clear();
|
|
refs.AddAll(src.refs);
|
|
return *this;
|
|
}
|
|
|
|
|
|
inline int ClustersCoarser::ClusterBackRefs::Compare(
|
|
const ClustersCoarser::ClusterBackRefs& a,
|
|
const ClustersCoarser::ClusterBackRefs& b) {
|
|
int cmp = JSObjectsCluster::CompareConstructors(a.cluster, b.cluster);
|
|
if (cmp != 0) return cmp;
|
|
if (a.refs.length() < b.refs.length()) return -1;
|
|
if (a.refs.length() > b.refs.length()) return 1;
|
|
for (int i = 0; i < a.refs.length(); ++i) {
|
|
int cmp = JSObjectsCluster::Compare(a.refs[i], b.refs[i]);
|
|
if (cmp != 0) return cmp;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
ClustersCoarser::ClustersCoarser()
|
|
: zscope_(DELETE_ON_EXIT),
|
|
sim_list_(ClustersCoarser::kInitialSimilarityListCapacity),
|
|
current_pair_(NULL),
|
|
current_set_(NULL),
|
|
self_(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();
|
|
self_ = &cluster;
|
|
tree->ForEach(this);
|
|
sim_list_.Add(pair);
|
|
current_pair_ = NULL;
|
|
current_set_ = NULL;
|
|
self_ = NULL;
|
|
}
|
|
|
|
|
|
void ClustersCoarser::Call(const JSObjectsCluster& cluster,
|
|
const NumberAndSizeInfo& number_and_size) {
|
|
ASSERT(current_pair_ != NULL);
|
|
ASSERT(current_set_ != NULL);
|
|
ASSERT(self_ != NULL);
|
|
JSObjectsRetainerTree::Locator loc;
|
|
if (JSObjectsCluster::Compare(*self_, cluster) == 0) {
|
|
current_pair_->refs.Add(JSObjectsCluster(JSObjectsCluster::SELF));
|
|
return;
|
|
}
|
|
JSObjectsCluster eq = GetCoarseEquivalent(cluster);
|
|
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);
|
|
}
|
|
}
|
|
|
|
|
|
void ClustersCoarser::Process(JSObjectsRetainerTree* tree) {
|
|
int last_eq_clusters = -1;
|
|
for (int i = 0; i < kMaxPassesCount; ++i) {
|
|
sim_list_.Clear();
|
|
const int curr_eq_clusters = DoProcess(tree);
|
|
// If no new cluster equivalents discovered, abort processing.
|
|
if (last_eq_clusters == curr_eq_clusters) break;
|
|
last_eq_clusters = curr_eq_clusters;
|
|
}
|
|
}
|
|
|
|
|
|
int ClustersCoarser::DoProcess(JSObjectsRetainerTree* tree) {
|
|
tree->ForEach(this);
|
|
sim_list_.Iterate(ClusterBackRefs::SortRefsIterator);
|
|
sim_list_.Sort(ClusterBackRefsCmp);
|
|
return FillEqualityTree();
|
|
}
|
|
|
|
|
|
JSObjectsCluster ClustersCoarser::GetCoarseEquivalent(
|
|
const JSObjectsCluster& cluster) {
|
|
if (!cluster.can_be_coarsed()) return JSObjectsCluster();
|
|
EqualityTree::Locator loc;
|
|
return eq_tree_.Find(cluster, &loc) ? loc.value() : JSObjectsCluster();
|
|
}
|
|
|
|
|
|
bool ClustersCoarser::HasAnEquivalent(const JSObjectsCluster& cluster) {
|
|
// Return true for coarsible clusters that have a non-identical equivalent.
|
|
if (!cluster.can_be_coarsed()) return false;
|
|
JSObjectsCluster eq = GetCoarseEquivalent(cluster);
|
|
return !eq.is_null() && JSObjectsCluster::Compare(cluster, eq) != 0;
|
|
}
|
|
|
|
|
|
int ClustersCoarser::FillEqualityTree() {
|
|
int eq_clusters_count = 0;
|
|
int eq_to = 0;
|
|
bool first_added = false;
|
|
for (int i = 1; i < sim_list_.length(); ++i) {
|
|
if (ClusterBackRefs::Compare(sim_list_[i], sim_list_[eq_to]) == 0) {
|
|
EqualityTree::Locator loc;
|
|
if (!first_added) {
|
|
// Add self-equivalence, if we have more than one item in this
|
|
// equivalence class.
|
|
eq_tree_.Insert(sim_list_[eq_to].cluster, &loc);
|
|
loc.set_value(sim_list_[eq_to].cluster);
|
|
first_added = true;
|
|
}
|
|
eq_tree_.Insert(sim_list_[i].cluster, &loc);
|
|
loc.set_value(sim_list_[eq_to].cluster);
|
|
++eq_clusters_count;
|
|
} else {
|
|
eq_to = i;
|
|
first_added = false;
|
|
}
|
|
}
|
|
return eq_clusters_count;
|
|
}
|
|
|
|
|
|
const JSObjectsCluster ClustersCoarser::ClusterEqualityConfig::kNoKey;
|
|
const JSObjectsCluster ClustersCoarser::ClusterEqualityConfig::kNoValue;
|
|
const JSObjectsRetainerTreeConfig::Key JSObjectsRetainerTreeConfig::kNoKey;
|
|
const JSObjectsRetainerTreeConfig::Value JSObjectsRetainerTreeConfig::kNoValue =
|
|
NULL;
|
|
|
|
|
|
RetainerHeapProfile::RetainerHeapProfile()
|
|
: 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);
|
|
if (ref_cluster.is_null()) return;
|
|
JSObjectsRetainerTree::Locator ref_loc;
|
|
if (retainers_tree_.Insert(ref_cluster, &ref_loc)) {
|
|
ref_loc.set_value(new JSObjectsClusterTree());
|
|
}
|
|
JSObjectsClusterTree* referenced_by = ref_loc.value();
|
|
Clusterizer::InsertReferenceIntoTree(referenced_by, cluster);
|
|
}
|
|
|
|
|
|
void RetainerHeapProfile::CollectStats(HeapObject* obj) {
|
|
const JSObjectsCluster cluster = Clusterizer::Clusterize(obj);
|
|
if (cluster.is_null()) return;
|
|
ReferencesExtractor extractor(cluster, this);
|
|
obj->Iterate(&extractor);
|
|
}
|
|
|
|
|
|
void RetainerHeapProfile::CoarseAndAggregate() {
|
|
coarser_.Process(&retainers_tree_);
|
|
ASSERT(aggregator_ == NULL);
|
|
aggregator_ = new RetainerTreeAggregator(&coarser_);
|
|
aggregator_->Process(&retainers_tree_);
|
|
}
|
|
|
|
|
|
void RetainerHeapProfile::DebugPrintStats(
|
|
RetainerHeapProfile::Printer* printer) {
|
|
// Print clusters that have no equivalents, aggregating their retainers.
|
|
AggregatingRetainerTreePrinter agg_printer(&coarser_, printer);
|
|
retainers_tree_.ForEach(&agg_printer);
|
|
// Print clusters that have equivalents.
|
|
SimpleRetainerTreePrinter s_printer(printer);
|
|
aggregator_->output_tree().ForEach(&s_printer);
|
|
}
|
|
|
|
|
|
void RetainerHeapProfile::PrintStats() {
|
|
RetainersPrinter printer;
|
|
DebugPrintStats(&printer);
|
|
}
|
|
|
|
|
|
//
|
|
// HeapProfiler class implementation.
|
|
//
|
|
static void StackWeakReferenceCallback(Persistent<Value> object,
|
|
void* trace) {
|
|
DeleteArray(static_cast<Address*>(trace));
|
|
object.Dispose();
|
|
}
|
|
|
|
|
|
static void PrintProducerStackTrace(Object* obj, void* trace) {
|
|
if (!obj->IsJSObject()) return;
|
|
String* constructor = GetConstructorNameForHeapProfile(JSObject::cast(obj));
|
|
SmartPointer<char> s_name(
|
|
constructor->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL));
|
|
LOG(HeapSampleJSProducerEvent(GetConstructorName(*s_name),
|
|
reinterpret_cast<Address*>(trace)));
|
|
}
|
|
|
|
|
|
void HeapProfiler::WriteSample() {
|
|
LOG(HeapSampleBeginEvent("Heap", "allocated"));
|
|
LOG(HeapSampleStats(
|
|
"Heap", "allocated", Heap::CommittedMemory(), Heap::SizeOfObjects()));
|
|
|
|
AggregatedHeapSnapshot snapshot;
|
|
AggregatedHeapSnapshotGenerator generator(&snapshot);
|
|
generator.GenerateSnapshot();
|
|
|
|
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<HistogramInfo>(
|
|
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.
|
|
#define INCREMENT_SIZE(type, size, name, camel_name) \
|
|
strings.increment_number(info[type].number()); \
|
|
strings.increment_bytes(info[type].bytes());
|
|
STRING_TYPE_LIST(INCREMENT_SIZE);
|
|
#undef INCREMENT_SIZE
|
|
}
|
|
|
|
|
|
void AggregatedHeapSnapshotGenerator::CollectStats(HeapObject* obj) {
|
|
InstanceType type = obj->map()->instance_type();
|
|
ASSERT(0 <= type && type <= LAST_TYPE);
|
|
agg_snapshot_->info()[type].increment_number(1);
|
|
agg_snapshot_->info()[type].increment_bytes(obj->Size());
|
|
}
|
|
|
|
|
|
void AggregatedHeapSnapshotGenerator::GenerateSnapshot() {
|
|
HeapIterator iterator(HeapIterator::kFilterUnreachable);
|
|
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);
|
|
}
|
|
|
|
private:
|
|
HeapSnapshot* snapshot_;
|
|
int* root_child_index_;
|
|
};
|
|
|
|
|
|
static HeapObject* ClusterAsHeapObject(const JSObjectsCluster& cluster) {
|
|
return cluster.can_be_coarsed() ?
|
|
reinterpret_cast<HeapObject*>(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 = GetConstructorNameForHeapProfile(
|
|
JSObject::cast(js_obj));
|
|
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))->SetIndexedReference(
|
|
HeapGraphEdge::kElement,
|
|
child_index,
|
|
number_and_size.number(),
|
|
child_entry_,
|
|
retainer_index);
|
|
}
|
|
|
|
private:
|
|
HeapObject* child_;
|
|
HeapEntriesMap* map_;
|
|
HeapEntry* child_entry_;
|
|
};
|
|
|
|
|
|
template<class RetainersIterator>
|
|
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<class Iterator>
|
|
void AggregatedHeapSnapshotGenerator::IterateRetainers(
|
|
HeapEntriesMap* entries_map) {
|
|
RetainerHeapProfile* p = agg_snapshot_->js_retainer_profile();
|
|
AggregatingRetainerTreeIterator<Iterator> agg_ret_iter_1(
|
|
p->coarser(), entries_map);
|
|
p->retainers_tree()->ForEach(&agg_ret_iter_1);
|
|
AggregatingRetainerTreeIterator<Iterator> 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<CountingRetainersIterator>(&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::kHidden,
|
|
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<AllocatingRetainersIterator>(&entries_map);
|
|
|
|
snapshot->SetDominatorsToSelf();
|
|
}
|
|
|
|
|
|
bool ProducerHeapProfile::can_log_ = false;
|
|
|
|
void ProducerHeapProfile::Setup() {
|
|
can_log_ = true;
|
|
}
|
|
|
|
void ProducerHeapProfile::DoRecordJSObjectAllocation(Object* obj) {
|
|
ASSERT(FLAG_log_producers);
|
|
if (!can_log_) return;
|
|
int framesCount = 0;
|
|
for (JavaScriptFrameIterator it; !it.done(); it.Advance()) {
|
|
++framesCount;
|
|
}
|
|
if (framesCount == 0) return;
|
|
++framesCount; // Reserve place for the terminator item.
|
|
Vector<Address> stack(NewArray<Address>(framesCount), framesCount);
|
|
int i = 0;
|
|
for (JavaScriptFrameIterator it; !it.done(); it.Advance()) {
|
|
stack[i++] = it.frame()->pc();
|
|
}
|
|
stack[i] = NULL;
|
|
Handle<Object> handle = GlobalHandles::Create(obj);
|
|
GlobalHandles::MakeWeak(handle.location(),
|
|
static_cast<void*>(stack.start()),
|
|
StackWeakReferenceCallback);
|
|
}
|
|
|
|
|
|
#endif // ENABLE_LOGGING_AND_PROFILING
|
|
|
|
|
|
} } // namespace v8::internal
|