From 2282387a5b106f1a637e99a82a4e446549290fe8 Mon Sep 17 00:00:00 2001 From: Slava Egorov Date: Fri, 21 Nov 2025 05:34:55 -0800 Subject: [PATCH] [vm] Fix profiler in PRODUCT mode. * Make sure to start and stop profiler as mutator pauses and resumes. * Allow profiler to call Code::GetPrologOffset without crashing * Make sure to emit mapping with id 0 into the perfetto output if necessary, otherwise Perfetto UI can't load the profile The last item seems a bug in Perfetto UI because 0 is supposed to mean "value not set". TEST=tested by running _perf_witness tests Change-Id: I57578ba3efafd2aaf83c2c41b0e0b1f36a6a6964 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/459744 Reviewed-by: Martin Kustermann Commit-Queue: Slava Egorov --- runtime/vm/dart_api_impl.cc | 8 ++++---- runtime/vm/object.cc | 1 - runtime/vm/os_thread.h | 5 +++++ runtime/vm/perfetto_utils.h | 22 ++++++++++++++++++++++ runtime/vm/profiler_service.cc | 4 ++++ runtime/vm/thread.cc | 6 +++--- runtime/vm/thread.h | 4 ++-- 7 files changed, 40 insertions(+), 10 deletions(-) diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc index cea93b4d8e9..03e50aaf973 100644 --- a/runtime/vm/dart_api_impl.cc +++ b/runtime/vm/dart_api_impl.cc @@ -1577,23 +1577,23 @@ DART_EXPORT void Dart_StopProfiling() { } DART_EXPORT void Dart_ThreadDisableProfiling() { -#if !defined(PRODUCT) +#if defined(DART_INCLUDE_PROFILER) OSThread* os_thread = OSThread::Current(); if (os_thread == nullptr) { return; } os_thread->DisableThreadInterrupts(); -#endif // !defined(PRODUCT) +#endif // defined(DART_INCLUDE_PROFILER) } DART_EXPORT void Dart_ThreadEnableProfiling() { -#if !defined(PRODUCT) +#if defined(DART_INCLUDE_PROFILER) OSThread* os_thread = OSThread::Current(); if (os_thread == nullptr) { return; } os_thread->EnableThreadInterrupts(); -#endif // !defined(PRODUCT) +#endif // defined(DART_INCLUDE_PROFILER) } DART_EXPORT void Dart_AddSymbols(const char* dso_name, diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc index 6e9636a3645..367121e3d39 100644 --- a/runtime/vm/object.cc +++ b/runtime/vm/object.cc @@ -18737,7 +18737,6 @@ void Code::SetPrologueOffset(intptr_t offset) const { intptr_t Code::GetPrologueOffset() const { #if defined(PRODUCT) - UNREACHABLE(); return -1; #else const Object& object = Object::Handle(untag()->return_address_metadata()); diff --git a/runtime/vm/os_thread.h b/runtime/vm/os_thread.h index bbba518c90e..a6e03ace1e0 100644 --- a/runtime/vm/os_thread.h +++ b/runtime/vm/os_thread.h @@ -133,6 +133,11 @@ class OSThread : public BaseThread { void DisableThreadInterrupts(); void EnableThreadInterrupts(); bool ThreadInterruptsEnabled(); +#else + // Used to temporarily disable or enable thread interrupts. + void DisableThreadInterrupts() {} + void EnableThreadInterrupts() {} + bool ThreadInterruptsEnabled() { return false; } #endif // defined(DART_INCLUDE_PROFILER) // The currently executing thread, or nullptr if not yet initialized. diff --git a/runtime/vm/perfetto_utils.h b/runtime/vm/perfetto_utils.h index 11e566c6cf2..ae9b8e43345 100644 --- a/runtime/vm/perfetto_utils.h +++ b/runtime/vm/perfetto_utils.h @@ -308,6 +308,8 @@ class InternedDataBuilder : public ValueObject { private: using SequenceFlags = perfetto::protos::pbzero::TracePacket_SequenceFlags; + enum class UnknownMappingState { kNotNeeded, kNeeded, kEmitted }; + public: // InternedData contains multiple independent interning dictionaries which // are used for different attributes. @@ -331,6 +333,12 @@ class InternedDataBuilder : public ValueObject { InternedDataBuilder() = default; + void MarkNeedUnknownMapping() { + if (unknown_mapping_ == UnknownMappingState::kNotNeeded) { + unknown_mapping_ = UnknownMappingState::kNeeded; + } + } + // Emit all strings added since the last invocation of |AttachInternedDataTo| // into |interned_data| of the given |TracePacket|. // @@ -372,6 +380,18 @@ class InternedDataBuilder : public ValueObject { } }); + // Perfetto proto message definition claim that mapping iid 0 means + // the same as frame not having mapping information. However Perfetto UI + // fails to load profiles if they contain any frames without mapping iid or + // with 0 mapping iid - but such mapping (with 0 iid) is not present in + // interned mappings. To work-around this bug we simply emit an empty + // mapping with 0 iid if we need it. + if (unknown_mapping_ == UnknownMappingState::kNeeded) { + auto mapping = interned_data->add_mappings(); + mapping->set_iid(0); + unknown_mapping_ = UnknownMappingState::kEmitted; + } + mappings_.FlushNewlyInternedTo([interned_data](const auto& interned) { auto mapping = interned_data->add_mappings(); mapping->set_iid(interned.iid); @@ -458,6 +478,8 @@ class InternedDataBuilder : public ValueObject { uint32_t sequence_flags_ = SequenceFlags::SEQ_INCREMENTAL_STATE_CLEARED | SequenceFlags::SEQ_NEEDS_INCREMENTAL_STATE; + UnknownMappingState unknown_mapping_ = UnknownMappingState::kNotNeeded; + // These are interned in debug_annotation_string_values space. IdToIidMap isolate_id_to_iid_of_formatted_string_; IdToIidMap isolate_group_id_to_iid_of_formatted_string_; diff --git a/runtime/vm/profiler_service.cc b/runtime/vm/profiler_service.cc index 8fc49620f04..e36bca7bfdf 100644 --- a/runtime/vm/profiler_service.cc +++ b/runtime/vm/profiler_service.cc @@ -1976,6 +1976,10 @@ void Profile::PrintProfilePerfettoImpl( /*length=*/1); } + if (mapping_iid == 0) { + interned_data_builder.MarkNeedUnknownMapping(); + } + // Add a |Frame| to the interned data table that is linked to |function|'s // name and source location (through the interned data table). A Perfetto // |Callstack| consists of a stack of |Frame|s, so the |Callstack|s diff --git a/runtime/vm/thread.cc b/runtime/vm/thread.cc index 53f07f387b3..50611597b40 100644 --- a/runtime/vm/thread.cc +++ b/runtime/vm/thread.cc @@ -651,7 +651,7 @@ void Thread::ResumeThreadInternal(Thread* thread) { thread->set_os_thread(os_thread); os_thread->set_thread(thread); Thread::SetCurrent(thread); - NOT_IN_PRODUCT(os_thread->EnableThreadInterrupts()); + os_thread->EnableThreadInterrupts(); #if !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER) thread->heap_sampler().Initialize(); @@ -667,7 +667,7 @@ void Thread::SuspendThreadInternal(Thread* thread, VMTag::VMTagId tag) { OSThread* os_thread = thread->os_thread(); ASSERT(os_thread != nullptr); - NOT_IN_PRODUCT(os_thread->DisableThreadInterrupts()); + os_thread->DisableThreadInterrupts(); os_thread->set_thread(nullptr); OSThread::SetCurrent(os_thread); thread->set_os_thread(nullptr); @@ -1711,7 +1711,7 @@ void Thread::VisitMutators(MutatorThreadVisitor* visitor) { }); } -#if !defined(PRODUCT) +#if defined(DART_INCLUDE_PROFILER) DisableThreadInterruptsScope::DisableThreadInterruptsScope(Thread* thread) : StackResource(thread) { if (thread != nullptr) { diff --git a/runtime/vm/thread.h b/runtime/vm/thread.h index 20ed20842e7..e10c2c5dac0 100644 --- a/runtime/vm/thread.h +++ b/runtime/vm/thread.h @@ -1782,7 +1782,7 @@ class RuntimeCallDeoptScope : public StackResource { void WindowsThreadCleanUp(); #endif -#if !defined(PRODUCT) +#if defined(DART_INCLUDE_PROFILER) // Disable thread interrupts. class DisableThreadInterruptsScope : public StackResource { public: @@ -1796,7 +1796,7 @@ class DisableThreadInterruptsScope : public StackResource { : StackResource(thread) {} ~DisableThreadInterruptsScope() {} }; -#endif // !defined(PRODUCT) +#endif // defined(DART_INCLUDE_PROFILER) // Within a NoSafepointScope, the thread must not reach any safepoint. Used // around code that manipulates raw object pointers directly without handles.