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
This commit is contained in:
parent
0e63056cf5
commit
9b4c950963
@ -37,18 +37,70 @@ namespace internal {
|
||||
#ifdef ENABLE_LOGGING_AND_PROFILING
|
||||
namespace {
|
||||
|
||||
// JSStatsHelper provides service functions for examining
|
||||
// JS objects allocated on heap. It is run during garbage
|
||||
// collection cycle, thus it doesn't need to use handles.
|
||||
class JSStatsHelper {
|
||||
// Clusterizer is a set of helper functions for converting
|
||||
// object references into clusters.
|
||||
class Clusterizer : public AllStatic {
|
||||
public:
|
||||
static int CalculateNetworkSize(JSObject* obj);
|
||||
static JSObjectsCluster Clusterize(HeapObject* obj) {
|
||||
return Clusterize(obj, true);
|
||||
}
|
||||
static void InsertIntoTree(JSObjectsClusterTree* tree,
|
||||
HeapObject* obj, bool fine_grain);
|
||||
static void InsertReferenceIntoTree(JSObjectsClusterTree* tree,
|
||||
const JSObjectsCluster& cluster) {
|
||||
InsertIntoTree(tree, cluster, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_IMPLICIT_CONSTRUCTORS(JSStatsHelper);
|
||||
static JSObjectsCluster Clusterize(HeapObject* obj, bool fine_grain);
|
||||
static int CalculateNetworkSize(JSObject* obj);
|
||||
static int GetObjectSize(HeapObject* obj) {
|
||||
return obj->IsJSObject() ?
|
||||
CalculateNetworkSize(JSObject::cast(obj)) : obj->Size();
|
||||
}
|
||||
static void InsertIntoTree(JSObjectsClusterTree* tree,
|
||||
const JSObjectsCluster& cluster, int size);
|
||||
};
|
||||
|
||||
|
||||
int JSStatsHelper::CalculateNetworkSize(JSObject* obj) {
|
||||
JSObjectsCluster Clusterizer::Clusterize(HeapObject* obj, bool fine_grain) {
|
||||
if (obj->IsJSObject()) {
|
||||
JSObject* js_obj = JSObject::cast(obj);
|
||||
String* constructor = JSObject::cast(js_obj)->constructor_name();
|
||||
// Differentiate Object and Array instances.
|
||||
if (fine_grain && (constructor == Heap::Object_symbol() ||
|
||||
constructor == Heap::Array_symbol())) {
|
||||
return JSObjectsCluster(constructor, obj);
|
||||
} else {
|
||||
return JSObjectsCluster(constructor);
|
||||
}
|
||||
} else if (obj->IsString()) {
|
||||
return JSObjectsCluster(Heap::String_symbol());
|
||||
}
|
||||
return JSObjectsCluster();
|
||||
}
|
||||
|
||||
|
||||
void Clusterizer::InsertIntoTree(JSObjectsClusterTree* tree,
|
||||
HeapObject* obj, bool fine_grain) {
|
||||
JSObjectsCluster cluster = Clusterize(obj, fine_grain);
|
||||
if (cluster.is_null()) return;
|
||||
InsertIntoTree(tree, cluster, GetObjectSize(obj));
|
||||
}
|
||||
|
||||
|
||||
void Clusterizer::InsertIntoTree(JSObjectsClusterTree* tree,
|
||||
const JSObjectsCluster& cluster, int size) {
|
||||
JSObjectsClusterTree::Locator loc;
|
||||
tree->Insert(cluster, &loc);
|
||||
NumberAndSizeInfo number_and_size = loc.value();
|
||||
number_and_size.increment_number(1);
|
||||
number_and_size.increment_bytes(size);
|
||||
loc.set_value(number_and_size);
|
||||
}
|
||||
|
||||
|
||||
int Clusterizer::CalculateNetworkSize(JSObject* obj) {
|
||||
int size = obj->Size();
|
||||
// If 'properties' and 'elements' are non-empty (thus, non-shared),
|
||||
// take their size into account.
|
||||
@ -65,8 +117,8 @@ int JSStatsHelper::CalculateNetworkSize(JSObject* obj) {
|
||||
// A helper class for recording back references.
|
||||
class ReferencesExtractor : public ObjectVisitor {
|
||||
public:
|
||||
ReferencesExtractor(
|
||||
const JSObjectsCluster& cluster, RetainerHeapProfile* profile)
|
||||
ReferencesExtractor(const JSObjectsCluster& cluster,
|
||||
RetainerHeapProfile* profile)
|
||||
: cluster_(cluster),
|
||||
profile_(profile),
|
||||
inside_array_(false) {
|
||||
@ -74,7 +126,7 @@ class ReferencesExtractor : public ObjectVisitor {
|
||||
|
||||
void VisitPointer(Object** o) {
|
||||
if ((*o)->IsJSObject() || (*o)->IsString()) {
|
||||
profile_->StoreReference(cluster_, *o);
|
||||
profile_->StoreReference(cluster_, HeapObject::cast(*o));
|
||||
} else if ((*o)->IsFixedArray() && !inside_array_) {
|
||||
// Traverse one level deep for data members that are fixed arrays.
|
||||
// This covers the case of 'elements' and 'properties' of JSObject,
|
||||
@ -99,18 +151,47 @@ class ReferencesExtractor : public ObjectVisitor {
|
||||
// A printer interface implementation for the Retainers profile.
|
||||
class RetainersPrinter : public RetainerHeapProfile::Printer {
|
||||
public:
|
||||
void PrintRetainers(const StringStream& retainers) {
|
||||
LOG(HeapSampleJSRetainersEvent(*(retainers.ToCString())));
|
||||
void PrintRetainers(const JSObjectsCluster& cluster,
|
||||
const StringStream& retainers) {
|
||||
HeapStringAllocator allocator;
|
||||
StringStream stream(&allocator);
|
||||
cluster.Print(&stream);
|
||||
LOG(HeapSampleJSRetainersEvent(
|
||||
*(stream.ToCString()), *(retainers.ToCString())));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class RetainerTreePrinter BASE_EMBEDDED {
|
||||
public:
|
||||
explicit RetainerTreePrinter(StringStream* stream) : stream_(stream) {}
|
||||
void Call(const JSObjectsCluster& cluster,
|
||||
const NumberAndSizeInfo& number_and_size) {
|
||||
Print(stream_, cluster, number_and_size);
|
||||
}
|
||||
static void Print(StringStream* stream,
|
||||
const JSObjectsCluster& cluster,
|
||||
const NumberAndSizeInfo& numNNber_and_size);
|
||||
|
||||
private:
|
||||
StringStream* stream_;
|
||||
};
|
||||
|
||||
|
||||
void RetainerTreePrinter::Print(StringStream* stream,
|
||||
const JSObjectsCluster& cluster,
|
||||
const NumberAndSizeInfo& number_and_size) {
|
||||
stream->Put(',');
|
||||
cluster.Print(stream);
|
||||
stream->Add(";%d", number_and_size.number());
|
||||
}
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
const ConstructorHeapProfile::TreeConfig::Key
|
||||
ConstructorHeapProfile::TreeConfig::kNoKey = NULL;
|
||||
const ConstructorHeapProfile::TreeConfig::Value
|
||||
ConstructorHeapProfile::TreeConfig::kNoValue;
|
||||
const JSObjectsClusterTreeConfig::Key JSObjectsClusterTreeConfig::kNoKey;
|
||||
const JSObjectsClusterTreeConfig::Value JSObjectsClusterTreeConfig::kNoValue;
|
||||
|
||||
|
||||
ConstructorHeapProfile::ConstructorHeapProfile()
|
||||
@ -118,39 +199,19 @@ ConstructorHeapProfile::ConstructorHeapProfile()
|
||||
}
|
||||
|
||||
|
||||
void ConstructorHeapProfile::Call(String* name,
|
||||
const NumberAndSizeInfo& number_and_size) {
|
||||
ASSERT(name != NULL);
|
||||
SmartPointer<char> s_name(
|
||||
name->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL));
|
||||
LOG(HeapSampleJSConstructorEvent(*s_name,
|
||||
void ConstructorHeapProfile::Call(const JSObjectsCluster& cluster,
|
||||
const NumberAndSizeInfo& number_and_size) {
|
||||
HeapStringAllocator allocator;
|
||||
StringStream stream(&allocator);
|
||||
cluster.Print(&stream);
|
||||
LOG(HeapSampleJSConstructorEvent(*(stream.ToCString()),
|
||||
number_and_size.number(),
|
||||
number_and_size.bytes()));
|
||||
}
|
||||
|
||||
|
||||
void ConstructorHeapProfile::CollectStats(HeapObject* obj) {
|
||||
String* constructor = NULL;
|
||||
int size;
|
||||
if (obj->IsString()) {
|
||||
constructor = Heap::String_symbol();
|
||||
size = obj->Size();
|
||||
} else if (obj->IsJSObject()) {
|
||||
JSObject* js_obj = JSObject::cast(obj);
|
||||
constructor = js_obj->constructor_name();
|
||||
size = JSStatsHelper::CalculateNetworkSize(js_obj);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
JSObjectsInfoTree::Locator loc;
|
||||
if (!js_objects_info_tree_.Find(constructor, &loc)) {
|
||||
js_objects_info_tree_.Insert(constructor, &loc);
|
||||
}
|
||||
NumberAndSizeInfo number_and_size = loc.value();
|
||||
number_and_size.increment_number(1);
|
||||
number_and_size.increment_bytes(size);
|
||||
loc.set_value(number_and_size);
|
||||
Clusterizer::InsertIntoTree(&js_objects_info_tree_, obj, false);
|
||||
}
|
||||
|
||||
|
||||
@ -231,37 +292,37 @@ ClustersCoarser::ClustersCoarser()
|
||||
}
|
||||
|
||||
|
||||
void ClustersCoarser::Call(
|
||||
const JSObjectsCluster& cluster, JSObjectsClusterTree* tree) {
|
||||
if (tree != NULL) {
|
||||
// First level of retainer graph.
|
||||
if (!cluster.can_be_coarsed()) return;
|
||||
ClusterBackRefs pair(cluster);
|
||||
ASSERT(current_pair_ == NULL);
|
||||
current_pair_ = &pair;
|
||||
current_set_ = new JSObjectsClusterTree();
|
||||
tree->ForEach(this);
|
||||
sim_list_.Add(pair);
|
||||
current_pair_ = NULL;
|
||||
current_set_ = NULL;
|
||||
void ClustersCoarser::Call(const JSObjectsCluster& cluster,
|
||||
JSObjectsClusterTree* tree) {
|
||||
if (!cluster.can_be_coarsed()) return;
|
||||
ClusterBackRefs pair(cluster);
|
||||
ASSERT(current_pair_ == NULL);
|
||||
current_pair_ = &pair;
|
||||
current_set_ = new JSObjectsRetainerTree();
|
||||
tree->ForEach(this);
|
||||
sim_list_.Add(pair);
|
||||
current_pair_ = NULL;
|
||||
current_set_ = NULL;
|
||||
}
|
||||
|
||||
|
||||
void ClustersCoarser::Call(const JSObjectsCluster& cluster,
|
||||
const NumberAndSizeInfo& number_and_size) {
|
||||
ASSERT(current_pair_ != NULL);
|
||||
ASSERT(current_set_ != NULL);
|
||||
JSObjectsCluster eq = GetCoarseEquivalent(cluster);
|
||||
JSObjectsRetainerTree::Locator loc;
|
||||
if (!eq.is_null()) {
|
||||
if (current_set_->Find(eq, &loc)) return;
|
||||
current_pair_->refs.Add(eq);
|
||||
current_set_->Insert(eq, &loc);
|
||||
} else {
|
||||
// Second level of retainer graph.
|
||||
ASSERT(current_pair_ != NULL);
|
||||
ASSERT(current_set_ != NULL);
|
||||
JSObjectsCluster eq = GetCoarseEquivalent(cluster);
|
||||
JSObjectsClusterTree::Locator loc;
|
||||
if (!eq.is_null()) {
|
||||
if (current_set_->Find(eq, &loc)) return;
|
||||
current_pair_->refs.Add(eq);
|
||||
current_set_->Insert(eq, &loc);
|
||||
} else {
|
||||
current_pair_->refs.Add(cluster);
|
||||
}
|
||||
current_pair_->refs.Add(cluster);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ClustersCoarser::Process(JSObjectsClusterTree* tree) {
|
||||
void ClustersCoarser::Process(JSObjectsRetainerTree* tree) {
|
||||
int last_eq_clusters = -1;
|
||||
for (int i = 0; i < kMaxPassesCount; ++i) {
|
||||
sim_list_.Clear();
|
||||
@ -273,7 +334,7 @@ void ClustersCoarser::Process(JSObjectsClusterTree* tree) {
|
||||
}
|
||||
|
||||
|
||||
int ClustersCoarser::DoProcess(JSObjectsClusterTree* tree) {
|
||||
int ClustersCoarser::DoProcess(JSObjectsRetainerTree* tree) {
|
||||
tree->ForEach(this);
|
||||
// To sort similarity list properly, references list of a cluster is
|
||||
// required to be sorted, thus 'O1 <- A, B' and 'O2 <- B, A' would
|
||||
@ -328,60 +389,37 @@ int ClustersCoarser::FillEqualityTree() {
|
||||
|
||||
const JSObjectsCluster ClustersCoarser::ClusterEqualityConfig::kNoKey;
|
||||
const JSObjectsCluster ClustersCoarser::ClusterEqualityConfig::kNoValue;
|
||||
const JSObjectsClusterTreeConfig::Key JSObjectsClusterTreeConfig::kNoKey;
|
||||
const JSObjectsClusterTreeConfig::Value JSObjectsClusterTreeConfig::kNoValue =
|
||||
const JSObjectsRetainerTreeConfig::Key JSObjectsRetainerTreeConfig::kNoKey;
|
||||
const JSObjectsRetainerTreeConfig::Value JSObjectsRetainerTreeConfig::kNoValue =
|
||||
NULL;
|
||||
|
||||
|
||||
RetainerHeapProfile::RetainerHeapProfile()
|
||||
: zscope_(DELETE_ON_EXIT),
|
||||
coarse_cluster_tree_(NULL),
|
||||
retainers_printed_(0),
|
||||
current_printer_(NULL),
|
||||
current_stream_(NULL) {
|
||||
JSObjectsCluster roots(JSObjectsCluster::ROOTS);
|
||||
ReferencesExtractor extractor(
|
||||
roots, this);
|
||||
ReferencesExtractor extractor(roots, this);
|
||||
Heap::IterateRoots(&extractor);
|
||||
}
|
||||
|
||||
|
||||
JSObjectsCluster RetainerHeapProfile::Clusterize(Object* obj) {
|
||||
if (obj->IsJSObject()) {
|
||||
String* constructor = JSObject::cast(obj)->constructor_name();
|
||||
// Differentiate Object and Array instances.
|
||||
if (constructor == Heap::Object_symbol() ||
|
||||
constructor == Heap::Array_symbol()) {
|
||||
return JSObjectsCluster(constructor, obj);
|
||||
} else {
|
||||
return JSObjectsCluster(constructor);
|
||||
}
|
||||
} else if (obj->IsString()) {
|
||||
return JSObjectsCluster(Heap::String_symbol());
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
return JSObjectsCluster();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void RetainerHeapProfile::StoreReference(
|
||||
const JSObjectsCluster& cluster,
|
||||
Object* ref) {
|
||||
JSObjectsCluster ref_cluster = Clusterize(ref);
|
||||
JSObjectsClusterTree::Locator ref_loc;
|
||||
void RetainerHeapProfile::StoreReference(const JSObjectsCluster& cluster,
|
||||
HeapObject* ref) {
|
||||
JSObjectsCluster ref_cluster = Clusterizer::Clusterize(ref);
|
||||
JSObjectsRetainerTree::Locator ref_loc;
|
||||
if (retainers_tree_.Insert(ref_cluster, &ref_loc)) {
|
||||
ref_loc.set_value(new JSObjectsClusterTree());
|
||||
}
|
||||
JSObjectsClusterTree* referenced_by = ref_loc.value();
|
||||
JSObjectsClusterTree::Locator obj_loc;
|
||||
referenced_by->Insert(cluster, &obj_loc);
|
||||
Clusterizer::InsertReferenceIntoTree(referenced_by, cluster);
|
||||
}
|
||||
|
||||
|
||||
void RetainerHeapProfile::CollectStats(HeapObject* obj) {
|
||||
if (obj->IsJSObject()) {
|
||||
const JSObjectsCluster cluster = Clusterize(JSObject::cast(obj));
|
||||
const JSObjectsCluster cluster = Clusterizer::Clusterize(obj);
|
||||
ReferencesExtractor extractor(cluster, this);
|
||||
obj->Iterate(&extractor);
|
||||
} else if (obj->IsJSGlobalPropertyCell()) {
|
||||
@ -408,50 +446,40 @@ void RetainerHeapProfile::PrintStats() {
|
||||
}
|
||||
|
||||
|
||||
void RetainerHeapProfile::Call(
|
||||
const JSObjectsCluster& cluster,
|
||||
JSObjectsClusterTree* tree) {
|
||||
ASSERT(current_printer_ != NULL);
|
||||
if (tree != NULL) {
|
||||
// First level of retainer graph.
|
||||
if (coarser_.HasAnEquivalent(cluster)) return;
|
||||
ASSERT(current_stream_ == NULL);
|
||||
HeapStringAllocator allocator;
|
||||
StringStream stream(&allocator);
|
||||
current_stream_ = &stream;
|
||||
cluster.Print(current_stream_);
|
||||
ASSERT(coarse_cluster_tree_ == NULL);
|
||||
coarse_cluster_tree_ = new JSObjectsClusterTree();
|
||||
retainers_printed_ = 0;
|
||||
tree->ForEach(this);
|
||||
coarse_cluster_tree_ = NULL;
|
||||
current_printer_->PrintRetainers(stream);
|
||||
current_stream_ = NULL;
|
||||
void RetainerHeapProfile::Call(const JSObjectsCluster& cluster,
|
||||
JSObjectsClusterTree* tree) {
|
||||
// First level of retainer graph.
|
||||
if (coarser_.HasAnEquivalent(cluster)) return;
|
||||
ASSERT(current_stream_ == NULL);
|
||||
HeapStringAllocator allocator;
|
||||
StringStream stream(&allocator);
|
||||
current_stream_ = &stream;
|
||||
ASSERT(coarse_cluster_tree_ == NULL);
|
||||
coarse_cluster_tree_ = new JSObjectsClusterTree();
|
||||
tree->ForEach(this);
|
||||
// Print aggregated counts and sizes.
|
||||
RetainerTreePrinter printer(current_stream_);
|
||||
coarse_cluster_tree_->ForEach(&printer);
|
||||
coarse_cluster_tree_ = NULL;
|
||||
current_printer_->PrintRetainers(cluster, stream);
|
||||
current_stream_ = NULL;
|
||||
}
|
||||
|
||||
|
||||
void RetainerHeapProfile::Call(const JSObjectsCluster& cluster,
|
||||
const NumberAndSizeInfo& number_and_size) {
|
||||
ASSERT(coarse_cluster_tree_ != NULL);
|
||||
ASSERT(current_stream_ != NULL);
|
||||
JSObjectsCluster eq = coarser_.GetCoarseEquivalent(cluster);
|
||||
if (eq.is_null()) {
|
||||
RetainerTreePrinter::Print(current_stream_, cluster, number_and_size);
|
||||
} else {
|
||||
// Second level of retainer graph.
|
||||
ASSERT(coarse_cluster_tree_ != NULL);
|
||||
ASSERT(current_stream_ != NULL);
|
||||
if (retainers_printed_ >= kMaxRetainersToPrint) {
|
||||
if (retainers_printed_ == kMaxRetainersToPrint) {
|
||||
// TODO(mnaganov): Print the exact count.
|
||||
current_stream_->Add(",...");
|
||||
++retainers_printed_; // avoid printing ellipsis next time.
|
||||
}
|
||||
return;
|
||||
}
|
||||
JSObjectsCluster eq = coarser_.GetCoarseEquivalent(cluster);
|
||||
if (eq.is_null()) {
|
||||
current_stream_->Put(',');
|
||||
cluster.Print(current_stream_);
|
||||
++retainers_printed_;
|
||||
} else {
|
||||
JSObjectsClusterTree::Locator loc;
|
||||
if (coarse_cluster_tree_->Insert(eq, &loc)) {
|
||||
current_stream_->Put(',');
|
||||
eq.Print(current_stream_);
|
||||
++retainers_printed_;
|
||||
}
|
||||
}
|
||||
// Aggregate counts and sizes for equivalent clusters.
|
||||
JSObjectsClusterTree::Locator loc;
|
||||
coarse_cluster_tree_->Insert(eq, &loc);
|
||||
NumberAndSizeInfo eq_number_and_size = loc.value();
|
||||
eq_number_and_size.increment_number(number_and_size.number());
|
||||
loc.set_value(eq_number_and_size);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,41 +46,11 @@ class HeapProfiler {
|
||||
};
|
||||
|
||||
|
||||
// ConstructorHeapProfile is responsible for gathering and logging
|
||||
// "constructor profile" of JS objects allocated on heap.
|
||||
// It is run during garbage collection cycle, thus it doesn't need
|
||||
// to use handles.
|
||||
class ConstructorHeapProfile BASE_EMBEDDED {
|
||||
public:
|
||||
ConstructorHeapProfile();
|
||||
virtual ~ConstructorHeapProfile() {}
|
||||
void CollectStats(HeapObject* obj);
|
||||
void PrintStats();
|
||||
// Used by ZoneSplayTree::ForEach. Made virtual to allow overriding in tests.
|
||||
virtual void Call(String* name, const NumberAndSizeInfo& number_and_size);
|
||||
|
||||
private:
|
||||
struct TreeConfig {
|
||||
typedef String* Key;
|
||||
typedef NumberAndSizeInfo Value;
|
||||
static const Key kNoKey;
|
||||
static const Value kNoValue;
|
||||
static int Compare(const Key& a, const Key& b) {
|
||||
// Strings are unique, so it is sufficient to compare their pointers.
|
||||
return a == b ? 0 : (a < b ? -1 : 1);
|
||||
}
|
||||
};
|
||||
typedef ZoneSplayTree<TreeConfig> JSObjectsInfoTree;
|
||||
|
||||
ZoneScope zscope_;
|
||||
JSObjectsInfoTree js_objects_info_tree_;
|
||||
};
|
||||
|
||||
|
||||
// JSObjectsCluster describes a group of JS objects that are
|
||||
// considered equivalent in terms of retainer profile.
|
||||
// considered equivalent in terms of a particular profile.
|
||||
class JSObjectsCluster BASE_EMBEDDED {
|
||||
public:
|
||||
// These special cases are used in retainer profile.
|
||||
enum SpecialCase {
|
||||
ROOTS = 1,
|
||||
GLOBAL_PROPERTY = 2
|
||||
@ -94,8 +64,8 @@ class JSObjectsCluster BASE_EMBEDDED {
|
||||
JSObjectsCluster(String* constructor, Object* instance)
|
||||
: constructor_(constructor), instance_(instance) {}
|
||||
|
||||
static int CompareConstructors(
|
||||
const JSObjectsCluster& a, const JSObjectsCluster& b) {
|
||||
static int CompareConstructors(const JSObjectsCluster& a,
|
||||
const JSObjectsCluster& b) {
|
||||
// Strings are unique, so it is sufficient to compare their pointers.
|
||||
return a.constructor_ == b.constructor_ ? 0
|
||||
: (a.constructor_ < b.constructor_ ? -1 : 1);
|
||||
@ -110,6 +80,7 @@ class JSObjectsCluster BASE_EMBEDDED {
|
||||
|
||||
bool is_null() const { return constructor_ == NULL; }
|
||||
bool can_be_coarsed() const { return instance_ != NULL; }
|
||||
String* constructor() const { return constructor_; }
|
||||
|
||||
void Print(StringStream* accumulator) const;
|
||||
// Allows null clusters to be printed.
|
||||
@ -133,14 +104,47 @@ class JSObjectsCluster BASE_EMBEDDED {
|
||||
};
|
||||
|
||||
|
||||
struct JSObjectsClusterTreeConfig;
|
||||
struct JSObjectsClusterTreeConfig {
|
||||
typedef JSObjectsCluster Key;
|
||||
typedef NumberAndSizeInfo Value;
|
||||
static const Key kNoKey;
|
||||
static const Value kNoValue;
|
||||
static int Compare(const Key& a, const Key& b) {
|
||||
return Key::Compare(a, b);
|
||||
}
|
||||
};
|
||||
typedef ZoneSplayTree<JSObjectsClusterTreeConfig> JSObjectsClusterTree;
|
||||
|
||||
// JSObjectsClusterTree is used to represent retainer graphs using
|
||||
// adjacency list form. That is, the first level maps JS object
|
||||
// clusters to adjacency lists, which in their turn are degenerate
|
||||
// JSObjectsClusterTrees (their values are NULLs.)
|
||||
struct JSObjectsClusterTreeConfig {
|
||||
|
||||
// ConstructorHeapProfile is responsible for gathering and logging
|
||||
// "constructor profile" of JS objects allocated on heap.
|
||||
// It is run during garbage collection cycle, thus it doesn't need
|
||||
// to use handles.
|
||||
class ConstructorHeapProfile BASE_EMBEDDED {
|
||||
public:
|
||||
ConstructorHeapProfile();
|
||||
virtual ~ConstructorHeapProfile() {}
|
||||
void CollectStats(HeapObject* obj);
|
||||
void PrintStats();
|
||||
// Used by ZoneSplayTree::ForEach. Made virtual to allow overriding in tests.
|
||||
virtual void Call(const JSObjectsCluster& cluster,
|
||||
const NumberAndSizeInfo& number_and_size);
|
||||
|
||||
private:
|
||||
ZoneScope zscope_;
|
||||
JSObjectsClusterTree js_objects_info_tree_;
|
||||
};
|
||||
|
||||
|
||||
// JSObjectsRetainerTree is used to represent retainer graphs using
|
||||
// adjacency list form:
|
||||
//
|
||||
// Cluster -> (Cluster -> NumberAndSizeInfo)
|
||||
//
|
||||
// Subordinate splay trees are stored by pointer. They are zone-allocated,
|
||||
// so it isn't needed to manage their lifetime.
|
||||
//
|
||||
struct JSObjectsRetainerTreeConfig {
|
||||
typedef JSObjectsCluster Key;
|
||||
typedef JSObjectsClusterTree* Value;
|
||||
static const Key kNoKey;
|
||||
@ -149,6 +153,7 @@ struct JSObjectsClusterTreeConfig {
|
||||
return Key::Compare(a, b);
|
||||
}
|
||||
};
|
||||
typedef ZoneSplayTree<JSObjectsRetainerTreeConfig> JSObjectsRetainerTree;
|
||||
|
||||
|
||||
class ClustersCoarser BASE_EMBEDDED {
|
||||
@ -156,7 +161,7 @@ class ClustersCoarser BASE_EMBEDDED {
|
||||
ClustersCoarser();
|
||||
|
||||
// Processes a given retainer graph.
|
||||
void Process(JSObjectsClusterTree* tree);
|
||||
void Process(JSObjectsRetainerTree* tree);
|
||||
|
||||
// Returns an equivalent cluster (can be the cluster itself).
|
||||
// If the given cluster doesn't have an equivalent, returns null cluster.
|
||||
@ -165,8 +170,10 @@ class ClustersCoarser BASE_EMBEDDED {
|
||||
// skipped in some cases.
|
||||
bool HasAnEquivalent(const JSObjectsCluster& cluster);
|
||||
|
||||
// Used by ZoneSplayTree::ForEach.
|
||||
// Used by JSObjectsRetainerTree::ForEach.
|
||||
void Call(const JSObjectsCluster& cluster, JSObjectsClusterTree* tree);
|
||||
void Call(const JSObjectsCluster& cluster,
|
||||
const NumberAndSizeInfo& number_and_size);
|
||||
|
||||
private:
|
||||
// Stores a list of back references for a cluster.
|
||||
@ -194,11 +201,11 @@ class ClustersCoarser BASE_EMBEDDED {
|
||||
};
|
||||
typedef ZoneSplayTree<ClusterEqualityConfig> EqualityTree;
|
||||
|
||||
static int ClusterBackRefsCmp(
|
||||
const ClusterBackRefs* a, const ClusterBackRefs* b) {
|
||||
static int ClusterBackRefsCmp(const ClusterBackRefs* a,
|
||||
const ClusterBackRefs* b) {
|
||||
return ClusterBackRefs::Compare(*a, *b);
|
||||
}
|
||||
int DoProcess(JSObjectsClusterTree* tree);
|
||||
int DoProcess(JSObjectsRetainerTree* tree);
|
||||
int FillEqualityTree();
|
||||
|
||||
static const int kInitialBackrefsListCapacity = 2;
|
||||
@ -211,7 +218,7 @@ class ClustersCoarser BASE_EMBEDDED {
|
||||
SimilarityList sim_list_;
|
||||
EqualityTree eq_tree_;
|
||||
ClusterBackRefs* current_pair_;
|
||||
JSObjectsClusterTree* current_set_;
|
||||
JSObjectsRetainerTree* current_set_;
|
||||
};
|
||||
|
||||
|
||||
@ -224,31 +231,31 @@ class RetainerHeapProfile BASE_EMBEDDED {
|
||||
class Printer {
|
||||
public:
|
||||
virtual ~Printer() {}
|
||||
virtual void PrintRetainers(const StringStream& retainers) = 0;
|
||||
virtual void PrintRetainers(const JSObjectsCluster& cluster,
|
||||
const StringStream& retainers) = 0;
|
||||
};
|
||||
|
||||
RetainerHeapProfile();
|
||||
void CollectStats(HeapObject* obj);
|
||||
void PrintStats();
|
||||
void DebugPrintStats(Printer* printer);
|
||||
void StoreReference(const JSObjectsCluster& cluster, Object* ref);
|
||||
void StoreReference(const JSObjectsCluster& cluster, HeapObject* ref);
|
||||
|
||||
private:
|
||||
JSObjectsCluster Clusterize(Object* obj);
|
||||
|
||||
// Limit on the number of retainers to be printed per cluster.
|
||||
static const int kMaxRetainersToPrint = 50;
|
||||
ZoneScope zscope_;
|
||||
JSObjectsClusterTree retainers_tree_;
|
||||
JSObjectsRetainerTree retainers_tree_;
|
||||
ClustersCoarser coarser_;
|
||||
// TODO(mnaganov): Use some helper class to hold these state variables.
|
||||
JSObjectsClusterTree* coarse_cluster_tree_;
|
||||
int retainers_printed_;
|
||||
Printer* current_printer_;
|
||||
StringStream* current_stream_;
|
||||
public:
|
||||
// Used by JSObjectsClusterTree::ForEach.
|
||||
// Used by JSObjectsRetainerTree::ForEach.
|
||||
void Call(const JSObjectsCluster& cluster, JSObjectsClusterTree* tree);
|
||||
void Call(const JSObjectsCluster& cluster,
|
||||
const NumberAndSizeInfo& number_and_size);
|
||||
};
|
||||
|
||||
|
||||
|
@ -310,6 +310,18 @@ void LogMessageBuilder::AppendDetailed(String* str, bool show_impl_info) {
|
||||
}
|
||||
|
||||
|
||||
void LogMessageBuilder::AppendStringPart(const char* str, int len) {
|
||||
if (pos_ + len > Log::kMessageBufferSize) {
|
||||
len = Log::kMessageBufferSize - pos_;
|
||||
ASSERT(len >= 0);
|
||||
if (len == 0) return;
|
||||
}
|
||||
strncpy(Log::message_buffer_ + pos_, str, len);
|
||||
pos_ += len;
|
||||
ASSERT(pos_ <= Log::kMessageBufferSize);
|
||||
}
|
||||
|
||||
|
||||
bool LogMessageBuilder::StoreInCompressor(LogRecordCompressor* compressor) {
|
||||
return compressor->Store(Vector<const char>(Log::message_buffer_, pos_));
|
||||
}
|
||||
|
@ -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);
|
||||
|
41
src/log.cc
41
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
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace i = v8::internal;
|
||||
using i::ClustersCoarser;
|
||||
using i::JSObjectsCluster;
|
||||
using i::JSObjectsRetainerTree;
|
||||
using i::JSObjectsClusterTree;
|
||||
using i::RetainerHeapProfile;
|
||||
|
||||
@ -31,9 +32,9 @@ class ConstructorHeapProfileTestHelper : public i::ConstructorHeapProfile {
|
||||
f_count_(0) {
|
||||
}
|
||||
|
||||
void Call(i::String* name, const i::NumberAndSizeInfo& number_and_size) {
|
||||
CHECK(name != NULL);
|
||||
if (f_name_->Equals(name)) {
|
||||
void Call(const JSObjectsCluster& cluster,
|
||||
const i::NumberAndSizeInfo& number_and_size) {
|
||||
if (f_name_->Equals(cluster.constructor())) {
|
||||
CHECK_EQ(f_count_, 0);
|
||||
f_count_ = number_and_size.number();
|
||||
CHECK_GT(f_count_, 0);
|
||||
@ -74,7 +75,7 @@ TEST(ConstructorProfile) {
|
||||
|
||||
|
||||
static JSObjectsCluster AddHeapObjectToTree(
|
||||
JSObjectsClusterTree* tree,
|
||||
JSObjectsRetainerTree* tree,
|
||||
i::String* constructor,
|
||||
int instance,
|
||||
JSObjectsCluster* ref1 = NULL,
|
||||
@ -82,10 +83,11 @@ static JSObjectsCluster AddHeapObjectToTree(
|
||||
JSObjectsCluster* ref3 = NULL) {
|
||||
JSObjectsCluster o(constructor, reinterpret_cast<i::Object*>(instance));
|
||||
JSObjectsClusterTree* o_tree = new JSObjectsClusterTree();
|
||||
JSObjectsClusterTree::Locator loc;
|
||||
if (ref1 != NULL) o_tree->Insert(*ref1, &loc);
|
||||
if (ref2 != NULL) o_tree->Insert(*ref2, &loc);
|
||||
if (ref3 != NULL) o_tree->Insert(*ref3, &loc);
|
||||
JSObjectsClusterTree::Locator o_loc;
|
||||
if (ref1 != NULL) o_tree->Insert(*ref1, &o_loc);
|
||||
if (ref2 != NULL) o_tree->Insert(*ref2, &o_loc);
|
||||
if (ref3 != NULL) o_tree->Insert(*ref3, &o_loc);
|
||||
JSObjectsRetainerTree::Locator loc;
|
||||
tree->Insert(o, &loc);
|
||||
loc.set_value(o_tree);
|
||||
return o;
|
||||
@ -137,7 +139,7 @@ TEST(ClustersCoarserSimple) {
|
||||
|
||||
i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
|
||||
|
||||
JSObjectsClusterTree tree;
|
||||
JSObjectsRetainerTree tree;
|
||||
JSObjectsCluster function(i::Heap::function_class_symbol());
|
||||
JSObjectsCluster a(*i::Factory::NewStringFromAscii(i::CStrVector("A")));
|
||||
JSObjectsCluster b(*i::Factory::NewStringFromAscii(i::CStrVector("B")));
|
||||
@ -176,7 +178,7 @@ TEST(ClustersCoarserMultipleConstructors) {
|
||||
|
||||
i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
|
||||
|
||||
JSObjectsClusterTree tree;
|
||||
JSObjectsRetainerTree tree;
|
||||
JSObjectsCluster function(i::Heap::function_class_symbol());
|
||||
|
||||
// o1 <- Function
|
||||
@ -207,7 +209,7 @@ TEST(ClustersCoarserPathsTraversal) {
|
||||
|
||||
i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
|
||||
|
||||
JSObjectsClusterTree tree;
|
||||
JSObjectsRetainerTree tree;
|
||||
|
||||
// On the following graph:
|
||||
//
|
||||
@ -257,7 +259,9 @@ class RetainerProfilePrinter : public RetainerHeapProfile::Printer {
|
||||
public:
|
||||
RetainerProfilePrinter() : stream_(&allocator_), lines_(100) {}
|
||||
|
||||
void PrintRetainers(const i::StringStream& retainers) {
|
||||
void PrintRetainers(const JSObjectsCluster& cluster,
|
||||
const i::StringStream& retainers) {
|
||||
cluster.Print(&stream_);
|
||||
stream_.Add("%s", *(retainers.ToCString()));
|
||||
stream_.Put('\0');
|
||||
}
|
||||
@ -304,8 +308,10 @@ TEST(RetainerProfile) {
|
||||
CompileAndRunScript(
|
||||
"function A() {}\n"
|
||||
"function B(x) { this.x = x; }\n"
|
||||
"function C(x) { this.x1 = x; this.x2 = x; }\n"
|
||||
"var a = new A();\n"
|
||||
"var b = new B(a);\n");
|
||||
"var b1 = new B(a), b2 = new B(a);\n"
|
||||
"var c = new C(a);");
|
||||
|
||||
RetainerHeapProfile ret_profile;
|
||||
i::AssertNoAllocation no_alloc;
|
||||
@ -316,8 +322,9 @@ TEST(RetainerProfile) {
|
||||
}
|
||||
RetainerProfilePrinter printer;
|
||||
ret_profile.DebugPrintStats(&printer);
|
||||
CHECK_EQ("(global property),B", printer.GetRetainers("A"));
|
||||
CHECK_EQ("(global property)", printer.GetRetainers("B"));
|
||||
CHECK_EQ("(global property);1,B;2,C;2", printer.GetRetainers("A"));
|
||||
CHECK_EQ("(global property);2", printer.GetRetainers("B"));
|
||||
CHECK_EQ("(global property);1", printer.GetRetainers("C"));
|
||||
}
|
||||
|
||||
#endif // ENABLE_LOGGING_AND_PROFILING
|
||||
|
Loading…
Reference in New Issue
Block a user