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 {
size_t position;
};
namespace internal {
class CpuProfile;
} // namespace internal
} // namespace v8
#ifdef V8_OS_WIN
......@@ -94,6 +98,10 @@ struct V8_EXPORT TickSample {
* register state rather than the one provided
* with |state| argument. Otherwise the method
* 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
* when the JS thread is paused or interrupted.
* Otherwise the behavior is undefined.
......@@ -102,7 +110,8 @@ struct V8_EXPORT TickSample {
RecordCEntryFrame record_c_entry_frame,
void** frames, size_t frames_limit,
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.
void* pc; // Instruction pointer.
union {
......@@ -112,6 +121,8 @@ struct V8_EXPORT TickSample {
static const unsigned kMaxFramesCountLog2 = 8;
static const unsigned kMaxFramesCount = (1 << kMaxFramesCountLog2) - 1;
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.
bool has_external_callback : 1;
bool update_stats : 1; // Whether the sample should update aggregated stats.
......@@ -337,21 +348,25 @@ class V8_EXPORT CpuProfilingOptions {
* zero, the sampling interval will be equal to
* the profiler's sampling interval.
*/
CpuProfilingOptions(CpuProfilingMode mode = kLeafNodeLineNumbers,
unsigned max_samples = kNoSampleLimit,
int sampling_interval_us = 0)
: mode_(mode),
max_samples_(max_samples),
sampling_interval_us_(sampling_interval_us) {}
CpuProfilingOptions(
CpuProfilingMode mode = kLeafNodeLineNumbers,
unsigned max_samples = kNoSampleLimit, int sampling_interval_us = 0,
MaybeLocal<Context> filter_context = MaybeLocal<Context>());
CpuProfilingMode mode() const { return mode_; }
unsigned max_samples() const { return max_samples_; }
int sampling_interval_us() const { return sampling_interval_us_; }
private:
friend class internal::CpuProfile;
bool has_filter_context() const { return !filter_context_.IsEmpty(); }
void* raw_filter_context() const;
CpuProfilingMode mode_;
unsigned max_samples_;
int sampling_interval_us_;
CopyablePersistentTraits<Context>::CopyablePersistent filter_context_;
};
/**
......
......@@ -1968,6 +1968,7 @@ struct SampleInfo {
StateTag vm_state; // Current VM state.
void* external_callback_entry; // External callback address if VM is
// executing an external callback.
void* top_context; // Incumbent native context address.
};
struct MemoryRange {
......
......@@ -9845,6 +9845,27 @@ CpuProfiler* CpuProfiler::New(Isolate* isolate,
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); }
// static
......
......@@ -251,9 +251,9 @@ class Utils {
template <class From, class To>
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(
const v8::Persistent<T>& persistent) {
const v8::Persistent<T, M>& persistent) {
return v8::internal::Handle<v8::internal::Object>(
reinterpret_cast<v8::internal::Address*>(persistent.val_));
}
......
......@@ -270,6 +270,7 @@ SafeStackFrameIterator::SafeStackFrameIterator(Isolate* isolate, Address pc,
low_bound_(sp),
high_bound_(js_entry_sp),
top_frame_type_(StackFrame::NONE),
top_context_address_(kNullAddress),
external_callback_scope_(isolate->external_callback_scope()),
top_link_register_(lr) {
StackFrame::State state;
......@@ -342,6 +343,13 @@ SafeStackFrameIterator::SafeStackFrameIterator(Isolate* isolate, Address pc,
if (type != StackFrame::INTERPRETED) {
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 {
// Mark the frame as OPTIMIZED if we cannot determine its type.
// We chose OPTIMIZED rather than INTERPRETED because it's closer to
......
......@@ -1285,6 +1285,7 @@ class SafeStackFrameIterator : public StackFrameIteratorBase {
void Advance();
StackFrame::Type top_frame_type() const { return top_frame_type_; }
Address top_context_address() const { return top_context_address_; }
private:
void AdvanceOneFrame();
......@@ -1308,6 +1309,7 @@ class SafeStackFrameIterator : public StackFrameIteratorBase {
const Address low_bound_;
const Address high_bound_;
StackFrame::Type top_frame_type_;
Address top_context_address_;
ExternalCallbackScope* external_callback_scope_;
Address top_link_register_;
};
......
......@@ -2923,6 +2923,9 @@ void Heap::OnMoveEvent(HeapObject target, HeapObject source,
if (target.IsSharedFunctionInfo()) {
LOG_CODE_EVENT(isolate_, SharedFunctionInfoMoveEvent(source.address(),
target.address()));
} else if (target.IsNativeContext()) {
PROFILE(isolate_,
NativeContextMoveEvent(source.address(), target.address()));
}
if (FLAG_verify_predictable) {
......
......@@ -89,6 +89,7 @@ class CodeEventListener {
virtual void RegExpCodeCreateEvent(AbstractCode code, String source) = 0;
virtual void CodeMoveEvent(AbstractCode from, AbstractCode to) = 0;
virtual void SharedFunctionInfoMoveEvent(Address from, Address to) = 0;
virtual void NativeContextMoveEvent(Address from, Address to) = 0;
virtual void CodeMovingGCEvent() = 0;
virtual void CodeDisableOptEvent(AbstractCode code,
SharedFunctionInfo shared) = 0;
......@@ -164,6 +165,9 @@ class CodeEventDispatcher {
void SharedFunctionInfoMoveEvent(Address from, Address 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 CodeDisableOptEvent(AbstractCode code, SharedFunctionInfo shared) {
CODE_EVENT_DISPATCH(CodeDisableOptEvent(code, shared));
......
......@@ -216,6 +216,8 @@ class Logger : public CodeEventListener {
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 CodeDeoptEvent(Code code, DeoptimizeKind kind, Address pc,
......@@ -401,6 +403,7 @@ class V8_EXPORT_PRIVATE CodeEventLogger : public CodeEventListener {
void GetterCallbackEvent(Name name, Address entry_point) override {}
void SetterCallbackEvent(Name name, Address entry_point) override {}
void SharedFunctionInfoMoveEvent(Address from, Address to) override {}
void NativeContextMoveEvent(Address from, Address to) override {}
void CodeMovingGCEvent() override {}
void CodeDeoptEvent(Code code, DeoptimizeKind kind, Address pc,
int fp_to_sp_delta) override {}
......@@ -453,6 +456,7 @@ class ExternalCodeEventListener : public CodeEventListener {
void GetterCallbackEvent(Name name, Address entry_point) override {}
void SetterCallbackEvent(Name name, Address entry_point) override {}
void SharedFunctionInfoMoveEvent(Address from, Address to) override {}
void NativeContextMoveEvent(Address from, Address to) override {}
void CodeMoveEvent(AbstractCode from, AbstractCode to) override {}
void CodeDisableOptEvent(AbstractCode code,
SharedFunctionInfo shared) override {}
......
......@@ -161,7 +161,14 @@ void ProfilerEventsProcessor::StopSynchronously() {
bool ProfilerEventsProcessor::ProcessCodeEvent() {
CodeEventsContainer record;
if (events_buffer_.Dequeue(&record)) {
code_observer_->CodeEventHandlerInternal(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);
}
last_processed_code_event_id_ = record.generic.order;
return true;
}
......@@ -174,6 +181,7 @@ void ProfilerEventsProcessor::CodeEventHandler(
case CodeEventRecord::CODE_CREATION:
case CodeEventRecord::CODE_MOVE:
case CodeEventRecord::CODE_DISABLE_OPT:
case CodeEventRecord::NATIVE_CONTEXT_MOVE:
Enqueue(evt_rec);
break;
case CodeEventRecord::CODE_DEOPT: {
......
......@@ -13,6 +13,7 @@
#include "src/base/platform/mutex.h"
#include "src/base/platform/time.h"
#include "src/execution/isolate.h"
#include "src/handles/maybe-handles.h"
#include "src/libsampler/sampler.h"
#include "src/profiler/circular-queue.h"
#include "src/profiler/profiler-listener.h"
......@@ -30,21 +31,21 @@ class CpuProfile;
class CpuProfilesCollection;
class ProfileGenerator;
#define CODE_EVENTS_TYPE_LIST(V) \
V(CODE_CREATION, CodeCreateEventRecord) \
V(CODE_MOVE, CodeMoveEventRecord) \
V(CODE_DISABLE_OPT, CodeDisableOptEventRecord) \
V(CODE_DEOPT, CodeDeoptEventRecord) \
#define CODE_EVENTS_TYPE_LIST(V) \
V(CODE_CREATION, CodeCreateEventRecord) \
V(CODE_MOVE, CodeMoveEventRecord) \
V(CODE_DISABLE_OPT, CodeDisableOptEventRecord) \
V(CODE_DEOPT, CodeDeoptEventRecord) \
V(REPORT_BUILTIN, ReportBuiltinEventRecord)
#define VM_EVENTS_TYPE_LIST(V) \
CODE_EVENTS_TYPE_LIST(V) \
V(NATIVE_CONTEXT_MOVE, NativeContextMoveEventRecord)
class CodeEventRecord {
public:
#define DECLARE_TYPE(type, ignore) type,
enum Type {
NONE = 0,
CODE_EVENTS_TYPE_LIST(DECLARE_TYPE)
};
enum Type { NONE = 0, VM_EVENTS_TYPE_LIST(DECLARE_TYPE) };
#undef DECLARE_TYPE
Type type;
......@@ -102,6 +103,12 @@ class ReportBuiltinEventRecord : public CodeEventRecord {
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 {
public:
......@@ -124,7 +131,7 @@ class CodeEventsContainer {
union {
CodeEventRecord generic;
#define DECLARE_CLASS(ignore, type) type type##_;
CODE_EVENTS_TYPE_LIST(DECLARE_CLASS)
VM_EVENTS_TYPE_LIST(DECLARE_CLASS)
#undef DECLARE_CLASS
};
};
......@@ -293,6 +300,7 @@ class V8_EXPORT_PRIVATE CpuProfiler {
void CollectSample();
void StartProfiling(const char* title, CpuProfilingOptions options = {});
void StartProfiling(String title, CpuProfilingOptions options = {});
CpuProfile* StopProfiling(const char* title);
CpuProfile* StopProfiling(String title);
int GetProfilesCount();
......
......@@ -412,16 +412,18 @@ ProfileNode* ProfileTree::AddPathFromEnd(const std::vector<CodeEntry*>& path,
ProfileNode* ProfileTree::AddPathFromEnd(const ProfileStackTrace& path,
int src_line, bool update_stats,
ProfilingMode mode) {
ProfilingMode mode,
ContextFilter* context_filter) {
ProfileNode* node = root_;
CodeEntry* last_entry = nullptr;
int parent_line_number = v8::CpuProfileNode::kNoLineNumberInfo;
for (auto it = path.rbegin(); it != path.rend(); ++it) {
if ((*it).code_entry == nullptr) continue;
last_entry = (*it).code_entry;
node = node->FindOrAddChild((*it).code_entry, parent_line_number);
if (it->entry.code_entry == nullptr) continue;
if (context_filter && !context_filter->Accept(*it)) continue;
last_entry = (*it).entry.code_entry;
node = node->FindOrAddChild((*it).entry.code_entry, parent_line_number);
parent_line_number = mode == ProfilingMode::kCallerLineNumbers
? (*it).line_number
? (*it).entry.line_number
: v8::CpuProfileNode::kNoLineNumberInfo;
}
if (last_entry && last_entry->has_deopt_info()) {
......@@ -436,7 +438,6 @@ ProfileNode* ProfileTree::AddPathFromEnd(const ProfileStackTrace& path,
return node;
}
class Position {
public:
explicit Position(ProfileNode* node)
......@@ -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;
std::atomic<uint32_t> CpuProfile::last_id_;
......@@ -496,6 +512,13 @@ CpuProfile::CpuProfile(CpuProfiler* profiler, const char* title,
(start_time_ - base::TimeTicks()).InMicroseconds());
TRACE_EVENT_SAMPLE_WITH_ID1(TRACE_DISABLED_BY_DEFAULT("v8.cpu_profiler"),
"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) {
......@@ -520,11 +543,11 @@ void CpuProfile::AddPath(base::TimeTicks timestamp,
bool update_stats, base::TimeDelta sampling_interval) {
if (!CheckSubsample(sampling_interval)) return;
ProfileNode* top_frame_node =
top_down_.AddPathFromEnd(path, src_line, update_stats, options_.mode());
ProfileNode* top_frame_node = top_down_.AddPathFromEnd(
path, src_line, update_stats, options_.mode(), context_filter_.get());
bool should_record_sample =
!timestamp.IsNull() &&
!timestamp.IsNull() && timestamp >= start_time_ &&
(options_.max_samples() == CpuProfilingOptions::kNoSampleLimit ||
samples_.size() < options_.max_samples());
......@@ -623,6 +646,8 @@ void CpuProfile::StreamPendingTraceEvents() {
void CpuProfile::FinishProfile() {
end_time_ = base::TimeTicks::HighResolutionNow();
// Stop tracking context movements after profiling stops.
context_filter_ = nullptr;
StreamPendingTraceEvents();
auto value = TracedValue::Create();
value->SetDouble("endTime", (end_time_ - base::TimeTicks()).InMicroseconds());
......@@ -833,6 +858,17 @@ void CpuProfilesCollection::AddPathToCurrentProfiles(
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,
CodeMap* code_map)
: profiles_(profiles), code_map_(code_map) {}
......@@ -857,9 +893,11 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
// Don't use PC when in external callback code, as it can point
// inside a callback's code, and we will erroneously report
// that a callback calls itself.
stack_trace.push_back(
{FindEntry(reinterpret_cast<Address>(sample.external_callback_entry)),
no_line_info});
stack_trace.push_back({{FindEntry(reinterpret_cast<Address>(
sample.external_callback_entry)),
no_line_info},
reinterpret_cast<Address>(sample.top_context),
true});
} else {
Address attributed_pc = reinterpret_cast<Address>(sample.pc);
CodeEntry* pc_entry = FindEntry(attributed_pc);
......@@ -883,7 +921,9 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
src_line = pc_entry->line_number();
}
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 ||
pc_entry->builtin_id() == Builtins::kFunctionPrototypeCall) {
......@@ -895,7 +935,9 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
// 'unresolved' entry.
if (!sample.has_external_callback) {
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) {
for (unsigned i = 0; i < sample.frames_count; ++i) {
Address stack_pos = reinterpret_cast<Address>(sample.stack[i]);
Address native_context = reinterpret_cast<Address>(sample.contexts[i]);
CodeEntry* entry = FindEntry(stack_pos);
int line_number = no_line_info;
if (entry) {
......@@ -914,8 +957,13 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
entry->GetInlineStack(pc_offset);
if (inline_stack) {
int most_inlined_frame_line_number = entry->GetSourceLine(pc_offset);
stack_trace.insert(stack_trace.end(), inline_stack->begin(),
inline_stack->end());
for (auto entry : *inline_stack) {
// 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
// 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
......@@ -925,7 +973,7 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
// inlining_id.
DCHECK(!inline_stack->empty());
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
// the first JS caller.
......@@ -944,21 +992,22 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
// so we use it instead of pushing entry to stack_trace.
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) {
bool no_symbolized_entries = true;
for (auto e : stack_trace) {
if (e.code_entry != nullptr) {
if (e.entry.code_entry != nullptr) {
no_symbolized_entries = false;
break;
}
}
// If no frames were symbolized, put the VM state entry in.
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) {
sample.sampling_interval);
}
void ProfileGenerator::UpdateNativeContextAddress(Address from, Address to) {
profiles_->UpdateNativeContextAddressForCurrentProfiles(from, to);
}
CodeEntry* ProfileGenerator::EntryForVMState(StateTag tag) {
switch (tag) {
case GC:
......
......@@ -234,7 +234,36 @@ struct CodeEntryAndLineNumber {
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;
......@@ -321,7 +350,8 @@ class V8_EXPORT_PRIVATE ProfileTree {
const ProfileStackTrace& path,
int src_line = v8::CpuProfileNode::kNoLineNumberInfo,
bool update_stats = true,
ProfilingMode mode = ProfilingMode::kLeafNodeLineNumbers);
ProfilingMode mode = ProfilingMode::kLeafNodeLineNumbers,
ContextFilter* context_filter = nullptr);
ProfileNode* root() const { return root_; }
unsigned next_node_id() { return next_node_id_++; }
unsigned GetFunctionId(const ProfileNode* node);
......@@ -389,6 +419,7 @@ class CpuProfile {
base::TimeTicks start_time() const { return start_time_; }
base::TimeTicks end_time() const { return end_time_; }
CpuProfiler* cpu_profiler() const { return profiler_; }
ContextFilter* context_filter() const { return context_filter_.get(); }
void UpdateTicksScale();
......@@ -399,6 +430,7 @@ class CpuProfile {
const char* title_;
const CpuProfilingOptions options_;
std::unique_ptr<ContextFilter> context_filter_;
base::TimeTicks start_time_;
base::TimeTicks end_time_;
std::deque<SampleInfo> samples_;
......@@ -477,6 +509,9 @@ class V8_EXPORT_PRIVATE CpuProfilesCollection {
bool update_stats,
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.
static const int kMaxSimultaneousProfiles = 100;
......@@ -498,6 +533,8 @@ class V8_EXPORT_PRIVATE ProfileGenerator {
void RecordTickSample(const TickSample& sample);
void UpdateNativeContextAddress(Address from, Address to);
CodeMap* code_map() { return code_map_; }
private:
......
......@@ -177,8 +177,7 @@ void ProfilerListener::CodeCreateEvent(CodeEventListener::LogEventsAndTags tag,
CodeEntry* cached_entry = GetOrInsertCachedEntry(
&cached_inline_entries, std::move(inline_entry));
inline_stack.push_back(
CodeEntryAndLineNumber{cached_entry, line_number});
inline_stack.push_back({cached_entry, line_number});
}
DCHECK(!inline_stack.empty());
inline_stacks.emplace(inlining_id, std::move(inline_stack));
......@@ -280,6 +279,13 @@ void ProfilerListener::SetterCallbackEvent(Name name, Address entry_point) {
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) {
if (name.IsString() && String::cast(name).length()) return name;
if (!info.script().IsScript()) return name;
......
......@@ -55,6 +55,7 @@ class V8_EXPORT_PRIVATE ProfilerListener : public CodeEventListener {
void RegExpCodeCreateEvent(AbstractCode code, String source) override;
void SetterCallbackEvent(Name name, Address entry_point) override;
void SharedFunctionInfoMoveEvent(Address from, Address to) override {}
void NativeContextMoveEvent(Address from, Address to) override;
const char* GetName(Name name) {
return function_and_resource_names_.GetName(name);
......
......@@ -147,6 +147,42 @@ bool SimulatorHelper::FillRegisters(Isolate* isolate,
}
#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 internal
......@@ -162,7 +198,8 @@ DISABLE_ASAN void TickSample::Init(Isolate* v8_isolate,
SampleInfo info;
RegisterState regs = reg_state;
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.
// Mark the sample as spoiled.
pc = nullptr;
......@@ -173,6 +210,7 @@ DISABLE_ASAN void TickSample::Init(Isolate* v8_isolate,
pc = regs.pc;
frames_count = static_cast<unsigned>(info.frames_count);
has_external_callback = info.external_callback_entry != nullptr;
top_context = info.top_context;
if (has_external_callback) {
external_callback_entry = info.external_callback_entry;
} else if (frames_count) {
......@@ -197,11 +235,12 @@ bool TickSample::GetStackSample(Isolate* v8_isolate, RegisterState* regs,
RecordCEntryFrame record_c_entry_frame,
void** frames, size_t frames_limit,
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);
sample_info->frames_count = 0;
sample_info->vm_state = isolate->current_vm_state();
sample_info->external_callback_entry = nullptr;
sample_info->top_context = nullptr;
if (sample_info->vm_state == GC) return true;
i::Address js_entry_sp = isolate->js_entry_sp();
......@@ -229,7 +268,7 @@ bool TickSample::GetStackSample(Isolate* v8_isolate, RegisterState* regs,
i::ExternalCallbackScope* scope = isolate->external_callback_scope();
i::Address handler = i::Isolate::handler(isolate->thread_local_top());
// 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.
if (scope && scope->scope_address() < handler) {
i::Address* external_callback_entry_ptr =
......@@ -245,23 +284,62 @@ bool TickSample::GetStackSample(Isolate* v8_isolate, RegisterState* regs,
reinterpret_cast<i::Address>(regs->sp),
reinterpret_cast<i::Address>(regs->lr),
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;
size_t i = 0;
if (record_c_entry_frame == kIncludeCEntryFrame &&
(it.top_frame_type() == internal::StackFrame::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 =
isolate->counters()->runtime_call_stats()->current_timer();
for (; !it.done() && i < frames_limit; it.Advance()) {
while (timer && reinterpret_cast<i::Address>(timer) < it.frame()->fp() &&
i < frames_limit) {
if (contexts) contexts[i] = nullptr;
frames[i++] = reinterpret_cast<void*>(timer->counter());
timer = timer->parent();
}
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()) {
// For interpreted frames use the bytecode array pointer as the pc.
i::InterpretedFrame* frame =
......
......@@ -1305,6 +1305,7 @@ RUNTIME_FUNCTION(Runtime_EnableCodeLoggingForTesting) {
void RegExpCodeCreateEvent(AbstractCode code, String source) final {}
void CodeMoveEvent(AbstractCode from, AbstractCode to) final {}
void SharedFunctionInfoMoveEvent(Address from, Address to) final {}
void NativeContextMoveEvent(Address from, Address to) final {}
void CodeMovingGCEvent() final {}
void CodeDisableOptEvent(AbstractCode code,
SharedFunctionInfo shared) final {}
......
......@@ -37,6 +37,7 @@
#include "src/base/platform/platform.h"
#include "src/codegen/source-position-table.h"
#include "src/deoptimizer/deoptimizer.h"
#include "src/heap/spaces.h"
#include "src/libplatform/default-platform.h"
#include "src/logging/log.h"
#include "src/objects/objects-inl.h"
......@@ -45,6 +46,7 @@
#include "src/profiler/tracing-cpu-profiler.h"
#include "src/utils/utils.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-utils.h"
#include "test/cctest/profiler-extension.h"
#include "include/libplatform/v8-tracing.h"
......@@ -456,7 +458,8 @@ class ProfilerHelper {
v8::Local<v8::Function> function, v8::Local<v8::Value> argv[], int argc,
unsigned min_js_samples = 0, unsigned min_external_samples = 0,
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_; }
......@@ -469,11 +472,12 @@ v8::CpuProfile* ProfilerHelper::Run(v8::Local<v8::Function> function,
v8::Local<v8::Value> argv[], int argc,
unsigned min_js_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");
profiler_->SetSamplingInterval(100);
profiler_->StartProfiling(profile_name, {mode, max_samples});
profiler_->StartProfiling(profile_name, {mode, max_samples, 0, context});
v8::internal::CpuProfiler* iprofiler =
reinterpret_cast<v8::internal::CpuProfiler*>(profiler_);
......@@ -481,6 +485,7 @@ v8::CpuProfile* ProfilerHelper::Run(v8::Local<v8::Function> function,
reinterpret_cast<i::SamplingEventsProcessor*>(iprofiler->processor())
->sampler();
sampler->StartCountingSamples();
do {
function->Call(context_, context_->Global(), argc, argv).ToLocalChecked();
} while (sampler->js_sample_count() < min_js_samples ||
......@@ -1825,7 +1830,7 @@ TEST(Inlining2) {
v8::Local<v8::String> profile_name = v8_str("inlining");
profiler->StartProfiling(
profile_name,
CpuProfilingOptions{v8::CpuProfilingMode::kCallerLineNumbers});
v8::CpuProfilingOptions{v8::CpuProfilingMode::kCallerLineNumbers});
v8::Local<v8::Value> args[] = {
v8::Integer::New(env->GetIsolate(), 50000 * load_factor)};
......@@ -3467,6 +3472,140 @@ TEST(Bug9151StaleCodeEntries) {
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 };
// Count the number of unique source positions.
......
......@@ -200,7 +200,9 @@ TEST(ProfileTreeAddPathFromEndWithLineNumbers) {
ProfileTree tree(CcTest::i_isolate());
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,
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