The new JS Heap Profiler: the main part.
There is no test in this patch, because the test uses V8 API, which is coming in a separate change list. Review URL: http://codereview.chromium.org/2722005 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@4848 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
parent
a9d252749f
commit
7e13119880
@ -130,6 +130,17 @@ CodeEntry* ProfileGenerator::EntryForVMState(StateTag tag) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<class Visitor>
|
||||
void HeapEntriesMap::Apply(Visitor* visitor) {
|
||||
for (HashMap::Entry* p = entries_.Start();
|
||||
p != NULL;
|
||||
p = entries_.Next(p)) {
|
||||
if (!IsAlias(p->value))
|
||||
visitor->Apply(reinterpret_cast<HeapEntry*>(p->value));
|
||||
}
|
||||
}
|
||||
|
||||
} } // namespace v8::internal
|
||||
|
||||
#endif // ENABLE_LOGGING_AND_PROFILING
|
||||
|
@ -29,11 +29,12 @@
|
||||
|
||||
#include "v8.h"
|
||||
#include "global-handles.h"
|
||||
#include "scopeinfo.h"
|
||||
#include "top.h"
|
||||
#include "zone-inl.h"
|
||||
|
||||
#include "profile-generator-inl.h"
|
||||
|
||||
#include "../include/v8-profiler.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
@ -811,6 +812,794 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
|
||||
profiles_->AddPathToCurrentProfiles(entries);
|
||||
}
|
||||
|
||||
|
||||
HeapGraphEdge::HeapGraphEdge(Type type,
|
||||
const char* name,
|
||||
HeapEntry* from,
|
||||
HeapEntry* to)
|
||||
: type_(type), name_(name), from_(from), to_(to) {
|
||||
ASSERT(type_ == CONTEXT_VARIABLE || type_ == PROPERTY);
|
||||
}
|
||||
|
||||
|
||||
HeapGraphEdge::HeapGraphEdge(int index,
|
||||
HeapEntry* from,
|
||||
HeapEntry* to)
|
||||
: type_(ELEMENT), index_(index), from_(from), to_(to) {
|
||||
}
|
||||
|
||||
|
||||
static void DeleteHeapGraphEdge(HeapGraphEdge** edge_ptr) {
|
||||
delete *edge_ptr;
|
||||
}
|
||||
|
||||
|
||||
static void DeleteHeapGraphPath(HeapGraphPath** path_ptr) {
|
||||
delete *path_ptr;
|
||||
}
|
||||
|
||||
|
||||
HeapEntry::~HeapEntry() {
|
||||
children_.Iterate(DeleteHeapGraphEdge);
|
||||
retaining_paths_.Iterate(DeleteHeapGraphPath);
|
||||
}
|
||||
|
||||
|
||||
void HeapEntry::SetClosureReference(const char* name, HeapEntry* entry) {
|
||||
HeapGraphEdge* edge =
|
||||
new HeapGraphEdge(HeapGraphEdge::CONTEXT_VARIABLE, name, this, entry);
|
||||
children_.Add(edge);
|
||||
entry->retainers_.Add(edge);
|
||||
}
|
||||
|
||||
|
||||
void HeapEntry::SetElementReference(int index, HeapEntry* entry) {
|
||||
HeapGraphEdge* edge = new HeapGraphEdge(index, this, entry);
|
||||
children_.Add(edge);
|
||||
entry->retainers_.Add(edge);
|
||||
}
|
||||
|
||||
|
||||
void HeapEntry::SetPropertyReference(const char* name, HeapEntry* entry) {
|
||||
HeapGraphEdge* edge =
|
||||
new HeapGraphEdge(HeapGraphEdge::PROPERTY, name, this, entry);
|
||||
children_.Add(edge);
|
||||
entry->retainers_.Add(edge);
|
||||
}
|
||||
|
||||
|
||||
void HeapEntry::SetAutoIndexReference(HeapEntry* entry) {
|
||||
SetElementReference(next_auto_index_++, entry);
|
||||
}
|
||||
|
||||
|
||||
int HeapEntry::TotalSize() {
|
||||
return total_size_ != kUnknownSize ? total_size_ : CalculateTotalSize();
|
||||
}
|
||||
|
||||
|
||||
int HeapEntry::NonSharedTotalSize() {
|
||||
return non_shared_total_size_ != kUnknownSize ?
|
||||
non_shared_total_size_ : CalculateNonSharedTotalSize();
|
||||
}
|
||||
|
||||
|
||||
int HeapEntry::CalculateTotalSize() {
|
||||
snapshot_->ClearPaint();
|
||||
List<HeapEntry*> list(10);
|
||||
list.Add(this);
|
||||
total_size_ = self_size_;
|
||||
this->PaintReachable();
|
||||
while (!list.is_empty()) {
|
||||
HeapEntry* entry = list.RemoveLast();
|
||||
const int children_count = entry->children_.length();
|
||||
for (int i = 0; i < children_count; ++i) {
|
||||
HeapEntry* child = entry->children_[i]->to();
|
||||
if (!child->painted_reachable()) {
|
||||
list.Add(child);
|
||||
child->PaintReachable();
|
||||
total_size_ += child->self_size_;
|
||||
}
|
||||
}
|
||||
}
|
||||
return total_size_;
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
class NonSharedSizeCalculator {
|
||||
public:
|
||||
NonSharedSizeCalculator()
|
||||
: non_shared_total_size_(0) {
|
||||
}
|
||||
|
||||
int non_shared_total_size() const { return non_shared_total_size_; }
|
||||
|
||||
void Apply(HeapEntry* entry) {
|
||||
if (entry->painted_reachable()) {
|
||||
non_shared_total_size_ += entry->self_size();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int non_shared_total_size_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
int HeapEntry::CalculateNonSharedTotalSize() {
|
||||
// To calculate non-shared total size, first we paint all reachable
|
||||
// nodes in one color, then we paint all nodes reachable from other
|
||||
// nodes with a different color. Then we consider only nodes painted
|
||||
// with the first color for caclulating the total size.
|
||||
snapshot_->ClearPaint();
|
||||
List<HeapEntry*> list(10);
|
||||
list.Add(this);
|
||||
this->PaintReachable();
|
||||
while (!list.is_empty()) {
|
||||
HeapEntry* entry = list.RemoveLast();
|
||||
const int children_count = entry->children_.length();
|
||||
for (int i = 0; i < children_count; ++i) {
|
||||
HeapEntry* child = entry->children_[i]->to();
|
||||
if (!child->painted_reachable()) {
|
||||
list.Add(child);
|
||||
child->PaintReachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<HeapEntry*> list2(10);
|
||||
if (this != snapshot_->root()) {
|
||||
list2.Add(snapshot_->root());
|
||||
snapshot_->root()->PaintReachableFromOthers();
|
||||
}
|
||||
while (!list2.is_empty()) {
|
||||
HeapEntry* entry = list2.RemoveLast();
|
||||
const int children_count = entry->children_.length();
|
||||
for (int i = 0; i < children_count; ++i) {
|
||||
HeapEntry* child = entry->children_[i]->to();
|
||||
if (child != this && child->not_painted_reachable_from_others()) {
|
||||
list2.Add(child);
|
||||
child->PaintReachableFromOthers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NonSharedSizeCalculator calculator;
|
||||
snapshot_->IterateEntries(&calculator);
|
||||
return calculator.non_shared_total_size();
|
||||
}
|
||||
|
||||
|
||||
class CachedHeapGraphPath {
|
||||
public:
|
||||
CachedHeapGraphPath()
|
||||
: nodes_(NodesMatch) { }
|
||||
CachedHeapGraphPath(const CachedHeapGraphPath& src)
|
||||
: nodes_(NodesMatch, &HashMap::DefaultAllocator, src.nodes_.capacity()),
|
||||
path_(src.path_.length() + 1) {
|
||||
for (HashMap::Entry* p = src.nodes_.Start();
|
||||
p != NULL;
|
||||
p = src.nodes_.Next(p)) {
|
||||
nodes_.Lookup(p->key, p->hash, true);
|
||||
}
|
||||
path_.AddAll(src.path_);
|
||||
}
|
||||
void Add(HeapGraphEdge* edge) {
|
||||
nodes_.Lookup(edge->to(), Hash(edge->to()), true);
|
||||
path_.Add(edge);
|
||||
}
|
||||
bool ContainsNode(HeapEntry* node) {
|
||||
return nodes_.Lookup(node, Hash(node), false) != NULL;
|
||||
}
|
||||
const List<HeapGraphEdge*>* path() const { return &path_; }
|
||||
|
||||
private:
|
||||
static uint32_t Hash(HeapEntry* entry) {
|
||||
return static_cast<uint32_t>(reinterpret_cast<intptr_t>(entry));
|
||||
}
|
||||
static bool NodesMatch(void* key1, void* key2) { return key1 == key2; }
|
||||
|
||||
HashMap nodes_;
|
||||
List<HeapGraphEdge*> path_;
|
||||
};
|
||||
|
||||
|
||||
const List<HeapGraphPath*>* HeapEntry::GetRetainingPaths() {
|
||||
if (retaining_paths_.length() == 0 && retainers_.length() != 0) {
|
||||
CachedHeapGraphPath path;
|
||||
FindRetainingPaths(this, &path);
|
||||
}
|
||||
return &retaining_paths_;
|
||||
}
|
||||
|
||||
|
||||
void HeapEntry::FindRetainingPaths(HeapEntry* node,
|
||||
CachedHeapGraphPath* prev_path) {
|
||||
for (int i = 0; i < node->retainers_.length(); ++i) {
|
||||
HeapGraphEdge* ret_edge = node->retainers_[i];
|
||||
if (prev_path->ContainsNode(ret_edge->from())) continue;
|
||||
if (ret_edge->from() != snapshot_->root()) {
|
||||
CachedHeapGraphPath path(*prev_path);
|
||||
path.Add(ret_edge);
|
||||
FindRetainingPaths(ret_edge->from(), &path);
|
||||
} else {
|
||||
HeapGraphPath* ret_path = new HeapGraphPath(*prev_path->path());
|
||||
ret_path->Set(0, ret_edge);
|
||||
retaining_paths_.Add(ret_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void RemoveEdge(List<HeapGraphEdge*>* list, HeapGraphEdge* edge) {
|
||||
for (int i = 0; i < list->length(); ) {
|
||||
if (list->at(i) == edge) {
|
||||
list->Remove(i);
|
||||
return;
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
|
||||
void HeapEntry::RemoveChild(HeapGraphEdge* edge) {
|
||||
RemoveEdge(&children_, edge);
|
||||
delete edge;
|
||||
}
|
||||
|
||||
|
||||
void HeapEntry::RemoveRetainer(HeapGraphEdge* edge) {
|
||||
RemoveEdge(&retainers_, edge);
|
||||
}
|
||||
|
||||
|
||||
void HeapEntry::CutEdges() {
|
||||
for (int i = 0; i < children_.length(); ++i) {
|
||||
HeapGraphEdge* edge = children_[i];
|
||||
edge->to()->RemoveRetainer(edge);
|
||||
}
|
||||
children_.Iterate(DeleteHeapGraphEdge);
|
||||
children_.Clear();
|
||||
|
||||
for (int i = 0; i < retainers_.length(); ++i) {
|
||||
HeapGraphEdge* edge = retainers_[i];
|
||||
edge->from()->RemoveChild(edge);
|
||||
}
|
||||
retainers_.Clear();
|
||||
}
|
||||
|
||||
|
||||
void HeapEntry::Print(int max_depth, int indent) {
|
||||
OS::Print("%6d %6d %6d", self_size_, TotalSize(), NonSharedTotalSize());
|
||||
if (type_ != STRING) {
|
||||
OS::Print("%s %.40s\n", TypeAsString(), name_);
|
||||
} else {
|
||||
OS::Print("\"");
|
||||
const char* c = name_;
|
||||
while (*c && (c - name_) <= 40) {
|
||||
if (*c != '\n')
|
||||
OS::Print("%c", *c);
|
||||
else
|
||||
OS::Print("\\n");
|
||||
++c;
|
||||
}
|
||||
OS::Print("\"\n");
|
||||
}
|
||||
if (--max_depth == 0) return;
|
||||
const int children_count = children_.length();
|
||||
for (int i = 0; i < children_count; ++i) {
|
||||
HeapGraphEdge* edge = children_[i];
|
||||
switch (edge->type()) {
|
||||
case HeapGraphEdge::CONTEXT_VARIABLE:
|
||||
OS::Print(" %*c #%s: ", indent, ' ', edge->name());
|
||||
break;
|
||||
case HeapGraphEdge::ELEMENT:
|
||||
OS::Print(" %*c %d: ", indent, ' ', edge->index());
|
||||
break;
|
||||
case HeapGraphEdge::PROPERTY:
|
||||
OS::Print(" %*c %s: ", indent, ' ', edge->name());
|
||||
break;
|
||||
default:
|
||||
OS::Print("!!! unknown edge type: %d ", edge->type());
|
||||
}
|
||||
edge->to()->Print(max_depth, indent + 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char* HeapEntry::TypeAsString() {
|
||||
switch (type_) {
|
||||
case INTERNAL: return "/internal/";
|
||||
case JS_OBJECT: return "/object/";
|
||||
case CLOSURE: return "/closure/";
|
||||
case STRING: return "/string/";
|
||||
case CODE: return "/code/";
|
||||
case ARRAY: return "/array/";
|
||||
default: return "???";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
HeapGraphPath::HeapGraphPath(const List<HeapGraphEdge*>& path)
|
||||
: path_(path.length() + 1) {
|
||||
Add(NULL);
|
||||
for (int i = path.length() - 1; i >= 0; --i) {
|
||||
Add(path[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void HeapGraphPath::Print() {
|
||||
path_[0]->from()->Print(1, 0);
|
||||
for (int i = 0; i < path_.length(); ++i) {
|
||||
OS::Print(" -> ");
|
||||
HeapGraphEdge* edge = path_[i];
|
||||
switch (edge->type()) {
|
||||
case HeapGraphEdge::CONTEXT_VARIABLE:
|
||||
OS::Print("[#%s] ", edge->name());
|
||||
break;
|
||||
case HeapGraphEdge::ELEMENT:
|
||||
OS::Print("[%d] ", edge->index());
|
||||
break;
|
||||
case HeapGraphEdge::PROPERTY:
|
||||
OS::Print("[%s] ", edge->name());
|
||||
break;
|
||||
default:
|
||||
OS::Print("!!! unknown edge type: %d ", edge->type());
|
||||
}
|
||||
edge->to()->Print(1, 0);
|
||||
}
|
||||
OS::Print("\n");
|
||||
}
|
||||
|
||||
|
||||
class IndexedReferencesExtractor : public ObjectVisitor {
|
||||
public:
|
||||
IndexedReferencesExtractor(HeapSnapshot* snapshot, HeapEntry* parent)
|
||||
: snapshot_(snapshot),
|
||||
parent_(parent) {
|
||||
}
|
||||
|
||||
void VisitPointer(Object** o) {
|
||||
if (!(*o)->IsHeapObject()) return;
|
||||
HeapEntry* entry = snapshot_->GetEntry(HeapObject::cast(*o));
|
||||
if (entry != NULL) {
|
||||
parent_->SetAutoIndexReference(entry);
|
||||
}
|
||||
}
|
||||
|
||||
void VisitPointers(Object** start, Object** end) {
|
||||
for (Object** p = start; p < end; p++) VisitPointer(p);
|
||||
}
|
||||
|
||||
private:
|
||||
HeapSnapshot* snapshot_;
|
||||
HeapEntry* parent_;
|
||||
};
|
||||
|
||||
|
||||
HeapEntriesMap::HeapEntriesMap()
|
||||
: entries_(HeapObjectsMatch) {
|
||||
}
|
||||
|
||||
|
||||
HeapEntriesMap::~HeapEntriesMap() {
|
||||
for (HashMap::Entry* p = entries_.Start();
|
||||
p != NULL;
|
||||
p = entries_.Next(p)) {
|
||||
if (!IsAlias(p->value)) delete reinterpret_cast<HeapEntry*>(p->value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void HeapEntriesMap::Alias(HeapObject* object, HeapEntry* entry) {
|
||||
HashMap::Entry* cache_entry = entries_.Lookup(object, Hash(object), true);
|
||||
if (cache_entry->value == NULL)
|
||||
cache_entry->value = reinterpret_cast<void*>(
|
||||
reinterpret_cast<intptr_t>(entry) | kAliasTag);
|
||||
}
|
||||
|
||||
|
||||
void HeapEntriesMap::Apply(void (HeapEntry::*Func)(void)) {
|
||||
for (HashMap::Entry* p = entries_.Start();
|
||||
p != NULL;
|
||||
p = entries_.Next(p)) {
|
||||
if (!IsAlias(p->value)) (reinterpret_cast<HeapEntry*>(p->value)->*Func)();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
HeapEntry* HeapEntriesMap::Map(HeapObject* object) {
|
||||
HashMap::Entry* cache_entry = entries_.Lookup(object, Hash(object), false);
|
||||
return cache_entry != NULL ?
|
||||
reinterpret_cast<HeapEntry*>(
|
||||
reinterpret_cast<intptr_t>(cache_entry->value) & (~kAliasTag)) : NULL;
|
||||
}
|
||||
|
||||
|
||||
void HeapEntriesMap::Pair(HeapObject* object, HeapEntry* entry) {
|
||||
HashMap::Entry* cache_entry = entries_.Lookup(object, Hash(object), true);
|
||||
ASSERT(cache_entry->value == NULL);
|
||||
cache_entry->value = entry;
|
||||
}
|
||||
|
||||
|
||||
HeapSnapshot::HeapSnapshot(HeapSnapshotsCollection* collection,
|
||||
const char* title,
|
||||
unsigned uid)
|
||||
: collection_(collection),
|
||||
title_(title),
|
||||
uid_(uid),
|
||||
root_(this) {
|
||||
}
|
||||
|
||||
|
||||
void HeapSnapshot::ClearPaint() {
|
||||
root_.ClearPaint();
|
||||
entries_.Apply(&HeapEntry::ClearPaint);
|
||||
}
|
||||
|
||||
|
||||
HeapEntry* HeapSnapshot::GetEntry(Object* obj) {
|
||||
if (!obj->IsHeapObject()) return NULL;
|
||||
HeapObject* object = HeapObject::cast(obj);
|
||||
|
||||
{
|
||||
HeapEntry* existing = FindEntry(object);
|
||||
if (existing != NULL) return existing;
|
||||
}
|
||||
|
||||
// Add new entry.
|
||||
if (object->IsJSFunction()) {
|
||||
JSFunction* func = JSFunction::cast(object);
|
||||
SharedFunctionInfo* shared = func->shared();
|
||||
String* name = String::cast(shared->name())->length() > 0 ?
|
||||
String::cast(shared->name()) : shared->inferred_name();
|
||||
return AddEntry(object, HeapEntry::CLOSURE, collection_->GetName(name));
|
||||
} else if (object->IsJSObject()) {
|
||||
return AddEntry(object,
|
||||
HeapEntry::JS_OBJECT,
|
||||
collection_->GetName(
|
||||
JSObject::cast(object)->constructor_name()));
|
||||
} else if (object->IsJSGlobalPropertyCell()) {
|
||||
HeapEntry* value = GetEntry(JSGlobalPropertyCell::cast(object)->value());
|
||||
// If GPC references an object that we have interest in, add the object.
|
||||
// We don't store HeapEntries for GPCs. Instead, we make our hash map
|
||||
// to point to object's HeapEntry by GPCs address.
|
||||
if (value != NULL) AddEntryAlias(object, value);
|
||||
return value;
|
||||
} else if (object->IsString()) {
|
||||
return AddEntry(object,
|
||||
HeapEntry::STRING,
|
||||
collection_->GetName(String::cast(object)));
|
||||
} else if (object->IsCode()
|
||||
|| object->IsSharedFunctionInfo()
|
||||
|| object->IsScript()) {
|
||||
return AddEntry(object, HeapEntry::CODE);
|
||||
} else if (object->IsFixedArray()) {
|
||||
return AddEntry(object, HeapEntry::ARRAY);
|
||||
}
|
||||
// No interest in this object.
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void HeapSnapshot::SetClosureReference(HeapEntry* parent,
|
||||
String* reference_name,
|
||||
Object* child) {
|
||||
HeapEntry* child_entry = GetEntry(child);
|
||||
if (child_entry != NULL) {
|
||||
parent->SetClosureReference(
|
||||
collection_->GetName(reference_name), child_entry);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void HeapSnapshot::SetElementReference(HeapEntry* parent,
|
||||
int index,
|
||||
Object* child) {
|
||||
HeapEntry* child_entry = GetEntry(child);
|
||||
if (child_entry != NULL) {
|
||||
parent->SetElementReference(index, child_entry);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void HeapSnapshot::SetPropertyReference(HeapEntry* parent,
|
||||
String* reference_name,
|
||||
Object* child) {
|
||||
HeapEntry* child_entry = GetEntry(child);
|
||||
if (child_entry != NULL) {
|
||||
parent->SetPropertyReference(
|
||||
collection_->GetName(reference_name), child_entry);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
HeapEntry* HeapSnapshot::AddEntry(HeapObject* object,
|
||||
HeapEntry::Type type,
|
||||
const char* name) {
|
||||
HeapEntry* entry = new HeapEntry(this,
|
||||
type,
|
||||
name,
|
||||
GetObjectSize(object),
|
||||
GetObjectSecurityToken(object));
|
||||
entries_.Pair(object, entry);
|
||||
|
||||
// Detect, if this is a JS global object of the current context, and
|
||||
// add it to snapshot's roots. There can be several JS global objects
|
||||
// in a context.
|
||||
if (object->IsJSGlobalProxy()) {
|
||||
int global_security_token = GetGlobalSecurityToken();
|
||||
int object_security_token =
|
||||
collection_->token_enumerator()->GetTokenId(
|
||||
Context::cast(
|
||||
JSGlobalProxy::cast(object)->context())->security_token());
|
||||
if (object_security_token == TokenEnumerator::kNoSecurityToken
|
||||
|| object_security_token == global_security_token) {
|
||||
HeapEntry* global_object_entry =
|
||||
GetEntry(HeapObject::cast(object->map()->prototype()));
|
||||
ASSERT(global_object_entry != NULL);
|
||||
root_.SetAutoIndexReference(global_object_entry);
|
||||
}
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
class EdgesCutter {
|
||||
public:
|
||||
explicit EdgesCutter(int global_security_token)
|
||||
: global_security_token_(global_security_token) {
|
||||
}
|
||||
|
||||
void Apply(HeapEntry* entry) {
|
||||
if (entry->security_token_id() != TokenEnumerator::kNoSecurityToken
|
||||
&& entry->security_token_id() != global_security_token_) {
|
||||
entry->CutEdges();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const int global_security_token_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void HeapSnapshot::CutObjectsFromForeignSecurityContexts() {
|
||||
EdgesCutter cutter(GetGlobalSecurityToken());
|
||||
entries_.Apply(&cutter);
|
||||
}
|
||||
|
||||
|
||||
int HeapSnapshot::GetGlobalSecurityToken() {
|
||||
return collection_->token_enumerator()->GetTokenId(
|
||||
Top::context()->global()->global_context()->security_token());
|
||||
}
|
||||
|
||||
|
||||
int HeapSnapshot::GetObjectSize(HeapObject* obj) {
|
||||
return obj->IsJSObject() ?
|
||||
CalculateNetworkSize(JSObject::cast(obj)) : obj->Size();
|
||||
}
|
||||
|
||||
|
||||
int HeapSnapshot::GetObjectSecurityToken(HeapObject* obj) {
|
||||
if (obj->IsGlobalContext()) {
|
||||
return collection_->token_enumerator()->GetTokenId(
|
||||
Context::cast(obj)->security_token());
|
||||
} else {
|
||||
return TokenEnumerator::kNoSecurityToken;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int HeapSnapshot::CalculateNetworkSize(JSObject* obj) {
|
||||
int size = obj->Size();
|
||||
// If 'properties' and 'elements' are non-empty (thus, non-shared),
|
||||
// take their size into account.
|
||||
if (FixedArray::cast(obj->properties())->length() != 0) {
|
||||
size += obj->properties()->Size();
|
||||
}
|
||||
if (FixedArray::cast(obj->elements())->length() != 0) {
|
||||
size += obj->elements()->Size();
|
||||
}
|
||||
// For functions, also account non-empty context and literals sizes.
|
||||
if (obj->IsJSFunction()) {
|
||||
JSFunction* f = JSFunction::cast(obj);
|
||||
if (f->unchecked_context()->IsContext()) {
|
||||
size += f->context()->Size();
|
||||
}
|
||||
if (f->literals()->length() != 0) {
|
||||
size += f->literals()->Size();
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
void HeapSnapshot::Print(int max_depth) {
|
||||
root_.Print(max_depth, 0);
|
||||
}
|
||||
|
||||
|
||||
HeapSnapshotsCollection::HeapSnapshotsCollection()
|
||||
: snapshots_uids_(HeapSnapshotsMatch),
|
||||
token_enumerator_(new TokenEnumerator()) {
|
||||
}
|
||||
|
||||
|
||||
static void DeleteHeapSnapshot(HeapSnapshot** snapshot_ptr) {
|
||||
delete *snapshot_ptr;
|
||||
}
|
||||
|
||||
|
||||
HeapSnapshotsCollection::~HeapSnapshotsCollection() {
|
||||
delete token_enumerator_;
|
||||
snapshots_.Iterate(DeleteHeapSnapshot);
|
||||
}
|
||||
|
||||
|
||||
HeapSnapshot* HeapSnapshotsCollection::NewSnapshot(const char* name,
|
||||
unsigned uid) {
|
||||
HeapSnapshot* snapshot = new HeapSnapshot(this, name, uid);
|
||||
snapshots_.Add(snapshot);
|
||||
HashMap::Entry* entry =
|
||||
snapshots_uids_.Lookup(reinterpret_cast<void*>(snapshot->uid()),
|
||||
static_cast<uint32_t>(snapshot->uid()),
|
||||
true);
|
||||
ASSERT(entry->value == NULL);
|
||||
entry->value = snapshot;
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
|
||||
HeapSnapshot* HeapSnapshotsCollection::GetSnapshot(unsigned uid) {
|
||||
HashMap::Entry* entry = snapshots_uids_.Lookup(reinterpret_cast<void*>(uid),
|
||||
static_cast<uint32_t>(uid),
|
||||
false);
|
||||
return entry != NULL ? reinterpret_cast<HeapSnapshot*>(entry->value) : NULL;
|
||||
}
|
||||
|
||||
|
||||
HeapSnapshotGenerator::HeapSnapshotGenerator(HeapSnapshot* snapshot)
|
||||
: snapshot_(snapshot) {
|
||||
}
|
||||
|
||||
|
||||
void HeapSnapshotGenerator::GenerateSnapshot() {
|
||||
AssertNoAllocation no_alloc;
|
||||
|
||||
// Iterate heap contents.
|
||||
HeapIterator iterator;
|
||||
for (HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next()) {
|
||||
ExtractReferences(obj);
|
||||
}
|
||||
|
||||
snapshot_->CutObjectsFromForeignSecurityContexts();
|
||||
}
|
||||
|
||||
|
||||
void HeapSnapshotGenerator::ExtractReferences(HeapObject* obj) {
|
||||
HeapEntry* entry = snapshot_->GetEntry(obj);
|
||||
if (entry == NULL) return;
|
||||
if (entry->visited()) return;
|
||||
|
||||
if (obj->IsJSObject()) {
|
||||
JSObject* js_obj = JSObject::cast(obj);
|
||||
ExtractClosureReferences(js_obj, entry);
|
||||
ExtractPropertyReferences(js_obj, entry);
|
||||
ExtractElementReferences(js_obj, entry);
|
||||
snapshot_->SetPropertyReference(
|
||||
entry, Heap::prototype_symbol(), js_obj->map()->prototype());
|
||||
} else if (obj->IsJSGlobalPropertyCell()) {
|
||||
JSGlobalPropertyCell* cell = JSGlobalPropertyCell::cast(obj);
|
||||
snapshot_->SetElementReference(entry, 0, cell->value());
|
||||
} else if (obj->IsString()) {
|
||||
if (obj->IsConsString()) {
|
||||
ConsString* cs = ConsString::cast(obj);
|
||||
snapshot_->SetElementReference(entry, 0, cs->first());
|
||||
snapshot_->SetElementReference(entry, 1, cs->second());
|
||||
}
|
||||
} else if (obj->IsCode() || obj->IsSharedFunctionInfo() || obj->IsScript()) {
|
||||
IndexedReferencesExtractor refs_extractor(snapshot_, entry);
|
||||
obj->Iterate(&refs_extractor);
|
||||
} else if (obj->IsFixedArray()) {
|
||||
IndexedReferencesExtractor refs_extractor(snapshot_, entry);
|
||||
obj->Iterate(&refs_extractor);
|
||||
}
|
||||
entry->MarkAsVisited();
|
||||
}
|
||||
|
||||
|
||||
void HeapSnapshotGenerator::ExtractClosureReferences(JSObject* js_obj,
|
||||
HeapEntry* entry) {
|
||||
if (js_obj->IsJSFunction()) {
|
||||
HandleScope hs;
|
||||
JSFunction* func = JSFunction::cast(js_obj);
|
||||
Context* context = func->context();
|
||||
ZoneScope zscope(DELETE_ON_EXIT);
|
||||
ScopeInfo<ZoneListAllocationPolicy> scope_info(
|
||||
context->closure()->shared()->code());
|
||||
int locals_number = scope_info.NumberOfLocals();
|
||||
for (int i = 0; i < locals_number; ++i) {
|
||||
String* local_name = *scope_info.LocalName(i);
|
||||
int idx = ScopeInfo<>::ContextSlotIndex(
|
||||
context->closure()->shared()->code(), local_name, NULL);
|
||||
if (idx >= 0 && idx < context->length()) {
|
||||
snapshot_->SetClosureReference(entry, local_name, context->get(idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void HeapSnapshotGenerator::ExtractPropertyReferences(JSObject* js_obj,
|
||||
HeapEntry* entry) {
|
||||
if (js_obj->HasFastProperties()) {
|
||||
DescriptorArray* descs = js_obj->map()->instance_descriptors();
|
||||
for (int i = 0; i < descs->number_of_descriptors(); i++) {
|
||||
switch (descs->GetType(i)) {
|
||||
case FIELD: {
|
||||
int index = descs->GetFieldIndex(i);
|
||||
snapshot_->SetPropertyReference(
|
||||
entry, descs->GetKey(i), js_obj->FastPropertyAt(index));
|
||||
break;
|
||||
}
|
||||
case CONSTANT_FUNCTION:
|
||||
snapshot_->SetPropertyReference(
|
||||
entry, descs->GetKey(i), descs->GetConstantFunction(i));
|
||||
break;
|
||||
default: ;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
StringDictionary* dictionary = js_obj->property_dictionary();
|
||||
int length = dictionary->Capacity();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
Object* k = dictionary->KeyAt(i);
|
||||
if (dictionary->IsKey(k)) {
|
||||
snapshot_->SetPropertyReference(
|
||||
entry, String::cast(k), dictionary->ValueAt(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void HeapSnapshotGenerator::ExtractElementReferences(JSObject* js_obj,
|
||||
HeapEntry* entry) {
|
||||
if (js_obj->HasFastElements()) {
|
||||
FixedArray* elements = FixedArray::cast(js_obj->elements());
|
||||
int length = js_obj->IsJSArray() ?
|
||||
Smi::cast(JSArray::cast(js_obj)->length())->value() :
|
||||
elements->length();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
if (!elements->get(i)->IsTheHole()) {
|
||||
snapshot_->SetElementReference(entry, i, elements->get(i));
|
||||
}
|
||||
}
|
||||
} else if (js_obj->HasDictionaryElements()) {
|
||||
NumberDictionary* dictionary = js_obj->element_dictionary();
|
||||
int length = dictionary->Capacity();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
Object* k = dictionary->KeyAt(i);
|
||||
if (dictionary->IsKey(k)) {
|
||||
ASSERT(k->IsNumber());
|
||||
uint32_t index = static_cast<uint32_t>(k->Number());
|
||||
snapshot_->SetElementReference(entry, index, dictionary->ValueAt(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} } // namespace v8::internal
|
||||
|
||||
#endif // ENABLE_LOGGING_AND_PROFILING
|
||||
|
@ -31,6 +31,7 @@
|
||||
#ifdef ENABLE_LOGGING_AND_PROFILING
|
||||
|
||||
#include "hashmap.h"
|
||||
#include "../include/v8-profiler.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
@ -53,6 +54,8 @@ class TokenEnumerator {
|
||||
List<bool> token_removed_;
|
||||
|
||||
friend class TokenEnumeratorTester;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TokenEnumerator);
|
||||
};
|
||||
|
||||
|
||||
@ -357,6 +360,8 @@ class SampleRateCalculator {
|
||||
unsigned measurements_count_;
|
||||
unsigned wall_time_query_countdown_;
|
||||
double last_wall_time_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SampleRateCalculator);
|
||||
};
|
||||
|
||||
|
||||
@ -416,6 +421,310 @@ class ProfileGenerator {
|
||||
DISALLOW_COPY_AND_ASSIGN(ProfileGenerator);
|
||||
};
|
||||
|
||||
|
||||
class HeapSnapshot;
|
||||
class HeapEntry;
|
||||
|
||||
|
||||
class HeapGraphEdge {
|
||||
public:
|
||||
enum Type {
|
||||
CONTEXT_VARIABLE,
|
||||
ELEMENT,
|
||||
PROPERTY
|
||||
};
|
||||
|
||||
HeapGraphEdge(Type type, const char* name, HeapEntry* from, HeapEntry* to);
|
||||
HeapGraphEdge(int index, HeapEntry* from, HeapEntry* to);
|
||||
|
||||
Type type() const { return type_; }
|
||||
int index() const {
|
||||
ASSERT(type_ == ELEMENT);
|
||||
return index_;
|
||||
}
|
||||
const char* name() const {
|
||||
ASSERT(type_ == CONTEXT_VARIABLE || type_ == PROPERTY);
|
||||
return name_;
|
||||
}
|
||||
HeapEntry* from() const { return from_; }
|
||||
HeapEntry* to() const { return to_; }
|
||||
|
||||
private:
|
||||
Type type_;
|
||||
union {
|
||||
int index_;
|
||||
const char* name_;
|
||||
};
|
||||
HeapEntry* from_;
|
||||
HeapEntry* to_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(HeapGraphEdge);
|
||||
};
|
||||
|
||||
|
||||
class HeapGraphPath;
|
||||
class CachedHeapGraphPath;
|
||||
|
||||
class HeapEntry {
|
||||
public:
|
||||
enum Type {
|
||||
INTERNAL,
|
||||
ARRAY,
|
||||
STRING,
|
||||
JS_OBJECT,
|
||||
CODE,
|
||||
CLOSURE
|
||||
};
|
||||
|
||||
explicit HeapEntry(HeapSnapshot* snapshot)
|
||||
: snapshot_(snapshot),
|
||||
visited_(false),
|
||||
type_(INTERNAL),
|
||||
name_(""),
|
||||
next_auto_index_(0),
|
||||
self_size_(0),
|
||||
security_token_id_(TokenEnumerator::kNoSecurityToken),
|
||||
children_(1),
|
||||
retainers_(0),
|
||||
retaining_paths_(0),
|
||||
total_size_(kUnknownSize),
|
||||
non_shared_total_size_(kUnknownSize),
|
||||
painted_(kUnpainted) { }
|
||||
HeapEntry(HeapSnapshot* snapshot,
|
||||
Type type,
|
||||
const char* name,
|
||||
int self_size,
|
||||
int security_token_id)
|
||||
: snapshot_(snapshot),
|
||||
visited_(false),
|
||||
type_(type),
|
||||
name_(name),
|
||||
next_auto_index_(1),
|
||||
self_size_(self_size),
|
||||
security_token_id_(security_token_id),
|
||||
children_(4),
|
||||
retainers_(4),
|
||||
retaining_paths_(4),
|
||||
total_size_(kUnknownSize),
|
||||
non_shared_total_size_(kUnknownSize),
|
||||
painted_(kUnpainted) { }
|
||||
~HeapEntry();
|
||||
|
||||
bool visited() const { return visited_; }
|
||||
Type type() const { return type_; }
|
||||
const char* name() const { return name_; }
|
||||
int self_size() const { return self_size_; }
|
||||
int security_token_id() const { return security_token_id_; }
|
||||
bool painted_reachable() { return painted_ == kPaintReachable; }
|
||||
bool not_painted_reachable_from_others() {
|
||||
return painted_ != kPaintReachableFromOthers;
|
||||
}
|
||||
const List<HeapGraphEdge*>* children() const { return &children_; }
|
||||
const List<HeapGraphEdge*>* retainers() const { return &retainers_; }
|
||||
const List<HeapGraphPath*>* GetRetainingPaths();
|
||||
|
||||
void ClearPaint() { painted_ = kUnpainted; }
|
||||
void CutEdges();
|
||||
void MarkAsVisited() { visited_ = true; }
|
||||
void PaintReachable() {
|
||||
ASSERT(painted_ == kUnpainted);
|
||||
painted_ = kPaintReachable;
|
||||
}
|
||||
void PaintReachableFromOthers() { painted_ = kPaintReachableFromOthers; }
|
||||
void SetClosureReference(const char* name, HeapEntry* entry);
|
||||
void SetElementReference(int index, HeapEntry* entry);
|
||||
void SetPropertyReference(const char* name, HeapEntry* entry);
|
||||
void SetAutoIndexReference(HeapEntry* entry);
|
||||
|
||||
int TotalSize();
|
||||
int NonSharedTotalSize();
|
||||
|
||||
void Print(int max_depth, int indent);
|
||||
|
||||
private:
|
||||
int CalculateTotalSize();
|
||||
int CalculateNonSharedTotalSize();
|
||||
void FindRetainingPaths(HeapEntry* node, CachedHeapGraphPath* prev_path);
|
||||
void RemoveChild(HeapGraphEdge* edge);
|
||||
void RemoveRetainer(HeapGraphEdge* edge);
|
||||
|
||||
const char* TypeAsString();
|
||||
|
||||
HeapSnapshot* snapshot_;
|
||||
bool visited_;
|
||||
Type type_;
|
||||
const char* name_;
|
||||
int next_auto_index_;
|
||||
int self_size_;
|
||||
int security_token_id_;
|
||||
List<HeapGraphEdge*> children_;
|
||||
List<HeapGraphEdge*> retainers_;
|
||||
List<HeapGraphPath*> retaining_paths_;
|
||||
int total_size_;
|
||||
int non_shared_total_size_;
|
||||
int painted_;
|
||||
|
||||
static const int kUnknownSize = -1;
|
||||
static const int kUnpainted = 0;
|
||||
static const int kPaintReachable = 1;
|
||||
static const int kPaintReachableFromOthers = 2;
|
||||
|
||||
DISALLOW_IMPLICIT_CONSTRUCTORS(HeapEntry);
|
||||
};
|
||||
|
||||
|
||||
class HeapGraphPath {
|
||||
public:
|
||||
HeapGraphPath()
|
||||
: path_(8) { }
|
||||
explicit HeapGraphPath(const List<HeapGraphEdge*>& path);
|
||||
|
||||
void Add(HeapGraphEdge* edge) { path_.Add(edge); }
|
||||
void Set(int index, HeapGraphEdge* edge) { path_[index] = edge; }
|
||||
const List<HeapGraphEdge*>* path() const { return &path_; }
|
||||
|
||||
void Print();
|
||||
|
||||
private:
|
||||
List<HeapGraphEdge*> path_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(HeapGraphPath);
|
||||
};
|
||||
|
||||
|
||||
class HeapEntriesMap {
|
||||
public:
|
||||
HeapEntriesMap();
|
||||
~HeapEntriesMap();
|
||||
|
||||
void Alias(HeapObject* object, HeapEntry* entry);
|
||||
void Apply(void (HeapEntry::*Func)(void));
|
||||
template<class Visitor>
|
||||
void Apply(Visitor* visitor);
|
||||
HeapEntry* Map(HeapObject* object);
|
||||
void Pair(HeapObject* object, HeapEntry* entry);
|
||||
|
||||
private:
|
||||
INLINE(uint32_t Hash(HeapObject* object)) {
|
||||
return static_cast<uint32_t>(reinterpret_cast<intptr_t>(object));
|
||||
}
|
||||
INLINE(static bool HeapObjectsMatch(void* key1, void* key2)) {
|
||||
return key1 == key2;
|
||||
}
|
||||
INLINE(bool IsAlias(void* ptr)) {
|
||||
return reinterpret_cast<intptr_t>(ptr) & kAliasTag;
|
||||
}
|
||||
|
||||
static const intptr_t kAliasTag = 1;
|
||||
|
||||
HashMap entries_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(HeapEntriesMap);
|
||||
};
|
||||
|
||||
|
||||
class HeapSnapshotsCollection;
|
||||
|
||||
// HeapSnapshot represents a single heap snapshot. It is stored in
|
||||
// HeapSnapshotsCollection, which is also a factory for
|
||||
// HeapSnapshots. All HeapSnapshots share strings copied from JS heap
|
||||
// to be able to return them even if they were collected.
|
||||
// HeapSnapshotGenerator fills in a HeapSnapshot.
|
||||
class HeapSnapshot {
|
||||
public:
|
||||
HeapSnapshot(HeapSnapshotsCollection* collection,
|
||||
const char* title,
|
||||
unsigned uid);
|
||||
void ClearPaint();
|
||||
void CutObjectsFromForeignSecurityContexts();
|
||||
HeapEntry* GetEntry(Object* object);
|
||||
void SetClosureReference(
|
||||
HeapEntry* parent, String* reference_name, Object* child);
|
||||
void SetElementReference(HeapEntry* parent, int index, Object* child);
|
||||
void SetPropertyReference(
|
||||
HeapEntry* parent, String* reference_name, Object* child);
|
||||
|
||||
INLINE(const char* title() const) { return title_; }
|
||||
INLINE(unsigned uid() const) { return uid_; }
|
||||
const HeapEntry* const_root() const { return &root_; }
|
||||
HeapEntry* root() { return &root_; }
|
||||
template<class Visitor>
|
||||
void IterateEntries(Visitor* visitor) { entries_.Apply(visitor); }
|
||||
|
||||
void Print(int max_depth);
|
||||
|
||||
private:
|
||||
HeapEntry* AddEntry(HeapObject* object, HeapEntry::Type type) {
|
||||
return AddEntry(object, type, "");
|
||||
}
|
||||
HeapEntry* AddEntry(
|
||||
HeapObject* object, HeapEntry::Type type, const char* name);
|
||||
void AddEntryAlias(HeapObject* object, HeapEntry* entry) {
|
||||
entries_.Alias(object, entry);
|
||||
}
|
||||
HeapEntry* FindEntry(HeapObject* object) {
|
||||
return entries_.Map(object);
|
||||
}
|
||||
int GetGlobalSecurityToken();
|
||||
int GetObjectSecurityToken(HeapObject* obj);
|
||||
static int GetObjectSize(HeapObject* obj);
|
||||
static int CalculateNetworkSize(JSObject* obj);
|
||||
|
||||
HeapSnapshotsCollection* collection_;
|
||||
const char* title_;
|
||||
unsigned uid_;
|
||||
HeapEntry root_;
|
||||
// HeapObject* -> HeapEntry*
|
||||
HeapEntriesMap entries_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(HeapSnapshot);
|
||||
};
|
||||
|
||||
|
||||
class HeapSnapshotsCollection {
|
||||
public:
|
||||
HeapSnapshotsCollection();
|
||||
~HeapSnapshotsCollection();
|
||||
|
||||
HeapSnapshot* NewSnapshot(const char* name, unsigned uid);
|
||||
List<HeapSnapshot*>* snapshots() { return &snapshots_; }
|
||||
HeapSnapshot* GetSnapshot(unsigned uid);
|
||||
|
||||
const char* GetName(String* name) { return names_.GetName(name); }
|
||||
|
||||
TokenEnumerator* token_enumerator() { return token_enumerator_; }
|
||||
|
||||
private:
|
||||
INLINE(static bool HeapSnapshotsMatch(void* key1, void* key2)) {
|
||||
return key1 == key2;
|
||||
}
|
||||
|
||||
List<HeapSnapshot*> snapshots_;
|
||||
// uid -> HeapSnapshot*
|
||||
HashMap snapshots_uids_;
|
||||
StringsStorage names_;
|
||||
TokenEnumerator* token_enumerator_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(HeapSnapshotsCollection);
|
||||
};
|
||||
|
||||
|
||||
class HeapSnapshotGenerator {
|
||||
public:
|
||||
explicit HeapSnapshotGenerator(HeapSnapshot* snapshot);
|
||||
void GenerateSnapshot();
|
||||
|
||||
private:
|
||||
void ExtractReferences(HeapObject* obj);
|
||||
void ExtractClosureReferences(JSObject* js_obj, HeapEntry* entry);
|
||||
void ExtractPropertyReferences(JSObject* js_obj, HeapEntry* entry);
|
||||
void ExtractElementReferences(JSObject* js_obj, HeapEntry* entry);
|
||||
|
||||
HeapSnapshot* snapshot_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(HeapSnapshotGenerator);
|
||||
};
|
||||
|
||||
} } // namespace v8::internal
|
||||
|
||||
#endif // ENABLE_LOGGING_AND_PROFILING
|
||||
|
Loading…
Reference in New Issue
Block a user