Revert "Revert of [profiler] Implement POC Sampling Heap Profiler (patchset #12 id:220001 of https://codereview.chromium.org/1555553002/ )"
This reverts commit 77df8659c2
.
BUG=
Review URL: https://codereview.chromium.org/1618693004
Cr-Commit-Position: refs/heads/master@{#33473}
This commit is contained in:
parent
475d178f2e
commit
f7304757ae
2
BUILD.gn
2
BUILD.gn
@ -1185,6 +1185,8 @@ source_set("v8_base") {
|
|||||||
"src/profiler/profile-generator.h",
|
"src/profiler/profile-generator.h",
|
||||||
"src/profiler/sampler.cc",
|
"src/profiler/sampler.cc",
|
||||||
"src/profiler/sampler.h",
|
"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.cc",
|
||||||
"src/profiler/strings-storage.h",
|
"src/profiler/strings-storage.h",
|
||||||
"src/profiler/unbound-queue-inl.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
|
* Interface for controlling heap profiling. Instance of the
|
||||||
* profiler can be retrieved using v8::Isolate::GetHeapProfiler.
|
* profiler can be retrieved using v8::Isolate::GetHeapProfiler.
|
||||||
@ -521,6 +605,49 @@ class V8_EXPORT HeapProfiler {
|
|||||||
*/
|
*/
|
||||||
void StopTrackingHeapObjects();
|
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
|
* Deletes all snapshots taken. All previously returned pointers to
|
||||||
* snapshots and their contents become invalid after this call.
|
* 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() {
|
void HeapProfiler::DeleteAllHeapSnapshots() {
|
||||||
reinterpret_cast<i::HeapProfiler*>(this)->DeleteAllSnapshots();
|
reinterpret_cast<i::HeapProfiler*>(this)->DeleteAllSnapshots();
|
||||||
}
|
}
|
||||||
|
@ -718,6 +718,11 @@ DEFINE_BOOL(heap_profiler_trace_objects, false,
|
|||||||
"Dump heap object allocations/movements/size_updates")
|
"Dump heap object allocations/movements/size_updates")
|
||||||
|
|
||||||
|
|
||||||
|
// sampling-heap-profiler.cc
|
||||||
|
DEFINE_BOOL(sampling_heap_profiler_suppress_randomness, false,
|
||||||
|
"Use constant sample intervals to eliminate test flakiness")
|
||||||
|
|
||||||
|
|
||||||
// v8.cc
|
// v8.cc
|
||||||
DEFINE_BOOL(use_idle_notification, true,
|
DEFINE_BOOL(use_idle_notification, true,
|
||||||
"Use idle notification to reduce memory footprint.")
|
"Use idle notification to reduce memory footprint.")
|
||||||
|
@ -478,6 +478,7 @@ class GCTracer;
|
|||||||
class HeapObjectsFilter;
|
class HeapObjectsFilter;
|
||||||
class HeapStats;
|
class HeapStats;
|
||||||
class HistogramTimer;
|
class HistogramTimer;
|
||||||
|
class InlineAllocationObserver;
|
||||||
class Isolate;
|
class Isolate;
|
||||||
class MemoryReducer;
|
class MemoryReducer;
|
||||||
class ObjectStats;
|
class ObjectStats;
|
||||||
@ -2792,6 +2793,60 @@ class PathTracer : public ObjectVisitor {
|
|||||||
DISALLOW_IMPLICIT_CONSTRUCTORS(PathTracer);
|
DISALLOW_IMPLICIT_CONSTRUCTORS(PathTracer);
|
||||||
};
|
};
|
||||||
#endif // DEBUG
|
#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 internal
|
||||||
} // namespace v8
|
} // namespace v8
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "src/cancelable-task.h"
|
#include "src/cancelable-task.h"
|
||||||
#include "src/execution.h"
|
#include "src/execution.h"
|
||||||
|
#include "src/heap/heap.h"
|
||||||
#include "src/heap/incremental-marking-job.h"
|
#include "src/heap/incremental-marking-job.h"
|
||||||
#include "src/heap/spaces.h"
|
#include "src/heap/spaces.h"
|
||||||
#include "src/objects.h"
|
#include "src/objects.h"
|
||||||
|
@ -20,6 +20,7 @@ namespace v8 {
|
|||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
class CompactionSpaceCollection;
|
class CompactionSpaceCollection;
|
||||||
|
class InlineAllocationObserver;
|
||||||
class Isolate;
|
class Isolate;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@ -2558,54 +2559,6 @@ class NewSpacePageIterator BASE_EMBEDDED {
|
|||||||
NewSpacePage* last_page_;
|
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.
|
// The young generation space.
|
||||||
|
@ -1939,13 +1939,14 @@ void Isolate::Deinit() {
|
|||||||
delete basic_block_profiler_;
|
delete basic_block_profiler_;
|
||||||
basic_block_profiler_ = NULL;
|
basic_block_profiler_ = NULL;
|
||||||
|
|
||||||
|
delete heap_profiler_;
|
||||||
|
heap_profiler_ = NULL;
|
||||||
|
|
||||||
heap_.TearDown();
|
heap_.TearDown();
|
||||||
logger_->TearDown();
|
logger_->TearDown();
|
||||||
|
|
||||||
cancelable_task_manager()->CancelAndWait();
|
cancelable_task_manager()->CancelAndWait();
|
||||||
|
|
||||||
delete heap_profiler_;
|
|
||||||
heap_profiler_ = NULL;
|
|
||||||
delete cpu_profiler_;
|
delete cpu_profiler_;
|
||||||
cpu_profiler_ = NULL;
|
cpu_profiler_ = NULL;
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#include "src/debug/debug.h"
|
#include "src/debug/debug.h"
|
||||||
#include "src/profiler/allocation-tracker.h"
|
#include "src/profiler/allocation-tracker.h"
|
||||||
#include "src/profiler/heap-snapshot-generator-inl.h"
|
#include "src/profiler/heap-snapshot-generator-inl.h"
|
||||||
|
#include "src/profiler/sampling-heap-profiler.h"
|
||||||
|
|
||||||
namespace v8 {
|
namespace v8 {
|
||||||
namespace internal {
|
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) {
|
void HeapProfiler::StartHeapObjectsTracking(bool track_allocations) {
|
||||||
ids_->UpdateHeapObjectsMap();
|
ids_->UpdateHeapObjectsMap();
|
||||||
is_tracking_object_moves_ = true;
|
is_tracking_object_moves_ = true;
|
||||||
|
@ -16,6 +16,7 @@ namespace internal {
|
|||||||
class AllocationTracker;
|
class AllocationTracker;
|
||||||
class HeapObjectsMap;
|
class HeapObjectsMap;
|
||||||
class HeapSnapshot;
|
class HeapSnapshot;
|
||||||
|
class SamplingHeapProfiler;
|
||||||
class StringsStorage;
|
class StringsStorage;
|
||||||
|
|
||||||
class HeapProfiler {
|
class HeapProfiler {
|
||||||
@ -29,6 +30,10 @@ class HeapProfiler {
|
|||||||
v8::ActivityControl* control,
|
v8::ActivityControl* control,
|
||||||
v8::HeapProfiler::ObjectNameResolver* resolver);
|
v8::HeapProfiler::ObjectNameResolver* resolver);
|
||||||
|
|
||||||
|
bool StartSamplingHeapProfiler(uint64_t sample_interval, int stack_depth);
|
||||||
|
void StopSamplingHeapProfiler();
|
||||||
|
AllocationProfile* GetAllocationProfile();
|
||||||
|
|
||||||
void StartHeapObjectsTracking(bool track_allocations);
|
void StartHeapObjectsTracking(bool track_allocations);
|
||||||
void StopHeapObjectsTracking();
|
void StopHeapObjectsTracking();
|
||||||
AllocationTracker* allocation_tracker() const {
|
AllocationTracker* allocation_tracker() const {
|
||||||
@ -79,6 +84,7 @@ class HeapProfiler {
|
|||||||
base::SmartPointer<AllocationTracker> allocation_tracker_;
|
base::SmartPointer<AllocationTracker> allocation_tracker_;
|
||||||
bool is_tracking_object_moves_;
|
bool is_tracking_object_moves_;
|
||||||
base::Mutex profiler_mutex_;
|
base::Mutex profiler_mutex_;
|
||||||
|
base::SmartPointer<SamplingHeapProfiler> sampling_heap_profiler_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
|
253
src/profiler/sampling-heap-profiler.cc
Normal file
253
src/profiler/sampling-heap-profiler.cc
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
// 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) {
|
||||||
|
if (FLAG_sampling_heap_profiler_suppress_randomness) {
|
||||||
|
return 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,155 @@ TEST(AddressToTraceMap) {
|
|||||||
CHECK_EQ(0u, map.size());
|
CHECK_EQ(0u, map.size());
|
||||||
CHECK_EQ(0u, map.GetTraceNodeId(ToAddress(0x400)));
|
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;
|
||||||
|
|
||||||
|
// Suppress randomness to avoid flakiness in tests.
|
||||||
|
v8::internal::FLAG_sampling_heap_profiler_suppress_randomness = true;
|
||||||
|
|
||||||
|
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,
|
||||||
|
// alignment and boundaries might tweak the numbers slightly. We use a
|
||||||
|
// slightly weaker test to account for this.
|
||||||
|
CHECK_GT(count_32kb, 8 * count_512kb);
|
||||||
|
|
||||||
|
heap_profiler->StopSamplingHeapProfiler();
|
||||||
|
}
|
||||||
|
|
||||||
|
// A more complicated test cases with deeper call graph and dynamically
|
||||||
|
// generated function names.
|
||||||
|
{
|
||||||
|
heap_profiler->StartSamplingHeapProfiler(64);
|
||||||
|
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();
|
||||||
|
|
||||||
|
// Suppress randomness to avoid flakiness in tests.
|
||||||
|
v8::internal::FLAG_sampling_heap_profiler_suppress_randomness = true;
|
||||||
|
|
||||||
|
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/profile-generator.h',
|
||||||
'../../src/profiler/sampler.cc',
|
'../../src/profiler/sampler.cc',
|
||||||
'../../src/profiler/sampler.h',
|
'../../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.cc',
|
||||||
'../../src/profiler/strings-storage.h',
|
'../../src/profiler/strings-storage.h',
|
||||||
'../../src/profiler/unbound-queue-inl.h',
|
'../../src/profiler/unbound-queue-inl.h',
|
||||||
|
Loading…
Reference in New Issue
Block a user