Remove base/sampling_heap_profiler
Change-Id: I718cf9253fe4ca6f81817ec7f1b485eedafffc21
Reviewed-on: https://gn-review.googlesource.com/1422
Reviewed-by: Brett Wilson <brettw@chromium.org>
Commit-Queue: Scott Graham <scottmg@chromium.org>
diff --git a/base/sampling_heap_profiler/OWNERS b/base/sampling_heap_profiler/OWNERS
deleted file mode 100644
index 87c9661..0000000
--- a/base/sampling_heap_profiler/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-alph@chromium.org
diff --git a/base/sampling_heap_profiler/sampling_heap_profiler.cc b/base/sampling_heap_profiler/sampling_heap_profiler.cc
deleted file mode 100644
index aa695d8..0000000
--- a/base/sampling_heap_profiler/sampling_heap_profiler.cc
+++ /dev/null
@@ -1,447 +0,0 @@
-// 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/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/rand_util.h"
-#include "base/threading/thread_local_storage.h"
-#include "build_config.h"
-
-namespace base {
-
-using base::allocator::AllocatorDispatch;
-using base::subtle::Atomic32;
-using base::subtle::AtomicWord;
-
-namespace {
-
-// Control how many top frames to skip when recording call stack.
-// These frames correspond to the profiler own frames.
-const uint32_t kSkipBaseAllocatorFrames = 2;
-
-const size_t kDefaultSamplingIntervalBytes = 128 * 1024;
-
-// Controls if sample intervals should not be randomized. Used for testing.
-bool g_deterministic;
-
-// A positive value if profiling is running, otherwise it's zero.
-Atomic32 g_running;
-
-// Number of lock-free safe (not causing rehashing) accesses to samples_ map
-// currently being performed.
-Atomic32 g_operations_in_flight;
-
-// Controls if new incoming lock-free accesses are allowed.
-// When set to true, threads should not enter lock-free paths.
-Atomic32 g_fast_path_is_closed;
-
-// Sampling interval parameter, the mean value for intervals between samples.
-AtomicWord g_sampling_interval = kDefaultSamplingIntervalBytes;
-
-// Last generated sample ordinal number.
-uint32_t g_last_sample_ordinal = 0;
-
-void (*g_hooks_install_callback)();
-Atomic32 g_hooks_installed;
-
-void* AllocFn(const AllocatorDispatch* self, size_t size, void* context) {
- void* address = self->next->alloc_function(self->next, size, context);
- SamplingHeapProfiler::RecordAlloc(address, size, kSkipBaseAllocatorFrames);
- return address;
-}
-
-void* AllocZeroInitializedFn(const AllocatorDispatch* self,
- size_t n,
- size_t size,
- void* context) {
- void* address =
- self->next->alloc_zero_initialized_function(self->next, n, size, context);
- SamplingHeapProfiler::RecordAlloc(address, n * size,
- kSkipBaseAllocatorFrames);
- return address;
-}
-
-void* AllocAlignedFn(const AllocatorDispatch* self,
- size_t alignment,
- size_t size,
- void* context) {
- void* address =
- self->next->alloc_aligned_function(self->next, alignment, size, context);
- SamplingHeapProfiler::RecordAlloc(address, size, kSkipBaseAllocatorFrames);
- return address;
-}
-
-void* ReallocFn(const AllocatorDispatch* self,
- void* address,
- size_t size,
- void* context) {
- // Note: size == 0 actually performs free.
- SamplingHeapProfiler::RecordFree(address);
- address = self->next->realloc_function(self->next, address, size, context);
- SamplingHeapProfiler::RecordAlloc(address, size, kSkipBaseAllocatorFrames);
- return address;
-}
-
-void FreeFn(const AllocatorDispatch* self, void* address, void* context) {
- SamplingHeapProfiler::RecordFree(address);
- self->next->free_function(self->next, address, context);
-}
-
-size_t GetSizeEstimateFn(const AllocatorDispatch* self,
- void* address,
- void* context) {
- return self->next->get_size_estimate_function(self->next, address, context);
-}
-
-unsigned BatchMallocFn(const AllocatorDispatch* self,
- size_t size,
- void** results,
- unsigned num_requested,
- void* context) {
- unsigned num_allocated = self->next->batch_malloc_function(
- self->next, size, results, num_requested, context);
- for (unsigned i = 0; i < num_allocated; ++i) {
- SamplingHeapProfiler::RecordAlloc(results[i], size,
- kSkipBaseAllocatorFrames);
- }
- return num_allocated;
-}
-
-void BatchFreeFn(const AllocatorDispatch* self,
- void** to_be_freed,
- unsigned num_to_be_freed,
- void* context) {
- for (unsigned i = 0; i < num_to_be_freed; ++i)
- SamplingHeapProfiler::RecordFree(to_be_freed[i]);
- self->next->batch_free_function(self->next, to_be_freed, num_to_be_freed,
- context);
-}
-
-void FreeDefiniteSizeFn(const AllocatorDispatch* self,
- void* address,
- size_t size,
- void* context) {
- SamplingHeapProfiler::RecordFree(address);
- self->next->free_definite_size_function(self->next, address, size, context);
-}
-
-AllocatorDispatch g_allocator_dispatch = {&AllocFn,
- &AllocZeroInitializedFn,
- &AllocAlignedFn,
- &ReallocFn,
- &FreeFn,
- &GetSizeEstimateFn,
- &BatchMallocFn,
- &BatchFreeFn,
- &FreeDefiniteSizeFn,
- nullptr};
-
-#if BUILDFLAG(USE_PARTITION_ALLOC) && !defined(OS_NACL)
-
-void PartitionAllocHook(void* address, size_t size, const char*) {
- SamplingHeapProfiler::RecordAlloc(address, size);
-}
-
-void PartitionFreeHook(void* address) {
- SamplingHeapProfiler::RecordFree(address);
-}
-
-#endif // BUILDFLAG(USE_PARTITION_ALLOC) && !defined(OS_NACL)
-
-ThreadLocalStorage::Slot& AccumulatedBytesTLS() {
- static base::NoDestructor<base::ThreadLocalStorage::Slot>
- accumulated_bytes_tls;
- return *accumulated_bytes_tls;
-}
-
-} // namespace
-
-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::instance_;
-
-SamplingHeapProfiler::SamplingHeapProfiler() {
- instance_ = this;
-}
-
-// static
-void SamplingHeapProfiler::InitTLSSlot() {
- // Preallocate the TLS slot early, so it can't cause reentracy issues
- // when sampling is started.
- ignore_result(AccumulatedBytesTLS().Get());
-}
-
-// static
-void SamplingHeapProfiler::InstallAllocatorHooksOnce() {
- static bool hook_installed = InstallAllocatorHooks();
- ignore_result(hook_installed);
-}
-
-// static
-bool SamplingHeapProfiler::InstallAllocatorHooks() {
- ignore_result(g_allocator_dispatch);
- DLOG(WARNING)
- << "base::allocator shims are not available for memory sampling.";
-
-#if BUILDFLAG(USE_PARTITION_ALLOC) && !defined(OS_NACL)
- base::PartitionAllocHooks::SetAllocationHook(&PartitionAllocHook);
- base::PartitionAllocHooks::SetFreeHook(&PartitionFreeHook);
-#endif // BUILDFLAG(USE_PARTITION_ALLOC) && !defined(OS_NACL)
-
- int32_t hooks_install_callback_has_been_set =
- base::subtle::Acquire_CompareAndSwap(&g_hooks_installed, 0, 1);
- if (hooks_install_callback_has_been_set)
- g_hooks_install_callback();
-
- return true;
-}
-
-// static
-void SamplingHeapProfiler::SetHooksInstallCallback(
- void (*hooks_install_callback)()) {
- CHECK(!g_hooks_install_callback && hooks_install_callback);
- g_hooks_install_callback = hooks_install_callback;
-
- int32_t profiler_has_already_been_initialized =
- base::subtle::Release_CompareAndSwap(&g_hooks_installed, 0, 1);
- if (profiler_has_already_been_initialized)
- g_hooks_install_callback();
-}
-
-uint32_t SamplingHeapProfiler::Start() {
- InstallAllocatorHooksOnce();
- base::subtle::Barrier_AtomicIncrement(&g_running, 1);
- return g_last_sample_ordinal;
-}
-
-void SamplingHeapProfiler::Stop() {
- AtomicWord count = base::subtle::Barrier_AtomicIncrement(&g_running, -1);
- CHECK_GE(count, 0);
-}
-
-void SamplingHeapProfiler::SetSamplingInterval(size_t sampling_interval) {
- // TODO(alph): Reset the sample being collected if running.
- base::subtle::Release_Store(&g_sampling_interval,
- static_cast<AtomicWord>(sampling_interval));
-}
-
-// static
-size_t SamplingHeapProfiler::GetNextSampleInterval(size_t interval) {
- if (UNLIKELY(g_deterministic))
- return interval;
-
- // We sample with a Poisson process, with constant average sampling
- // interval. This follows the exponential probability distribution with
- // parameter λ = 1/interval where |interval| is the average number of bytes
- // between samples.
- // Let u be a uniformly distributed random number between 0 and 1, then
- // next_sample = -ln(u) / λ
- double uniform = base::RandDouble();
- double value = -log(uniform) * interval;
- size_t min_value = sizeof(intptr_t);
- // We limit the upper bound of a sample interval to make sure we don't have
- // huge gaps in the sampling stream. Probability of the upper bound gets hit
- // is exp(-20) ~ 2e-9, so it should not skew the distibution.
- size_t max_value = interval * 20;
- if (UNLIKELY(value < min_value))
- return min_value;
- if (UNLIKELY(value > max_value))
- return max_value;
- return static_cast<size_t>(value);
-}
-
-// static
-void SamplingHeapProfiler::RecordAlloc(void* address,
- size_t size,
- uint32_t skip_frames) {
- if (UNLIKELY(!base::subtle::NoBarrier_Load(&g_running)))
- return;
- if (UNLIKELY(base::ThreadLocalStorage::HasBeenDestroyed()))
- return;
-
- // TODO(alph): On MacOS it may call the hook several times for a single
- // allocation. Handle the case.
-
- intptr_t accumulated_bytes =
- reinterpret_cast<intptr_t>(AccumulatedBytesTLS().Get());
- accumulated_bytes += size;
- if (LIKELY(accumulated_bytes < 0)) {
- AccumulatedBytesTLS().Set(reinterpret_cast<void*>(accumulated_bytes));
- return;
- }
-
- size_t mean_interval = base::subtle::NoBarrier_Load(&g_sampling_interval);
- size_t samples = accumulated_bytes / mean_interval;
- accumulated_bytes %= mean_interval;
-
- do {
- accumulated_bytes -= GetNextSampleInterval(mean_interval);
- ++samples;
- } while (accumulated_bytes >= 0);
-
- AccumulatedBytesTLS().Set(reinterpret_cast<void*>(accumulated_bytes));
-
- instance_->DoRecordAlloc(samples * mean_interval, size, address, skip_frames);
-}
-
-void SamplingHeapProfiler::RecordStackTrace(Sample* sample,
- uint32_t skip_frames) {
-#if !defined(OS_NACL)
- // TODO(alph): Consider using debug::TraceStackFramePointers. It should be
- // somewhat faster than base::debug::StackTrace.
- base::debug::StackTrace trace;
- size_t count;
- void* const* addresses = const_cast<void* const*>(trace.Addresses(&count));
- const uint32_t kSkipProfilerOwnFrames = 2;
- skip_frames += kSkipProfilerOwnFrames;
- sample->stack.insert(
- sample->stack.end(), &addresses[skip_frames],
- &addresses[std::max(count, static_cast<size_t>(skip_frames))]);
-#endif
-}
-
-void SamplingHeapProfiler::DoRecordAlloc(size_t total_allocated,
- size_t size,
- void* address,
- uint32_t skip_frames) {
- if (entered_.Get())
- return;
- entered_.Set(true);
- {
- base::AutoLock lock(mutex_);
-
- Sample sample(size, total_allocated, ++g_last_sample_ordinal);
- RecordStackTrace(&sample, skip_frames);
-
- if (MayRehashOnInsert()) {
- // Close the fast path as inserting an element into samples_ may cause
- // rehashing that invalidates iterators affecting all the concurrent
- // readers.
- base::subtle::Release_Store(&g_fast_path_is_closed, 1);
- // Wait until all current readers leave.
- while (base::subtle::Acquire_Load(&g_operations_in_flight)) {
- while (base::subtle::NoBarrier_Load(&g_operations_in_flight)) {
- }
- }
- samples_.emplace(address, std::move(sample));
- // Open the fast path.
- base::subtle::Release_Store(&g_fast_path_is_closed, 0);
- } else {
- samples_.emplace(address, std::move(sample));
- }
-
- for (auto* observer : observers_)
- observer->SampleAdded(sample.ordinal, size, total_allocated);
- }
-
- entered_.Set(false);
-}
-
-// static
-void SamplingHeapProfiler::RecordFree(void* address) {
- bool maybe_sampled = true; // Pessimistically assume allocation was sampled.
- base::subtle::Barrier_AtomicIncrement(&g_operations_in_flight, 1);
- if (LIKELY(!base::subtle::NoBarrier_Load(&g_fast_path_is_closed))) {
- maybe_sampled =
- instance_->samples_.find(address) != instance_->samples_.end();
- }
- base::subtle::Barrier_AtomicIncrement(&g_operations_in_flight, -1);
- if (maybe_sampled)
- instance_->DoRecordFree(address);
-}
-
-void SamplingHeapProfiler::DoRecordFree(void* address) {
- if (UNLIKELY(base::ThreadLocalStorage::HasBeenDestroyed()))
- return;
- if (entered_.Get())
- return;
- entered_.Set(true);
- {
- base::AutoLock lock(mutex_);
- auto it = samples_.find(address);
- if (it != samples_.end()) {
- for (auto* observer : observers_)
- observer->SampleRemoved(it->second.ordinal);
- samples_.erase(it);
- }
- }
- entered_.Set(false);
-}
-
-bool SamplingHeapProfiler::MayRehashOnInsert() {
- size_t max_items_before_rehash =
- std::floor(samples_.bucket_count() * samples_.max_load_factor());
- // Conservatively use 2 instead of 1 to workaround potential rounding errors.
- return samples_.size() + 2 >= max_items_before_rehash;
-}
-
-// static
-SamplingHeapProfiler* SamplingHeapProfiler::GetInstance() {
- static base::NoDestructor<SamplingHeapProfiler> instance;
- return instance.get();
-}
-
-// static
-void SamplingHeapProfiler::SuppressRandomnessForTest(bool suppress) {
- g_deterministic = suppress;
-}
-
-void SamplingHeapProfiler::AddSamplesObserver(SamplesObserver* observer) {
- CHECK(!entered_.Get());
- entered_.Set(true);
- {
- base::AutoLock lock(mutex_);
- observers_.push_back(observer);
- }
- entered_.Set(false);
-}
-
-void SamplingHeapProfiler::RemoveSamplesObserver(SamplesObserver* observer) {
- CHECK(!entered_.Get());
- entered_.Set(true);
- {
- base::AutoLock lock(mutex_);
- auto it = std::find(observers_.begin(), observers_.end(), observer);
- CHECK(it != observers_.end());
- observers_.erase(it);
- }
- entered_.Set(false);
-}
-
-std::vector<SamplingHeapProfiler::Sample> SamplingHeapProfiler::GetSamples(
- uint32_t profile_id) {
- CHECK(!entered_.Get());
- entered_.Set(true);
- std::vector<Sample> samples;
- {
- base::AutoLock lock(mutex_);
- for (auto& it : samples_) {
- Sample& sample = it.second;
- if (sample.ordinal > profile_id)
- samples.push_back(sample);
- }
- }
- entered_.Set(false);
- return samples;
-}
-
-} // namespace base
diff --git a/base/sampling_heap_profiler/sampling_heap_profiler.h b/base/sampling_heap_profiler/sampling_heap_profiler.h
deleted file mode 100644
index 3f2f227..0000000
--- a/base/sampling_heap_profiler/sampling_heap_profiler.h
+++ /dev/null
@@ -1,111 +0,0 @@
-// 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.
-
-#ifndef BASE_SAMPLING_HEAP_PROFILER_SAMPLING_HEAP_PROFILER_H
-#define BASE_SAMPLING_HEAP_PROFILER_SAMPLING_HEAP_PROFILER_H
-
-#include <unordered_map>
-#include <vector>
-
-#include "base/base_export.h"
-#include "base/macros.h"
-#include "base/synchronization/lock.h"
-#include "base/threading/thread_local.h"
-
-namespace base {
-
-template <typename T>
-class NoDestructor;
-
-// The class implements sampling profiling of native memory heap.
-// It hooks on base::allocator and base::PartitionAlloc.
-// When started it selects and records allocation samples based on
-// the sampling_interval parameter.
-// The recorded samples can then be retrieved using GetSamples method.
-class BASE_EXPORT SamplingHeapProfiler {
- public:
- class BASE_EXPORT Sample {
- public:
- Sample(const Sample&);
- ~Sample();
-
- size_t size; // Allocation size.
- size_t total; // Total size attributed to the sample.
- std::vector<void*> stack;
-
- private:
- friend class SamplingHeapProfiler;
-
- Sample(size_t, size_t total, uint32_t ordinal);
-
- uint32_t ordinal;
- };
-
- class SamplesObserver {
- public:
- virtual ~SamplesObserver() = default;
- virtual void SampleAdded(uint32_t id, size_t size, size_t total) = 0;
- virtual void SampleRemoved(uint32_t id) = 0;
- };
-
- // Must be called early during the process initialization. It creates and
- // reserves a TLS slot.
- static void InitTLSSlot();
-
- // This is an entry point for plugging in an external allocator.
- // Profiler will invoke the provided callback upon initialization.
- // The callback should install hooks onto the corresponding memory allocator
- // and make them invoke SamplingHeapProfiler::RecordAlloc and
- // SamplingHeapProfiler::RecordFree upon corresponding allocation events.
- //
- // If the method is called after profiler is initialized, the callback
- // is invoked right away.
- static void SetHooksInstallCallback(void (*hooks_install_callback)());
-
- void AddSamplesObserver(SamplesObserver*);
- void RemoveSamplesObserver(SamplesObserver*);
-
- uint32_t Start();
- void Stop();
- void SetSamplingInterval(size_t sampling_interval);
- void SuppressRandomnessForTest(bool suppress);
-
- std::vector<Sample> GetSamples(uint32_t profile_id);
-
- static void RecordAlloc(void* address, size_t, uint32_t skip_frames = 0);
- static void RecordFree(void* address);
-
- static SamplingHeapProfiler* GetInstance();
-
- private:
- SamplingHeapProfiler();
- ~SamplingHeapProfiler() = delete;
-
- static void InstallAllocatorHooksOnce();
- static bool InstallAllocatorHooks();
- static size_t GetNextSampleInterval(size_t base_interval);
-
- void DoRecordAlloc(size_t total_allocated,
- size_t allocation_size,
- void* address,
- uint32_t skip_frames);
- void DoRecordFree(void* address);
- void RecordStackTrace(Sample*, uint32_t skip_frames);
- bool MayRehashOnInsert();
-
- base::ThreadLocalBoolean entered_;
- base::Lock mutex_;
- std::unordered_map<void*, Sample> samples_;
- std::vector<SamplesObserver*> observers_;
-
- static SamplingHeapProfiler* instance_;
-
- friend class base::NoDestructor<SamplingHeapProfiler>;
-
- DISALLOW_COPY_AND_ASSIGN(SamplingHeapProfiler);
-};
-
-} // namespace base
-
-#endif // BASE_SAMPLING_HEAP_PROFILER_SAMPLING_HEAP_PROFILER_H