[profiler] Implement POC Sampling Heap Profiler
This implements a proof-of-concept sampling based heap profiler inspired by tcmalloc's heap profiler [1] and Go's mprof/memprofile [2]. The basic idea is the sample allocations using a randomized Poisson process. At any point in time we can cheaply request the set of live sample objects that should be a representative sample of heap. Samples include stack-traces from the allocation sites, making this an effective tool for memory leak debugging. Unlike AllocationTracking, this is intended to be cheap and usable online in production. The proof-of-concept is only sampling new-space allocations at this point. Support for sampling paged space and native allocations is anticipated in the future. [1] http://goog-perftools.sourceforge.net/doc/heap_profiler.html [2] http://blog.golang.org/profiling-go-programs Review URL: https://codereview.chromium.org/1555553002 Cr-Commit-Position: refs/heads/master@{#33448}
This commit is contained in:
parent
cbc0564037
commit
e5a9947811
2
BUILD.gn
2
BUILD.gn
@ -1185,6 +1185,8 @@ source_set("v8_base") {
|
||||
"src/profiler/profile-generator.h",
|
||||
"src/profiler/sampler.cc",
|
||||
"src/profiler/sampler.h",
|
||||
"src/profiler/sampling-heap-profiler.cc",
|
||||
"src/profiler/sampling-heap-profiler.h",
|
||||
"src/profiler/strings-storage.cc",
|
||||
"src/profiler/strings-storage.h",
|
||||
"src/profiler/unbound-queue-inl.h",
|
||||
|
@ -418,6 +418,90 @@ class V8_EXPORT ActivityControl { // NOLINT
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* AllocationProfile is a sampled profile of allocations done by the program.
|
||||
* This is structured as a call-graph.
|
||||
*/
|
||||
class V8_EXPORT AllocationProfile {
|
||||
public:
|
||||
struct Allocation {
|
||||
/**
|
||||
* Size of the sampled allocation object.
|
||||
*/
|
||||
size_t size;
|
||||
|
||||
/**
|
||||
* The number of objects of such size that were sampled.
|
||||
*/
|
||||
unsigned int count;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a node in the call-graph.
|
||||
*/
|
||||
struct Node {
|
||||
/**
|
||||
* Name of the function. May be empty for anonymous functions or if the
|
||||
* script corresponding to this function has been unloaded.
|
||||
*/
|
||||
Local<String> name;
|
||||
|
||||
/**
|
||||
* Name of the script containing the function. May be empty if the script
|
||||
* name is not available, or if the script has been unloaded.
|
||||
*/
|
||||
Local<String> script_name;
|
||||
|
||||
/**
|
||||
* id of the script where the function is located. May be equal to
|
||||
* v8::UnboundScript::kNoScriptId in cases where the script doesn't exist.
|
||||
*/
|
||||
int script_id;
|
||||
|
||||
/**
|
||||
* Start position of the function in the script.
|
||||
*/
|
||||
int start_position;
|
||||
|
||||
/**
|
||||
* 1-indexed line number where the function starts. May be
|
||||
* kNoLineNumberInfo if no line number information is available.
|
||||
*/
|
||||
int line_number;
|
||||
|
||||
/**
|
||||
* 1-indexed column number where the function starts. May be
|
||||
* kNoColumnNumberInfo if no line number information is available.
|
||||
*/
|
||||
int column_number;
|
||||
|
||||
/**
|
||||
* List of callees called from this node for which we have sampled
|
||||
* allocations. The lifetime of the children is scoped to the containing
|
||||
* AllocationProfile.
|
||||
*/
|
||||
std::vector<Node*> children;
|
||||
|
||||
/**
|
||||
* List of self allocations done by this node in the call-graph.
|
||||
*/
|
||||
std::vector<Allocation> allocations;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the root node of the call-graph. The root node corresponds to an
|
||||
* empty JS call-stack. The lifetime of the returned Node* is scoped to the
|
||||
* containing AllocationProfile.
|
||||
*/
|
||||
virtual Node* GetRootNode() = 0;
|
||||
|
||||
virtual ~AllocationProfile() {}
|
||||
|
||||
static const int kNoLineNumberInfo = Message::kNoLineNumberInfo;
|
||||
static const int kNoColumnNumberInfo = Message::kNoColumnInfo;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Interface for controlling heap profiling. Instance of the
|
||||
* profiler can be retrieved using v8::Isolate::GetHeapProfiler.
|
||||
@ -521,6 +605,49 @@ class V8_EXPORT HeapProfiler {
|
||||
*/
|
||||
void StopTrackingHeapObjects();
|
||||
|
||||
/**
|
||||
* Starts gathering a sampling heap profile. A sampling heap profile is
|
||||
* similar to tcmalloc's heap profiler and Go's mprof. It samples object
|
||||
* allocations and builds an online 'sampling' heap profile. At any point in
|
||||
* time, this profile is expected to be a representative sample of objects
|
||||
* currently live in the system. Each sampled allocation includes the stack
|
||||
* trace at the time of allocation, which makes this really useful for memory
|
||||
* leak detection.
|
||||
*
|
||||
* This mechanism is intended to be cheap enough that it can be used in
|
||||
* production with minimal performance overhead.
|
||||
*
|
||||
* Allocations are sampled using a randomized Poisson process. On average, one
|
||||
* allocation will be sampled every |sample_interval| bytes allocated. The
|
||||
* |stack_depth| parameter controls the maximum number of stack frames to be
|
||||
* captured on each allocation.
|
||||
*
|
||||
* NOTE: This is a proof-of-concept at this point. Right now we only sample
|
||||
* newspace allocations. Support for paged space allocation (e.g. pre-tenured
|
||||
* objects, large objects, code objects, etc.) and native allocations
|
||||
* doesn't exist yet, but is anticipated in the future.
|
||||
*
|
||||
* Objects allocated before the sampling is started will not be included in
|
||||
* the profile.
|
||||
*
|
||||
* Returns false if a sampling heap profiler is already running.
|
||||
*/
|
||||
bool StartSamplingHeapProfiler(uint64_t sample_interval = 512 * 1024,
|
||||
int stack_depth = 16);
|
||||
|
||||
/**
|
||||
* Stops the sampling heap profile and discards the current profile.
|
||||
*/
|
||||
void StopSamplingHeapProfiler();
|
||||
|
||||
/**
|
||||
* Returns the sampled profile of allocations allocated (and still live) since
|
||||
* StartSamplingHeapProfiler was called. The ownership of the pointer is
|
||||
* transfered to the caller. Returns nullptr if sampling heap profiler is not
|
||||
* active.
|
||||
*/
|
||||
AllocationProfile* GetAllocationProfile();
|
||||
|
||||
/**
|
||||
* Deletes all snapshots taken. All previously returned pointers to
|
||||
* snapshots and their contents become invalid after this call.
|
||||
|
17
src/api.cc
17
src/api.cc
@ -8277,6 +8277,23 @@ SnapshotObjectId HeapProfiler::GetHeapStats(OutputStream* stream,
|
||||
}
|
||||
|
||||
|
||||
bool HeapProfiler::StartSamplingHeapProfiler(uint64_t sample_interval,
|
||||
int stack_depth) {
|
||||
return reinterpret_cast<i::HeapProfiler*>(this)
|
||||
->StartSamplingHeapProfiler(sample_interval, stack_depth);
|
||||
}
|
||||
|
||||
|
||||
void HeapProfiler::StopSamplingHeapProfiler() {
|
||||
reinterpret_cast<i::HeapProfiler*>(this)->StopSamplingHeapProfiler();
|
||||
}
|
||||
|
||||
|
||||
AllocationProfile* HeapProfiler::GetAllocationProfile() {
|
||||
return reinterpret_cast<i::HeapProfiler*>(this)->GetAllocationProfile();
|
||||
}
|
||||
|
||||
|
||||
void HeapProfiler::DeleteAllHeapSnapshots() {
|
||||
reinterpret_cast<i::HeapProfiler*>(this)->DeleteAllSnapshots();
|
||||
}
|
||||
|
@ -476,6 +476,7 @@ class GCTracer;
|
||||
class HeapObjectsFilter;
|
||||
class HeapStats;
|
||||
class HistogramTimer;
|
||||
class InlineAllocationObserver;
|
||||
class Isolate;
|
||||
class MemoryReducer;
|
||||
class ObjectStats;
|
||||
@ -2790,6 +2791,60 @@ class PathTracer : public ObjectVisitor {
|
||||
DISALLOW_IMPLICIT_CONSTRUCTORS(PathTracer);
|
||||
};
|
||||
#endif // DEBUG
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Allows observation of inline allocation in the new space.
|
||||
class InlineAllocationObserver {
|
||||
public:
|
||||
explicit InlineAllocationObserver(intptr_t step_size)
|
||||
: step_size_(step_size), bytes_to_next_step_(step_size) {
|
||||
DCHECK(step_size >= kPointerSize);
|
||||
}
|
||||
virtual ~InlineAllocationObserver() {}
|
||||
|
||||
// Called each time the new space does an inline allocation step. This may be
|
||||
// more frequently than the step_size we are monitoring (e.g. when there are
|
||||
// multiple observers, or when page or space boundary is encountered.)
|
||||
void InlineAllocationStep(int bytes_allocated, Address soon_object,
|
||||
size_t size) {
|
||||
bytes_to_next_step_ -= bytes_allocated;
|
||||
if (bytes_to_next_step_ <= 0) {
|
||||
Step(static_cast<int>(step_size_ - bytes_to_next_step_), soon_object,
|
||||
size);
|
||||
step_size_ = GetNextStepSize();
|
||||
bytes_to_next_step_ = step_size_;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
intptr_t step_size() const { return step_size_; }
|
||||
intptr_t bytes_to_next_step() const { return bytes_to_next_step_; }
|
||||
|
||||
// Pure virtual method provided by the subclasses that gets called when at
|
||||
// least step_size bytes have been allocated. soon_object is the address just
|
||||
// allocated (but not yet initialized.) size is the size of the object as
|
||||
// requested (i.e. w/o the alignment fillers). Some complexities to be aware
|
||||
// of:
|
||||
// 1) soon_object will be nullptr in cases where we end up observing an
|
||||
// allocation that happens to be a filler space (e.g. page boundaries.)
|
||||
// 2) size is the requested size at the time of allocation. Right-trimming
|
||||
// may change the object size dynamically.
|
||||
// 3) soon_object may actually be the first object in an allocation-folding
|
||||
// group. In such a case size is the size of the group rather than the
|
||||
// first object.
|
||||
virtual void Step(int bytes_allocated, Address soon_object, size_t size) = 0;
|
||||
|
||||
// Subclasses can override this method to make step size dynamic.
|
||||
virtual intptr_t GetNextStepSize() { return step_size_; }
|
||||
|
||||
intptr_t step_size_;
|
||||
intptr_t bytes_to_next_step_;
|
||||
|
||||
private:
|
||||
friend class NewSpace;
|
||||
DISALLOW_COPY_AND_ASSIGN(InlineAllocationObserver);
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include "src/cancelable-task.h"
|
||||
#include "src/execution.h"
|
||||
#include "src/heap/heap.h"
|
||||
#include "src/heap/incremental-marking-job.h"
|
||||
#include "src/heap/spaces.h"
|
||||
#include "src/objects.h"
|
||||
|
@ -20,6 +20,7 @@ namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
class CompactionSpaceCollection;
|
||||
class InlineAllocationObserver;
|
||||
class Isolate;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@ -2558,54 +2559,6 @@ class NewSpacePageIterator BASE_EMBEDDED {
|
||||
NewSpacePage* last_page_;
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Allows observation of inline allocation in the new space.
|
||||
class InlineAllocationObserver {
|
||||
public:
|
||||
explicit InlineAllocationObserver(intptr_t step_size)
|
||||
: step_size_(step_size), bytes_to_next_step_(step_size) {
|
||||
DCHECK(step_size >= kPointerSize);
|
||||
}
|
||||
virtual ~InlineAllocationObserver() {}
|
||||
|
||||
private:
|
||||
intptr_t step_size() const { return step_size_; }
|
||||
intptr_t bytes_to_next_step() const { return bytes_to_next_step_; }
|
||||
|
||||
// Pure virtual method provided by the subclasses that gets called when at
|
||||
// least step_size bytes have been allocated. soon_object is the address just
|
||||
// allocated (but not yet initialized.) size is the size of the object as
|
||||
// requested (i.e. w/o the alignment fillers). Some complexities to be aware
|
||||
// of:
|
||||
// 1) soon_object will be nullptr in cases where we end up observing an
|
||||
// allocation that happens to be a filler space (e.g. page boundaries.)
|
||||
// 2) size is the requested size at the time of allocation. Right-trimming
|
||||
// may change the object size dynamically.
|
||||
// 3) soon_object may actually be the first object in an allocation-folding
|
||||
// group. In such a case size is the size of the group rather than the
|
||||
// first object.
|
||||
virtual void Step(int bytes_allocated, Address soon_object, size_t size) = 0;
|
||||
|
||||
// Called each time the new space does an inline allocation step. This may be
|
||||
// more frequently than the step_size we are monitoring (e.g. when there are
|
||||
// multiple observers, or when page or space boundary is encountered.)
|
||||
void InlineAllocationStep(int bytes_allocated, Address soon_object,
|
||||
size_t size) {
|
||||
bytes_to_next_step_ -= bytes_allocated;
|
||||
if (bytes_to_next_step_ <= 0) {
|
||||
Step(static_cast<int>(step_size_ - bytes_to_next_step_), soon_object,
|
||||
size);
|
||||
bytes_to_next_step_ = step_size_;
|
||||
}
|
||||
}
|
||||
|
||||
intptr_t step_size_;
|
||||
intptr_t bytes_to_next_step_;
|
||||
|
||||
friend class NewSpace;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(InlineAllocationObserver);
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// The young generation space.
|
||||
|
@ -1939,13 +1939,14 @@ void Isolate::Deinit() {
|
||||
delete basic_block_profiler_;
|
||||
basic_block_profiler_ = NULL;
|
||||
|
||||
delete heap_profiler_;
|
||||
heap_profiler_ = NULL;
|
||||
|
||||
heap_.TearDown();
|
||||
logger_->TearDown();
|
||||
|
||||
cancelable_task_manager()->CancelAndWait();
|
||||
|
||||
delete heap_profiler_;
|
||||
heap_profiler_ = NULL;
|
||||
delete cpu_profiler_;
|
||||
cpu_profiler_ = NULL;
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "src/debug/debug.h"
|
||||
#include "src/profiler/allocation-tracker.h"
|
||||
#include "src/profiler/heap-snapshot-generator-inl.h"
|
||||
#include "src/profiler/sampling-heap-profiler.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
@ -84,6 +85,31 @@ HeapSnapshot* HeapProfiler::TakeSnapshot(
|
||||
}
|
||||
|
||||
|
||||
bool HeapProfiler::StartSamplingHeapProfiler(uint64_t sample_interval,
|
||||
int stack_depth) {
|
||||
if (sampling_heap_profiler_.get()) {
|
||||
return false;
|
||||
}
|
||||
sampling_heap_profiler_.Reset(new SamplingHeapProfiler(
|
||||
heap(), names_.get(), sample_interval, stack_depth));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void HeapProfiler::StopSamplingHeapProfiler() {
|
||||
sampling_heap_profiler_.Reset(nullptr);
|
||||
}
|
||||
|
||||
|
||||
v8::AllocationProfile* HeapProfiler::GetAllocationProfile() {
|
||||
if (sampling_heap_profiler_.get()) {
|
||||
return sampling_heap_profiler_->GetAllocationProfile();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void HeapProfiler::StartHeapObjectsTracking(bool track_allocations) {
|
||||
ids_->UpdateHeapObjectsMap();
|
||||
is_tracking_object_moves_ = true;
|
||||
|
@ -16,6 +16,7 @@ namespace internal {
|
||||
class AllocationTracker;
|
||||
class HeapObjectsMap;
|
||||
class HeapSnapshot;
|
||||
class SamplingHeapProfiler;
|
||||
class StringsStorage;
|
||||
|
||||
class HeapProfiler {
|
||||
@ -29,6 +30,10 @@ class HeapProfiler {
|
||||
v8::ActivityControl* control,
|
||||
v8::HeapProfiler::ObjectNameResolver* resolver);
|
||||
|
||||
bool StartSamplingHeapProfiler(uint64_t sample_interval, int stack_depth);
|
||||
void StopSamplingHeapProfiler();
|
||||
AllocationProfile* GetAllocationProfile();
|
||||
|
||||
void StartHeapObjectsTracking(bool track_allocations);
|
||||
void StopHeapObjectsTracking();
|
||||
AllocationTracker* allocation_tracker() const {
|
||||
@ -79,6 +84,7 @@ class HeapProfiler {
|
||||
base::SmartPointer<AllocationTracker> allocation_tracker_;
|
||||
bool is_tracking_object_moves_;
|
||||
base::Mutex profiler_mutex_;
|
||||
base::SmartPointer<SamplingHeapProfiler> sampling_heap_profiler_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
250
src/profiler/sampling-heap-profiler.cc
Normal file
250
src/profiler/sampling-heap-profiler.cc
Normal file
@ -0,0 +1,250 @@
|
||||
// Copyright 2015 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "src/profiler/sampling-heap-profiler.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <memory>
|
||||
#include "src/api.h"
|
||||
#include "src/base/utils/random-number-generator.h"
|
||||
#include "src/frames-inl.h"
|
||||
#include "src/heap/heap.h"
|
||||
#include "src/isolate.h"
|
||||
#include "src/profiler/strings-storage.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
SamplingHeapProfiler::SamplingHeapProfiler(Heap* heap, StringsStorage* names,
|
||||
uint64_t rate, int stack_depth)
|
||||
: InlineAllocationObserver(GetNextSampleInterval(
|
||||
heap->isolate()->random_number_generator(), rate)),
|
||||
isolate_(heap->isolate()),
|
||||
heap_(heap),
|
||||
random_(isolate_->random_number_generator()),
|
||||
names_(names),
|
||||
samples_(),
|
||||
rate_(rate),
|
||||
stack_depth_(stack_depth) {
|
||||
heap->new_space()->AddInlineAllocationObserver(this);
|
||||
}
|
||||
|
||||
|
||||
SamplingHeapProfiler::~SamplingHeapProfiler() {
|
||||
heap_->new_space()->RemoveInlineAllocationObserver(this);
|
||||
|
||||
// Clear samples and drop all the weak references we are keeping.
|
||||
std::set<SampledAllocation*>::iterator it;
|
||||
for (it = samples_.begin(); it != samples_.end(); ++it) {
|
||||
delete *it;
|
||||
}
|
||||
std::set<SampledAllocation*> empty;
|
||||
samples_.swap(empty);
|
||||
}
|
||||
|
||||
void SamplingHeapProfiler::Step(int bytes_allocated, Address soon_object,
|
||||
size_t size) {
|
||||
DCHECK(heap_->gc_state() == Heap::NOT_IN_GC);
|
||||
DCHECK(soon_object);
|
||||
SampleObject(soon_object, size);
|
||||
}
|
||||
|
||||
|
||||
void SamplingHeapProfiler::SampleObject(Address soon_object, size_t size) {
|
||||
DisallowHeapAllocation no_allocation;
|
||||
|
||||
HandleScope scope(isolate_);
|
||||
HeapObject* heap_object = HeapObject::FromAddress(soon_object);
|
||||
Handle<Object> obj(heap_object, isolate_);
|
||||
|
||||
// Mark the new block as FreeSpace to make sure the heap is iterable while we
|
||||
// are taking the sample.
|
||||
heap()->CreateFillerObjectAt(soon_object, static_cast<int>(size));
|
||||
|
||||
Local<v8::Value> loc = v8::Utils::ToLocal(obj);
|
||||
|
||||
SampledAllocation* sample =
|
||||
new SampledAllocation(this, isolate_, loc, size, stack_depth_);
|
||||
samples_.insert(sample);
|
||||
}
|
||||
|
||||
|
||||
// We sample with a Poisson process, with constant average sampling interval.
|
||||
// This follows the exponential probability distribution with parameter
|
||||
// λ = 1/rate where rate is the average number of bytes between samples.
|
||||
//
|
||||
// Let u be a uniformly distributed random number between 0 and 1, then
|
||||
// next_sample = (- ln u) / λ
|
||||
intptr_t SamplingHeapProfiler::GetNextSampleInterval(
|
||||
base::RandomNumberGenerator* random, uint64_t rate) {
|
||||
double u = random->NextDouble();
|
||||
double next = (-std::log(u)) * rate;
|
||||
return next < kPointerSize
|
||||
? kPointerSize
|
||||
: (next > INT_MAX ? INT_MAX : static_cast<intptr_t>(next));
|
||||
}
|
||||
|
||||
|
||||
void SamplingHeapProfiler::SampledAllocation::OnWeakCallback(
|
||||
const WeakCallbackInfo<SampledAllocation>& data) {
|
||||
SampledAllocation* sample = data.GetParameter();
|
||||
sample->sampling_heap_profiler_->samples_.erase(sample);
|
||||
delete sample;
|
||||
}
|
||||
|
||||
|
||||
SamplingHeapProfiler::FunctionInfo::FunctionInfo(SharedFunctionInfo* shared,
|
||||
StringsStorage* names)
|
||||
: name_(names->GetFunctionName(shared->DebugName())),
|
||||
script_name_(""),
|
||||
script_id_(v8::UnboundScript::kNoScriptId),
|
||||
start_position_(shared->start_position()) {
|
||||
if (shared->script()->IsScript()) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SamplingHeapProfiler::SampledAllocation::SampledAllocation(
|
||||
SamplingHeapProfiler* sampling_heap_profiler, Isolate* 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;
|
||||
while (!it.done() && frames_captured < max_frames) {
|
||||
JavaScriptFrame* frame = it.frame();
|
||||
SharedFunctionInfo* shared = frame->function()->shared();
|
||||
stack_.push_back(new FunctionInfo(shared, sampling_heap_profiler->names()));
|
||||
|
||||
frames_captured++;
|
||||
it.Advance();
|
||||
}
|
||||
|
||||
if (frames_captured == 0) {
|
||||
const char* name = nullptr;
|
||||
switch (isolate->current_vm_state()) {
|
||||
case GC:
|
||||
name = "(GC)";
|
||||
break;
|
||||
case COMPILER:
|
||||
name = "(COMPILER)";
|
||||
break;
|
||||
case OTHER:
|
||||
name = "(V8 API)";
|
||||
break;
|
||||
case EXTERNAL:
|
||||
name = "(EXTERNAL)";
|
||||
break;
|
||||
case IDLE:
|
||||
name = "(IDLE)";
|
||||
break;
|
||||
case JS:
|
||||
name = "(JS)";
|
||||
break;
|
||||
}
|
||||
stack_.push_back(new FunctionInfo(name));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SamplingHeapProfiler::Node* SamplingHeapProfiler::AllocateNode(
|
||||
AllocationProfile* profile, const std::map<int, Script*>& scripts,
|
||||
FunctionInfo* function_info) {
|
||||
DCHECK(function_info->get_name());
|
||||
DCHECK(function_info->get_script_name());
|
||||
|
||||
int line = v8::AllocationProfile::kNoLineNumberInfo;
|
||||
int column = v8::AllocationProfile::kNoColumnNumberInfo;
|
||||
|
||||
if (function_info->get_script_id() != v8::UnboundScript::kNoScriptId) {
|
||||
// Cannot use std::map<T>::at because it is not available on android.
|
||||
auto non_const_scripts = const_cast<std::map<int, Script*>&>(scripts);
|
||||
Handle<Script> script(non_const_scripts[function_info->get_script_id()]);
|
||||
|
||||
line =
|
||||
1 + Script::GetLineNumber(script, function_info->get_start_position());
|
||||
column = 1 + Script::GetColumnNumber(script,
|
||||
function_info->get_start_position());
|
||||
}
|
||||
|
||||
profile->nodes().push_back(
|
||||
Node({ToApiHandle<v8::String>(isolate_->factory()->InternalizeUtf8String(
|
||||
function_info->get_name())),
|
||||
ToApiHandle<v8::String>(isolate_->factory()->InternalizeUtf8String(
|
||||
function_info->get_script_name())),
|
||||
function_info->get_script_id(), function_info->get_start_position(),
|
||||
line, column, std::vector<Node*>(),
|
||||
std::vector<v8::AllocationProfile::Allocation>()}));
|
||||
|
||||
return &profile->nodes().back();
|
||||
}
|
||||
|
||||
|
||||
SamplingHeapProfiler::Node* SamplingHeapProfiler::FindOrAddChildNode(
|
||||
AllocationProfile* profile, const std::map<int, Script*>& scripts,
|
||||
Node* parent, FunctionInfo* function_info) {
|
||||
for (Node* child : parent->children) {
|
||||
if (child->script_id == function_info->get_script_id() &&
|
||||
child->start_position == function_info->get_start_position())
|
||||
return child;
|
||||
}
|
||||
Node* child = AllocateNode(profile, scripts, function_info);
|
||||
parent->children.push_back(child);
|
||||
return child;
|
||||
}
|
||||
|
||||
|
||||
SamplingHeapProfiler::Node* SamplingHeapProfiler::AddStack(
|
||||
AllocationProfile* profile, const std::map<int, Script*>& scripts,
|
||||
const std::vector<FunctionInfo*>& stack) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
v8::AllocationProfile* SamplingHeapProfiler::GetAllocationProfile() {
|
||||
// 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.
|
||||
std::map<int, Script*> scripts;
|
||||
{
|
||||
Script::Iterator iterator(isolate_);
|
||||
Script* script;
|
||||
while ((script = iterator.Next())) {
|
||||
scripts[script->id()] = script;
|
||||
}
|
||||
}
|
||||
|
||||
auto profile = new v8::internal::AllocationProfile();
|
||||
|
||||
// Create the root node.
|
||||
FunctionInfo function_info("(root)");
|
||||
AllocateNode(profile, scripts, &function_info);
|
||||
|
||||
for (SampledAllocation* allocation : samples_) {
|
||||
Node* node = AddStack(profile, scripts, allocation->get_stack());
|
||||
node->allocations.push_back({allocation->get_size(), 1});
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
135
src/profiler/sampling-heap-profiler.h
Normal file
135
src/profiler/sampling-heap-profiler.h
Normal file
@ -0,0 +1,135 @@
|
||||
// Copyright 2015 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef V8_PROFILER_SAMPLING_HEAP_PROFILER_H_
|
||||
#define V8_PROFILER_SAMPLING_HEAP_PROFILER_H_
|
||||
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include "include/v8-profiler.h"
|
||||
#include "src/heap/heap.h"
|
||||
#include "src/profiler/strings-storage.h"
|
||||
|
||||
namespace v8 {
|
||||
|
||||
namespace base {
|
||||
class RandomNumberGenerator;
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
|
||||
|
||||
class AllocationProfile : public v8::AllocationProfile {
|
||||
public:
|
||||
AllocationProfile() : nodes_() {}
|
||||
|
||||
Node* GetRootNode() override {
|
||||
return nodes_.size() == 0 ? nullptr : &nodes_.front();
|
||||
}
|
||||
|
||||
std::deque<Node>& nodes() { return nodes_; }
|
||||
|
||||
private:
|
||||
std::deque<Node> nodes_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(AllocationProfile);
|
||||
};
|
||||
|
||||
class SamplingHeapProfiler : public InlineAllocationObserver {
|
||||
public:
|
||||
SamplingHeapProfiler(Heap* heap, StringsStorage* names, uint64_t rate,
|
||||
int stack_depth);
|
||||
~SamplingHeapProfiler();
|
||||
|
||||
v8::AllocationProfile* GetAllocationProfile();
|
||||
|
||||
void Step(int bytes_allocated, Address soon_object, size_t size) override;
|
||||
intptr_t GetNextStepSize() override {
|
||||
return GetNextSampleInterval(random_, rate_);
|
||||
}
|
||||
|
||||
StringsStorage* names() const { return names_; }
|
||||
|
||||
class FunctionInfo {
|
||||
public:
|
||||
FunctionInfo(SharedFunctionInfo* shared, StringsStorage* names);
|
||||
explicit FunctionInfo(const char* name)
|
||||
: name_(name),
|
||||
script_name_(""),
|
||||
script_id_(v8::UnboundScript::kNoScriptId),
|
||||
start_position_(0) {}
|
||||
|
||||
const char* get_name() const { return name_; }
|
||||
const char* get_script_name() const { return script_name_; }
|
||||
int get_script_id() const { return script_id_; }
|
||||
int get_start_position() const { return start_position_; }
|
||||
|
||||
private:
|
||||
const char* const name_;
|
||||
const char* script_name_;
|
||||
int script_id_;
|
||||
const int start_position_;
|
||||
};
|
||||
|
||||
class SampledAllocation {
|
||||
public:
|
||||
SampledAllocation(SamplingHeapProfiler* sampling_heap_profiler,
|
||||
Isolate* isolate, Local<Value> local, size_t size,
|
||||
int max_frames);
|
||||
~SampledAllocation() {
|
||||
for (auto info : stack_) {
|
||||
delete info;
|
||||
}
|
||||
global_.Reset(); // drop the reference.
|
||||
}
|
||||
size_t get_size() const { return size_; }
|
||||
const std::vector<FunctionInfo*>& get_stack() const { return stack_; }
|
||||
|
||||
private:
|
||||
static void OnWeakCallback(const WeakCallbackInfo<SampledAllocation>& data);
|
||||
|
||||
SamplingHeapProfiler* const sampling_heap_profiler_;
|
||||
Global<Value> global_;
|
||||
std::vector<FunctionInfo*> stack_;
|
||||
const size_t size_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SampledAllocation);
|
||||
};
|
||||
|
||||
private:
|
||||
using Node = v8::AllocationProfile::Node;
|
||||
|
||||
Heap* heap() const { return heap_; }
|
||||
|
||||
void SampleObject(Address soon_object, size_t size);
|
||||
|
||||
static intptr_t GetNextSampleInterval(base::RandomNumberGenerator* random,
|
||||
uint64_t rate);
|
||||
|
||||
// Methods that construct v8::AllocationProfile.
|
||||
Node* AddStack(AllocationProfile* profile,
|
||||
const std::map<int, Script*>& scripts,
|
||||
const std::vector<FunctionInfo*>& stack);
|
||||
Node* FindOrAddChildNode(AllocationProfile* profile,
|
||||
const std::map<int, Script*>& scripts, Node* parent,
|
||||
FunctionInfo* function_info);
|
||||
Node* AllocateNode(AllocationProfile* profile,
|
||||
const std::map<int, Script*>& scripts,
|
||||
FunctionInfo* function_info);
|
||||
|
||||
Isolate* const isolate_;
|
||||
Heap* const heap_;
|
||||
base::RandomNumberGenerator* const random_;
|
||||
StringsStorage* const names_;
|
||||
std::set<SampledAllocation*> samples_;
|
||||
const uint64_t rate_;
|
||||
const int stack_depth_;
|
||||
};
|
||||
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
#endif // V8_PROFILER_SAMPLING_HEAP_PROFILER_H_
|
@ -2852,3 +2852,148 @@ TEST(AddressToTraceMap) {
|
||||
CHECK_EQ(0u, map.size());
|
||||
CHECK_EQ(0u, map.GetTraceNodeId(ToAddress(0x400)));
|
||||
}
|
||||
|
||||
|
||||
static const v8::AllocationProfile::Node* FindAllocationProfileNode(
|
||||
v8::AllocationProfile& profile, const Vector<const char*>& names) {
|
||||
v8::AllocationProfile::Node* node = profile.GetRootNode();
|
||||
for (int i = 0; node != nullptr && i < names.length(); ++i) {
|
||||
const char* name = names[i];
|
||||
auto children = node->children;
|
||||
node = nullptr;
|
||||
for (v8::AllocationProfile::Node* child : children) {
|
||||
v8::String::Utf8Value child_name(child->name);
|
||||
if (strcmp(*child_name, name) == 0) {
|
||||
node = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
TEST(SamplingHeapProfiler) {
|
||||
v8::HandleScope scope(v8::Isolate::GetCurrent());
|
||||
LocalContext env;
|
||||
v8::HeapProfiler* heap_profiler = env->GetIsolate()->GetHeapProfiler();
|
||||
|
||||
// Turn off always_opt. Inlining can cause stack traces to be shorter than
|
||||
// what we expect in this test.
|
||||
v8::internal::FLAG_always_opt = false;
|
||||
|
||||
const char* script_source =
|
||||
"var A = [];\n"
|
||||
"function bar(size) { return new Array(size); }\n"
|
||||
"var foo = function() {\n"
|
||||
" for (var i = 0; i < 1024; ++i) {\n"
|
||||
" A[i] = bar(1024);\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"foo();";
|
||||
|
||||
// Sample should be empty if requested before sampling has started.
|
||||
{
|
||||
v8::AllocationProfile* profile = heap_profiler->GetAllocationProfile();
|
||||
CHECK(profile == nullptr);
|
||||
}
|
||||
|
||||
int count_512kb = 0;
|
||||
{
|
||||
heap_profiler->StartSamplingHeapProfiler(512 * 1024);
|
||||
CompileRun(script_source);
|
||||
|
||||
v8::base::SmartPointer<v8::AllocationProfile> profile(
|
||||
heap_profiler->GetAllocationProfile());
|
||||
CHECK(!profile.is_empty());
|
||||
|
||||
const char* names[] = {"", "foo", "bar"};
|
||||
auto node_bar = FindAllocationProfileNode(
|
||||
*profile, Vector<const char*>(names, arraysize(names)));
|
||||
CHECK(node_bar);
|
||||
|
||||
// Count the number of allocations we sampled from bar.
|
||||
for (auto allocation : node_bar->allocations) {
|
||||
count_512kb += allocation.count;
|
||||
}
|
||||
|
||||
heap_profiler->StopSamplingHeapProfiler();
|
||||
}
|
||||
|
||||
// Samples should get cleared once sampling is stopped.
|
||||
{
|
||||
v8::AllocationProfile* profile = heap_profiler->GetAllocationProfile();
|
||||
CHECK(profile == nullptr);
|
||||
}
|
||||
|
||||
// Sampling at a higher rate should give us more sampled objects.
|
||||
{
|
||||
heap_profiler->StartSamplingHeapProfiler(32 * 1024);
|
||||
CompileRun(script_source);
|
||||
|
||||
v8::base::SmartPointer<v8::AllocationProfile> profile(
|
||||
heap_profiler->GetAllocationProfile());
|
||||
CHECK(!profile.is_empty());
|
||||
|
||||
const char* names[] = {"", "foo", "bar"};
|
||||
auto node_bar = FindAllocationProfileNode(
|
||||
*profile, Vector<const char*>(names, arraysize(names)));
|
||||
CHECK(node_bar);
|
||||
|
||||
// Count the number of allocations we sampled from bar.
|
||||
int count_32kb = 0;
|
||||
for (auto allocation : node_bar->allocations) {
|
||||
count_32kb += allocation.count;
|
||||
}
|
||||
|
||||
// We should have roughly 16x as many sampled allocations. However, since
|
||||
// sampling is a randomized process, we use a weaker test.
|
||||
CHECK_GT(count_32kb, count_512kb);
|
||||
|
||||
heap_profiler->StopSamplingHeapProfiler();
|
||||
}
|
||||
|
||||
// A more complicated test cases with deeper call graph and dynamically
|
||||
// generated function names.
|
||||
{
|
||||
heap_profiler->StartSamplingHeapProfiler(128);
|
||||
CompileRun(record_trace_tree_source);
|
||||
|
||||
v8::base::SmartPointer<v8::AllocationProfile> profile(
|
||||
heap_profiler->GetAllocationProfile());
|
||||
CHECK(!profile.is_empty());
|
||||
|
||||
const char* names1[] = {"", "start", "f_0_0", "f_0_1", "f_0_2"};
|
||||
auto node1 = FindAllocationProfileNode(
|
||||
*profile, Vector<const char*>(names1, arraysize(names1)));
|
||||
CHECK(node1);
|
||||
|
||||
const char* names2[] = {"", "generateFunctions"};
|
||||
auto node2 = FindAllocationProfileNode(
|
||||
*profile, Vector<const char*>(names2, arraysize(names2)));
|
||||
CHECK(node2);
|
||||
|
||||
heap_profiler->StopSamplingHeapProfiler();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(SamplingHeapProfilerApiAllocation) {
|
||||
v8::HandleScope scope(v8::Isolate::GetCurrent());
|
||||
LocalContext env;
|
||||
v8::HeapProfiler* heap_profiler = env->GetIsolate()->GetHeapProfiler();
|
||||
|
||||
heap_profiler->StartSamplingHeapProfiler(256);
|
||||
|
||||
for (int i = 0; i < 8 * 1024; ++i) v8::Object::New(env->GetIsolate());
|
||||
|
||||
v8::base::SmartPointer<v8::AllocationProfile> profile(
|
||||
heap_profiler->GetAllocationProfile());
|
||||
CHECK(!profile.is_empty());
|
||||
const char* names[] = {"(V8 API)"};
|
||||
auto node = FindAllocationProfileNode(
|
||||
*profile, Vector<const char*>(names, arraysize(names)));
|
||||
CHECK(node);
|
||||
|
||||
heap_profiler->StopSamplingHeapProfiler();
|
||||
}
|
||||
|
@ -955,6 +955,8 @@
|
||||
'../../src/profiler/profile-generator.h',
|
||||
'../../src/profiler/sampler.cc',
|
||||
'../../src/profiler/sampler.h',
|
||||
'../../src/profiler/sampling-heap-profiler.cc',
|
||||
'../../src/profiler/sampling-heap-profiler.h',
|
||||
'../../src/profiler/strings-storage.cc',
|
||||
'../../src/profiler/strings-storage.h',
|
||||
'../../src/profiler/unbound-queue-inl.h',
|
||||
|
Loading…
Reference in New Issue
Block a user