[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:
ofrobots 2016-01-21 13:13:27 -08:00 committed by Commit bot
parent cbc0564037
commit e5a9947811
13 changed files with 770 additions and 50 deletions

View File

@ -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",

View File

@ -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.

View File

@ -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();
}

View File

@ -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

View File

@ -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"

View File

@ -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.

View File

@ -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;

View File

@ -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;

View File

@ -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

View 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

View 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_

View File

@ -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();
}

View File

@ -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',