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
parent 0e63056c
This diff is collapsed.
......@@ -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);
......
......@@ -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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment