Commit f93fe955 authored by Andrew Comminos's avatar Andrew Comminos Committed by Commit Bot

[cpu-profiler] Add parameter to filter profiles by v8::Context

Adds support to the CPU profiler for scraping the incumbent contexts of
V8 stack frames. While it is generally unsafe to access heap objects
during a profiling interrupt, the native context is uniquely usable due
to being guaranteed an alive root on the stack, as well as its slots
being immutable after context creation.

Change-Id: I2c3149c1302b74d2f13aa99d1fdd0cf006e0f9d1
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1580020
Commit-Queue: Andrew Comminos <acomminos@fb.com>
Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Reviewed-by: 's avatarAlexei Filippov <alph@chromium.org>
Reviewed-by: 's avatarMichael Lippautz <mlippautz@chromium.org>
Reviewed-by: 's avatarPeter Marshall <petermarshall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#63258}
parent 728e9cd8
...@@ -26,6 +26,10 @@ struct CpuProfileDeoptFrame { ...@@ -26,6 +26,10 @@ struct CpuProfileDeoptFrame {
size_t position; size_t position;
}; };
namespace internal {
class CpuProfile;
} // namespace internal
} // namespace v8 } // namespace v8
#ifdef V8_OS_WIN #ifdef V8_OS_WIN
...@@ -94,6 +98,10 @@ struct V8_EXPORT TickSample { ...@@ -94,6 +98,10 @@ struct V8_EXPORT TickSample {
* register state rather than the one provided * register state rather than the one provided
* with |state| argument. Otherwise the method * with |state| argument. Otherwise the method
* will use provided register |state| as is. * will use provided register |state| as is.
* \param contexts If set, contexts[i] will be set to the address of the
* incumbent native context associated with frames[i]. It
* should be large enough to hold |frames_limit| frame
* contexts.
* \note GetStackSample is thread and signal safe and should only be called * \note GetStackSample is thread and signal safe and should only be called
* when the JS thread is paused or interrupted. * when the JS thread is paused or interrupted.
* Otherwise the behavior is undefined. * Otherwise the behavior is undefined.
...@@ -102,7 +110,8 @@ struct V8_EXPORT TickSample { ...@@ -102,7 +110,8 @@ struct V8_EXPORT TickSample {
RecordCEntryFrame record_c_entry_frame, RecordCEntryFrame record_c_entry_frame,
void** frames, size_t frames_limit, void** frames, size_t frames_limit,
v8::SampleInfo* sample_info, v8::SampleInfo* sample_info,
bool use_simulator_reg_state = true); bool use_simulator_reg_state = true,
void** contexts = nullptr);
StateTag state; // The state of the VM. StateTag state; // The state of the VM.
void* pc; // Instruction pointer. void* pc; // Instruction pointer.
union { union {
...@@ -112,6 +121,8 @@ struct V8_EXPORT TickSample { ...@@ -112,6 +121,8 @@ struct V8_EXPORT TickSample {
static const unsigned kMaxFramesCountLog2 = 8; static const unsigned kMaxFramesCountLog2 = 8;
static const unsigned kMaxFramesCount = (1 << kMaxFramesCountLog2) - 1; static const unsigned kMaxFramesCount = (1 << kMaxFramesCountLog2) - 1;
void* stack[kMaxFramesCount]; // Call stack. void* stack[kMaxFramesCount]; // Call stack.
void* contexts[kMaxFramesCount]; // Stack of associated native contexts.
void* top_context = nullptr; // Address of the incumbent native context.
unsigned frames_count : kMaxFramesCountLog2; // Number of captured frames. unsigned frames_count : kMaxFramesCountLog2; // Number of captured frames.
bool has_external_callback : 1; bool has_external_callback : 1;
bool update_stats : 1; // Whether the sample should update aggregated stats. bool update_stats : 1; // Whether the sample should update aggregated stats.
...@@ -337,21 +348,25 @@ class V8_EXPORT CpuProfilingOptions { ...@@ -337,21 +348,25 @@ class V8_EXPORT CpuProfilingOptions {
* zero, the sampling interval will be equal to * zero, the sampling interval will be equal to
* the profiler's sampling interval. * the profiler's sampling interval.
*/ */
CpuProfilingOptions(CpuProfilingMode mode = kLeafNodeLineNumbers, CpuProfilingOptions(
unsigned max_samples = kNoSampleLimit, CpuProfilingMode mode = kLeafNodeLineNumbers,
int sampling_interval_us = 0) unsigned max_samples = kNoSampleLimit, int sampling_interval_us = 0,
: mode_(mode), MaybeLocal<Context> filter_context = MaybeLocal<Context>());
max_samples_(max_samples),
sampling_interval_us_(sampling_interval_us) {}
CpuProfilingMode mode() const { return mode_; } CpuProfilingMode mode() const { return mode_; }
unsigned max_samples() const { return max_samples_; } unsigned max_samples() const { return max_samples_; }
int sampling_interval_us() const { return sampling_interval_us_; } int sampling_interval_us() const { return sampling_interval_us_; }
private: private:
friend class internal::CpuProfile;
bool has_filter_context() const { return !filter_context_.IsEmpty(); }
void* raw_filter_context() const;
CpuProfilingMode mode_; CpuProfilingMode mode_;
unsigned max_samples_; unsigned max_samples_;
int sampling_interval_us_; int sampling_interval_us_;
CopyablePersistentTraits<Context>::CopyablePersistent filter_context_;
}; };
/** /**
......
...@@ -1968,6 +1968,7 @@ struct SampleInfo { ...@@ -1968,6 +1968,7 @@ struct SampleInfo {
StateTag vm_state; // Current VM state. StateTag vm_state; // Current VM state.
void* external_callback_entry; // External callback address if VM is void* external_callback_entry; // External callback address if VM is
// executing an external callback. // executing an external callback.
void* top_context; // Incumbent native context address.
}; };
struct MemoryRange { struct MemoryRange {
......
...@@ -9845,6 +9845,27 @@ CpuProfiler* CpuProfiler::New(Isolate* isolate, ...@@ -9845,6 +9845,27 @@ CpuProfiler* CpuProfiler::New(Isolate* isolate,
reinterpret_cast<i::Isolate*>(isolate), naming_mode, logging_mode)); reinterpret_cast<i::Isolate*>(isolate), naming_mode, logging_mode));
} }
CpuProfilingOptions::CpuProfilingOptions(CpuProfilingMode mode,
unsigned max_samples,
int sampling_interval_us,
MaybeLocal<Context> filter_context)
: mode_(mode),
max_samples_(max_samples),
sampling_interval_us_(sampling_interval_us) {
if (!filter_context.IsEmpty()) {
Local<Context> local_filter_context = filter_context.ToLocalChecked();
filter_context_.Reset(local_filter_context->GetIsolate(),
local_filter_context);
}
}
void* CpuProfilingOptions::raw_filter_context() const {
return reinterpret_cast<void*>(
i::Context::cast(*Utils::OpenPersistent(filter_context_))
.native_context()
.address());
}
void CpuProfiler::Dispose() { delete reinterpret_cast<i::CpuProfiler*>(this); } void CpuProfiler::Dispose() { delete reinterpret_cast<i::CpuProfiler*>(this); }
// static // static
......
...@@ -251,9 +251,9 @@ class Utils { ...@@ -251,9 +251,9 @@ class Utils {
template <class From, class To> template <class From, class To>
static inline Local<To> Convert(v8::internal::Handle<From> obj); static inline Local<To> Convert(v8::internal::Handle<From> obj);
template <class T> template <class T, class M>
static inline v8::internal::Handle<v8::internal::Object> OpenPersistent( static inline v8::internal::Handle<v8::internal::Object> OpenPersistent(
const v8::Persistent<T>& persistent) { const v8::Persistent<T, M>& persistent) {
return v8::internal::Handle<v8::internal::Object>( return v8::internal::Handle<v8::internal::Object>(
reinterpret_cast<v8::internal::Address*>(persistent.val_)); reinterpret_cast<v8::internal::Address*>(persistent.val_));
} }
......
...@@ -270,6 +270,7 @@ SafeStackFrameIterator::SafeStackFrameIterator(Isolate* isolate, Address pc, ...@@ -270,6 +270,7 @@ SafeStackFrameIterator::SafeStackFrameIterator(Isolate* isolate, Address pc,
low_bound_(sp), low_bound_(sp),
high_bound_(js_entry_sp), high_bound_(js_entry_sp),
top_frame_type_(StackFrame::NONE), top_frame_type_(StackFrame::NONE),
top_context_address_(kNullAddress),
external_callback_scope_(isolate->external_callback_scope()), external_callback_scope_(isolate->external_callback_scope()),
top_link_register_(lr) { top_link_register_(lr) {
StackFrame::State state; StackFrame::State state;
...@@ -342,6 +343,13 @@ SafeStackFrameIterator::SafeStackFrameIterator(Isolate* isolate, Address pc, ...@@ -342,6 +343,13 @@ SafeStackFrameIterator::SafeStackFrameIterator(Isolate* isolate, Address pc,
if (type != StackFrame::INTERPRETED) { if (type != StackFrame::INTERPRETED) {
advance_frame = true; advance_frame = true;
} }
MSAN_MEMORY_IS_INITIALIZED(
fp + CommonFrameConstants::kContextOrFrameTypeOffset,
kSystemPointerSize);
Address type_or_context_address =
Memory<Address>(fp + CommonFrameConstants::kContextOrFrameTypeOffset);
if (!StackFrame::IsTypeMarker(type_or_context_address))
top_context_address_ = type_or_context_address;
} else { } else {
// Mark the frame as OPTIMIZED if we cannot determine its type. // Mark the frame as OPTIMIZED if we cannot determine its type.
// We chose OPTIMIZED rather than INTERPRETED because it's closer to // We chose OPTIMIZED rather than INTERPRETED because it's closer to
......
...@@ -1285,6 +1285,7 @@ class SafeStackFrameIterator : public StackFrameIteratorBase { ...@@ -1285,6 +1285,7 @@ class SafeStackFrameIterator : public StackFrameIteratorBase {
void Advance(); void Advance();
StackFrame::Type top_frame_type() const { return top_frame_type_; } StackFrame::Type top_frame_type() const { return top_frame_type_; }
Address top_context_address() const { return top_context_address_; }
private: private:
void AdvanceOneFrame(); void AdvanceOneFrame();
...@@ -1308,6 +1309,7 @@ class SafeStackFrameIterator : public StackFrameIteratorBase { ...@@ -1308,6 +1309,7 @@ class SafeStackFrameIterator : public StackFrameIteratorBase {
const Address low_bound_; const Address low_bound_;
const Address high_bound_; const Address high_bound_;
StackFrame::Type top_frame_type_; StackFrame::Type top_frame_type_;
Address top_context_address_;
ExternalCallbackScope* external_callback_scope_; ExternalCallbackScope* external_callback_scope_;
Address top_link_register_; Address top_link_register_;
}; };
......
...@@ -2923,6 +2923,9 @@ void Heap::OnMoveEvent(HeapObject target, HeapObject source, ...@@ -2923,6 +2923,9 @@ void Heap::OnMoveEvent(HeapObject target, HeapObject source,
if (target.IsSharedFunctionInfo()) { if (target.IsSharedFunctionInfo()) {
LOG_CODE_EVENT(isolate_, SharedFunctionInfoMoveEvent(source.address(), LOG_CODE_EVENT(isolate_, SharedFunctionInfoMoveEvent(source.address(),
target.address())); target.address()));
} else if (target.IsNativeContext()) {
PROFILE(isolate_,
NativeContextMoveEvent(source.address(), target.address()));
} }
if (FLAG_verify_predictable) { if (FLAG_verify_predictable) {
......
...@@ -89,6 +89,7 @@ class CodeEventListener { ...@@ -89,6 +89,7 @@ class CodeEventListener {
virtual void RegExpCodeCreateEvent(AbstractCode code, String source) = 0; virtual void RegExpCodeCreateEvent(AbstractCode code, String source) = 0;
virtual void CodeMoveEvent(AbstractCode from, AbstractCode to) = 0; virtual void CodeMoveEvent(AbstractCode from, AbstractCode to) = 0;
virtual void SharedFunctionInfoMoveEvent(Address from, Address to) = 0; virtual void SharedFunctionInfoMoveEvent(Address from, Address to) = 0;
virtual void NativeContextMoveEvent(Address from, Address to) = 0;
virtual void CodeMovingGCEvent() = 0; virtual void CodeMovingGCEvent() = 0;
virtual void CodeDisableOptEvent(AbstractCode code, virtual void CodeDisableOptEvent(AbstractCode code,
SharedFunctionInfo shared) = 0; SharedFunctionInfo shared) = 0;
...@@ -164,6 +165,9 @@ class CodeEventDispatcher { ...@@ -164,6 +165,9 @@ class CodeEventDispatcher {
void SharedFunctionInfoMoveEvent(Address from, Address to) { void SharedFunctionInfoMoveEvent(Address from, Address to) {
CODE_EVENT_DISPATCH(SharedFunctionInfoMoveEvent(from, to)); CODE_EVENT_DISPATCH(SharedFunctionInfoMoveEvent(from, to));
} }
void NativeContextMoveEvent(Address from, Address to) {
CODE_EVENT_DISPATCH(NativeContextMoveEvent(from, to));
}
void CodeMovingGCEvent() { CODE_EVENT_DISPATCH(CodeMovingGCEvent()); } void CodeMovingGCEvent() { CODE_EVENT_DISPATCH(CodeMovingGCEvent()); }
void CodeDisableOptEvent(AbstractCode code, SharedFunctionInfo shared) { void CodeDisableOptEvent(AbstractCode code, SharedFunctionInfo shared) {
CODE_EVENT_DISPATCH(CodeDisableOptEvent(code, shared)); CODE_EVENT_DISPATCH(CodeDisableOptEvent(code, shared));
......
...@@ -216,6 +216,8 @@ class Logger : public CodeEventListener { ...@@ -216,6 +216,8 @@ class Logger : public CodeEventListener {
void SharedFunctionInfoMoveEvent(Address from, Address to) override; void SharedFunctionInfoMoveEvent(Address from, Address to) override;
void NativeContextMoveEvent(Address from, Address to) override {}
void CodeNameEvent(Address addr, int pos, const char* code_name); void CodeNameEvent(Address addr, int pos, const char* code_name);
void CodeDeoptEvent(Code code, DeoptimizeKind kind, Address pc, void CodeDeoptEvent(Code code, DeoptimizeKind kind, Address pc,
...@@ -401,6 +403,7 @@ class V8_EXPORT_PRIVATE CodeEventLogger : public CodeEventListener { ...@@ -401,6 +403,7 @@ class V8_EXPORT_PRIVATE CodeEventLogger : public CodeEventListener {
void GetterCallbackEvent(Name name, Address entry_point) override {} void GetterCallbackEvent(Name name, Address entry_point) override {}
void SetterCallbackEvent(Name name, Address entry_point) override {} void SetterCallbackEvent(Name name, Address entry_point) override {}
void SharedFunctionInfoMoveEvent(Address from, Address to) override {} void SharedFunctionInfoMoveEvent(Address from, Address to) override {}
void NativeContextMoveEvent(Address from, Address to) override {}
void CodeMovingGCEvent() override {} void CodeMovingGCEvent() override {}
void CodeDeoptEvent(Code code, DeoptimizeKind kind, Address pc, void CodeDeoptEvent(Code code, DeoptimizeKind kind, Address pc,
int fp_to_sp_delta) override {} int fp_to_sp_delta) override {}
...@@ -453,6 +456,7 @@ class ExternalCodeEventListener : public CodeEventListener { ...@@ -453,6 +456,7 @@ class ExternalCodeEventListener : public CodeEventListener {
void GetterCallbackEvent(Name name, Address entry_point) override {} void GetterCallbackEvent(Name name, Address entry_point) override {}
void SetterCallbackEvent(Name name, Address entry_point) override {} void SetterCallbackEvent(Name name, Address entry_point) override {}
void SharedFunctionInfoMoveEvent(Address from, Address to) override {} void SharedFunctionInfoMoveEvent(Address from, Address to) override {}
void NativeContextMoveEvent(Address from, Address to) override {}
void CodeMoveEvent(AbstractCode from, AbstractCode to) override {} void CodeMoveEvent(AbstractCode from, AbstractCode to) override {}
void CodeDisableOptEvent(AbstractCode code, void CodeDisableOptEvent(AbstractCode code,
SharedFunctionInfo shared) override {} SharedFunctionInfo shared) override {}
......
...@@ -161,7 +161,14 @@ void ProfilerEventsProcessor::StopSynchronously() { ...@@ -161,7 +161,14 @@ void ProfilerEventsProcessor::StopSynchronously() {
bool ProfilerEventsProcessor::ProcessCodeEvent() { bool ProfilerEventsProcessor::ProcessCodeEvent() {
CodeEventsContainer record; CodeEventsContainer record;
if (events_buffer_.Dequeue(&record)) { if (events_buffer_.Dequeue(&record)) {
if (record.generic.type == CodeEventRecord::NATIVE_CONTEXT_MOVE) {
NativeContextMoveEventRecord& nc_record =
record.NativeContextMoveEventRecord_;
generator_->UpdateNativeContextAddress(nc_record.from_address,
nc_record.to_address);
} else {
code_observer_->CodeEventHandlerInternal(record); code_observer_->CodeEventHandlerInternal(record);
}
last_processed_code_event_id_ = record.generic.order; last_processed_code_event_id_ = record.generic.order;
return true; return true;
} }
...@@ -174,6 +181,7 @@ void ProfilerEventsProcessor::CodeEventHandler( ...@@ -174,6 +181,7 @@ void ProfilerEventsProcessor::CodeEventHandler(
case CodeEventRecord::CODE_CREATION: case CodeEventRecord::CODE_CREATION:
case CodeEventRecord::CODE_MOVE: case CodeEventRecord::CODE_MOVE:
case CodeEventRecord::CODE_DISABLE_OPT: case CodeEventRecord::CODE_DISABLE_OPT:
case CodeEventRecord::NATIVE_CONTEXT_MOVE:
Enqueue(evt_rec); Enqueue(evt_rec);
break; break;
case CodeEventRecord::CODE_DEOPT: { case CodeEventRecord::CODE_DEOPT: {
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "src/base/platform/mutex.h" #include "src/base/platform/mutex.h"
#include "src/base/platform/time.h" #include "src/base/platform/time.h"
#include "src/execution/isolate.h" #include "src/execution/isolate.h"
#include "src/handles/maybe-handles.h"
#include "src/libsampler/sampler.h" #include "src/libsampler/sampler.h"
#include "src/profiler/circular-queue.h" #include "src/profiler/circular-queue.h"
#include "src/profiler/profiler-listener.h" #include "src/profiler/profiler-listener.h"
...@@ -37,14 +38,14 @@ class ProfileGenerator; ...@@ -37,14 +38,14 @@ class ProfileGenerator;
V(CODE_DEOPT, CodeDeoptEventRecord) \ V(CODE_DEOPT, CodeDeoptEventRecord) \
V(REPORT_BUILTIN, ReportBuiltinEventRecord) V(REPORT_BUILTIN, ReportBuiltinEventRecord)
#define VM_EVENTS_TYPE_LIST(V) \
CODE_EVENTS_TYPE_LIST(V) \
V(NATIVE_CONTEXT_MOVE, NativeContextMoveEventRecord)
class CodeEventRecord { class CodeEventRecord {
public: public:
#define DECLARE_TYPE(type, ignore) type, #define DECLARE_TYPE(type, ignore) type,
enum Type { enum Type { NONE = 0, VM_EVENTS_TYPE_LIST(DECLARE_TYPE) };
NONE = 0,
CODE_EVENTS_TYPE_LIST(DECLARE_TYPE)
};
#undef DECLARE_TYPE #undef DECLARE_TYPE
Type type; Type type;
...@@ -102,6 +103,12 @@ class ReportBuiltinEventRecord : public CodeEventRecord { ...@@ -102,6 +103,12 @@ class ReportBuiltinEventRecord : public CodeEventRecord {
V8_INLINE void UpdateCodeMap(CodeMap* code_map); V8_INLINE void UpdateCodeMap(CodeMap* code_map);
}; };
// Signals that a native context's address has changed.
class NativeContextMoveEventRecord : public CodeEventRecord {
public:
Address from_address;
Address to_address;
};
class TickSampleEventRecord { class TickSampleEventRecord {
public: public:
...@@ -124,7 +131,7 @@ class CodeEventsContainer { ...@@ -124,7 +131,7 @@ class CodeEventsContainer {
union { union {
CodeEventRecord generic; CodeEventRecord generic;
#define DECLARE_CLASS(ignore, type) type type##_; #define DECLARE_CLASS(ignore, type) type type##_;
CODE_EVENTS_TYPE_LIST(DECLARE_CLASS) VM_EVENTS_TYPE_LIST(DECLARE_CLASS)
#undef DECLARE_CLASS #undef DECLARE_CLASS
}; };
}; };
...@@ -293,6 +300,7 @@ class V8_EXPORT_PRIVATE CpuProfiler { ...@@ -293,6 +300,7 @@ class V8_EXPORT_PRIVATE CpuProfiler {
void CollectSample(); void CollectSample();
void StartProfiling(const char* title, CpuProfilingOptions options = {}); void StartProfiling(const char* title, CpuProfilingOptions options = {});
void StartProfiling(String title, CpuProfilingOptions options = {}); void StartProfiling(String title, CpuProfilingOptions options = {});
CpuProfile* StopProfiling(const char* title); CpuProfile* StopProfiling(const char* title);
CpuProfile* StopProfiling(String title); CpuProfile* StopProfiling(String title);
int GetProfilesCount(); int GetProfilesCount();
......
...@@ -412,16 +412,18 @@ ProfileNode* ProfileTree::AddPathFromEnd(const std::vector<CodeEntry*>& path, ...@@ -412,16 +412,18 @@ ProfileNode* ProfileTree::AddPathFromEnd(const std::vector<CodeEntry*>& path,
ProfileNode* ProfileTree::AddPathFromEnd(const ProfileStackTrace& path, ProfileNode* ProfileTree::AddPathFromEnd(const ProfileStackTrace& path,
int src_line, bool update_stats, int src_line, bool update_stats,
ProfilingMode mode) { ProfilingMode mode,
ContextFilter* context_filter) {
ProfileNode* node = root_; ProfileNode* node = root_;
CodeEntry* last_entry = nullptr; CodeEntry* last_entry = nullptr;
int parent_line_number = v8::CpuProfileNode::kNoLineNumberInfo; int parent_line_number = v8::CpuProfileNode::kNoLineNumberInfo;
for (auto it = path.rbegin(); it != path.rend(); ++it) { for (auto it = path.rbegin(); it != path.rend(); ++it) {
if ((*it).code_entry == nullptr) continue; if (it->entry.code_entry == nullptr) continue;
last_entry = (*it).code_entry; if (context_filter && !context_filter->Accept(*it)) continue;
node = node->FindOrAddChild((*it).code_entry, parent_line_number); last_entry = (*it).entry.code_entry;
node = node->FindOrAddChild((*it).entry.code_entry, parent_line_number);
parent_line_number = mode == ProfilingMode::kCallerLineNumbers parent_line_number = mode == ProfilingMode::kCallerLineNumbers
? (*it).line_number ? (*it).entry.line_number
: v8::CpuProfileNode::kNoLineNumberInfo; : v8::CpuProfileNode::kNoLineNumberInfo;
} }
if (last_entry && last_entry->has_deopt_info()) { if (last_entry && last_entry->has_deopt_info()) {
...@@ -436,7 +438,6 @@ ProfileNode* ProfileTree::AddPathFromEnd(const ProfileStackTrace& path, ...@@ -436,7 +438,6 @@ ProfileNode* ProfileTree::AddPathFromEnd(const ProfileStackTrace& path,
return node; return node;
} }
class Position { class Position {
public: public:
explicit Position(ProfileNode* node) explicit Position(ProfileNode* node)
...@@ -478,6 +479,21 @@ void ProfileTree::TraverseDepthFirst(Callback* callback) { ...@@ -478,6 +479,21 @@ void ProfileTree::TraverseDepthFirst(Callback* callback) {
} }
} }
bool ContextFilter::Accept(const ProfileStackFrame& frame) {
// If a frame should always be included in profiles (e.g. metadata frames),
// skip the context check.
if (!frame.filterable) return true;
// Strip heap object tag from frame.
return (frame.native_context & ~kHeapObjectTag) == native_context_address_;
}
void ContextFilter::OnMoveEvent(Address from_address, Address to_address) {
if (native_context_address() != from_address) return;
set_native_context_address(to_address);
}
using v8::tracing::TracedValue; using v8::tracing::TracedValue;
std::atomic<uint32_t> CpuProfile::last_id_; std::atomic<uint32_t> CpuProfile::last_id_;
...@@ -496,6 +512,13 @@ CpuProfile::CpuProfile(CpuProfiler* profiler, const char* title, ...@@ -496,6 +512,13 @@ CpuProfile::CpuProfile(CpuProfiler* profiler, const char* title,
(start_time_ - base::TimeTicks()).InMicroseconds()); (start_time_ - base::TimeTicks()).InMicroseconds());
TRACE_EVENT_SAMPLE_WITH_ID1(TRACE_DISABLED_BY_DEFAULT("v8.cpu_profiler"), TRACE_EVENT_SAMPLE_WITH_ID1(TRACE_DISABLED_BY_DEFAULT("v8.cpu_profiler"),
"Profile", id_, "data", std::move(value)); "Profile", id_, "data", std::move(value));
if (options_.has_filter_context()) {
DisallowHeapAllocation no_gc;
i::Address raw_filter_context =
reinterpret_cast<i::Address>(options_.raw_filter_context());
context_filter_ = base::make_unique<ContextFilter>(raw_filter_context);
}
} }
bool CpuProfile::CheckSubsample(base::TimeDelta source_sampling_interval) { bool CpuProfile::CheckSubsample(base::TimeDelta source_sampling_interval) {
...@@ -520,11 +543,11 @@ void CpuProfile::AddPath(base::TimeTicks timestamp, ...@@ -520,11 +543,11 @@ void CpuProfile::AddPath(base::TimeTicks timestamp,
bool update_stats, base::TimeDelta sampling_interval) { bool update_stats, base::TimeDelta sampling_interval) {
if (!CheckSubsample(sampling_interval)) return; if (!CheckSubsample(sampling_interval)) return;
ProfileNode* top_frame_node = ProfileNode* top_frame_node = top_down_.AddPathFromEnd(
top_down_.AddPathFromEnd(path, src_line, update_stats, options_.mode()); path, src_line, update_stats, options_.mode(), context_filter_.get());
bool should_record_sample = bool should_record_sample =
!timestamp.IsNull() && !timestamp.IsNull() && timestamp >= start_time_ &&
(options_.max_samples() == CpuProfilingOptions::kNoSampleLimit || (options_.max_samples() == CpuProfilingOptions::kNoSampleLimit ||
samples_.size() < options_.max_samples()); samples_.size() < options_.max_samples());
...@@ -623,6 +646,8 @@ void CpuProfile::StreamPendingTraceEvents() { ...@@ -623,6 +646,8 @@ void CpuProfile::StreamPendingTraceEvents() {
void CpuProfile::FinishProfile() { void CpuProfile::FinishProfile() {
end_time_ = base::TimeTicks::HighResolutionNow(); end_time_ = base::TimeTicks::HighResolutionNow();
// Stop tracking context movements after profiling stops.
context_filter_ = nullptr;
StreamPendingTraceEvents(); StreamPendingTraceEvents();
auto value = TracedValue::Create(); auto value = TracedValue::Create();
value->SetDouble("endTime", (end_time_ - base::TimeTicks()).InMicroseconds()); value->SetDouble("endTime", (end_time_ - base::TimeTicks()).InMicroseconds());
...@@ -833,6 +858,17 @@ void CpuProfilesCollection::AddPathToCurrentProfiles( ...@@ -833,6 +858,17 @@ void CpuProfilesCollection::AddPathToCurrentProfiles(
current_profiles_semaphore_.Signal(); current_profiles_semaphore_.Signal();
} }
void CpuProfilesCollection::UpdateNativeContextAddressForCurrentProfiles(
Address from, Address to) {
current_profiles_semaphore_.Wait();
for (const std::unique_ptr<CpuProfile>& profile : current_profiles_) {
if (auto* context_filter = profile->context_filter()) {
context_filter->OnMoveEvent(from, to);
}
}
current_profiles_semaphore_.Signal();
}
ProfileGenerator::ProfileGenerator(CpuProfilesCollection* profiles, ProfileGenerator::ProfileGenerator(CpuProfilesCollection* profiles,
CodeMap* code_map) CodeMap* code_map)
: profiles_(profiles), code_map_(code_map) {} : profiles_(profiles), code_map_(code_map) {}
...@@ -857,9 +893,11 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) { ...@@ -857,9 +893,11 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
// Don't use PC when in external callback code, as it can point // Don't use PC when in external callback code, as it can point
// inside a callback's code, and we will erroneously report // inside a callback's code, and we will erroneously report
// that a callback calls itself. // that a callback calls itself.
stack_trace.push_back( stack_trace.push_back({{FindEntry(reinterpret_cast<Address>(
{FindEntry(reinterpret_cast<Address>(sample.external_callback_entry)), sample.external_callback_entry)),
no_line_info}); no_line_info},
reinterpret_cast<Address>(sample.top_context),
true});
} else { } else {
Address attributed_pc = reinterpret_cast<Address>(sample.pc); Address attributed_pc = reinterpret_cast<Address>(sample.pc);
CodeEntry* pc_entry = FindEntry(attributed_pc); CodeEntry* pc_entry = FindEntry(attributed_pc);
...@@ -883,7 +921,9 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) { ...@@ -883,7 +921,9 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
src_line = pc_entry->line_number(); src_line = pc_entry->line_number();
} }
src_line_not_found = false; src_line_not_found = false;
stack_trace.push_back({pc_entry, src_line}); stack_trace.push_back({{pc_entry, src_line},
reinterpret_cast<Address>(sample.top_context),
true});
if (pc_entry->builtin_id() == Builtins::kFunctionPrototypeApply || if (pc_entry->builtin_id() == Builtins::kFunctionPrototypeApply ||
pc_entry->builtin_id() == Builtins::kFunctionPrototypeCall) { pc_entry->builtin_id() == Builtins::kFunctionPrototypeCall) {
...@@ -895,7 +935,9 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) { ...@@ -895,7 +935,9 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
// 'unresolved' entry. // 'unresolved' entry.
if (!sample.has_external_callback) { if (!sample.has_external_callback) {
stack_trace.push_back( stack_trace.push_back(
{CodeEntry::unresolved_entry(), no_line_info}); {{CodeEntry::unresolved_entry(), no_line_info},
kNullAddress,
true});
} }
} }
} }
...@@ -903,6 +945,7 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) { ...@@ -903,6 +945,7 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
for (unsigned i = 0; i < sample.frames_count; ++i) { for (unsigned i = 0; i < sample.frames_count; ++i) {
Address stack_pos = reinterpret_cast<Address>(sample.stack[i]); Address stack_pos = reinterpret_cast<Address>(sample.stack[i]);
Address native_context = reinterpret_cast<Address>(sample.contexts[i]);
CodeEntry* entry = FindEntry(stack_pos); CodeEntry* entry = FindEntry(stack_pos);
int line_number = no_line_info; int line_number = no_line_info;
if (entry) { if (entry) {
...@@ -914,8 +957,13 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) { ...@@ -914,8 +957,13 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
entry->GetInlineStack(pc_offset); entry->GetInlineStack(pc_offset);
if (inline_stack) { if (inline_stack) {
int most_inlined_frame_line_number = entry->GetSourceLine(pc_offset); int most_inlined_frame_line_number = entry->GetSourceLine(pc_offset);
stack_trace.insert(stack_trace.end(), inline_stack->begin(), for (auto entry : *inline_stack) {
inline_stack->end()); // Set the native context of inlined frames to be equal to that of
// their parent. This is safe, as functions cannot inline themselves
// into a parent from another native context.
stack_trace.push_back({entry, native_context, true});
}
// This is a bit of a messy hack. The line number for the most-inlined // This is a bit of a messy hack. The line number for the most-inlined
// frame (the function at the end of the chain of function calls) has // frame (the function at the end of the chain of function calls) has
// the wrong line number in inline_stack. The actual line number in // the wrong line number in inline_stack. The actual line number in
...@@ -925,7 +973,7 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) { ...@@ -925,7 +973,7 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
// inlining_id. // inlining_id.
DCHECK(!inline_stack->empty()); DCHECK(!inline_stack->empty());
size_t index = stack_trace.size() - inline_stack->size(); size_t index = stack_trace.size() - inline_stack->size();
stack_trace[index].line_number = most_inlined_frame_line_number; stack_trace[index].entry.line_number = most_inlined_frame_line_number;
} }
// Skip unresolved frames (e.g. internal frame) and get source line of // Skip unresolved frames (e.g. internal frame) and get source line of
// the first JS caller. // the first JS caller.
...@@ -944,21 +992,22 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) { ...@@ -944,21 +992,22 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
// so we use it instead of pushing entry to stack_trace. // so we use it instead of pushing entry to stack_trace.
if (inline_stack) continue; if (inline_stack) continue;
} }
stack_trace.push_back({entry, line_number}); stack_trace.push_back({{entry, line_number}, native_context, true});
} }
} }
if (FLAG_prof_browser_mode) { if (FLAG_prof_browser_mode) {
bool no_symbolized_entries = true; bool no_symbolized_entries = true;
for (auto e : stack_trace) { for (auto e : stack_trace) {
if (e.code_entry != nullptr) { if (e.entry.code_entry != nullptr) {
no_symbolized_entries = false; no_symbolized_entries = false;
break; break;
} }
} }
// If no frames were symbolized, put the VM state entry in. // If no frames were symbolized, put the VM state entry in.
if (no_symbolized_entries) { if (no_symbolized_entries) {
stack_trace.push_back({EntryForVMState(sample.state), no_line_info}); stack_trace.push_back(
{{EntryForVMState(sample.state), no_line_info}, kNullAddress, false});
} }
} }
...@@ -967,6 +1016,10 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) { ...@@ -967,6 +1016,10 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
sample.sampling_interval); sample.sampling_interval);
} }
void ProfileGenerator::UpdateNativeContextAddress(Address from, Address to) {
profiles_->UpdateNativeContextAddressForCurrentProfiles(from, to);
}
CodeEntry* ProfileGenerator::EntryForVMState(StateTag tag) { CodeEntry* ProfileGenerator::EntryForVMState(StateTag tag) {
switch (tag) { switch (tag) {
case GC: case GC:
......
...@@ -234,7 +234,36 @@ struct CodeEntryAndLineNumber { ...@@ -234,7 +234,36 @@ struct CodeEntryAndLineNumber {
int line_number; int line_number;
}; };
using ProfileStackTrace = std::vector<CodeEntryAndLineNumber>; struct ProfileStackFrame {
CodeEntryAndLineNumber entry;
Address native_context;
bool filterable; // If true, the frame should be filtered by context (if a
// filter is present).
};
typedef std::vector<ProfileStackFrame> ProfileStackTrace;
// Filters stack frames from sources other than a target native context.
class ContextFilter {
public:
explicit ContextFilter(Address native_context_address)
: native_context_address_(native_context_address) {}
// Returns true if the stack frame passes a context check.
bool Accept(const ProfileStackFrame&);
// Invoked when a native context has changed address.
void OnMoveEvent(Address from_address, Address to_address);
// Update the context's tracked address based on VM-thread events.
void set_native_context_address(Address address) {
native_context_address_ = address;
}
Address native_context_address() const { return native_context_address_; }
private:
Address native_context_address_;
};
class ProfileTree; class ProfileTree;
...@@ -321,7 +350,8 @@ class V8_EXPORT_PRIVATE ProfileTree { ...@@ -321,7 +350,8 @@ class V8_EXPORT_PRIVATE ProfileTree {
const ProfileStackTrace& path, const ProfileStackTrace& path,
int src_line = v8::CpuProfileNode::kNoLineNumberInfo, int src_line = v8::CpuProfileNode::kNoLineNumberInfo,
bool update_stats = true, bool update_stats = true,
ProfilingMode mode = ProfilingMode::kLeafNodeLineNumbers); ProfilingMode mode = ProfilingMode::kLeafNodeLineNumbers,
ContextFilter* context_filter = nullptr);
ProfileNode* root() const { return root_; } ProfileNode* root() const { return root_; }
unsigned next_node_id() { return next_node_id_++; } unsigned next_node_id() { return next_node_id_++; }
unsigned GetFunctionId(const ProfileNode* node); unsigned GetFunctionId(const ProfileNode* node);
...@@ -389,6 +419,7 @@ class CpuProfile { ...@@ -389,6 +419,7 @@ class CpuProfile {
base::TimeTicks start_time() const { return start_time_; } base::TimeTicks start_time() const { return start_time_; }
base::TimeTicks end_time() const { return end_time_; } base::TimeTicks end_time() const { return end_time_; }
CpuProfiler* cpu_profiler() const { return profiler_; } CpuProfiler* cpu_profiler() const { return profiler_; }
ContextFilter* context_filter() const { return context_filter_.get(); }
void UpdateTicksScale(); void UpdateTicksScale();
...@@ -399,6 +430,7 @@ class CpuProfile { ...@@ -399,6 +430,7 @@ class CpuProfile {
const char* title_; const char* title_;
const CpuProfilingOptions options_; const CpuProfilingOptions options_;
std::unique_ptr<ContextFilter> context_filter_;
base::TimeTicks start_time_; base::TimeTicks start_time_;
base::TimeTicks end_time_; base::TimeTicks end_time_;
std::deque<SampleInfo> samples_; std::deque<SampleInfo> samples_;
...@@ -477,6 +509,9 @@ class V8_EXPORT_PRIVATE CpuProfilesCollection { ...@@ -477,6 +509,9 @@ class V8_EXPORT_PRIVATE CpuProfilesCollection {
bool update_stats, bool update_stats,
base::TimeDelta sampling_interval); base::TimeDelta sampling_interval);
// Called from profile generator thread.
void UpdateNativeContextAddressForCurrentProfiles(Address from, Address to);
// Limits the number of profiles that can be simultaneously collected. // Limits the number of profiles that can be simultaneously collected.
static const int kMaxSimultaneousProfiles = 100; static const int kMaxSimultaneousProfiles = 100;
...@@ -498,6 +533,8 @@ class V8_EXPORT_PRIVATE ProfileGenerator { ...@@ -498,6 +533,8 @@ class V8_EXPORT_PRIVATE ProfileGenerator {
void RecordTickSample(const TickSample& sample); void RecordTickSample(const TickSample& sample);
void UpdateNativeContextAddress(Address from, Address to);
CodeMap* code_map() { return code_map_; } CodeMap* code_map() { return code_map_; }
private: private:
......
...@@ -177,8 +177,7 @@ void ProfilerListener::CodeCreateEvent(CodeEventListener::LogEventsAndTags tag, ...@@ -177,8 +177,7 @@ void ProfilerListener::CodeCreateEvent(CodeEventListener::LogEventsAndTags tag,
CodeEntry* cached_entry = GetOrInsertCachedEntry( CodeEntry* cached_entry = GetOrInsertCachedEntry(
&cached_inline_entries, std::move(inline_entry)); &cached_inline_entries, std::move(inline_entry));
inline_stack.push_back( inline_stack.push_back({cached_entry, line_number});
CodeEntryAndLineNumber{cached_entry, line_number});
} }
DCHECK(!inline_stack.empty()); DCHECK(!inline_stack.empty());
inline_stacks.emplace(inlining_id, std::move(inline_stack)); inline_stacks.emplace(inlining_id, std::move(inline_stack));
...@@ -280,6 +279,13 @@ void ProfilerListener::SetterCallbackEvent(Name name, Address entry_point) { ...@@ -280,6 +279,13 @@ void ProfilerListener::SetterCallbackEvent(Name name, Address entry_point) {
DispatchCodeEvent(evt_rec); DispatchCodeEvent(evt_rec);
} }
void ProfilerListener::NativeContextMoveEvent(Address from, Address to) {
CodeEventsContainer evt_rec(CodeEventRecord::NATIVE_CONTEXT_MOVE);
evt_rec.NativeContextMoveEventRecord_.from_address = from;
evt_rec.NativeContextMoveEventRecord_.to_address = to;
DispatchCodeEvent(evt_rec);
}
Name ProfilerListener::InferScriptName(Name name, SharedFunctionInfo info) { Name ProfilerListener::InferScriptName(Name name, SharedFunctionInfo info) {
if (name.IsString() && String::cast(name).length()) return name; if (name.IsString() && String::cast(name).length()) return name;
if (!info.script().IsScript()) return name; if (!info.script().IsScript()) return name;
......
...@@ -55,6 +55,7 @@ class V8_EXPORT_PRIVATE ProfilerListener : public CodeEventListener { ...@@ -55,6 +55,7 @@ class V8_EXPORT_PRIVATE ProfilerListener : public CodeEventListener {
void RegExpCodeCreateEvent(AbstractCode code, String source) override; void RegExpCodeCreateEvent(AbstractCode code, String source) override;
void SetterCallbackEvent(Name name, Address entry_point) override; void SetterCallbackEvent(Name name, Address entry_point) override;
void SharedFunctionInfoMoveEvent(Address from, Address to) override {} void SharedFunctionInfoMoveEvent(Address from, Address to) override {}
void NativeContextMoveEvent(Address from, Address to) override;
const char* GetName(Name name) { const char* GetName(Name name) {
return function_and_resource_names_.GetName(name); return function_and_resource_names_.GetName(name);
......
...@@ -147,6 +147,42 @@ bool SimulatorHelper::FillRegisters(Isolate* isolate, ...@@ -147,6 +147,42 @@ bool SimulatorHelper::FillRegisters(Isolate* isolate,
} }
#endif // USE_SIMULATOR #endif // USE_SIMULATOR
// Attempts to safely dereference the address of a native context at a given
// context's address. Returns kNullAddress on failure, in the event that the
// context is in an inconsistent state.
Address ScrapeNativeContextAddress(Heap* heap, Address context_address) {
DCHECK_EQ(heap->gc_state(), Heap::NOT_IN_GC);
if (!HAS_STRONG_HEAP_OBJECT_TAG(context_address)) return kNullAddress;
if (heap->memory_allocator()->IsOutsideAllocatedSpace(context_address))
return kNullAddress;
// Note that once a native context has been assigned to a context, the slot
// is no longer mutated except during pointer updates / evictions. Since
// pointer updates exclusively occur on the main thread, and we don't record
// TickSamples when the main thread's VM state is GC, the only other
// situation where the address here would be invalid is if it's being
// reassigned -- which isn't possible.
int native_context_offset =
i::Context::SlotOffset(i::Context::NATIVE_CONTEXT_INDEX);
i::Address native_context_slot_address =
context_address + native_context_offset;
// By the prior hypothesis, the indirect native context address should always
// be valid.
if (heap->memory_allocator()->IsOutsideAllocatedSpace(
native_context_slot_address)) {
DCHECK(false);
return kNullAddress;
}
i::ObjectSlot native_context_slot(native_context_slot_address);
i::Object native_context = native_context_slot.Relaxed_Load();
return native_context.ptr();
}
} // namespace } // namespace
} // namespace internal } // namespace internal
...@@ -162,7 +198,8 @@ DISABLE_ASAN void TickSample::Init(Isolate* v8_isolate, ...@@ -162,7 +198,8 @@ DISABLE_ASAN void TickSample::Init(Isolate* v8_isolate,
SampleInfo info; SampleInfo info;
RegisterState regs = reg_state; RegisterState regs = reg_state;
if (!GetStackSample(v8_isolate, &regs, record_c_entry_frame, stack, if (!GetStackSample(v8_isolate, &regs, record_c_entry_frame, stack,
kMaxFramesCount, &info, use_simulator_reg_state)) { kMaxFramesCount, &info, use_simulator_reg_state,
contexts)) {
// It is executing JS but failed to collect a stack trace. // It is executing JS but failed to collect a stack trace.
// Mark the sample as spoiled. // Mark the sample as spoiled.
pc = nullptr; pc = nullptr;
...@@ -173,6 +210,7 @@ DISABLE_ASAN void TickSample::Init(Isolate* v8_isolate, ...@@ -173,6 +210,7 @@ DISABLE_ASAN void TickSample::Init(Isolate* v8_isolate,
pc = regs.pc; pc = regs.pc;
frames_count = static_cast<unsigned>(info.frames_count); frames_count = static_cast<unsigned>(info.frames_count);
has_external_callback = info.external_callback_entry != nullptr; has_external_callback = info.external_callback_entry != nullptr;
top_context = info.top_context;
if (has_external_callback) { if (has_external_callback) {
external_callback_entry = info.external_callback_entry; external_callback_entry = info.external_callback_entry;
} else if (frames_count) { } else if (frames_count) {
...@@ -197,11 +235,12 @@ bool TickSample::GetStackSample(Isolate* v8_isolate, RegisterState* regs, ...@@ -197,11 +235,12 @@ bool TickSample::GetStackSample(Isolate* v8_isolate, RegisterState* regs,
RecordCEntryFrame record_c_entry_frame, RecordCEntryFrame record_c_entry_frame,
void** frames, size_t frames_limit, void** frames, size_t frames_limit,
v8::SampleInfo* sample_info, v8::SampleInfo* sample_info,
bool use_simulator_reg_state) { bool use_simulator_reg_state, void** contexts) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate); i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
sample_info->frames_count = 0; sample_info->frames_count = 0;
sample_info->vm_state = isolate->current_vm_state(); sample_info->vm_state = isolate->current_vm_state();
sample_info->external_callback_entry = nullptr; sample_info->external_callback_entry = nullptr;
sample_info->top_context = nullptr;
if (sample_info->vm_state == GC) return true; if (sample_info->vm_state == GC) return true;
i::Address js_entry_sp = isolate->js_entry_sp(); i::Address js_entry_sp = isolate->js_entry_sp();
...@@ -229,7 +268,7 @@ bool TickSample::GetStackSample(Isolate* v8_isolate, RegisterState* regs, ...@@ -229,7 +268,7 @@ bool TickSample::GetStackSample(Isolate* v8_isolate, RegisterState* regs,
i::ExternalCallbackScope* scope = isolate->external_callback_scope(); i::ExternalCallbackScope* scope = isolate->external_callback_scope();
i::Address handler = i::Isolate::handler(isolate->thread_local_top()); i::Address handler = i::Isolate::handler(isolate->thread_local_top());
// If there is a handler on top of the external callback scope then // If there is a handler on top of the external callback scope then
// we have already entrered JavaScript again and the external callback // we have already entered JavaScript again and the external callback
// is not the top function. // is not the top function.
if (scope && scope->scope_address() < handler) { if (scope && scope->scope_address() < handler) {
i::Address* external_callback_entry_ptr = i::Address* external_callback_entry_ptr =
...@@ -245,23 +284,62 @@ bool TickSample::GetStackSample(Isolate* v8_isolate, RegisterState* regs, ...@@ -245,23 +284,62 @@ bool TickSample::GetStackSample(Isolate* v8_isolate, RegisterState* regs,
reinterpret_cast<i::Address>(regs->sp), reinterpret_cast<i::Address>(regs->sp),
reinterpret_cast<i::Address>(regs->lr), reinterpret_cast<i::Address>(regs->lr),
js_entry_sp); js_entry_sp);
i::Address top_context_address = it.top_context_address();
if (top_context_address != i::kNullAddress) {
sample_info->top_context = reinterpret_cast<void*>(
i::ScrapeNativeContextAddress(isolate->heap(), top_context_address));
} else {
sample_info->top_context = nullptr;
}
if (it.done()) return true; if (it.done()) return true;
size_t i = 0; size_t i = 0;
if (record_c_entry_frame == kIncludeCEntryFrame && if (record_c_entry_frame == kIncludeCEntryFrame &&
(it.top_frame_type() == internal::StackFrame::EXIT || (it.top_frame_type() == internal::StackFrame::EXIT ||
it.top_frame_type() == internal::StackFrame::BUILTIN_EXIT)) { it.top_frame_type() == internal::StackFrame::BUILTIN_EXIT)) {
frames[i++] = reinterpret_cast<void*>(isolate->c_function()); frames[i] = reinterpret_cast<void*>(isolate->c_function());
if (contexts) contexts[i] = sample_info->top_context;
i++;
} }
// If we couldn't get a context address from the top frame due to execution
// being in a callback, borrow it from the next context on the stack.
bool borrows_top_context = it.top_frame_type() == i::StackFrame::EXIT ||
it.top_frame_type() == i::StackFrame::BUILTIN_EXIT;
i::RuntimeCallTimer* timer = i::RuntimeCallTimer* timer =
isolate->counters()->runtime_call_stats()->current_timer(); isolate->counters()->runtime_call_stats()->current_timer();
for (; !it.done() && i < frames_limit; it.Advance()) { for (; !it.done() && i < frames_limit; it.Advance()) {
while (timer && reinterpret_cast<i::Address>(timer) < it.frame()->fp() && while (timer && reinterpret_cast<i::Address>(timer) < it.frame()->fp() &&
i < frames_limit) { i < frames_limit) {
if (contexts) contexts[i] = nullptr;
frames[i++] = reinterpret_cast<void*>(timer->counter()); frames[i++] = reinterpret_cast<void*>(timer->counter());
timer = timer->parent(); timer = timer->parent();
} }
if (i == frames_limit) break; if (i == frames_limit) break;
// Attempt to read the native context associated with the frame from the
// heap for standard frames.
if (it.frame()->is_standard() && (contexts || borrows_top_context)) {
i::Address context_address = base::Memory<i::Address>(
it.frame()->fp() + i::StandardFrameConstants::kContextOffset);
i::Address native_context_address =
i::ScrapeNativeContextAddress(isolate->heap(), context_address);
if (contexts)
contexts[i] = reinterpret_cast<void*>(native_context_address);
if (borrows_top_context) {
DCHECK(!sample_info->top_context);
sample_info->top_context =
reinterpret_cast<void*>(native_context_address);
}
} else if (contexts) {
contexts[i] = nullptr;
}
borrows_top_context = false;
if (it.frame()->is_interpreted()) { if (it.frame()->is_interpreted()) {
// For interpreted frames use the bytecode array pointer as the pc. // For interpreted frames use the bytecode array pointer as the pc.
i::InterpretedFrame* frame = i::InterpretedFrame* frame =
......
...@@ -1305,6 +1305,7 @@ RUNTIME_FUNCTION(Runtime_EnableCodeLoggingForTesting) { ...@@ -1305,6 +1305,7 @@ RUNTIME_FUNCTION(Runtime_EnableCodeLoggingForTesting) {
void RegExpCodeCreateEvent(AbstractCode code, String source) final {} void RegExpCodeCreateEvent(AbstractCode code, String source) final {}
void CodeMoveEvent(AbstractCode from, AbstractCode to) final {} void CodeMoveEvent(AbstractCode from, AbstractCode to) final {}
void SharedFunctionInfoMoveEvent(Address from, Address to) final {} void SharedFunctionInfoMoveEvent(Address from, Address to) final {}
void NativeContextMoveEvent(Address from, Address to) final {}
void CodeMovingGCEvent() final {} void CodeMovingGCEvent() final {}
void CodeDisableOptEvent(AbstractCode code, void CodeDisableOptEvent(AbstractCode code,
SharedFunctionInfo shared) final {} SharedFunctionInfo shared) final {}
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
#include "src/base/platform/platform.h" #include "src/base/platform/platform.h"
#include "src/codegen/source-position-table.h" #include "src/codegen/source-position-table.h"
#include "src/deoptimizer/deoptimizer.h" #include "src/deoptimizer/deoptimizer.h"
#include "src/heap/spaces.h"
#include "src/libplatform/default-platform.h" #include "src/libplatform/default-platform.h"
#include "src/logging/log.h" #include "src/logging/log.h"
#include "src/objects/objects-inl.h" #include "src/objects/objects-inl.h"
...@@ -45,6 +46,7 @@ ...@@ -45,6 +46,7 @@
#include "src/profiler/tracing-cpu-profiler.h" #include "src/profiler/tracing-cpu-profiler.h"
#include "src/utils/utils.h" #include "src/utils/utils.h"
#include "test/cctest/cctest.h" #include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-utils.h"
#include "test/cctest/profiler-extension.h" #include "test/cctest/profiler-extension.h"
#include "include/libplatform/v8-tracing.h" #include "include/libplatform/v8-tracing.h"
...@@ -456,7 +458,8 @@ class ProfilerHelper { ...@@ -456,7 +458,8 @@ class ProfilerHelper {
v8::Local<v8::Function> function, v8::Local<v8::Value> argv[], int argc, v8::Local<v8::Function> function, v8::Local<v8::Value> argv[], int argc,
unsigned min_js_samples = 0, unsigned min_external_samples = 0, unsigned min_js_samples = 0, unsigned min_external_samples = 0,
ProfilingMode mode = ProfilingMode::kLeafNodeLineNumbers, ProfilingMode mode = ProfilingMode::kLeafNodeLineNumbers,
unsigned max_samples = CpuProfilingOptions::kNoSampleLimit); unsigned max_samples = v8::CpuProfilingOptions::kNoSampleLimit,
v8::Local<v8::Context> context = v8::Local<v8::Context>());
v8::CpuProfiler* profiler() { return profiler_; } v8::CpuProfiler* profiler() { return profiler_; }
...@@ -469,11 +472,12 @@ v8::CpuProfile* ProfilerHelper::Run(v8::Local<v8::Function> function, ...@@ -469,11 +472,12 @@ v8::CpuProfile* ProfilerHelper::Run(v8::Local<v8::Function> function,
v8::Local<v8::Value> argv[], int argc, v8::Local<v8::Value> argv[], int argc,
unsigned min_js_samples, unsigned min_js_samples,
unsigned min_external_samples, unsigned min_external_samples,
ProfilingMode mode, unsigned max_samples) { ProfilingMode mode, unsigned max_samples,
v8::Local<v8::Context> context) {
v8::Local<v8::String> profile_name = v8_str("my_profile"); v8::Local<v8::String> profile_name = v8_str("my_profile");
profiler_->SetSamplingInterval(100); profiler_->SetSamplingInterval(100);
profiler_->StartProfiling(profile_name, {mode, max_samples}); profiler_->StartProfiling(profile_name, {mode, max_samples, 0, context});
v8::internal::CpuProfiler* iprofiler = v8::internal::CpuProfiler* iprofiler =
reinterpret_cast<v8::internal::CpuProfiler*>(profiler_); reinterpret_cast<v8::internal::CpuProfiler*>(profiler_);
...@@ -481,6 +485,7 @@ v8::CpuProfile* ProfilerHelper::Run(v8::Local<v8::Function> function, ...@@ -481,6 +485,7 @@ v8::CpuProfile* ProfilerHelper::Run(v8::Local<v8::Function> function,
reinterpret_cast<i::SamplingEventsProcessor*>(iprofiler->processor()) reinterpret_cast<i::SamplingEventsProcessor*>(iprofiler->processor())
->sampler(); ->sampler();
sampler->StartCountingSamples(); sampler->StartCountingSamples();
do { do {
function->Call(context_, context_->Global(), argc, argv).ToLocalChecked(); function->Call(context_, context_->Global(), argc, argv).ToLocalChecked();
} while (sampler->js_sample_count() < min_js_samples || } while (sampler->js_sample_count() < min_js_samples ||
...@@ -1825,7 +1830,7 @@ TEST(Inlining2) { ...@@ -1825,7 +1830,7 @@ TEST(Inlining2) {
v8::Local<v8::String> profile_name = v8_str("inlining"); v8::Local<v8::String> profile_name = v8_str("inlining");
profiler->StartProfiling( profiler->StartProfiling(
profile_name, profile_name,
CpuProfilingOptions{v8::CpuProfilingMode::kCallerLineNumbers}); v8::CpuProfilingOptions{v8::CpuProfilingMode::kCallerLineNumbers});
v8::Local<v8::Value> args[] = { v8::Local<v8::Value> args[] = {
v8::Integer::New(env->GetIsolate(), 50000 * load_factor)}; v8::Integer::New(env->GetIsolate(), 50000 * load_factor)};
...@@ -3467,6 +3472,140 @@ TEST(Bug9151StaleCodeEntries) { ...@@ -3467,6 +3472,140 @@ TEST(Bug9151StaleCodeEntries) {
CHECK(callback); CHECK(callback);
} }
// Tests that functions from other contexts aren't recorded when filtering for
// another context.
TEST(ContextIsolation) {
i::FLAG_allow_natives_syntax = true;
LocalContext execution_env;
i::HandleScope scope(CcTest::i_isolate());
// Install CollectSample callback for more deterministic sampling.
v8::Local<v8::FunctionTemplate> func_template = v8::FunctionTemplate::New(
execution_env.local()->GetIsolate(), CallCollectSample);
v8::Local<v8::Function> func =
func_template->GetFunction(execution_env.local()).ToLocalChecked();
func->SetName(v8_str("CallCollectSample"));
execution_env->Global()
->Set(execution_env.local(), v8_str("CallCollectSample"), func)
.FromJust();
ProfilerHelper helper(execution_env.local());
CompileRun(R"(
function optimized() {
CallCollectSample();
}
function unoptimized() {
CallCollectSample();
}
function start() {
// Test optimized functions
%PrepareFunctionForOptimization(optimized);
optimized();
optimized();
%OptimizeFunctionOnNextCall(optimized);
optimized();
// Test unoptimized functions
%NeverOptimizeFunction(unoptimized);
unoptimized();
// Test callback
CallCollectSample();
}
)");
v8::Local<v8::Function> function =
GetFunction(execution_env.local(), "start");
v8::CpuProfile* same_context_profile = helper.Run(
function, nullptr, 0, 0, 0, v8::CpuProfilingMode::kLeafNodeLineNumbers,
v8::CpuProfilingOptions::kNoSampleLimit, execution_env.local());
const v8::CpuProfileNode* root = same_context_profile->GetTopDownRoot();
const v8::CpuProfileNode* start_node = FindChild(root, "start");
CHECK(start_node);
const v8::CpuProfileNode* optimized_node = FindChild(start_node, "optimized");
CHECK(optimized_node);
const v8::CpuProfileNode* unoptimized_node =
FindChild(start_node, "unoptimized");
CHECK(unoptimized_node);
const v8::CpuProfileNode* callback_node =
FindChild(start_node, "CallCollectSample");
CHECK(callback_node);
{
LocalContext filter_env;
v8::CpuProfile* diff_context_profile = helper.Run(
function, nullptr, 0, 0, 0, v8::CpuProfilingMode::kLeafNodeLineNumbers,
v8::CpuProfilingOptions::kNoSampleLimit, filter_env.local());
const v8::CpuProfileNode* diff_root =
diff_context_profile->GetTopDownRoot();
// Ensure that no children were recorded (including callbacks, builtins).
CHECK(!FindChild(diff_root, "start"));
}
}
// Tests that when a native context that's being filtered is moved, we continue
// to track its execution.
TEST(ContextFilterMovedNativeContext) {
i::FLAG_allow_natives_syntax = true;
i::FLAG_manual_evacuation_candidates_selection = true;
LocalContext env;
i::HandleScope scope(CcTest::i_isolate());
{
// Install CollectSample callback for more deterministic sampling.
v8::Local<v8::FunctionTemplate> sample_func_template =
v8::FunctionTemplate::New(env.local()->GetIsolate(), CallCollectSample);
v8::Local<v8::Function> sample_func =
sample_func_template->GetFunction(env.local()).ToLocalChecked();
sample_func->SetName(v8_str("CallCollectSample"));
env->Global()
->Set(env.local(), v8_str("CallCollectSample"), sample_func)
.FromJust();
// Install a function that triggers the native context to be moved.
v8::Local<v8::FunctionTemplate> move_func_template =
v8::FunctionTemplate::New(
env.local()->GetIsolate(),
[](const v8::FunctionCallbackInfo<v8::Value>& info) {
i::Isolate* isolate =
reinterpret_cast<i::Isolate*>(info.GetIsolate());
i::heap::ForceEvacuationCandidate(
i::Page::FromHeapObject(isolate->raw_native_context()));
CcTest::CollectAllGarbage();
});
v8::Local<v8::Function> move_func =
move_func_template->GetFunction(env.local()).ToLocalChecked();
move_func->SetName(v8_str("ForceNativeContextMove"));
env->Global()
->Set(env.local(), v8_str("ForceNativeContextMove"), move_func)
.FromJust();
ProfilerHelper helper(env.local());
CompileRun(R"(
function start() {
ForceNativeContextMove();
CallCollectSample();
}
)");
v8::Local<v8::Function> function = GetFunction(env.local(), "start");
v8::CpuProfile* profile = helper.Run(
function, nullptr, 0, 0, 0, v8::CpuProfilingMode::kLeafNodeLineNumbers,
v8::CpuProfilingOptions::kNoSampleLimit, env.local());
const v8::CpuProfileNode* root = profile->GetTopDownRoot();
const v8::CpuProfileNode* start_node = FindChild(root, "start");
CHECK(start_node);
// Verify that after moving the native context, CallCollectSample is still
// recorded.
const v8::CpuProfileNode* callback_node =
FindChild(start_node, "CallCollectSample");
CHECK(callback_node);
}
}
enum class EntryCountMode { kAll, kOnlyInlined }; enum class EntryCountMode { kAll, kOnlyInlined };
// Count the number of unique source positions. // Count the number of unique source positions.
......
...@@ -200,7 +200,9 @@ TEST(ProfileTreeAddPathFromEndWithLineNumbers) { ...@@ -200,7 +200,9 @@ TEST(ProfileTreeAddPathFromEndWithLineNumbers) {
ProfileTree tree(CcTest::i_isolate()); ProfileTree tree(CcTest::i_isolate());
ProfileTreeTestHelper helper(&tree); ProfileTreeTestHelper helper(&tree);
ProfileStackTrace path = {{&c, 5}, {&b, 3}, {&a, 1}}; ProfileStackTrace path = {{{&c, 5}, kNullAddress, false},
{{&b, 3}, kNullAddress, false},
{{&a, 1}, kNullAddress, false}};
tree.AddPathFromEnd(path, v8::CpuProfileNode::kNoLineNumberInfo, true, tree.AddPathFromEnd(path, v8::CpuProfileNode::kNoLineNumberInfo, true,
v8::CpuProfilingMode::kCallerLineNumbers); v8::CpuProfilingMode::kCallerLineNumbers);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment