| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "services/tracing/public/cpp/stack_sampling/tracing_sampler_profiler.h" |
| |
| #include <limits> |
| #include <set> |
| #include <string_view> |
| |
| #include "base/android/library_loader/anchor_functions.h" |
| #include "base/debug/leak_annotations.h" |
| #include "base/debug/stack_trace.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/hash/hash.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/no_destructor.h" |
| #include "base/process/process.h" |
| #include "base/profiler/sampling_profiler_thread_token.h" |
| #include "base/profiler/stack_sampling_profiler.h" |
| #include "base/strings/strcat.h" |
| #include "base/task/thread_pool.h" |
| #include "base/task/thread_pool/thread_pool_instance.h" |
| #include "base/thread_annotations.h" |
| #include "base/threading/sequence_local_storage_slot.h" |
| #include "base/trace_event/trace_event.h" |
| #include "base/trace_event/trace_log.h" |
| #include "base/trace_event/typed_macros.h" |
| #include "build/build_config.h" |
| #include "services/tracing/public/cpp/buildflags.h" |
| #include "services/tracing/public/cpp/perfetto/perfetto_traced_process.h" |
| #include "third_party/perfetto/protos/perfetto/trace/interned_data/interned_data.pbzero.h" |
| #include "third_party/perfetto/protos/perfetto/trace/profiling/profile_common.pbzero.h" |
| #include "third_party/perfetto/protos/perfetto/trace/profiling/profile_packet.pbzero.h" |
| #include "third_party/perfetto/protos/perfetto/trace/trace_packet.pbzero.h" |
| #include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_thread_descriptor.pbzero.h" |
| #include "third_party/perfetto/protos/perfetto/trace/track_event/process_descriptor.pbzero.h" |
| #include "third_party/perfetto/protos/perfetto/trace/track_event/thread_descriptor.pbzero.h" |
| |
| #if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE) |
| #include "base/profiler/thread_delegate_posix.h" |
| #define INITIALIZE_THREAD_DELEGATE_POSIX 1 |
| #else // BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE) |
| #define INITIALIZE_THREAD_DELEGATE_POSIX 0 |
| #endif // BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE) |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| #include "base/profiler/core_unwinders.h" |
| #endif |
| |
| #if ANDROID_ARM64_UNWINDING_SUPPORTED || ANDROID_CFI_UNWINDING_SUPPORTED |
| #include <dlfcn.h> |
| |
| #include "base/debug/elf_reader.h" |
| #endif // ANDROID_ARM64_UNWINDING_SUPPORTED || ANDROID_CFI_UNWINDING_SUPPORTED |
| |
| #if BUILDFLAG(ENABLE_LOADER_LOCK_SAMPLING) |
| #include "services/tracing/public/cpp/stack_sampling/loader_lock_sampling_thread_win.h" |
| #endif |
| |
| using StreamingProfilePacketHandle = |
| protozero::MessageHandle<perfetto::protos::pbzero::StreamingProfilePacket>; |
| |
| namespace tracing { |
| |
| namespace { |
| |
| #if ANDROID_ARM64_UNWINDING_SUPPORTED || ANDROID_CFI_UNWINDING_SUPPORTED |
| extern "C" { |
| |
| // The address of |__executable_start| gives the start address of the |
| // executable or shared library. This value is used to find the offset address |
| // of the instruction in binary from PC. |
| extern char __executable_start; |
| |
| } // extern "C" |
| |
| bool is_chrome_address(uintptr_t pc) { |
| return pc >= base::android::kStartOfText && pc < base::android::kEndOfText; |
| } |
| |
| uintptr_t executable_start_addr() { |
| return reinterpret_cast<uintptr_t>(&__executable_start); |
| } |
| #endif // ANDROID_ARM64_UNWINDING_SUPPORTED || ANDROID_CFI_UNWINDING_SUPPORTED |
| |
| // Pointer to the main thread instance, if any. |
| TracingSamplerProfiler* g_main_thread_instance = nullptr; |
| |
| // A random UUID for the track used to emit streaming profile packets, to avoid |
| // collisions with other tracks. |
| constexpr uint64_t kStreamingProfileTrackUuid = 0x6A7E8A54C18B7778ul; |
| |
| class TracingSamplerProfilerManager { |
| public: |
| static TracingSamplerProfilerManager* Get() { |
| static base::NoDestructor<TracingSamplerProfilerManager> instance; |
| return instance.get(); |
| } |
| |
| void OnDataSourceStart(TracingSamplerProfiler::DataSource* data_source) { |
| base::AutoLock lock(lock_); |
| DCHECK_EQ(data_source_, nullptr); |
| data_source_ = data_source; |
| for (TracingSamplerProfiler* profiler : profilers_) { |
| profiler->StartTracing(data_source_->CreateTraceWriter(), |
| data_source_->privacy_filtering_enabled()); |
| } |
| } |
| void OnDataSourceStop(TracingSamplerProfiler::DataSource* data_source) { |
| base::AutoLock lock(lock_); |
| DCHECK_EQ(data_source_, data_source); |
| data_source_ = nullptr; |
| for (TracingSamplerProfiler* profiler : profilers_) { |
| profiler->StopTracing(); |
| } |
| } |
| |
| void WillClearIncrementalState() { |
| incremental_state_reset_id_.fetch_add(1u, std::memory_order_relaxed); |
| } |
| |
| uint32_t GetIncrementalStateResetID() { |
| return incremental_state_reset_id_.load(std::memory_order_relaxed); |
| } |
| |
| void RegisterProfiler(TracingSamplerProfiler* profiler) { |
| base::AutoLock lock(lock_); |
| if (!profilers_.insert(profiler).second) { |
| return; |
| } |
| |
| if (data_source_) { |
| profiler->StartTracing(data_source_->CreateTraceWriter(), |
| data_source_->privacy_filtering_enabled()); |
| } |
| } |
| |
| void UnregisterProfiler(TracingSamplerProfiler* profiler) { |
| base::AutoLock lock(lock_); |
| if (!profilers_.erase(profiler)) { |
| return; |
| } |
| |
| if (data_source_) { |
| profiler->StopTracing(); |
| } |
| } |
| |
| private: |
| friend class base::NoDestructor<TracingSamplerProfilerManager>; |
| |
| TracingSamplerProfilerManager() = default; |
| ~TracingSamplerProfilerManager() = default; |
| |
| base::Lock lock_; // Protects subsequent members. |
| std::set<raw_ptr<TracingSamplerProfiler, SetExperimental>> profilers_ |
| GUARDED_BY(lock_); |
| raw_ptr<TracingSamplerProfiler::DataSource> data_source_ GUARDED_BY(lock_); |
| std::atomic<uint32_t> incremental_state_reset_id_{1}; |
| }; |
| |
| base::SequenceLocalStorageSlot<TracingSamplerProfiler>& |
| GetSequenceLocalStorageProfilerSlot() { |
| static base::SequenceLocalStorageSlot<TracingSamplerProfiler> storage; |
| return storage; |
| } |
| |
| // Stores information about the StackFrame, to emit to the trace. |
| struct FrameDetails { |
| std::string frame_name; |
| std::string module_name; |
| std::string module_id; |
| uintptr_t module_base_address = 0; |
| uintptr_t rel_pc = 0; |
| |
| // True if the module of the stack frame will be considered valid by the trace |
| // processor. |
| bool has_valid_module() const { |
| return !module_name.empty() && module_base_address > 0; |
| } |
| |
| bool has_valid_frame() const { |
| // Valid only if |rel_pc|, since filtering mode does not record frame names. |
| return rel_pc > 0; |
| } |
| |
| // Gets module from the frame's module cache. |
| void SetModule(const base::ModuleCache::Module& module) { |
| module_base_address = module.GetBaseAddress(); |
| module_id = module.GetId(); |
| if (module_name.empty()) { |
| module_name = module.GetDebugBasename().MaybeAsASCII(); |
| } |
| } |
| |
| // Leaves the valid fields as is and fills in dummy values for invalid fields. |
| // Useful to observe errors in traces. |
| void FillWithDummyFields(uintptr_t frame_ip) { |
| if (rel_pc == 0) { |
| // Record the |frame_ip| as |rel_pc| if available, might be useful to |
| // debug. |
| rel_pc = frame_ip > 0 ? frame_ip : 1; |
| } |
| if (module_base_address == 0) { |
| module_base_address = 1; |
| } |
| // TODO(crbug.com/40248195): Investigate and maybe cleanup this logic. |
| if (module_name.empty()) { |
| module_name = "missing"; |
| } |
| DCHECK(has_valid_frame()); |
| DCHECK(has_valid_module()); |
| } |
| |
| #if ANDROID_ARM64_UNWINDING_SUPPORTED || ANDROID_CFI_UNWINDING_SUPPORTED |
| // Sets Chrome's module info for the frame. |
| void SetChromeModuleInfo() { |
| module_base_address = executable_start_addr(); |
| static const std::optional<std::string_view> library_name = |
| base::debug::ReadElfLibraryName( |
| reinterpret_cast<void*>(executable_start_addr())); |
| static const base::NoDestructor<std::string> chrome_debug_id([] { |
| base::debug::ElfBuildIdBuffer build_id; |
| size_t build_id_length = base::debug::ReadElfBuildId( |
| reinterpret_cast<void*>(executable_start_addr()), true, build_id); |
| return std::string(build_id, build_id_length); |
| }()); |
| if (library_name) { |
| module_name = std::string(*library_name); |
| } |
| module_id = *chrome_debug_id; |
| } |
| |
| // Sets system library module info for the frame. |
| void SetSystemModuleInfo(uintptr_t frame_ip) { |
| Dl_info info = {}; |
| // For addresses in framework libraries, symbolize and write the function |
| // name. |
| if (dladdr(reinterpret_cast<void*>(frame_ip), &info) == 0) { |
| return; |
| } |
| if (info.dli_sname) { |
| frame_name = info.dli_sname; |
| } |
| if (info.dli_fname) { |
| module_name = info.dli_fname; |
| } |
| module_base_address = reinterpret_cast<uintptr_t>(info.dli_fbase); |
| rel_pc = frame_ip - module_base_address; |
| |
| DCHECK(has_valid_frame()); |
| DCHECK(has_valid_module()); |
| } |
| #endif |
| }; |
| |
| #if BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_WIN) && defined(_WIN64) || \ |
| ANDROID_ARM64_UNWINDING_SUPPORTED || ANDROID_CFI_UNWINDING_SUPPORTED || \ |
| (BUILDFLAG(IS_CHROMEOS) && \ |
| (defined(ARCH_CPU_X86_64) || defined(ARCH_CPU_ARM64))) || \ |
| BUILDFLAG(IS_LINUX) |
| // Returns whether stack sampling is supported on the current platform. |
| bool IsStackSamplingSupported() { |
| return base::StackSamplingProfiler::IsSupportedForCurrentPlatform(); |
| } |
| #endif |
| |
| perfetto::StaticString UnwinderTypeToString( |
| const TracingSamplerProfiler::UnwinderType unwinder_type) { |
| switch (unwinder_type) { |
| case TracingSamplerProfiler::UnwinderType::kUnknown: |
| return "TracingSamplerProfiler (unknown unwinder)"; |
| case TracingSamplerProfiler::UnwinderType::kCustomAndroid: |
| return "TracingSamplerProfiler (custom android unwinder)"; |
| case TracingSamplerProfiler::UnwinderType::kDefault: |
| return "TracingSamplerProfiler (default unwinder)"; |
| case TracingSamplerProfiler::UnwinderType::kLibunwindstackUnwinderAndroid: |
| return "TracingSamplerProfiler (libunwindstack unwinder android)"; |
| } |
| } |
| |
| } // namespace |
| |
| TracingSamplerProfiler::DataSource::DataSource() = default; |
| |
| TracingSamplerProfiler::DataSource::~DataSource() = default; |
| |
| void TracingSamplerProfiler::DataSource::OnSetup(const SetupArgs& args) { |
| privacy_filtering_enabled_ = |
| args.config->chrome_config().privacy_filtering_enabled(); |
| } |
| |
| void TracingSamplerProfiler::DataSource::OnStart(const StartArgs&) { |
| TracingSamplerProfilerManager::Get()->OnDataSourceStart(this); |
| } |
| |
| void TracingSamplerProfiler::DataSource::OnStop(const StopArgs&) { |
| TracingSamplerProfilerManager::Get()->OnDataSourceStop(this); |
| } |
| |
| void TracingSamplerProfiler::DataSource::WillClearIncrementalState( |
| const ClearIncrementalStateArgs& args) { |
| TracingSamplerProfilerManager::Get()->WillClearIncrementalState(); |
| } |
| |
| TracingSamplerProfiler::TracingProfileBuilder::TracingProfileBuilder( |
| base::PlatformThreadId sampled_thread_id, |
| std::unique_ptr<perfetto::TraceWriterBase> trace_writer, |
| bool should_enable_filtering, |
| const base::RepeatingClosure& sample_callback_for_testing) |
| : sampled_thread_id_(sampled_thread_id), |
| trace_writer_(std::move(trace_writer)), |
| stack_profile_writer_(should_enable_filtering), |
| sample_callback_for_testing_(sample_callback_for_testing) {} |
| |
| TracingSamplerProfiler::TracingProfileBuilder::~TracingProfileBuilder() { |
| // Deleting a TraceWriter can end up triggering a Mojo call which calls |
| // task runner GetCurrentDefault() and isn't safe on thread shutdown, which is |
| // when TracingProfileBuilder gets destructed, so we make sure this happens on |
| // a different sequence. |
| if (base::ThreadPoolInstance::Get()) { |
| base::ThreadPool::CreateSequencedTaskRunner({})->DeleteSoon( |
| FROM_HERE, std::move(trace_writer_)); |
| } else { |
| // Intentionally leak; we have no way of safely destroying this at this |
| // point. |
| ANNOTATE_LEAKING_OBJECT_PTR(trace_writer_.get()); |
| trace_writer_.release(); |
| } |
| } |
| |
| base::ModuleCache* |
| TracingSamplerProfiler::TracingProfileBuilder::GetModuleCache() { |
| return &module_cache_; |
| } |
| |
| using SampleDebugProto = |
| perfetto::protos::pbzero::ChromeSamplingProfilerSampleCollected; |
| |
| void TracingSamplerProfiler::TracingProfileBuilder::OnSampleCompleted( |
| std::vector<base::Frame> frames, |
| base::TimeTicks sample_timestamp) { |
| WriteSampleToTrace(std::move(frames), sample_timestamp); |
| if (sample_callback_for_testing_) { |
| sample_callback_for_testing_.Run(); |
| } |
| } |
| |
| void TracingSamplerProfiler::TracingProfileBuilder::WriteSampleToTrace( |
| std::vector<base::Frame> frames, |
| base::TimeTicks sample_timestamp) { |
| uint32_t previous_incremental_state_reset_id = std::exchange( |
| last_incremental_state_reset_id_, |
| TracingSamplerProfilerManager::Get()->GetIncrementalStateResetID()); |
| bool reset_incremental_state = |
| (previous_incremental_state_reset_id != last_incremental_state_reset_id_); |
| |
| if (reset_incremental_state) { |
| stack_profile_writer_.ResetEmittedState(); |
| |
| TracePacketHandle trace_packet = trace_writer_->NewTracePacket(); |
| trace_packet->set_sequence_flags( |
| perfetto::protos::pbzero::TracePacket::SEQ_INCREMENTAL_STATE_CLEARED); |
| |
| // Note: Make sure ThreadDescriptors we emit here won't cause |
| // metadata events to be emitted from the JSON exporter which conflict |
| // with the metadata events emitted by the regular TrackEventDataSource. |
| auto thread_track = |
| perfetto::ThreadTrack::ForThread(sampled_thread_id_.raw()); |
| perfetto::Track profile_track(kStreamingProfileTrackUuid, thread_track); |
| auto* track_descriptor = trace_packet->set_track_descriptor(); |
| track_descriptor->set_uuid(profile_track.uuid); |
| auto* thread_descriptor = track_descriptor->set_thread(); |
| thread_descriptor->set_pid(thread_track.pid); |
| thread_descriptor->set_tid(thread_track.tid); |
| |
| last_timestamp_ = sample_timestamp; |
| thread_descriptor->set_reference_timestamp_us( |
| last_timestamp_.since_origin().InMicroseconds()); |
| |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_AIX) |
| if (base::GetCurrentProcId() != |
| base::trace_event::TraceLog::GetInstance()->process_id()) { |
| auto* chrome_thread = track_descriptor->set_chrome_thread(); |
| chrome_thread->set_is_sandboxed_tid(true); |
| } |
| #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_AIX) |
| |
| TRACE_EVENT_INSTANT(TRACE_DISABLED_BY_DEFAULT("cpu_profiler"), |
| UnwinderTypeToString(unwinder_type_)); |
| } |
| |
| TracePacketHandle trace_packet = trace_writer_->NewTracePacket(); |
| // Delta encoded timestamps and interned data require incremental state. |
| trace_packet->set_sequence_flags( |
| perfetto::protos::pbzero::TracePacket::SEQ_NEEDS_INCREMENTAL_STATE); |
| auto callstack_id = |
| stack_profile_writer_.GetCallstackIDAndMaybeEmit(frames, &trace_packet); |
| auto* streaming_profile_packet = trace_packet->set_streaming_profile_packet(); |
| streaming_profile_packet->add_callstack_iid(callstack_id); |
| |
| int32_t current_process_priority = base::Process::Current().GetOSPriority(); |
| if (current_process_priority != 0) { |
| streaming_profile_packet->set_process_priority(current_process_priority); |
| } |
| |
| streaming_profile_packet->add_timestamp_delta_us( |
| (sample_timestamp - last_timestamp_).InMicroseconds()); |
| last_timestamp_ = sample_timestamp; |
| } |
| |
| void TracingSamplerProfiler::TracingProfileBuilder::SetUnwinderType( |
| const TracingSamplerProfiler::UnwinderType unwinder_type) { |
| unwinder_type_ = unwinder_type; |
| } |
| |
| TracingSamplerProfiler::StackProfileWriter::StackProfileWriter( |
| bool enable_filtering) |
| : should_enable_filtering_(enable_filtering) {} |
| TracingSamplerProfiler::StackProfileWriter::~StackProfileWriter() = default; |
| |
| InterningID |
| TracingSamplerProfiler::StackProfileWriter::GetCallstackIDAndMaybeEmit( |
| std::vector<base::Frame>& frames, |
| TracePacketHandle* trace_packet) { |
| size_t ip_hash = 0; |
| for (const auto& frame : frames) { |
| ip_hash = base::HashInts(ip_hash, frame.instruction_pointer); |
| } |
| |
| InterningIndexEntry interned_callstack = |
| interned_callstacks_.LookupOrAdd(ip_hash); |
| |
| if (interned_callstack.was_emitted) { |
| return interned_callstack.id; |
| } |
| |
| auto* interned_data = (*trace_packet)->set_interned_data(); |
| |
| std::vector<InterningID> frame_ids; |
| for (auto& frame : frames) { |
| bool is_unwinder_provided_function_name = false; |
| FrameDetails frame_details; |
| if (frame.module) { |
| frame_details.SetModule(*frame.module); |
| frame_details.rel_pc = |
| frame.instruction_pointer - frame_details.module_base_address; |
| } |
| |
| #if ANDROID_ARM64_UNWINDING_SUPPORTED || ANDROID_CFI_UNWINDING_SUPPORTED |
| if (is_chrome_address(frame.instruction_pointer)) { |
| frame_details.rel_pc = |
| frame.instruction_pointer - executable_start_addr(); |
| if (!frame_details.has_valid_module()) { |
| frame_details.SetChromeModuleInfo(); |
| } |
| } else { |
| if (!frame.function_name.empty()) { |
| // Set function names for modules other than native chrome. |
| // This includes Java frames and native Android system frames. |
| // Chrome native frames can already be symbolized server side. |
| // Currently only libunwindstack_unwinder fills function_names in |
| // frames. |
| is_unwinder_provided_function_name = true; |
| frame_details.frame_name = std::move(frame.function_name); |
| } |
| if (frame.instruction_pointer == 0) { |
| // TODO(ssid): This frame is currently skipped from inserting. Find a |
| // way to specify that this frame is scanned in the trace. |
| frame_details.frame_name = "Scanned"; |
| } else if (frame_details.module_id.empty() || |
| !frame_details.has_valid_module()) { |
| // For AOT modules the build id is empty. Set full pathname for these |
| // modules, so that deobfuscation logic can work, since it depends on |
| // getting full path name to extract package name. |
| frame_details.SetSystemModuleInfo(frame.instruction_pointer); |
| } |
| } |
| #endif // !(ANDROID_ARM64_UNWINDING_SUPPORTED || |
| // ANDROID_CFI_UNWINDING_SUPPORTED) |
| |
| // If we do not have a valid module and a valid frame, add a frame with |
| // dummy details. Adding invalid frame would make trace processor invalidate |
| // the whole sample. |
| if (!frame_details.has_valid_module() || !frame_details.has_valid_frame()) { |
| frame_details.FillWithDummyFields(frame.instruction_pointer); |
| } |
| |
| if (!frame_details.module_id.empty()) { |
| // TODO(b/270470700): Remove this on all platforms once tools/tracing is |
| // fixed. |
| frame_details.module_id = |
| base::TransformModuleIDToSymbolServerFormat(frame_details.module_id); |
| } |
| |
| // Allow uploading function names passed from unwinder, which would be |
| // coming from static compile time strings. |
| bool should_emit_frame_names = |
| !frame_details.frame_name.empty() && |
| (is_unwinder_provided_function_name || !should_enable_filtering_); |
| |
| InterningIndexEntry interned_frame; |
| if (should_emit_frame_names) { |
| interned_frame = interned_frames_.LookupOrAdd( |
| std::make_pair(frame_details.frame_name, frame_details.module_id)); |
| } else { |
| interned_frame = interned_frames_.LookupOrAdd( |
| std::make_pair(frame_details.rel_pc, frame_details.module_id)); |
| } |
| |
| if (!interned_frame.was_emitted) { |
| InterningIndexEntry interned_frame_name; |
| if (should_emit_frame_names) { |
| interned_frame_name = |
| interned_frame_names_.LookupOrAdd(frame_details.frame_name); |
| if (!interned_frame_name.was_emitted) { |
| auto* frame_name_entry = interned_data->add_function_names(); |
| frame_name_entry->set_iid(interned_frame_name.id); |
| frame_name_entry->set_str( |
| reinterpret_cast<const uint8_t*>(frame_details.frame_name.data()), |
| frame_details.frame_name.length()); |
| } |
| } |
| |
| InterningIndexEntry interned_module; |
| if (frame_details.has_valid_module()) { |
| interned_module = |
| interned_modules_.LookupOrAdd(frame_details.module_base_address); |
| if (!interned_module.was_emitted) { |
| InterningIndexEntry interned_module_id = |
| interned_module_ids_.LookupOrAdd(frame_details.module_id); |
| if (!interned_module_id.was_emitted) { |
| auto* module_id_entry = interned_data->add_build_ids(); |
| module_id_entry->set_iid(interned_module_id.id); |
| module_id_entry->set_str(reinterpret_cast<const uint8_t*>( |
| frame_details.module_id.data()), |
| frame_details.module_id.length()); |
| } |
| |
| InterningIndexEntry interned_module_name = |
| interned_module_names_.LookupOrAdd(frame_details.module_name); |
| if (!interned_module_name.was_emitted) { |
| auto* module_name_entry = interned_data->add_mapping_paths(); |
| module_name_entry->set_iid(interned_module_name.id); |
| module_name_entry->set_str(reinterpret_cast<const uint8_t*>( |
| frame_details.module_name.data()), |
| frame_details.module_name.length()); |
| } |
| auto* module_entry = interned_data->add_mappings(); |
| module_entry->set_iid(interned_module.id); |
| module_entry->set_build_id(interned_module_id.id); |
| module_entry->add_path_string_ids(interned_module_name.id); |
| } |
| } |
| |
| auto* frame_entry = interned_data->add_frames(); |
| frame_entry->set_iid(interned_frame.id); |
| if (should_emit_frame_names) { |
| frame_entry->set_function_name_id(interned_frame_name.id); |
| } else { |
| frame_entry->set_rel_pc(frame_details.rel_pc); |
| } |
| if (frame_details.has_valid_module()) { |
| frame_entry->set_mapping_id(interned_module.id); |
| } |
| } |
| |
| frame_ids.push_back(interned_frame.id); |
| } |
| |
| auto* callstack_entry = interned_data->add_callstacks(); |
| callstack_entry->set_iid(interned_callstack.id); |
| // base::Unwinder starts from stack top and works to the bottom, but our |
| // Callstack proto wants bottom first to stack top, so we iterate in reverse. |
| // See b/241357440 for context. |
| for (auto it = frame_ids.rbegin(); it != frame_ids.rend(); ++it) { |
| callstack_entry->add_frame_ids(*it); |
| } |
| |
| return interned_callstack.id; |
| } |
| |
| void TracingSamplerProfiler::StackProfileWriter::ResetEmittedState() { |
| interned_callstacks_.ResetEmittedState(); |
| interned_frames_.ResetEmittedState(); |
| interned_frame_names_.ResetEmittedState(); |
| interned_module_names_.ResetEmittedState(); |
| interned_module_ids_.ResetEmittedState(); |
| interned_modules_.ResetEmittedState(); |
| } |
| |
| // static |
| std::unique_ptr<TracingSamplerProfiler> |
| TracingSamplerProfiler::CreateOnMainThread( |
| CoreUnwindersCallback core_unwinders_factory_function, |
| UnwinderType unwinder_type) { |
| auto profiler = std::make_unique<TracingSamplerProfiler>( |
| base::GetSamplingProfilerCurrentThreadToken(), |
| std::move(core_unwinders_factory_function), unwinder_type); |
| // If running in single process mode, there may be multiple "main thread" |
| // profilers created. In this case, we assume the first created one is the |
| // browser one. |
| if (!g_main_thread_instance) { |
| g_main_thread_instance = profiler.get(); |
| |
| #if BUILDFLAG(ENABLE_LOADER_LOCK_SAMPLING) |
| // The loader lock is process-wide so should only be sampled on a single |
| // thread. So only one TracingSamplerProfiler should create a |
| // LoaderLockSamplingThread. |
| profiler->loader_lock_sampling_thread_ = |
| std::make_unique<LoaderLockSamplingThread>(); |
| #endif |
| } |
| return profiler; |
| } |
| |
| // static |
| void TracingSamplerProfiler::CreateOnChildThread() { |
| CreateOnChildThreadWithCustomUnwinders(CoreUnwindersCallback()); |
| } |
| |
| // static |
| void TracingSamplerProfiler::CreateOnChildThreadWithCustomUnwinders( |
| CoreUnwindersCallback core_unwinders_factory_function) { |
| base::SequenceLocalStorageSlot<TracingSamplerProfiler>& slot = |
| GetSequenceLocalStorageProfilerSlot(); |
| if (slot) { |
| return; |
| } |
| |
| slot.emplace(base::GetSamplingProfilerCurrentThreadToken(), |
| std::move(core_unwinders_factory_function)); |
| } |
| |
| // static |
| void TracingSamplerProfiler::DeleteOnChildThreadForTesting() { |
| GetSequenceLocalStorageProfilerSlot().reset(); |
| } |
| |
| // static |
| void TracingSamplerProfiler::RegisterDataSource() { |
| perfetto::DataSourceDescriptor dsd; |
| dsd.set_name(mojom::kSamplerProfilerSourceName); |
| perfetto::DataSource<TracingSamplerProfiler::DataSource>::Register(dsd); |
| } |
| |
| // static |
| bool TracingSamplerProfiler::IsStackUnwindingSupportedForTesting() { |
| #if BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_WIN) && defined(_WIN64) || \ |
| ANDROID_ARM64_UNWINDING_SUPPORTED || ANDROID_CFI_UNWINDING_SUPPORTED || \ |
| (BUILDFLAG(IS_CHROMEOS) && \ |
| (defined(ARCH_CPU_X86_64) || defined(ARCH_CPU_ARM64))) || \ |
| BUILDFLAG(IS_LINUX) |
| return IsStackSamplingSupported(); |
| #else |
| return false; |
| #endif |
| } |
| |
| void TracingSamplerProfiler::SetAuxUnwinderFactoryOnMainThread( |
| const base::RepeatingCallback<std::unique_ptr<base::Unwinder>()>& factory) { |
| DCHECK(g_main_thread_instance); |
| g_main_thread_instance->SetAuxUnwinderFactory(factory); |
| } |
| |
| void TracingSamplerProfiler::SetSampleCallbackForTesting( |
| const base::RepeatingClosure& sample_callback_for_testing) { |
| base::AutoLock lock(lock_); |
| sample_callback_for_testing_ = sample_callback_for_testing; |
| } |
| |
| TracingSamplerProfiler::TracingSamplerProfiler( |
| base::SamplingProfilerThreadToken sampled_thread_token, |
| CoreUnwindersCallback core_unwinders_factory_function, |
| UnwinderType unwinder_type) |
| : sampled_thread_token_(sampled_thread_token), |
| core_unwinders_factory_function_( |
| std::move(core_unwinders_factory_function)), |
| unwinder_type_(unwinder_type) { |
| DCHECK_NE(sampled_thread_token_.id, base::kInvalidThreadId); |
| #if INITIALIZE_THREAD_DELEGATE_POSIX |
| // Since StackSamplingProfiler is scoped to a tracing session and lives on the |
| // thread where `StartTracing` is called, we use `ThreadDelegatePosix` to |
| // initialize global data, like the thread stack base address, that has to be |
| // created on the profiled thread. See crbug.com/1392158#c26 for details. |
| base::ThreadDelegatePosix::Create(sampled_thread_token_); |
| #endif // INITIALIZE_THREAD_DELEGATE_POSIX |
| TracingSamplerProfilerManager::Get()->RegisterProfiler(this); |
| } |
| |
| TracingSamplerProfiler::~TracingSamplerProfiler() { |
| TracingSamplerProfilerManager::Get()->UnregisterProfiler(this); |
| if (g_main_thread_instance == this) { |
| g_main_thread_instance = nullptr; |
| } |
| } |
| |
| void TracingSamplerProfiler::SetAuxUnwinderFactory( |
| const base::RepeatingCallback<std::unique_ptr<base::Unwinder>()>& factory) { |
| base::AutoLock lock(lock_); |
| aux_unwinder_factory_ = factory; |
| if (profiler_) { |
| profiler_->AddAuxUnwinder(aux_unwinder_factory_.Run()); |
| } |
| } |
| |
| void TracingSamplerProfiler::StartTracing( |
| std::unique_ptr<perfetto::TraceWriterBase> trace_writer, |
| bool should_enable_filtering) { |
| base::AutoLock lock(lock_); |
| DCHECK_EQ(profiler_, nullptr); |
| |
| if (!base::StackSamplingProfiler::IsSupportedForCurrentPlatform()) { |
| return; |
| } |
| |
| base::StackSamplingProfiler::SamplingParams params; |
| params.samples_per_profile = std::numeric_limits<int>::max(); |
| params.sampling_interval = base::Milliseconds(50); |
| |
| auto profile_builder = std::make_unique<TracingProfileBuilder>( |
| sampled_thread_token_.id, std::move(trace_writer), |
| should_enable_filtering, sample_callback_for_testing_); |
| |
| profile_builder_ = profile_builder.get(); |
| // There is a dichotomy between stack samplers for Android and other |
| // platforms. While Android explicitly needs a factory to provide "core" |
| // unwinders, other platforms explicitly check that no such factory is |
| // provided. |
| #if BUILDFLAG(IS_ANDROID) |
| base::StackSamplingProfiler::UnwindersFactory core_unwinders_factory; |
| if (core_unwinders_factory_function_) { |
| core_unwinders_factory = core_unwinders_factory_function_.Run(); |
| } |
| if (core_unwinders_factory) { |
| if (unwinder_type_ == UnwinderType::kUnknown) { |
| unwinder_type_ = UnwinderType::kCustomAndroid; |
| } |
| profile_builder->SetUnwinderType(unwinder_type_); |
| profiler_ = std::make_unique<base::StackSamplingProfiler>( |
| sampled_thread_token_, params, std::move(profile_builder), |
| std::move(core_unwinders_factory)); |
| } |
| #else // BUILDFLAG(IS_ANDROID) |
| if (unwinder_type_ == UnwinderType::kUnknown) { |
| unwinder_type_ = UnwinderType::kDefault; |
| } |
| profile_builder->SetUnwinderType(unwinder_type_); |
| profiler_ = std::make_unique<base::StackSamplingProfiler>( |
| sampled_thread_token_, params, std::move(profile_builder), |
| base::CreateCoreUnwindersFactory()); |
| #endif // BUILDFLAG(IS_ANDROID) |
| if (profiler_ != nullptr) { |
| if (aux_unwinder_factory_) { |
| profiler_->AddAuxUnwinder(aux_unwinder_factory_.Run()); |
| } |
| profiler_->Start(); |
| } |
| |
| #if BUILDFLAG(ENABLE_LOADER_LOCK_SAMPLING) |
| if (loader_lock_sampling_thread_) { |
| loader_lock_sampling_thread_->StartSampling(); |
| } |
| #endif |
| } |
| |
| void TracingSamplerProfiler::StopTracing() { |
| base::AutoLock lock(lock_); |
| if (!profiler_) { |
| return; |
| } |
| |
| // Stop and release the stack sampling profiler. |
| profiler_->Stop(); |
| profile_builder_ = nullptr; |
| profiler_.reset(); |
| |
| #if BUILDFLAG(ENABLE_LOADER_LOCK_SAMPLING) |
| if (loader_lock_sampling_thread_) { |
| loader_lock_sampling_thread_->StopSampling(); |
| } |
| #endif |
| } |
| |
| } // namespace tracing |
| |
| PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS_WITH_ATTRS( |
| COMPONENT_EXPORT(TRACING_CPP), |
| tracing::TracingSamplerProfiler::DataSource); |
| |
| // This should go after PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS_WITH_ATTRS |
| // to avoid instantiation of type() template method before specialization. |
| std::unique_ptr<perfetto::TraceWriterBase> |
| tracing::TracingSamplerProfiler::DataSource::CreateTraceWriter() { |
| perfetto::internal::DataSourceStaticState* static_state = |
| perfetto::DataSourceHelper<TracingSamplerProfiler::DataSource>::type() |
| .static_state(); |
| // DataSourceProxy disallows multiple instances, so our instance will always |
| // have index 0. |
| perfetto::internal::DataSourceState* instance_state = static_state->TryGet(0); |
| CHECK(instance_state); |
| return perfetto::internal::TracingMuxer::Get()->CreateTraceWriter( |
| static_state, 0, instance_state, perfetto::BufferExhaustedPolicy::kDrop); |
| } |