Reland "[profiler] Surface VM & Embedder State"
This is a reland of 2d087f237e
The changes are :
* Fix redundant reinterpret_cast in test file for MSVC failure
https://crbug.com/v8/12476
* Fix flaky test
https://crbug.com/v8/12475
If a sample is captured during a GC, no embedder context is obtained
defaulting to EMPTY. This is the expected behavior, made it in clear
in implementation and in test.
* Synchronized the embedder context filter behavior with existing
native context filter.
Original change's description:
> Add APIs to surface VMState and new EmbedderState to CpuProfile samples.
>
> EmbedderState:
> * An EmbedderState is defined as a value uint8_t and a v8::context used
> for filtering.
> * EmbedderStates are stack allocated by the embedder, construction and
> destruction set/unset the state to the isolate thread local top.
> * A v8::context is used to filter states that are added to a CpuProfile,
> if the CpuProfile do not have a ContextFilter set or if contexts do not
> match, state defaults to Empty.
>
> * v8:StateTag is already propagated all the way to a Sample, simply add
> an API to surface it.
>
> VMState:
> Change-Id: I7eed08907360b99b0ad20ddcff59c95c7076c85e
> Bug: chromium:1263871
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3188072
> Auto-Submit: Corentin Pescheloche <cpescheloche@fb.com>
> Reviewed-by: Camillo Bruni <cbruni@chromium.org>
> Reviewed-by: Dominik Inführ <dinfuehr@chromium.org>
> Reviewed-by: Igor Sheludko <ishell@chromium.org>
> Commit-Queue: Camillo Bruni <cbruni@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#78250}
Bug: chromium:1263871
Change-Id: Ief891b05da99c695e9fb70f94ed7ebdecc6c3b7b
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3320037
Auto-Submit: Corentin Pescheloche <cpescheloche@fb.com>
Reviewed-by: Camillo Bruni <cbruni@chromium.org>
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Reviewed-by: Dominik Inführ <dinfuehr@chromium.org>
Commit-Queue: Dominik Inführ <dinfuehr@chromium.org>
Cr-Commit-Position: refs/heads/main@{#78281}
This commit is contained in:
parent
dc01b43616
commit
e155881f24
@ -445,6 +445,7 @@ filegroup(
|
||||
"include/v8-date.h",
|
||||
"include/v8-debug.h",
|
||||
"include/v8-embedder-heap.h",
|
||||
"include/v8-embedder-state-scope.h,
|
||||
"include/v8-exception.h",
|
||||
"include/v8-extension.h",
|
||||
"include/v8-external.h",
|
||||
@ -1214,6 +1215,8 @@ filegroup(
|
||||
"src/execution/arguments.h",
|
||||
"src/execution/encoded-c-signature.cc",
|
||||
"src/execution/encoded-c-signature.h",
|
||||
"src/execution/embedder-state.h",
|
||||
"src/execution/embedder-state.cc",
|
||||
"src/execution/execution.cc",
|
||||
"src/execution/execution.h",
|
||||
"src/execution/frame-constants.h",
|
||||
|
3
BUILD.gn
3
BUILD.gn
@ -2487,6 +2487,7 @@ v8_header_set("v8_headers") {
|
||||
"include/v8-date.h",
|
||||
"include/v8-debug.h",
|
||||
"include/v8-embedder-heap.h",
|
||||
"include/v8-embedder-state-scope.h",
|
||||
"include/v8-exception.h",
|
||||
"include/v8-extension.h",
|
||||
"include/v8-external.h",
|
||||
@ -2864,6 +2865,7 @@ v8_header_set("v8_internal_headers") {
|
||||
"src/diagnostics/unwinder.h",
|
||||
"src/execution/arguments-inl.h",
|
||||
"src/execution/arguments.h",
|
||||
"src/execution/embedder-state.h",
|
||||
"src/execution/encoded-c-signature.h",
|
||||
"src/execution/execution.h",
|
||||
"src/execution/frame-constants.h",
|
||||
@ -4079,6 +4081,7 @@ v8_source_set("v8_base_without_compiler") {
|
||||
"src/diagnostics/perf-jit.cc",
|
||||
"src/diagnostics/unwinder.cc",
|
||||
"src/execution/arguments.cc",
|
||||
"src/execution/embedder-state.cc",
|
||||
"src/execution/encoded-c-signature.cc",
|
||||
"src/execution/execution.cc",
|
||||
"src/execution/frames.cc",
|
||||
|
48
include/v8-embedder-state-scope.h
Normal file
48
include/v8-embedder-state-scope.h
Normal file
@ -0,0 +1,48 @@
|
||||
// Copyright 2021 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_V8_EMBEDDER_STATE_SCOPE_H_
|
||||
#define INCLUDE_V8_EMBEDDER_STATE_SCOPE_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "v8-context.h" // NOLINT(build/include_directory)
|
||||
#include "v8-internal.h" // NOLINT(build/include_directory)
|
||||
#include "v8-local-handle.h" // NOLINT(build/include_directory)
|
||||
|
||||
namespace v8 {
|
||||
|
||||
namespace internal {
|
||||
class EmbedderState;
|
||||
} // namespace internal
|
||||
|
||||
// A StateTag represents a possible state of the embedder.
|
||||
enum class EmbedderStateTag : uint8_t {
|
||||
EMPTY = 0,
|
||||
// embedder can define any state in between
|
||||
OTHER = UINT8_MAX,
|
||||
};
|
||||
|
||||
// A stack-allocated class that manages an embedder state on the isolate.
|
||||
// After an EmbedderState scope has been created, a new embedder state will be
|
||||
// pushed on the isolate stack.
|
||||
class V8_EXPORT EmbedderStateScope {
|
||||
public:
|
||||
EmbedderStateScope(Isolate* isolate, Local<v8::Context> context,
|
||||
EmbedderStateTag tag);
|
||||
|
||||
private:
|
||||
// Declaring operator new and delete as deleted is not spec compliant.
|
||||
// Therefore declare them private instead to disable dynamic alloc
|
||||
void* operator new(size_t size);
|
||||
void* operator new[](size_t size);
|
||||
void operator delete(void*, size_t);
|
||||
void operator delete[](void*, size_t);
|
||||
|
||||
std::unique_ptr<internal::EmbedderState> embedder_state_;
|
||||
};
|
||||
|
||||
} // namespace v8
|
||||
|
||||
#endif // INCLUDE_V8_EMBEDDER_STATE_SCOPE_H_
|
@ -20,9 +20,11 @@
|
||||
*/
|
||||
namespace v8 {
|
||||
|
||||
enum class EmbedderStateTag : uint8_t;
|
||||
class HeapGraphNode;
|
||||
struct HeapStatsUpdate;
|
||||
class Object;
|
||||
enum StateTag : int;
|
||||
|
||||
using NativeObject = void*;
|
||||
using SnapshotObjectId = uint32_t;
|
||||
@ -210,6 +212,16 @@ class V8_EXPORT CpuProfile {
|
||||
*/
|
||||
int64_t GetStartTime() const;
|
||||
|
||||
/**
|
||||
* Returns state of the vm when sample was captured.
|
||||
*/
|
||||
StateTag GetSampleState(int index) const;
|
||||
|
||||
/**
|
||||
* Returns state of the embedder when sample was captured.
|
||||
*/
|
||||
EmbedderStateTag GetSampleEmbedderState(int index) const;
|
||||
|
||||
/**
|
||||
* Returns time when the profile recording was stopped (in microseconds)
|
||||
* since some unspecified starting point.
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "v8-embedder-state-scope.h" // NOLINT(build/include_directory)
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
namespace v8 {
|
||||
@ -32,7 +33,7 @@ struct V8_EXPORT RegisterState {
|
||||
};
|
||||
|
||||
// A StateTag represents a possible state of the VM.
|
||||
enum StateTag {
|
||||
enum StateTag : int {
|
||||
JS,
|
||||
GC,
|
||||
PARSER,
|
||||
@ -47,10 +48,12 @@ enum StateTag {
|
||||
// The output structure filled up by GetStackSample API function.
|
||||
struct SampleInfo {
|
||||
size_t frames_count; // Number of frames collected.
|
||||
StateTag vm_state; // Current VM state.
|
||||
void* external_callback_entry; // External callback address if VM is
|
||||
// executing an external callback.
|
||||
void* context; // Incumbent native context address.
|
||||
void* embedder_context; // Native context address for embedder state
|
||||
StateTag vm_state; // Current VM state.
|
||||
EmbedderStateTag embedder_state; // Current Embedder state
|
||||
};
|
||||
|
||||
struct MemoryRange {
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "include/v8-callbacks.h"
|
||||
#include "include/v8-cppgc.h"
|
||||
#include "include/v8-date.h"
|
||||
#include "include/v8-embedder-state-scope.h"
|
||||
#include "include/v8-extension.h"
|
||||
#include "include/v8-fast-api-calls.h"
|
||||
#include "include/v8-function.h"
|
||||
@ -46,6 +47,7 @@
|
||||
#include "src/debug/liveedit.h"
|
||||
#include "src/deoptimizer/deoptimizer.h"
|
||||
#include "src/diagnostics/gdb-jit.h"
|
||||
#include "src/execution/embedder-state.h"
|
||||
#include "src/execution/execution.h"
|
||||
#include "src/execution/frames-inl.h"
|
||||
#include "src/execution/isolate-inl.h"
|
||||
@ -9864,6 +9866,16 @@ int64_t CpuProfile::GetSampleTimestamp(int index) const {
|
||||
return profile->sample(index).timestamp.since_origin().InMicroseconds();
|
||||
}
|
||||
|
||||
StateTag CpuProfile::GetSampleState(int index) const {
|
||||
const i::CpuProfile* profile = reinterpret_cast<const i::CpuProfile*>(this);
|
||||
return profile->sample(index).state_tag;
|
||||
}
|
||||
|
||||
EmbedderStateTag CpuProfile::GetSampleEmbedderState(int index) const {
|
||||
const i::CpuProfile* profile = reinterpret_cast<const i::CpuProfile*>(this);
|
||||
return profile->sample(index).embedder_state_tag;
|
||||
}
|
||||
|
||||
int64_t CpuProfile::GetStartTime() const {
|
||||
const i::CpuProfile* profile = reinterpret_cast<const i::CpuProfile*>(this);
|
||||
return profile->start_time().since_origin().InMicroseconds();
|
||||
@ -10332,6 +10344,11 @@ void EmbedderHeapTracer::ResetHandleInNonTracingGC(
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
EmbedderStateScope::EmbedderStateScope(Isolate* isolate,
|
||||
Local<v8::Context> context,
|
||||
EmbedderStateTag tag)
|
||||
: embedder_state_(new internal::EmbedderState(isolate, context, tag)) {}
|
||||
|
||||
void TracedReferenceBase::CheckValue() const {
|
||||
#ifdef V8_HOST_ARCH_64_BIT
|
||||
if (!val_) return;
|
||||
|
45
src/execution/embedder-state.cc
Normal file
45
src/execution/embedder-state.cc
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright 2021 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "src/execution/embedder-state.h"
|
||||
|
||||
#include "src/api/api-inl.h"
|
||||
#include "src/base/logging.h"
|
||||
|
||||
namespace v8 {
|
||||
|
||||
namespace internal {
|
||||
|
||||
EmbedderState::EmbedderState(v8::Isolate* isolate, Local<v8::Context> context,
|
||||
EmbedderStateTag tag)
|
||||
: isolate_(reinterpret_cast<i::Isolate*>(isolate)),
|
||||
tag_(tag),
|
||||
previous_embedder_state_(isolate_->current_embedder_state()) {
|
||||
if (!context.IsEmpty()) {
|
||||
native_context_address_ =
|
||||
v8::Utils::OpenHandle(*context)->native_context().address();
|
||||
}
|
||||
|
||||
DCHECK_NE(this, isolate_->current_embedder_state());
|
||||
isolate_->set_current_embedder_state(this);
|
||||
}
|
||||
|
||||
EmbedderState::~EmbedderState() {
|
||||
DCHECK_EQ(this, isolate_->current_embedder_state());
|
||||
isolate_->set_current_embedder_state(previous_embedder_state_);
|
||||
}
|
||||
|
||||
void EmbedderState::OnMoveEvent(Address from, Address to) {
|
||||
EmbedderState* state = this;
|
||||
do {
|
||||
if (state->native_context_address_ == from) {
|
||||
native_context_address_ = to;
|
||||
}
|
||||
state = state->previous_embedder_state_;
|
||||
} while (state != nullptr);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace v8
|
39
src/execution/embedder-state.h
Normal file
39
src/execution/embedder-state.h
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright 2021 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef V8_EXECUTION_EMBEDDER_STATE_H_
|
||||
#define V8_EXECUTION_EMBEDDER_STATE_H_
|
||||
|
||||
#include "include/v8-local-handle.h"
|
||||
#include "src/execution/isolate.h"
|
||||
|
||||
namespace v8 {
|
||||
|
||||
enum class EmbedderStateTag : uint8_t;
|
||||
|
||||
namespace internal {
|
||||
class V8_EXPORT_PRIVATE EmbedderState {
|
||||
public:
|
||||
EmbedderState(v8::Isolate* isolate, Local<v8::Context> context,
|
||||
EmbedderStateTag tag);
|
||||
|
||||
~EmbedderState();
|
||||
|
||||
EmbedderStateTag GetState() const { return tag_; }
|
||||
|
||||
Address native_context_address() const { return native_context_address_; }
|
||||
|
||||
void OnMoveEvent(Address from, Address to);
|
||||
|
||||
private:
|
||||
Isolate* isolate_;
|
||||
EmbedderStateTag tag_;
|
||||
Address native_context_address_ = kNullAddress;
|
||||
EmbedderState* previous_embedder_state_;
|
||||
};
|
||||
} // namespace internal
|
||||
|
||||
} // namespace v8
|
||||
|
||||
#endif // V8_EXECUTION_EMBEDDER_STATE_H_
|
@ -65,6 +65,8 @@ class V8Inspector;
|
||||
|
||||
namespace v8 {
|
||||
|
||||
class EmbedderState;
|
||||
|
||||
namespace base {
|
||||
class RandomNumberGenerator;
|
||||
} // namespace base
|
||||
@ -1303,6 +1305,7 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
|
||||
THREAD_LOCAL_TOP_ACCESSOR(ExternalCallbackScope*, external_callback_scope)
|
||||
|
||||
THREAD_LOCAL_TOP_ACCESSOR(StateTag, current_vm_state)
|
||||
THREAD_LOCAL_TOP_ACCESSOR(EmbedderState*, current_embedder_state)
|
||||
|
||||
void SetData(uint32_t slot, void* data) {
|
||||
DCHECK_LT(slot, Internals::kNumIsolateDataSlots);
|
||||
|
@ -31,6 +31,7 @@ void ThreadLocalTop::Clear() {
|
||||
js_entry_sp_ = kNullAddress;
|
||||
external_callback_scope_ = nullptr;
|
||||
current_vm_state_ = EXTERNAL;
|
||||
current_embedder_state_ = nullptr;
|
||||
failed_access_check_callback_ = nullptr;
|
||||
thread_in_wasm_flag_address_ = kNullAddress;
|
||||
#ifdef V8_ENABLE_CONSERVATIVE_STACK_SCANNING
|
||||
|
@ -23,6 +23,7 @@ class TryCatch;
|
||||
|
||||
namespace internal {
|
||||
|
||||
class EmbedderState;
|
||||
class ExternalCallbackScope;
|
||||
class Isolate;
|
||||
class PromiseOnStack;
|
||||
@ -34,9 +35,9 @@ class ThreadLocalTop {
|
||||
// refactor this to really consist of just Addresses and 32-bit
|
||||
// integer fields.
|
||||
#ifdef V8_ENABLE_CONSERVATIVE_STACK_SCANNING
|
||||
static constexpr uint32_t kSizeInBytes = 25 * kSystemPointerSize;
|
||||
static constexpr uint32_t kSizeInBytes = 26 * kSystemPointerSize;
|
||||
#else
|
||||
static constexpr uint32_t kSizeInBytes = 24 * kSystemPointerSize;
|
||||
static constexpr uint32_t kSizeInBytes = 25 * kSystemPointerSize;
|
||||
#endif
|
||||
|
||||
// Does early low-level initialization that does not depend on the
|
||||
@ -151,6 +152,7 @@ class ThreadLocalTop {
|
||||
// The external callback we're currently in.
|
||||
ExternalCallbackScope* external_callback_scope_;
|
||||
StateTag current_vm_state_;
|
||||
EmbedderState* current_embedder_state_;
|
||||
|
||||
// Call back function to report unsafe JS accesses.
|
||||
v8::FailedAccessCheckCallback failed_access_check_callback_;
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "src/compiler-dispatcher/optimizing-compile-dispatcher.h"
|
||||
#include "src/debug/debug.h"
|
||||
#include "src/deoptimizer/deoptimizer.h"
|
||||
#include "src/execution/embedder-state.h"
|
||||
#include "src/execution/isolate-utils-inl.h"
|
||||
#include "src/execution/microtask-queue.h"
|
||||
#include "src/execution/runtime-profiler.h"
|
||||
@ -3323,6 +3324,10 @@ void Heap::OnMoveEvent(HeapObject target, HeapObject source,
|
||||
LOG_CODE_EVENT(isolate_, SharedFunctionInfoMoveEvent(source.address(),
|
||||
target.address()));
|
||||
} else if (target.IsNativeContext()) {
|
||||
if (isolate_->current_embedder_state() != nullptr) {
|
||||
isolate_->current_embedder_state()->OnMoveEvent(source.address(),
|
||||
target.address());
|
||||
}
|
||||
PROFILE(isolate_,
|
||||
NativeContextMoveEvent(source.address(), target.address()));
|
||||
}
|
||||
|
@ -230,12 +230,15 @@ void ProfilerEventsProcessor::CodeEventHandler(
|
||||
|
||||
void SamplingEventsProcessor::SymbolizeAndAddToProfiles(
|
||||
const TickSampleEventRecord* record) {
|
||||
const TickSample& tick_sample = record->sample;
|
||||
Symbolizer::SymbolizedSample symbolized =
|
||||
symbolizer_->SymbolizeTickSample(record->sample);
|
||||
symbolizer_->SymbolizeTickSample(tick_sample);
|
||||
profiles_->AddPathToCurrentProfiles(
|
||||
record->sample.timestamp, symbolized.stack_trace, symbolized.src_line,
|
||||
record->sample.update_stats_, record->sample.sampling_interval_,
|
||||
reinterpret_cast<Address>(record->sample.context));
|
||||
tick_sample.timestamp, symbolized.stack_trace, symbolized.src_line,
|
||||
tick_sample.update_stats_, tick_sample.sampling_interval_,
|
||||
tick_sample.state, tick_sample.embedder_state,
|
||||
reinterpret_cast<Address>(tick_sample.context),
|
||||
reinterpret_cast<Address>(tick_sample.embedder_context));
|
||||
}
|
||||
|
||||
ProfilerEventsProcessor::SampleProcessingResult
|
||||
|
@ -620,7 +620,9 @@ bool CpuProfile::CheckSubsample(base::TimeDelta source_sampling_interval) {
|
||||
|
||||
void CpuProfile::AddPath(base::TimeTicks timestamp,
|
||||
const ProfileStackTrace& path, int src_line,
|
||||
bool update_stats, base::TimeDelta sampling_interval) {
|
||||
bool update_stats, base::TimeDelta sampling_interval,
|
||||
StateTag state_tag,
|
||||
EmbedderStateTag embedder_state_tag) {
|
||||
if (!CheckSubsample(sampling_interval)) return;
|
||||
|
||||
ProfileNode* top_frame_node =
|
||||
@ -632,7 +634,8 @@ void CpuProfile::AddPath(base::TimeTicks timestamp,
|
||||
samples_.size() < options_.max_samples());
|
||||
|
||||
if (should_record_sample) {
|
||||
samples_.push_back({top_frame_node, timestamp, src_line});
|
||||
samples_.push_back(
|
||||
{top_frame_node, timestamp, src_line, state_tag, embedder_state_tag});
|
||||
}
|
||||
|
||||
if (!should_record_sample && delegate_ != nullptr) {
|
||||
@ -988,19 +991,24 @@ base::TimeDelta CpuProfilesCollection::GetCommonSamplingInterval() const {
|
||||
|
||||
void CpuProfilesCollection::AddPathToCurrentProfiles(
|
||||
base::TimeTicks timestamp, const ProfileStackTrace& path, int src_line,
|
||||
bool update_stats, base::TimeDelta sampling_interval,
|
||||
Address native_context_address) {
|
||||
bool update_stats, base::TimeDelta sampling_interval, StateTag state,
|
||||
EmbedderStateTag embedder_state_tag, Address native_context_address,
|
||||
Address embedder_native_context_address) {
|
||||
// As starting / stopping profiles is rare relatively to this
|
||||
// method, we don't bother minimizing the duration of lock holding,
|
||||
// e.g. copying contents of the list to a local vector.
|
||||
current_profiles_semaphore_.Wait();
|
||||
const ProfileStackTrace empty_path;
|
||||
for (const std::unique_ptr<CpuProfile>& profile : current_profiles_) {
|
||||
ContextFilter& context_filter = profile->context_filter();
|
||||
// If the context filter check failed, omit the contents of the stack.
|
||||
bool accepts_context =
|
||||
profile->context_filter().Accept(native_context_address);
|
||||
bool accepts_context = context_filter.Accept(native_context_address);
|
||||
bool accepts_embedder_context =
|
||||
context_filter.Accept(embedder_native_context_address);
|
||||
profile->AddPath(timestamp, accepts_context ? path : empty_path, src_line,
|
||||
update_stats, sampling_interval);
|
||||
update_stats, sampling_interval, state,
|
||||
accepts_embedder_context ? embedder_state_tag
|
||||
: EmbedderStateTag::EMPTY);
|
||||
}
|
||||
current_profiles_semaphore_.Signal();
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "include/v8-profiler.h"
|
||||
#include "src/base/platform/time.h"
|
||||
#include "src/builtins/builtins.h"
|
||||
#include "src/execution/vm-state.h"
|
||||
#include "src/logging/code-events.h"
|
||||
#include "src/profiler/strings-storage.h"
|
||||
#include "src/utils/allocation.h"
|
||||
@ -405,6 +406,8 @@ class CpuProfile {
|
||||
ProfileNode* node;
|
||||
base::TimeTicks timestamp;
|
||||
int line;
|
||||
StateTag state_tag;
|
||||
EmbedderStateTag embedder_state_tag;
|
||||
};
|
||||
|
||||
V8_EXPORT_PRIVATE CpuProfile(
|
||||
@ -419,7 +422,8 @@ class CpuProfile {
|
||||
// Add pc -> ... -> main() call path to the profile.
|
||||
void AddPath(base::TimeTicks timestamp, const ProfileStackTrace& path,
|
||||
int src_line, bool update_stats,
|
||||
base::TimeDelta sampling_interval);
|
||||
base::TimeDelta sampling_interval, StateTag state,
|
||||
EmbedderStateTag embedder_state);
|
||||
void FinishProfile();
|
||||
|
||||
const char* title() const { return title_; }
|
||||
@ -554,11 +558,12 @@ class V8_EXPORT_PRIVATE CpuProfilesCollection {
|
||||
base::TimeDelta GetCommonSamplingInterval() const;
|
||||
|
||||
// Called from profile generator thread.
|
||||
void AddPathToCurrentProfiles(base::TimeTicks timestamp,
|
||||
const ProfileStackTrace& path, int src_line,
|
||||
bool update_stats,
|
||||
base::TimeDelta sampling_interval,
|
||||
Address native_context_address = kNullAddress);
|
||||
void AddPathToCurrentProfiles(
|
||||
base::TimeTicks timestamp, const ProfileStackTrace& path, int src_line,
|
||||
bool update_stats, base::TimeDelta sampling_interval, StateTag state,
|
||||
EmbedderStateTag embedder_state_tag,
|
||||
Address native_context_address = kNullAddress,
|
||||
Address native_embedder_context_address = kNullAddress);
|
||||
|
||||
// Called from profile generator thread.
|
||||
void UpdateNativeContextAddressForCurrentProfiles(Address from, Address to);
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "include/v8-profiler.h"
|
||||
#include "src/base/sanitizer/asan.h"
|
||||
#include "src/base/sanitizer/msan.h"
|
||||
#include "src/execution/embedder-state.h"
|
||||
#include "src/execution/frames-inl.h"
|
||||
#include "src/execution/simulator.h"
|
||||
#include "src/execution/vm-state-inl.h"
|
||||
@ -178,6 +179,8 @@ DISABLE_ASAN void TickSample::Init(Isolate* v8_isolate,
|
||||
frames_count = static_cast<unsigned>(info.frames_count);
|
||||
has_external_callback = info.external_callback_entry != nullptr;
|
||||
context = info.context;
|
||||
embedder_context = info.embedder_context;
|
||||
embedder_state = info.embedder_state;
|
||||
if (has_external_callback) {
|
||||
external_callback_entry = info.external_callback_entry;
|
||||
} else if (frames_count) {
|
||||
@ -210,9 +213,19 @@ bool TickSample::GetStackSample(Isolate* v8_isolate, RegisterState* regs,
|
||||
sample_info->frames_count = 0;
|
||||
sample_info->vm_state = isolate->current_vm_state();
|
||||
sample_info->external_callback_entry = nullptr;
|
||||
sample_info->embedder_state = EmbedderStateTag::EMPTY;
|
||||
sample_info->embedder_context = nullptr;
|
||||
sample_info->context = nullptr;
|
||||
|
||||
if (sample_info->vm_state == GC) return true;
|
||||
|
||||
EmbedderState* embedder_state = isolate->current_embedder_state();
|
||||
if (embedder_state != nullptr) {
|
||||
sample_info->embedder_context =
|
||||
reinterpret_cast<void*>(embedder_state->native_context_address());
|
||||
sample_info->embedder_state = embedder_state->GetState();
|
||||
}
|
||||
|
||||
i::Address js_entry_sp = isolate->js_entry_sp();
|
||||
if (js_entry_sp == 0) return true; // Not executing JS now.
|
||||
|
||||
|
@ -23,6 +23,7 @@ struct V8_EXPORT TickSample {
|
||||
|
||||
TickSample()
|
||||
: state(OTHER),
|
||||
embedder_state(EmbedderStateTag::EMPTY),
|
||||
pc(nullptr),
|
||||
external_callback_entry(nullptr),
|
||||
frames_count(0),
|
||||
@ -82,6 +83,7 @@ struct V8_EXPORT TickSample {
|
||||
void print() const;
|
||||
|
||||
StateTag state; // The state of the VM.
|
||||
EmbedderStateTag embedder_state;
|
||||
void* pc; // Instruction pointer.
|
||||
union {
|
||||
void* tos; // Top stack value (*sp).
|
||||
@ -91,6 +93,7 @@ struct V8_EXPORT TickSample {
|
||||
static const unsigned kMaxFramesCount = (1 << kMaxFramesCountLog2) - 1;
|
||||
void* stack[kMaxFramesCount]; // Call stack.
|
||||
void* context = nullptr; // Address of the incumbent native context.
|
||||
void* embedder_context = nullptr; // Address of the embedder 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.
|
||||
|
@ -41,6 +41,7 @@
|
||||
#include "src/codegen/compilation-cache.h"
|
||||
#include "src/codegen/source-position-table.h"
|
||||
#include "src/deoptimizer/deoptimizer.h"
|
||||
#include "src/execution/embedder-state.h"
|
||||
#include "src/heap/spaces.h"
|
||||
#include "src/init/v8.h"
|
||||
#include "src/libplatform/default-platform.h"
|
||||
@ -3922,6 +3923,217 @@ TEST(ContextIsolation) {
|
||||
}
|
||||
}
|
||||
|
||||
void ValidateEmbedderState(v8::CpuProfile* profile,
|
||||
EmbedderStateTag expected_tag) {
|
||||
for (int i = 0; i < profile->GetSamplesCount(); i++) {
|
||||
if (profile->GetSampleState(i) == StateTag::GC) {
|
||||
// Samples captured during a GC do not have an EmbedderState
|
||||
CHECK_EQ(profile->GetSampleEmbedderState(i), EmbedderStateTag::EMPTY);
|
||||
} else {
|
||||
CHECK_EQ(profile->GetSampleEmbedderState(i), expected_tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that embedder states from other contexts aren't recorded
|
||||
TEST(EmbedderContextIsolation) {
|
||||
i::FLAG_allow_natives_syntax = true;
|
||||
LocalContext execution_env;
|
||||
i::HandleScope scope(CcTest::i_isolate());
|
||||
|
||||
v8::Isolate* isolate = execution_env.local()->GetIsolate();
|
||||
|
||||
// Install CollectSample callback for more deterministic sampling.
|
||||
v8::Local<v8::FunctionTemplate> func_template =
|
||||
v8::FunctionTemplate::New(isolate, 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();
|
||||
|
||||
v8::Local<v8::Context> diff_context = v8::Context::New(isolate);
|
||||
{
|
||||
CHECK_NULL(CcTest::i_isolate()->current_embedder_state());
|
||||
// prepare other embedder state
|
||||
EmbedderStateScope scope(isolate, diff_context, EmbedderStateTag::OTHER);
|
||||
CHECK_EQ(CcTest::i_isolate()->current_embedder_state()->GetState(),
|
||||
EmbedderStateTag::OTHER);
|
||||
|
||||
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* profile = helper.Run(
|
||||
function, nullptr, 0, 0, 0, v8::CpuProfilingMode::kLeafNodeLineNumbers,
|
||||
v8::CpuProfilingOptions::kNoSampleLimit, execution_env.local());
|
||||
ValidateEmbedderState(profile, EmbedderStateTag::EMPTY);
|
||||
}
|
||||
CHECK_NULL(CcTest::i_isolate()->current_embedder_state());
|
||||
}
|
||||
|
||||
// Tests that embedder states from same context are recorded
|
||||
TEST(EmbedderStatePropagate) {
|
||||
i::FLAG_allow_natives_syntax = true;
|
||||
LocalContext execution_env;
|
||||
i::HandleScope scope(CcTest::i_isolate());
|
||||
|
||||
v8::Isolate* isolate = execution_env.local()->GetIsolate();
|
||||
|
||||
// Install CollectSample callback for more deterministic sampling.
|
||||
v8::Local<v8::FunctionTemplate> func_template =
|
||||
v8::FunctionTemplate::New(isolate, 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();
|
||||
|
||||
{
|
||||
// prepare embedder state
|
||||
EmbedderState embedderState(isolate, execution_env.local(),
|
||||
EmbedderStateTag::OTHER);
|
||||
CHECK_EQ(CcTest::i_isolate()->current_embedder_state(), &embedderState);
|
||||
|
||||
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* profile = helper.Run(
|
||||
function, nullptr, 0, 0, 0, v8::CpuProfilingMode::kLeafNodeLineNumbers,
|
||||
v8::CpuProfilingOptions::kNoSampleLimit, execution_env.local());
|
||||
ValidateEmbedderState(profile, EmbedderStateTag::OTHER);
|
||||
}
|
||||
CHECK_NULL(CcTest::i_isolate()->current_embedder_state());
|
||||
}
|
||||
|
||||
// Tests that embedder states from same context are recorded
|
||||
// even after native context move
|
||||
TEST(EmbedderStatePropagateNativeContextMove) {
|
||||
// Reusing context addresses will cause this test to fail.
|
||||
if (i::FLAG_gc_global || i::FLAG_stress_compaction ||
|
||||
i::FLAG_stress_incremental_marking || i::FLAG_enable_third_party_heap) {
|
||||
return;
|
||||
}
|
||||
i::FLAG_allow_natives_syntax = true;
|
||||
i::FLAG_manual_evacuation_candidates_selection = true;
|
||||
LocalContext execution_env;
|
||||
i::HandleScope scope(CcTest::i_isolate());
|
||||
|
||||
v8::Isolate* isolate = execution_env.local()->GetIsolate();
|
||||
|
||||
// Install CollectSample callback for more deterministic sampling.
|
||||
v8::Local<v8::FunctionTemplate> func_template =
|
||||
v8::FunctionTemplate::New(isolate, 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();
|
||||
|
||||
{
|
||||
// prepare embedder state
|
||||
EmbedderState embedderState(isolate, execution_env.local(),
|
||||
EmbedderStateTag::OTHER);
|
||||
CHECK_EQ(CcTest::i_isolate()->current_embedder_state(), &embedderState);
|
||||
|
||||
i::Address initial_address =
|
||||
CcTest::i_isolate()->current_embedder_state()->native_context_address();
|
||||
|
||||
// Install a function that triggers the native context to be moved.
|
||||
v8::Local<v8::FunctionTemplate> move_func_template =
|
||||
v8::FunctionTemplate::New(
|
||||
execution_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(execution_env.local()).ToLocalChecked();
|
||||
move_func->SetName(v8_str("ForceNativeContextMove"));
|
||||
execution_env->Global()
|
||||
->Set(execution_env.local(), v8_str("ForceNativeContextMove"),
|
||||
move_func)
|
||||
.FromJust();
|
||||
|
||||
ProfilerHelper helper(execution_env.local());
|
||||
CompileRun(R"(
|
||||
function start() {
|
||||
ForceNativeContextMove();
|
||||
CallCollectSample();
|
||||
}
|
||||
)");
|
||||
v8::Local<v8::Function> function =
|
||||
GetFunction(execution_env.local(), "start");
|
||||
|
||||
v8::CpuProfile* profile = helper.Run(
|
||||
function, nullptr, 0, 0, 0, v8::CpuProfilingMode::kLeafNodeLineNumbers,
|
||||
v8::CpuProfilingOptions::kNoSampleLimit, execution_env.local());
|
||||
ValidateEmbedderState(profile, EmbedderStateTag::OTHER);
|
||||
|
||||
i::Address new_address =
|
||||
CcTest::i_isolate()->current_embedder_state()->native_context_address();
|
||||
CHECK_NE(initial_address, new_address);
|
||||
}
|
||||
CHECK_NULL(CcTest::i_isolate()->current_embedder_state());
|
||||
}
|
||||
|
||||
// Tests that when a native context that's being filtered is moved, we continue
|
||||
// to track its execution.
|
||||
TEST(ContextFilterMovedNativeContext) {
|
||||
|
@ -482,9 +482,9 @@ TEST(SampleIds) {
|
||||
sample1.stack[0] = ToPointer(0x1510);
|
||||
sample1.frames_count = 1;
|
||||
auto symbolized = symbolizer.SymbolizeTickSample(sample1);
|
||||
profiles.AddPathToCurrentProfiles(sample1.timestamp, symbolized.stack_trace,
|
||||
symbolized.src_line, true,
|
||||
base::TimeDelta());
|
||||
profiles.AddPathToCurrentProfiles(
|
||||
sample1.timestamp, symbolized.stack_trace, symbolized.src_line, true,
|
||||
base::TimeDelta(), StateTag::JS, EmbedderStateTag::EMPTY);
|
||||
|
||||
TickSample sample2;
|
||||
sample2.timestamp = v8::base::TimeTicks::HighResolutionNow();
|
||||
@ -494,9 +494,9 @@ TEST(SampleIds) {
|
||||
sample2.stack[2] = ToPointer(0x1620);
|
||||
sample2.frames_count = 3;
|
||||
symbolized = symbolizer.SymbolizeTickSample(sample2);
|
||||
profiles.AddPathToCurrentProfiles(sample2.timestamp, symbolized.stack_trace,
|
||||
symbolized.src_line, true,
|
||||
base::TimeDelta());
|
||||
profiles.AddPathToCurrentProfiles(
|
||||
sample2.timestamp, symbolized.stack_trace, symbolized.src_line, true,
|
||||
base::TimeDelta(), StateTag::JS, EmbedderStateTag::EMPTY);
|
||||
|
||||
TickSample sample3;
|
||||
sample3.timestamp = v8::base::TimeTicks::HighResolutionNow();
|
||||
@ -505,9 +505,9 @@ TEST(SampleIds) {
|
||||
sample3.stack[1] = ToPointer(0x1610);
|
||||
sample3.frames_count = 2;
|
||||
symbolized = symbolizer.SymbolizeTickSample(sample3);
|
||||
profiles.AddPathToCurrentProfiles(sample3.timestamp, symbolized.stack_trace,
|
||||
symbolized.src_line, true,
|
||||
base::TimeDelta());
|
||||
profiles.AddPathToCurrentProfiles(
|
||||
sample3.timestamp, symbolized.stack_trace, symbolized.src_line, true,
|
||||
base::TimeDelta(), StateTag::JS, EmbedderStateTag::EMPTY);
|
||||
|
||||
CpuProfile* profile = profiles.StopProfiling("");
|
||||
unsigned nodeId = 1;
|
||||
@ -603,9 +603,9 @@ TEST(MaxSamplesCallback) {
|
||||
sample1.stack[0] = ToPointer(0x1510);
|
||||
sample1.frames_count = 1;
|
||||
auto symbolized = symbolizer.SymbolizeTickSample(sample1);
|
||||
profiles.AddPathToCurrentProfiles(sample1.timestamp, symbolized.stack_trace,
|
||||
symbolized.src_line, true,
|
||||
base::TimeDelta());
|
||||
profiles.AddPathToCurrentProfiles(
|
||||
sample1.timestamp, symbolized.stack_trace, symbolized.src_line, true,
|
||||
base::TimeDelta(), StateTag::JS, EmbedderStateTag::EMPTY);
|
||||
CHECK_EQ(0, mock_platform->posted_count());
|
||||
TickSample sample2;
|
||||
sample2.timestamp = v8::base::TimeTicks::HighResolutionNow();
|
||||
@ -613,18 +613,18 @@ TEST(MaxSamplesCallback) {
|
||||
sample2.stack[0] = ToPointer(0x1780);
|
||||
sample2.frames_count = 2;
|
||||
symbolized = symbolizer.SymbolizeTickSample(sample2);
|
||||
profiles.AddPathToCurrentProfiles(sample2.timestamp, symbolized.stack_trace,
|
||||
symbolized.src_line, true,
|
||||
base::TimeDelta());
|
||||
profiles.AddPathToCurrentProfiles(
|
||||
sample2.timestamp, symbolized.stack_trace, symbolized.src_line, true,
|
||||
base::TimeDelta(), StateTag::JS, EmbedderStateTag::EMPTY);
|
||||
CHECK_EQ(1, mock_platform->posted_count());
|
||||
TickSample sample3;
|
||||
sample3.timestamp = v8::base::TimeTicks::HighResolutionNow();
|
||||
sample3.pc = ToPointer(0x1510);
|
||||
sample3.frames_count = 3;
|
||||
symbolized = symbolizer.SymbolizeTickSample(sample3);
|
||||
profiles.AddPathToCurrentProfiles(sample3.timestamp, symbolized.stack_trace,
|
||||
symbolized.src_line, true,
|
||||
base::TimeDelta());
|
||||
profiles.AddPathToCurrentProfiles(
|
||||
sample3.timestamp, symbolized.stack_trace, symbolized.src_line, true,
|
||||
base::TimeDelta(), StateTag::JS, EmbedderStateTag::EMPTY);
|
||||
CHECK_EQ(1, mock_platform->posted_count());
|
||||
|
||||
// Teardown
|
||||
@ -654,7 +654,8 @@ TEST(NoSamples) {
|
||||
auto symbolized = symbolizer.SymbolizeTickSample(sample1);
|
||||
profiles.AddPathToCurrentProfiles(v8::base::TimeTicks::HighResolutionNow(),
|
||||
symbolized.stack_trace, symbolized.src_line,
|
||||
true, base::TimeDelta());
|
||||
true, base::TimeDelta(), StateTag::JS,
|
||||
EmbedderStateTag::EMPTY);
|
||||
|
||||
CpuProfile* profile = profiles.StopProfiling("");
|
||||
unsigned nodeId = 1;
|
||||
|
Loading…
Reference in New Issue
Block a user