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:
mikhail.naganov@gmail.com 2009-09-18 12:05:18 +00:00
parent 0e63056cf5
commit 9b4c950963
7 changed files with 309 additions and 224 deletions

View File

@ -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);
}
}

View File

@ -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);
};

View File

@ -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_));
}

View File

@ -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);

View File

@ -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
}

View File

@ -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);

View File

@ -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