Commit d1f83dd9 authored by loislo@chromium.org's avatar loislo@chromium.org

This patch is introducing a way to grab heap stats.

The idea is to monitor the heap regulary and track each object in the heap.
With this data we will be able do draw heap usage diagram.
Where X is time and Y is the number of objects.

BUG=none
TEST=HeapSnapshotObjectsStats

Review URL: https://chromiumcodereview.appspot.com/10049002

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@11302 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 73e33992
......@@ -417,6 +417,33 @@ class V8EXPORT HeapProfiler {
HeapSnapshot::Type type = HeapSnapshot::kFull,
ActivityControl* control = NULL);
/**
* Starts tracking of heap objects population statistics. After calling
* this method, all heap objects relocations done by the garbage collector
* are being registered.
*/
static void StartHeapObjectsTracking();
/**
* Adds a new time interval entry to the aggregated statistics array. The
* time interval entry contains information on the current heap objects
* population size. The method also updates aggregated statistics and
* reports updates for all previous time intervals via the OutputStream
* object. Updates on each time interval are provided as pairs of time
* interval index and updated heap objects count.
*
* StartHeapObjectsTracking must be called before the first call to this
* method.
*/
static void PushHeapObjectsStats(OutputStream* stream);
/**
* Stops tracking of heap objects population statistics, cleans up all
* collected data. StartHeapObjectsTracking must be called again prior to
* calling PushHeapObjectsStats next time.
*/
static void StopHeapObjectsTracking();
/**
* Deletes all snapshots taken. All previously returned pointers to
* snapshots and their contents become invalid after this call.
......
......@@ -3741,7 +3741,8 @@ class V8EXPORT Locker {
class V8EXPORT OutputStream { // NOLINT
public:
enum OutputEncoding {
kAscii = 0 // 7-bit ASCII.
kAscii = 0, // 7-bit ASCII.
kUint32 = 1
};
enum WriteResult {
kContinue = 0,
......@@ -3760,6 +3761,12 @@ class V8EXPORT OutputStream { // NOLINT
* will not be called in case writing was aborted.
*/
virtual WriteResult WriteAsciiChunk(char* data, int size) = 0;
/**
* Writes the next chunk of heap stats data into the stream. Writing
* can be stopped by returning kAbort as function result. EndOfStream
* will not be called in case writing was aborted.
*/
virtual WriteResult WriteUint32Chunk(uint32_t* data, int count) = 0;
};
......
......@@ -6237,6 +6237,27 @@ const HeapSnapshot* HeapProfiler::TakeSnapshot(Handle<String> title,
}
void HeapProfiler::StartHeapObjectsTracking() {
i::Isolate* isolate = i::Isolate::Current();
IsDeadCheck(isolate, "v8::HeapProfiler::StartHeapObjectsTracking");
i::HeapProfiler::StartHeapObjectsTracking();
}
void HeapProfiler::StopHeapObjectsTracking() {
i::Isolate* isolate = i::Isolate::Current();
IsDeadCheck(isolate, "v8::HeapProfiler::StopHeapObjectsTracking");
i::HeapProfiler::StopHeapObjectsTracking();
}
void HeapProfiler::PushHeapObjectsStats(OutputStream* stream) {
i::Isolate* isolate = i::Isolate::Current();
IsDeadCheck(isolate, "v8::HeapProfiler::PushHeapObjectsStats");
return i::HeapProfiler::PushHeapObjectsStats(stream);
}
void HeapProfiler::DeleteAllSnapshots() {
i::Isolate* isolate = i::Isolate::Current();
IsDeadCheck(isolate, "v8::HeapProfiler::DeleteAllSnapshots");
......
......@@ -33,7 +33,6 @@
namespace v8 {
namespace internal {
HeapProfiler::HeapProfiler()
: snapshots_(new HeapSnapshotsCollection()),
next_snapshot_uid_(1) {
......@@ -86,6 +85,24 @@ HeapSnapshot* HeapProfiler::TakeSnapshot(String* name,
}
void HeapProfiler::StartHeapObjectsTracking() {
ASSERT(Isolate::Current()->heap_profiler() != NULL);
Isolate::Current()->heap_profiler()->StartHeapObjectsTrackingImpl();
}
void HeapProfiler::StopHeapObjectsTracking() {
ASSERT(Isolate::Current()->heap_profiler() != NULL);
Isolate::Current()->heap_profiler()->StopHeapObjectsTrackingImpl();
}
void HeapProfiler::PushHeapObjectsStats(v8::OutputStream* stream) {
ASSERT(Isolate::Current()->heap_profiler() != NULL);
return Isolate::Current()->heap_profiler()->PushHeapObjectsStatsImpl(stream);
}
void HeapProfiler::DefineWrapperClass(
uint16_t class_id, v8::HeapProfiler::WrapperInfoCallback callback) {
ASSERT(class_id != v8::HeapProfiler::kPersistentHandleNoClassId);
......@@ -136,6 +153,20 @@ HeapSnapshot* HeapProfiler::TakeSnapshotImpl(String* name,
return TakeSnapshotImpl(snapshots_->names()->GetName(name), type, control);
}
void HeapProfiler::StartHeapObjectsTrackingImpl() {
snapshots_->StartHeapObjectsTracking();
}
void HeapProfiler::PushHeapObjectsStatsImpl(OutputStream* stream) {
snapshots_->PushHeapObjectsStats(stream);
}
void HeapProfiler::StopHeapObjectsTrackingImpl() {
snapshots_->StopHeapObjectsTracking();
}
int HeapProfiler::GetSnapshotsCount() {
HeapProfiler* profiler = Isolate::Current()->heap_profiler();
......
......@@ -44,8 +44,6 @@ class HeapSnapshotsCollection;
} \
} while (false)
// The HeapProfiler writes data to the log files, which can be postprocessed
// to generate .hp files for use by the GHC/Valgrind tool hp2ps.
class HeapProfiler {
public:
static void SetUp();
......@@ -57,6 +55,10 @@ class HeapProfiler {
static HeapSnapshot* TakeSnapshot(String* name,
int type,
v8::ActivityControl* control);
static void StartHeapObjectsTracking();
static void StopHeapObjectsTracking();
static void PushHeapObjectsStats(OutputStream* stream);
static int GetSnapshotsCount();
static HeapSnapshot* GetSnapshot(int index);
static HeapSnapshot* FindSnapshot(unsigned uid);
......@@ -84,6 +86,10 @@ class HeapProfiler {
v8::ActivityControl* control);
void ResetSnapshots();
void StartHeapObjectsTrackingImpl();
void StopHeapObjectsTrackingImpl();
void PushHeapObjectsStatsImpl(OutputStream* stream);
HeapSnapshotsCollection* snapshots_;
unsigned next_snapshot_uid_;
List<v8::HeapProfiler::WrapperInfoCallback> wrapper_callbacks_;
......
......@@ -1406,6 +1406,82 @@ SnapshotObjectId HeapObjectsMap::FindEntry(Address addr) {
}
SnapshotObjectId HeapObjectsMap::FindOrAddEntry(Address addr) {
ASSERT(static_cast<uint32_t>(entries_->length()) > entries_map_.occupancy());
HashMap::Entry* entry = entries_map_.Lookup(addr, AddressHash(addr), true);
if (entry->value != NULL) {
int entry_index =
static_cast<int>(reinterpret_cast<intptr_t>(entry->value));
EntryInfo& entry_info = entries_->at(entry_index);
entry_info.accessed = true;
return entry_info.id;
}
entry->value = reinterpret_cast<void*>(entries_->length());
SnapshotObjectId id = next_id_;
next_id_ += kObjectIdStep;
entries_->Add(EntryInfo(id, addr));
ASSERT(static_cast<uint32_t>(entries_->length()) > entries_map_.occupancy());
return id;
}
void HeapObjectsMap::StopHeapObjectsTracking() {
time_intervals_.Clear();
}
void HeapObjectsMap::UpdateHeapObjectsMap() {
HEAP->CollectAllGarbage(Heap::kMakeHeapIterableMask,
"HeapSnapshotsCollection::UpdateHeapObjectsMap");
HeapIterator iterator(HeapIterator::kFilterUnreachable);
for (HeapObject* obj = iterator.next();
obj != NULL;
obj = iterator.next()) {
FindOrAddEntry(obj->address());
}
initial_fill_mode_ = false;
RemoveDeadEntries();
}
void HeapObjectsMap::PushHeapObjectsStats(OutputStream* stream) {
UpdateHeapObjectsMap();
time_intervals_.Add(TimeInterval(next_id_));
int prefered_chunk_size = stream->GetChunkSize();
List<uint32_t> stats_buffer;
ASSERT(!entries_->is_empty());
EntryInfo* entry_info = &entries_->first();
EntryInfo* end_entry_info = &entries_->last() + 1;
for (int time_interval_index = 0;
time_interval_index < time_intervals_.length();
++time_interval_index) {
TimeInterval& time_interval = time_intervals_[time_interval_index];
SnapshotObjectId time_interval_id = time_interval.id;
uint32_t entries_count = 0;
while (entry_info < end_entry_info && entry_info->id < time_interval_id) {
++entries_count;
++entry_info;
}
if (time_interval.count != entries_count) {
stats_buffer.Add(time_interval_index);
stats_buffer.Add(time_interval.count = entries_count);
if (stats_buffer.length() >= prefered_chunk_size) {
OutputStream::WriteResult result = stream->WriteUint32Chunk(
&stats_buffer.first(), stats_buffer.length());
if (result == OutputStream::kAbort) return;
stats_buffer.Clear();
}
}
}
ASSERT(entry_info == end_entry_info);
if (!stats_buffer.is_empty()) {
OutputStream::WriteResult result =
stream->WriteUint32Chunk(&stats_buffer.first(), stats_buffer.length());
if (result == OutputStream::kAbort) return;
}
stream->EndOfStream();
}
void HeapObjectsMap::RemoveDeadEntries() {
ASSERT(entries_->length() > 0 &&
entries_->at(0).id == 0 &&
......
......@@ -710,6 +710,9 @@ class HeapObjectsMap {
return next_id_ - kObjectIdStep;
}
void StopHeapObjectsTracking();
void PushHeapObjectsStats(OutputStream* stream);
static SnapshotObjectId GenerateId(v8::RetainedObjectInfo* info);
static inline SnapshotObjectId GetNthGcSubrootId(int delta);
......@@ -730,9 +733,16 @@ class HeapObjectsMap {
Address addr;
bool accessed;
};
struct TimeInterval {
explicit TimeInterval(SnapshotObjectId id) : id(id), count(0) { }
SnapshotObjectId id;
uint32_t count;
};
void AddEntry(Address addr, SnapshotObjectId id);
SnapshotObjectId FindEntry(Address addr);
SnapshotObjectId FindOrAddEntry(Address addr);
void UpdateHeapObjectsMap();
void RemoveDeadEntries();
static bool AddressesMatch(void* key1, void* key2) {
......@@ -749,6 +759,7 @@ class HeapObjectsMap {
SnapshotObjectId next_id_;
HashMap entries_map_;
List<EntryInfo>* entries_;
List<TimeInterval> time_intervals_;
DISALLOW_COPY_AND_ASSIGN(HeapObjectsMap);
};
......@@ -760,6 +771,11 @@ class HeapSnapshotsCollection {
~HeapSnapshotsCollection();
bool is_tracking_objects() { return is_tracking_objects_; }
void PushHeapObjectsStats(OutputStream* stream) {
return ids_.PushHeapObjectsStats(stream);
}
void StartHeapObjectsTracking() { is_tracking_objects_ = true; }
void StopHeapObjectsTracking() { ids_.StopHeapObjectsTracking(); }
HeapSnapshot* NewSnapshot(
HeapSnapshot::Type type, const char* name, unsigned uid);
......
......@@ -557,9 +557,14 @@ class TestJSONStream : public v8::OutputStream {
memcpy(chunk.start(), buffer, chars_written);
return kContinue;
}
virtual WriteResult WriteUint32Chunk(uint32_t* buffer, int chars_written) {
ASSERT(false);
return kAbort;
}
void WriteTo(i::Vector<char> dest) { buffer_.WriteTo(dest); }
int eos_signaled() { return eos_signaled_; }
int size() { return buffer_.size(); }
private:
i::Collector<char> buffer_;
int eos_signaled_;
......@@ -691,6 +696,153 @@ TEST(HeapSnapshotJSONSerializationAborting) {
CHECK_EQ(0, stream.eos_signaled());
}
namespace {
class TestStatsStream : public v8::OutputStream {
public:
TestStatsStream()
: eos_signaled_(0),
numbers_written_(0),
entries_count_(0),
intervals_count_(0),
first_interval_index_(-1) { }
TestStatsStream(const TestStatsStream& stream)
: eos_signaled_(stream.eos_signaled_),
numbers_written_(stream.numbers_written_),
entries_count_(stream.entries_count_),
intervals_count_(stream.intervals_count_),
first_interval_index_(stream.first_interval_index_) { }
virtual ~TestStatsStream() {}
virtual void EndOfStream() { ++eos_signaled_; }
virtual WriteResult WriteAsciiChunk(char* buffer, int chars_written) {
ASSERT(false);
return kAbort;
}
virtual WriteResult WriteUint32Chunk(uint32_t* buffer, int numbers_written) {
++intervals_count_;
ASSERT(numbers_written);
numbers_written_ += numbers_written;
entries_count_ = 0;
if (first_interval_index_ == -1 && numbers_written != 0)
first_interval_index_ = buffer[0];
for (int i = 1; i < numbers_written; i += 2)
entries_count_ += buffer[i];
return kContinue;
}
int eos_signaled() { return eos_signaled_; }
int numbers_written() { return numbers_written_; }
uint32_t entries_count() const { return entries_count_; }
int intervals_count() const { return intervals_count_; }
int first_interval_index() const { return first_interval_index_; }
private:
int eos_signaled_;
int numbers_written_;
uint32_t entries_count_;
int intervals_count_;
int first_interval_index_;
};
} // namespace
static TestStatsStream GetHeapStatsUpdate() {
TestStatsStream stream;
v8::HeapProfiler::PushHeapObjectsStats(&stream);
CHECK_EQ(1, stream.eos_signaled());
return stream;
}
TEST(HeapSnapshotObjectsStats) {
v8::HandleScope scope;
LocalContext env;
v8::HeapProfiler::StartHeapObjectsTracking();
// We have to call GC 5 times. In other case the garbage will be
// the reason of flakiness.
for (int i = 0; i < 5; ++i) {
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
}
{
// Single chunk of data expected in update. Initial data.
TestStatsStream stats_update = GetHeapStatsUpdate();
CHECK_EQ(1, stats_update.intervals_count());
CHECK_EQ(2, stats_update.numbers_written());
CHECK_EQ(0, stats_update.first_interval_index());
}
// No data expected in update because nothing has happened.
CHECK_EQ(0, GetHeapStatsUpdate().numbers_written());
{
v8::HandleScope inner_scope_1;
v8::Local<v8::String> string1 = v8_str("string1");
{
// Single chunk of data with one new entry expected in update.
TestStatsStream stats_update = GetHeapStatsUpdate();
CHECK_EQ(1, stats_update.intervals_count());
CHECK_EQ(2, stats_update.numbers_written());
CHECK_EQ(1, stats_update.entries_count());
CHECK_EQ(2, stats_update.first_interval_index());
}
// No data expected in update because nothing happened.
CHECK_EQ(0, GetHeapStatsUpdate().numbers_written());
{
v8::HandleScope inner_scope_2;
v8::Local<v8::String> string2 = v8_str("string2");
{
v8::HandleScope inner_scope_3;
v8::Handle<v8::String> string3 = v8::String::New("string3");
v8::Handle<v8::String> string4 = v8::String::New("string4");
{
// Single chunk of data with three new entries expected in update.
TestStatsStream stats_update = GetHeapStatsUpdate();
CHECK_EQ(1, stats_update.intervals_count());
CHECK_EQ(2, stats_update.numbers_written());
CHECK_EQ(3, stats_update.entries_count());
CHECK_EQ(4, stats_update.first_interval_index());
}
}
{
// Single chunk of data with two left entries expected in update.
TestStatsStream stats_update = GetHeapStatsUpdate();
CHECK_EQ(1, stats_update.intervals_count());
CHECK_EQ(2, stats_update.numbers_written());
CHECK_EQ(1, stats_update.entries_count());
// Two strings from forth interval were released.
CHECK_EQ(4, stats_update.first_interval_index());
}
}
{
// Single chunk of data with 0 left entries expected in update.
TestStatsStream stats_update = GetHeapStatsUpdate();
CHECK_EQ(1, stats_update.intervals_count());
CHECK_EQ(2, stats_update.numbers_written());
CHECK_EQ(0, stats_update.entries_count());
// The last string from forth interval was released.
CHECK_EQ(4, stats_update.first_interval_index());
}
}
{
// Single chunk of data with 0 left entries expected in update.
TestStatsStream stats_update = GetHeapStatsUpdate();
CHECK_EQ(1, stats_update.intervals_count());
CHECK_EQ(2, stats_update.numbers_written());
CHECK_EQ(0, stats_update.entries_count());
// The only string from the second interval was released.
CHECK_EQ(2, stats_update.first_interval_index());
}
v8::HeapProfiler::StopHeapObjectsTracking();
}
static void CheckChildrenIds(const v8::HeapSnapshot* snapshot,
const v8::HeapGraphNode* node,
......
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