Commit cdd55e2a authored by mattloring's avatar mattloring Committed by Commit bot

Sampling heap profiler data structure changes

Previously, the sampling heap profiler stored a list of samples and then
built a tree representation when the profile was queried by calling
GetAllocationProfile. This change reduces duplication by removing stacks
from all samples. Also, less information is stored in the tree
maintained by the profiler and remaining information (script name, line
no, etc) is resolved when a profile is requested.

BUG=

Review URL: https://codereview.chromium.org/1697903002

Cr-Commit-Position: refs/heads/master@{#34119}
parent 9bebb028
...@@ -44,6 +44,7 @@ SamplingHeapProfiler::SamplingHeapProfiler(Heap* heap, StringsStorage* names, ...@@ -44,6 +44,7 @@ SamplingHeapProfiler::SamplingHeapProfiler(Heap* heap, StringsStorage* names,
heap_, static_cast<intptr_t>(rate), rate, this, heap_, static_cast<intptr_t>(rate), rate, this,
heap->isolate()->random_number_generator())), heap->isolate()->random_number_generator())),
names_(names), names_(names),
profile_root_("(root)", v8::UnboundScript::kNoScriptId, 0),
samples_(), samples_(),
stack_depth_(stack_depth) { stack_depth_(stack_depth) {
heap->new_space()->AddAllocationObserver(new_space_observer_.get()); heap->new_space()->AddAllocationObserver(new_space_observer_.get());
...@@ -65,12 +66,10 @@ SamplingHeapProfiler::~SamplingHeapProfiler() { ...@@ -65,12 +66,10 @@ SamplingHeapProfiler::~SamplingHeapProfiler() {
} }
} }
// Clear samples and drop all the weak references we are keeping. for (auto sample : samples_) {
std::set<SampledAllocation*>::iterator it; delete sample;
for (it = samples_.begin(); it != samples_.end(); ++it) {
delete *it;
} }
std::set<SampledAllocation*> empty; std::set<Sample*> empty;
samples_.swap(empty); samples_.swap(empty);
} }
...@@ -88,51 +87,48 @@ void SamplingHeapProfiler::SampleObject(Address soon_object, size_t size) { ...@@ -88,51 +87,48 @@ void SamplingHeapProfiler::SampleObject(Address soon_object, size_t size) {
Local<v8::Value> loc = v8::Utils::ToLocal(obj); Local<v8::Value> loc = v8::Utils::ToLocal(obj);
SampledAllocation* sample = AllocationNode* node = AddStack();
new SampledAllocation(this, isolate_, loc, size, stack_depth_); node->allocations_[size]++;
Sample* sample = new Sample(size, node, loc, this);
samples_.insert(sample); samples_.insert(sample);
sample->global.SetWeak(sample, OnWeakCallback, WeakCallbackType::kParameter);
} }
void SamplingHeapProfiler::OnWeakCallback(
void SamplingHeapProfiler::SampledAllocation::OnWeakCallback( const WeakCallbackInfo<Sample>& data) {
const WeakCallbackInfo<SampledAllocation>& data) { Sample* sample = data.GetParameter();
SampledAllocation* sample = data.GetParameter(); AllocationNode* node = sample->owner;
sample->sampling_heap_profiler_->samples_.erase(sample); DCHECK(node->allocations_[sample->size] > 0);
node->allocations_[sample->size]--;
sample->profiler->samples_.erase(sample);
delete sample; delete sample;
} }
SamplingHeapProfiler::AllocationNode* SamplingHeapProfiler::FindOrAddChildNode(
SamplingHeapProfiler::FunctionInfo::FunctionInfo(SharedFunctionInfo* shared, AllocationNode* parent, const char* name, int script_id,
StringsStorage* names) int start_position) {
: name_(names->GetFunctionName(shared->DebugName())), for (AllocationNode* child : parent->children_) {
script_name_(""), if (child->script_id_ == script_id &&
script_id_(v8::UnboundScript::kNoScriptId), child->script_position_ == start_position &&
start_position_(shared->start_position()) { strcmp(child->name_, name) == 0) {
if (shared->script()->IsScript()) { return child;
Script* script = Script::cast(shared->script());
script_id_ = script->id();
if (script->name()->IsName()) {
Name* name = Name::cast(script->name());
script_name_ = names->GetName(name);
} }
} }
AllocationNode* child = new AllocationNode(name, script_id, start_position);
parent->children_.push_back(child);
return child;
} }
SamplingHeapProfiler::AllocationNode* SamplingHeapProfiler::AddStack() {
AllocationNode* node = &profile_root_;
SamplingHeapProfiler::SampledAllocation::SampledAllocation( std::vector<SharedFunctionInfo*> stack;
SamplingHeapProfiler* sampling_heap_profiler, Isolate* isolate, StackTraceFrameIterator it(isolate_);
Local<Value> local, size_t size, int max_frames)
: sampling_heap_profiler_(sampling_heap_profiler),
global_(reinterpret_cast<v8::Isolate*>(isolate), local),
size_(size) {
global_.SetWeak(this, OnWeakCallback, WeakCallbackType::kParameter);
StackTraceFrameIterator it(isolate);
int frames_captured = 0; int frames_captured = 0;
while (!it.done() && frames_captured < max_frames) { while (!it.done() && frames_captured < stack_depth_) {
JavaScriptFrame* frame = it.frame(); JavaScriptFrame* frame = it.frame();
SharedFunctionInfo* shared = frame->function()->shared(); SharedFunctionInfo* shared = frame->function()->shared();
stack_.push_back(new FunctionInfo(shared, sampling_heap_profiler->names())); stack.push_back(shared);
frames_captured++; frames_captured++;
it.Advance(); it.Advance();
...@@ -140,7 +136,7 @@ SamplingHeapProfiler::SampledAllocation::SampledAllocation( ...@@ -140,7 +136,7 @@ SamplingHeapProfiler::SampledAllocation::SampledAllocation(
if (frames_captured == 0) { if (frames_captured == 0) {
const char* name = nullptr; const char* name = nullptr;
switch (isolate->current_vm_state()) { switch (isolate_->current_vm_state()) {
case GC: case GC:
name = "(GC)"; name = "(GC)";
break; break;
...@@ -160,71 +156,63 @@ SamplingHeapProfiler::SampledAllocation::SampledAllocation( ...@@ -160,71 +156,63 @@ SamplingHeapProfiler::SampledAllocation::SampledAllocation(
name = "(JS)"; name = "(JS)";
break; break;
} }
stack_.push_back(new FunctionInfo(name)); return FindOrAddChildNode(node, name, v8::UnboundScript::kNoScriptId, 0);
} }
}
v8::AllocationProfile::Node* SamplingHeapProfiler::AllocateNode( // We need to process the stack in reverse order as the top of the stack is
AllocationProfile* profile, const std::map<int, Script*>& scripts, // the first element in the list.
FunctionInfo* function_info) { for (auto it = stack.rbegin(); it != stack.rend(); ++it) {
DCHECK(function_info->get_name()); SharedFunctionInfo* shared = *it;
DCHECK(function_info->get_script_name()); const char* name = this->names()->GetFunctionName(shared->DebugName());
int script_id = v8::UnboundScript::kNoScriptId;
if (shared->script()->IsScript()) {
Script* script = Script::cast(shared->script());
script_id = script->id();
}
node = FindOrAddChildNode(node, name, script_id, shared->start_position());
}
return node;
}
v8::AllocationProfile::Node* SamplingHeapProfiler::TranslateAllocationNode(
AllocationProfile* profile, SamplingHeapProfiler::AllocationNode* node,
const std::map<int, Script*>& scripts) {
Local<v8::String> script_name =
ToApiHandle<v8::String>(isolate_->factory()->InternalizeUtf8String(""));
int line = v8::AllocationProfile::kNoLineNumberInfo; int line = v8::AllocationProfile::kNoLineNumberInfo;
int column = v8::AllocationProfile::kNoColumnNumberInfo; int column = v8::AllocationProfile::kNoColumnNumberInfo;
std::vector<v8::AllocationProfile::Allocation> allocations;
if (function_info->get_script_id() != v8::UnboundScript::kNoScriptId) { if (node->script_id_ != v8::UnboundScript::kNoScriptId) {
// Cannot use std::map<T>::at because it is not available on android. // Cannot use std::map<T>::at because it is not available on android.
auto non_const_scripts = const_cast<std::map<int, Script*>&>(scripts); auto non_const_scripts = const_cast<std::map<int, Script*>&>(scripts);
Handle<Script> script(non_const_scripts[function_info->get_script_id()]); Script* script = non_const_scripts[node->script_id_];
if (script->name()->IsName()) {
Name* name = Name::cast(script->name());
script_name = ToApiHandle<v8::String>(
isolate_->factory()->InternalizeUtf8String(names_->GetName(name)));
}
Handle<Script> script_handle(script);
line = line = 1 + Script::GetLineNumber(script_handle, node->script_position_);
1 + Script::GetLineNumber(script, function_info->get_start_position()); column = 1 + Script::GetColumnNumber(script_handle, node->script_position_);
column = 1 + Script::GetColumnNumber(script, for (auto alloc : node->allocations_) {
function_info->get_start_position()); allocations.push_back({alloc.first, alloc.second});
}
} }
profile->nodes().push_back(v8::AllocationProfile::Node( profile->nodes().push_back(v8::AllocationProfile::Node(
{ToApiHandle<v8::String>(isolate_->factory()->InternalizeUtf8String( {ToApiHandle<v8::String>(
function_info->get_name())), isolate_->factory()->InternalizeUtf8String(node->name_)),
ToApiHandle<v8::String>(isolate_->factory()->InternalizeUtf8String( script_name, node->script_id_, node->script_position_, line, column,
function_info->get_script_name())), std::vector<v8::AllocationProfile::Node*>(), allocations}));
function_info->get_script_id(), function_info->get_start_position(), v8::AllocationProfile::Node* current = &profile->nodes().back();
line, column, std::vector<v8::AllocationProfile::Node*>(), for (auto child : node->children_) {
std::vector<v8::AllocationProfile::Allocation>()})); current->children.push_back(
TranslateAllocationNode(profile, child, scripts));
return &profile->nodes().back();
}
v8::AllocationProfile::Node* SamplingHeapProfiler::FindOrAddChildNode(
AllocationProfile* profile, const std::map<int, Script*>& scripts,
v8::AllocationProfile::Node* parent, FunctionInfo* function_info) {
for (v8::AllocationProfile::Node* child : parent->children) {
if (child->script_id == function_info->get_script_id() &&
child->start_position == function_info->get_start_position())
return child;
}
v8::AllocationProfile::Node* child =
AllocateNode(profile, scripts, function_info);
parent->children.push_back(child);
return child;
}
v8::AllocationProfile::Node* SamplingHeapProfiler::AddStack(
AllocationProfile* profile, const std::map<int, Script*>& scripts,
const std::vector<FunctionInfo*>& stack) {
v8::AllocationProfile::Node* node = profile->GetRootNode();
// We need to process the stack in reverse order as the top of the stack is
// the first element in the list.
for (auto it = stack.rbegin(); it != stack.rend(); ++it) {
FunctionInfo* function_info = *it;
node = FindOrAddChildNode(profile, scripts, node, function_info);
} }
return node; return current;
} }
v8::AllocationProfile* SamplingHeapProfiler::GetAllocationProfile() { v8::AllocationProfile* SamplingHeapProfiler::GetAllocationProfile() {
// To resolve positions to line/column numbers, we will need to look up // To resolve positions to line/column numbers, we will need to look up
// scripts. Build a map to allow fast mapping from script id to script. // scripts. Build a map to allow fast mapping from script id to script.
...@@ -239,15 +227,7 @@ v8::AllocationProfile* SamplingHeapProfiler::GetAllocationProfile() { ...@@ -239,15 +227,7 @@ v8::AllocationProfile* SamplingHeapProfiler::GetAllocationProfile() {
auto profile = new v8::internal::AllocationProfile(); auto profile = new v8::internal::AllocationProfile();
// Create the root node. TranslateAllocationNode(profile, &profile_root_, scripts);
FunctionInfo function_info("(root)");
AllocateNode(profile, scripts, &function_info);
for (SampledAllocation* allocation : samples_) {
v8::AllocationProfile::Node* node =
AddStack(profile, scripts, allocation->get_stack());
node->allocations.push_back({allocation->get_size(), 1});
}
return profile; return profile;
} }
......
...@@ -48,50 +48,50 @@ class SamplingHeapProfiler { ...@@ -48,50 +48,50 @@ class SamplingHeapProfiler {
StringsStorage* names() const { return names_; } StringsStorage* names() const { return names_; }
class FunctionInfo { class AllocationNode;
struct Sample {
public: public:
FunctionInfo(SharedFunctionInfo* shared, StringsStorage* names); Sample(size_t size_, AllocationNode* owner_, Local<Value> local_,
explicit FunctionInfo(const char* name) SamplingHeapProfiler* profiler_)
: name_(name), : size(size_),
script_name_(""), owner(owner_),
script_id_(v8::UnboundScript::kNoScriptId), global(Global<Value>(
start_position_(0) {} reinterpret_cast<v8::Isolate*>(profiler_->isolate_), local_)),
profiler(profiler_) {}
const char* get_name() const { return name_; } ~Sample() { global.Reset(); }
const char* get_script_name() const { return script_name_; } const size_t size;
int get_script_id() const { return script_id_; } AllocationNode* const owner;
int get_start_position() const { return start_position_; } Global<Value> global;
SamplingHeapProfiler* const profiler;
private: private:
const char* const name_; DISALLOW_COPY_AND_ASSIGN(Sample);
const char* script_name_;
int script_id_;
const int start_position_;
}; };
class SampledAllocation { class AllocationNode {
public: public:
SampledAllocation(SamplingHeapProfiler* sampling_heap_profiler, AllocationNode(const char* const name, int script_id,
Isolate* isolate, Local<Value> local, size_t size, const int start_position)
int max_frames); : script_id_(script_id),
~SampledAllocation() { script_position_(start_position),
for (auto info : stack_) { name_(name) {}
delete info; ~AllocationNode() {
for (auto child : children_) {
delete child;
} }
global_.Reset(); // drop the reference.
} }
size_t get_size() const { return size_; }
const std::vector<FunctionInfo*>& get_stack() const { return stack_; }
private: private:
static void OnWeakCallback(const WeakCallbackInfo<SampledAllocation>& data); std::map<size_t, unsigned int> allocations_;
std::vector<AllocationNode*> children_;
const int script_id_;
const int script_position_;
const char* const name_;
SamplingHeapProfiler* const sampling_heap_profiler_; friend class SamplingHeapProfiler;
Global<Value> global_;
std::vector<FunctionInfo*> stack_;
const size_t size_;
DISALLOW_COPY_AND_ASSIGN(SampledAllocation); DISALLOW_COPY_AND_ASSIGN(AllocationNode);
}; };
private: private:
...@@ -99,23 +99,29 @@ class SamplingHeapProfiler { ...@@ -99,23 +99,29 @@ class SamplingHeapProfiler {
void SampleObject(Address soon_object, size_t size); void SampleObject(Address soon_object, size_t size);
static void OnWeakCallback(const WeakCallbackInfo<Sample>& data);
// Methods that construct v8::AllocationProfile. // Methods that construct v8::AllocationProfile.
v8::AllocationProfile::Node* AddStack(
AllocationProfile* profile, const std::map<int, Script*>& scripts, // Translates the provided AllocationNode *node* returning an equivalent
const std::vector<FunctionInfo*>& stack); // AllocationProfile::Node. The newly created AllocationProfile::Node is added
v8::AllocationProfile::Node* FindOrAddChildNode( // to the provided AllocationProfile *profile*. Line numbers, column numbers,
AllocationProfile* profile, const std::map<int, Script*>& scripts, // and script names are resolved using *scripts* which maps all currently
v8::AllocationProfile::Node* parent, FunctionInfo* function_info); // loaded scripts keyed by their script id.
v8::AllocationProfile::Node* AllocateNode( v8::AllocationProfile::Node* TranslateAllocationNode(
AllocationProfile* profile, const std::map<int, Script*>& scripts, AllocationProfile* profile, SamplingHeapProfiler::AllocationNode* node,
FunctionInfo* function_info); const std::map<int, Script*>& scripts);
AllocationNode* AddStack();
AllocationNode* FindOrAddChildNode(AllocationNode* parent, const char* name,
int script_id, int start_position);
Isolate* const isolate_; Isolate* const isolate_;
Heap* const heap_; Heap* const heap_;
base::SmartPointer<SamplingAllocationObserver> new_space_observer_; base::SmartPointer<SamplingAllocationObserver> new_space_observer_;
base::SmartPointer<SamplingAllocationObserver> other_spaces_observer_; base::SmartPointer<SamplingAllocationObserver> other_spaces_observer_;
StringsStorage* const names_; StringsStorage* const names_;
std::set<SampledAllocation*> samples_; AllocationNode profile_root_;
std::set<Sample*> samples_;
const int stack_depth_; const int stack_depth_;
friend class SamplingAllocationObserver; friend class SamplingAllocationObserver;
......
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