Alexei Filippov | d6363e47 | 2018-08-27 19:31:39 | [diff] [blame] | 1 | // Copyright 2018 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #ifndef BASE_SAMPLING_HEAP_PROFILER_POISSON_ALLOCATION_SAMPLER_H_ |
| 6 | #define BASE_SAMPLING_HEAP_PROFILER_POISSON_ALLOCATION_SAMPLER_H_ |
| 7 | |
Alexei Filippov | d6363e47 | 2018-08-27 19:31:39 | [diff] [blame] | 8 | #include <vector> |
| 9 | |
| 10 | #include "base/base_export.h" |
Alexei Filippov | 0243574 | 2019-06-05 04:43:01 | [diff] [blame] | 11 | #include "base/compiler_specific.h" |
Alexei Filippov | d6363e47 | 2018-08-27 19:31:39 | [diff] [blame] | 12 | #include "base/macros.h" |
Avi Drissman | ded7717 | 2021-07-02 18:23:00 | [diff] [blame] | 13 | #include "base/no_destructor.h" |
Alexei Filippov | 0243574 | 2019-06-05 04:43:01 | [diff] [blame] | 14 | #include "base/sampling_heap_profiler/lock_free_address_hash_set.h" |
Alexei Filippov | d6363e47 | 2018-08-27 19:31:39 | [diff] [blame] | 15 | #include "base/synchronization/lock.h" |
Alexei Filippov | f071fd4 | 2019-09-11 21:37:34 | [diff] [blame] | 16 | #include "base/thread_annotations.h" |
Alexei Filippov | d6363e47 | 2018-08-27 19:31:39 | [diff] [blame] | 17 | |
| 18 | namespace base { |
| 19 | |
Alexei Filippov | d6363e47 | 2018-08-27 19:31:39 | [diff] [blame] | 20 | // This singleton class implements Poisson sampling of the incoming allocations |
| 21 | // stream. It hooks onto base::allocator and base::PartitionAlloc. |
| 22 | // An extra custom allocator can be hooked via SetHooksInstallCallback method. |
| 23 | // The only control parameter is sampling interval that controls average value |
| 24 | // of the sampling intervals. The actual intervals between samples are |
| 25 | // randomized using Poisson distribution to mitigate patterns in the allocation |
| 26 | // stream. |
| 27 | // Once accumulated allocation sizes fill up the current sample interval, |
| 28 | // a sample is generated and sent to the observers via |SampleAdded| call. |
| 29 | // When the corresponding memory that triggered the sample is freed observers |
| 30 | // get notified with |SampleRemoved| call. |
| 31 | // |
| 32 | class BASE_EXPORT PoissonAllocationSampler { |
| 33 | public: |
Michael Lippautz | ee0661c | 2021-02-24 07:41:11 | [diff] [blame] | 34 | enum AllocatorType : uint32_t { kMalloc, kPartitionAlloc }; |
Alexei Filippov | d6363e47 | 2018-08-27 19:31:39 | [diff] [blame] | 35 | |
| 36 | class SamplesObserver { |
| 37 | public: |
| 38 | virtual ~SamplesObserver() = default; |
| 39 | virtual void SampleAdded(void* address, |
| 40 | size_t size, |
| 41 | size_t total, |
| 42 | AllocatorType type, |
| 43 | const char* context) = 0; |
| 44 | virtual void SampleRemoved(void* address) = 0; |
| 45 | }; |
| 46 | |
Alexei Filippov | 28cc68d | 2018-09-13 07:58:36 | [diff] [blame] | 47 | // The instance of this class makes sampler do not report samples generated |
| 48 | // within the object scope for the current thread. |
| 49 | // It allows observers to allocate/deallocate memory while holding a lock |
| 50 | // without a chance to get into reentrancy problems. |
Alexei Filippov | da7a0571 | 2019-03-01 21:15:22 | [diff] [blame] | 51 | // The current implementation doesn't support ScopedMuteThreadSamples nesting. |
Alexei Filippov | bc37963 | 2018-09-14 22:29:06 | [diff] [blame] | 52 | class BASE_EXPORT ScopedMuteThreadSamples { |
Alexei Filippov | 28cc68d | 2018-09-13 07:58:36 | [diff] [blame] | 53 | public: |
Alexei Filippov | bc37963 | 2018-09-14 22:29:06 | [diff] [blame] | 54 | ScopedMuteThreadSamples(); |
| 55 | ~ScopedMuteThreadSamples(); |
Alexei Filippov | e48985e | 2019-02-01 00:27:41 | [diff] [blame] | 56 | |
| 57 | static bool IsMuted(); |
Alexei Filippov | 28cc68d | 2018-09-13 07:58:36 | [diff] [blame] | 58 | }; |
| 59 | |
Alexei Filippov | d6363e47 | 2018-08-27 19:31:39 | [diff] [blame] | 60 | // Must be called early during the process initialization. It creates and |
| 61 | // reserves a TLS slot. |
| 62 | static void Init(); |
| 63 | |
| 64 | // This is an entry point for plugging in an external allocator. |
| 65 | // Profiler will invoke the provided callback upon initialization. |
| 66 | // The callback should install hooks onto the corresponding memory allocator |
| 67 | // and make them invoke PoissonAllocationSampler::RecordAlloc and |
| 68 | // PoissonAllocationSampler::RecordFree upon corresponding allocation events. |
| 69 | // |
| 70 | // If the method is called after profiler is initialized, the callback |
| 71 | // is invoked right away. |
| 72 | static void SetHooksInstallCallback(void (*hooks_install_callback)()); |
| 73 | |
| 74 | void AddSamplesObserver(SamplesObserver*); |
Alexei Filippov | f071fd4 | 2019-09-11 21:37:34 | [diff] [blame] | 75 | |
| 76 | // Note: After an observer is removed it is still possible to receive |
| 77 | // a notification to that observer. This is not a problem currently as |
| 78 | // the only client of this interface is the base::SamplingHeapProfiler, |
| 79 | // which is a singleton. |
| 80 | // If there's a need for this functionality in the future, one might |
| 81 | // want to put observers notification loop under a reader-writer lock. |
Alexei Filippov | d6363e47 | 2018-08-27 19:31:39 | [diff] [blame] | 82 | void RemoveSamplesObserver(SamplesObserver*); |
| 83 | |
Alexei Filippov | d6363e47 | 2018-08-27 19:31:39 | [diff] [blame] | 84 | void SetSamplingInterval(size_t sampling_interval); |
| 85 | void SuppressRandomnessForTest(bool suppress); |
| 86 | |
| 87 | static void RecordAlloc(void* address, |
| 88 | size_t, |
| 89 | AllocatorType, |
| 90 | const char* context); |
Alexei Filippov | 0243574 | 2019-06-05 04:43:01 | [diff] [blame] | 91 | ALWAYS_INLINE static void RecordFree(void* address); |
Alexei Filippov | d6363e47 | 2018-08-27 19:31:39 | [diff] [blame] | 92 | |
| 93 | static PoissonAllocationSampler* Get(); |
| 94 | |
Peter Boström | 75cd3c0 | 2021-09-28 15:23:18 | [diff] [blame^] | 95 | PoissonAllocationSampler(const PoissonAllocationSampler&) = delete; |
| 96 | PoissonAllocationSampler& operator=(const PoissonAllocationSampler&) = delete; |
| 97 | |
Alexei Filippov | d6363e47 | 2018-08-27 19:31:39 | [diff] [blame] | 98 | private: |
| 99 | PoissonAllocationSampler(); |
| 100 | ~PoissonAllocationSampler() = delete; |
| 101 | |
| 102 | static void InstallAllocatorHooksOnce(); |
| 103 | static bool InstallAllocatorHooks(); |
| 104 | static size_t GetNextSampleInterval(size_t base_interval); |
| 105 | static LockFreeAddressHashSet& sampled_addresses_set(); |
| 106 | |
Alexei Filippov | 678cb25 | 2018-09-18 01:11:15 | [diff] [blame] | 107 | void DoRecordAlloc(intptr_t accumulated_bytes, |
| 108 | size_t size, |
Alexei Filippov | d6363e47 | 2018-08-27 19:31:39 | [diff] [blame] | 109 | void* address, |
| 110 | AllocatorType type, |
| 111 | const char* context); |
| 112 | void DoRecordFree(void* address); |
| 113 | |
| 114 | void BalanceAddressesHashSet(); |
| 115 | |
Alexei Filippov | d6363e47 | 2018-08-27 19:31:39 | [diff] [blame] | 116 | Lock mutex_; |
Alexei Filippov | f071fd4 | 2019-09-11 21:37:34 | [diff] [blame] | 117 | // The |observers_| list is guarded by |mutex_|, however a copy of it |
| 118 | // is made before invoking the observers (to avoid performing expensive |
| 119 | // operations under the lock) as such the SamplesObservers themselves need |
| 120 | // to be thread-safe and support being invoked racily after |
| 121 | // RemoveSamplesObserver(). |
| 122 | std::vector<SamplesObserver*> observers_ GUARDED_BY(mutex_); |
Alexei Filippov | d6363e47 | 2018-08-27 19:31:39 | [diff] [blame] | 123 | |
| 124 | static PoissonAllocationSampler* instance_; |
| 125 | |
Alexei Filippov | d6363e47 | 2018-08-27 19:31:39 | [diff] [blame] | 126 | friend class NoDestructor<PoissonAllocationSampler>; |
Alexei Filippov | 678cb25 | 2018-09-18 01:11:15 | [diff] [blame] | 127 | friend class SamplingHeapProfilerTest; |
| 128 | friend class ScopedMuteThreadSamples; |
Alexei Filippov | d6363e47 | 2018-08-27 19:31:39 | [diff] [blame] | 129 | }; |
| 130 | |
Alexei Filippov | 0243574 | 2019-06-05 04:43:01 | [diff] [blame] | 131 | // static |
| 132 | ALWAYS_INLINE void PoissonAllocationSampler::RecordFree(void* address) { |
| 133 | if (UNLIKELY(address == nullptr)) |
| 134 | return; |
| 135 | if (UNLIKELY(sampled_addresses_set().Contains(address))) |
| 136 | instance_->DoRecordFree(address); |
| 137 | } |
| 138 | |
Alexei Filippov | d6363e47 | 2018-08-27 19:31:39 | [diff] [blame] | 139 | } // namespace base |
| 140 | |
| 141 | #endif // BASE_SAMPLING_HEAP_PROFILER_POISSON_ALLOCATION_SAMPLER_H_ |