| // Copyright 2018 The Chromium 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 "base/sampling_heap_profiler/sampling_heap_profiler.h" |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <utility> |
| |
| #include "base/allocator/allocator_shim.h" |
| #include "base/allocator/buildflags.h" |
| #include "base/allocator/partition_allocator/partition_alloc.h" |
| #include "base/atomicops.h" |
| #include "base/debug/stack_trace.h" |
| #include "base/macros.h" |
| #include "base/no_destructor.h" |
| #include "base/partition_alloc_buildflags.h" |
| #include "base/sampling_heap_profiler/lock_free_address_hash_set.h" |
| #include "base/threading/thread_local_storage.h" |
| #include "build/build_config.h" |
| |
| #if defined(OS_ANDROID) && BUILDFLAG(CAN_UNWIND_WITH_CFI_TABLE) && \ |
| defined(OFFICIAL_BUILD) |
| #include "base/trace_event/cfi_backtrace_android.h" |
| #endif |
| |
| namespace base { |
| |
| SamplingHeapProfiler::Sample::Sample(size_t size, |
| size_t total, |
| uint32_t ordinal) |
| : size(size), total(total), ordinal(ordinal) {} |
| |
| SamplingHeapProfiler::Sample::Sample(const Sample&) = default; |
| SamplingHeapProfiler::Sample::~Sample() = default; |
| |
| SamplingHeapProfiler::SamplingHeapProfiler() = default; |
| SamplingHeapProfiler::~SamplingHeapProfiler() = default; |
| |
| uint32_t SamplingHeapProfiler::Start() { |
| #if defined(OS_ANDROID) && BUILDFLAG(CAN_UNWIND_WITH_CFI_TABLE) && \ |
| defined(OFFICIAL_BUILD) |
| if (!trace_event::CFIBacktraceAndroid::GetInitializedInstance() |
| ->can_unwind_stack_frames()) { |
| LOG(WARNING) << "Sampling heap profiler: Stack unwinding is not available."; |
| return 0; |
| } |
| #endif |
| auto* sampler = PoissonAllocationSampler::Get(); |
| sampler->AddSamplesObserver(this); |
| sampler->Start(); |
| return last_sample_ordinal_; |
| } |
| |
| void SamplingHeapProfiler::Stop() { |
| auto* sampler = PoissonAllocationSampler::Get(); |
| sampler->Stop(); |
| sampler->RemoveSamplesObserver(this); |
| } |
| |
| void SamplingHeapProfiler::SetSamplingInterval(size_t sampling_interval) { |
| PoissonAllocationSampler::Get()->SetSamplingInterval(sampling_interval); |
| } |
| |
| namespace { |
| void RecordStackTrace(SamplingHeapProfiler::Sample* sample) { |
| #if !defined(OS_NACL) |
| constexpr uint32_t kMaxStackEntries = 256; |
| constexpr uint32_t kSkipProfilerOwnFrames = 2; |
| uint32_t skip_frames = kSkipProfilerOwnFrames; |
| #if defined(OS_ANDROID) && BUILDFLAG(CAN_UNWIND_WITH_CFI_TABLE) && \ |
| defined(OFFICIAL_BUILD) |
| const void* frames[kMaxStackEntries]; |
| size_t frame_count = |
| trace_event::CFIBacktraceAndroid::GetInitializedInstance()->Unwind( |
| frames, kMaxStackEntries); |
| #elif BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS) |
| const void* frames[kMaxStackEntries]; |
| size_t frame_count = |
| debug::TraceStackFramePointers(frames, kMaxStackEntries, skip_frames); |
| skip_frames = 0; |
| #else |
| // Fall-back to capturing the stack with debug::StackTrace, |
| // which is likely slower, but more reliable. |
| debug::StackTrace stack_trace(kMaxStackEntries); |
| size_t frame_count = 0; |
| const void* const* frames = stack_trace.Addresses(&frame_count); |
| #endif |
| |
| sample->stack.insert( |
| sample->stack.end(), const_cast<void**>(&frames[skip_frames]), |
| const_cast<void**>(&frames[std::max<size_t>(frame_count, skip_frames)])); |
| #endif |
| } |
| } // namespace |
| |
| void SamplingHeapProfiler::SampleAdded(void* address, |
| size_t size, |
| size_t total, |
| PoissonAllocationSampler::AllocatorType, |
| const char*) { |
| AutoLock lock(mutex_); |
| Sample sample(size, total, ++last_sample_ordinal_); |
| RecordStackTrace(&sample); |
| samples_.emplace(address, std::move(sample)); |
| } |
| |
| void SamplingHeapProfiler::SampleRemoved(void* address) { |
| AutoLock lock(mutex_); |
| auto it = samples_.find(address); |
| if (it != samples_.end()) |
| samples_.erase(it); |
| } |
| |
| std::vector<SamplingHeapProfiler::Sample> SamplingHeapProfiler::GetSamples( |
| uint32_t profile_id) { |
| // Make sure the sampler does not invoke |SampleAdded| or |SampleRemoved| |
| // on this thread. Otherwise it could have end up with a deadlock. |
| // See crbug.com/882495 |
| PoissonAllocationSampler::ScopedMuteThreadSamples no_samples_scope; |
| AutoLock lock(mutex_); |
| std::vector<Sample> samples; |
| samples.reserve(samples_.size()); |
| for (auto& it : samples_) { |
| Sample& sample = it.second; |
| if (sample.ordinal > profile_id) |
| samples.push_back(sample); |
| } |
| return samples; |
| } |
| |
| // static |
| void SamplingHeapProfiler::Init() { |
| PoissonAllocationSampler::Init(); |
| } |
| |
| // static |
| SamplingHeapProfiler* SamplingHeapProfiler::Get() { |
| static NoDestructor<SamplingHeapProfiler> instance; |
| return instance.get(); |
| } |
| |
| } // namespace base |