• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 The Chromium Authors
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 
8 #include <atomic>
9 #include <vector>
10 
11 #include "base/allocator/dispatcher/notification_data.h"
12 #include "base/allocator/dispatcher/reentry_guard.h"
13 #include "base/allocator/dispatcher/subsystem.h"
14 #include "base/base_export.h"
15 #include "base/compiler_specific.h"
16 #include "base/gtest_prod_util.h"
17 #include "base/memory/raw_ptr_exclusion.h"
18 #include "base/no_destructor.h"
19 #include "base/sampling_heap_profiler/lock_free_address_hash_set.h"
20 #include "base/synchronization/lock.h"
21 #include "base/thread_annotations.h"
22 
23 namespace base {
24 
25 class SamplingHeapProfilerTest;
26 
27 // This singleton class implements Poisson sampling of the incoming allocations
28 // stream. It hooks onto base::allocator and base::PartitionAlloc.
29 // The only control parameter is sampling interval that controls average value
30 // of the sampling intervals. The actual intervals between samples are
31 // randomized using Poisson distribution to mitigate patterns in the allocation
32 // stream.
33 // Once accumulated allocation sizes fill up the current sample interval,
34 // a sample is generated and sent to the observers via |SampleAdded| call.
35 // When the corresponding memory that triggered the sample is freed observers
36 // get notified with |SampleRemoved| call.
37 //
38 class BASE_EXPORT PoissonAllocationSampler {
39  public:
40   class SamplesObserver {
41    public:
42     virtual ~SamplesObserver() = default;
43     virtual void SampleAdded(
44         void* address,
45         size_t size,
46         size_t total,
47         base::allocator::dispatcher::AllocationSubsystem type,
48         const char* context) = 0;
49     virtual void SampleRemoved(void* address) = 0;
50   };
51 
52   // An instance of this class makes the sampler not report samples generated
53   // within the object scope for the current thread.
54   // It allows observers to allocate/deallocate memory while holding a lock
55   // without a chance to get into reentrancy problems.
56   // The current implementation doesn't support ScopedMuteThreadSamples nesting.
57   class BASE_EXPORT ScopedMuteThreadSamples {
58    public:
59     ScopedMuteThreadSamples();
60     ~ScopedMuteThreadSamples();
61 
62     ScopedMuteThreadSamples(const ScopedMuteThreadSamples&) = delete;
63     ScopedMuteThreadSamples& operator=(const ScopedMuteThreadSamples&) = delete;
64 
65     static bool IsMuted();
66   };
67 
68   // An instance of this class makes the sampler behave deterministically to
69   // ensure test results are repeatable. Does not support nesting.
70   class BASE_EXPORT ScopedSuppressRandomnessForTesting {
71    public:
72     ScopedSuppressRandomnessForTesting();
73     ~ScopedSuppressRandomnessForTesting();
74 
75     ScopedSuppressRandomnessForTesting(
76         const ScopedSuppressRandomnessForTesting&) = delete;
77     ScopedSuppressRandomnessForTesting& operator=(
78         const ScopedSuppressRandomnessForTesting&) = delete;
79 
80     static bool IsSuppressed();
81   };
82 
83   // An instance of this class makes the sampler only report samples with
84   // AllocatorType kManualForTesting, not those from hooked allocators. This
85   // allows unit tests to set test expectations based on only explicit calls to
86   // RecordAlloc and RecordFree.
87   //
88   // The accumulated bytes on the thread that creates a
89   // ScopedMuteHookedSamplesForTesting will also be reset to 0, and restored
90   // when the object leaves scope. This gives tests a known state to start
91   // recording samples on one thread: a full interval must pass to record a
92   // sample. Other threads will still have a random number of accumulated bytes.
93   //
94   // Only one instance may exist at a time.
95   class BASE_EXPORT ScopedMuteHookedSamplesForTesting {
96    public:
97     ScopedMuteHookedSamplesForTesting();
98     ~ScopedMuteHookedSamplesForTesting();
99 
100     // Move-only.
101     ScopedMuteHookedSamplesForTesting(
102         const ScopedMuteHookedSamplesForTesting&) = delete;
103     ScopedMuteHookedSamplesForTesting& operator=(
104         const ScopedMuteHookedSamplesForTesting&) = delete;
105     ScopedMuteHookedSamplesForTesting(ScopedMuteHookedSamplesForTesting&&);
106     ScopedMuteHookedSamplesForTesting& operator=(
107         ScopedMuteHookedSamplesForTesting&&);
108 
109    private:
110     intptr_t accumulated_bytes_snapshot_;
111   };
112 
113   // Must be called early during the process initialization. It creates and
114   // reserves a TLS slot.
115   static void Init();
116 
117   void AddSamplesObserver(SamplesObserver*);
118 
119   // Note: After an observer is removed it is still possible to receive
120   // a notification to that observer. This is not a problem currently as
121   // the only client of this interface is the base::SamplingHeapProfiler,
122   // which is a singleton.
123   // If there's a need for this functionality in the future, one might
124   // want to put observers notification loop under a reader-writer lock.
125   void RemoveSamplesObserver(SamplesObserver*);
126 
127   // Sets the mean number of bytes that will be allocated before taking a
128   // sample.
129   void SetSamplingInterval(size_t sampling_interval_bytes);
130 
131   // Returns the current mean sampling interval, in bytes.
132   size_t SamplingInterval() const;
133 
134   ALWAYS_INLINE void OnAllocation(
135       const base::allocator::dispatcher::AllocationNotificationData&
136           allocation_data);
137   ALWAYS_INLINE void OnFree(
138       const base::allocator::dispatcher::FreeNotificationData& free_data);
139 
140   static PoissonAllocationSampler* Get();
141 
142   PoissonAllocationSampler(const PoissonAllocationSampler&) = delete;
143   PoissonAllocationSampler& operator=(const PoissonAllocationSampler&) = delete;
144 
145   // Returns true if a ScopedMuteHookedSamplesForTesting exists. This can be
146   // read from any thread.
AreHookedSamplesMuted()147   static bool AreHookedSamplesMuted() {
148     return profiling_state_.load(std::memory_order_relaxed) &
149            ProfilingStateFlag::kHookedSamplesMutedForTesting;
150   }
151 
152  private:
153   // Flags recording the state of the profiler. This does not use enum class so
154   // flags can be used in a bitmask.
155   enum ProfilingStateFlag {
156     // Set if profiling has ever been started in this session of Chrome. Once
157     // this is set, it is never reset. This is used to optimize the common case
158     // where profiling is never used.
159     kWasStarted = 1 << 0,
160     // Set if profiling is currently running. This flag is toggled on and off
161     // as sample observers are added and removed.
162     kIsRunning = 1 << 1,
163     // Set if a ScopedMuteHookedSamplesForTesting object exists.
164     kHookedSamplesMutedForTesting = 1 << 2,
165   };
166   using ProfilingStateFlagMask = int;
167 
168   PoissonAllocationSampler();
169   ~PoissonAllocationSampler() = delete;
170 
171   static size_t GetNextSampleInterval(size_t base_interval);
172 
173   // Return the set of sampled addresses. This is only valid to call after
174   // Init().
175   static LockFreeAddressHashSet& sampled_addresses_set();
176 
177   // Atomically adds `flag` to `profiling_state_`. DCHECK's if it was already
178   // set. If `flag` is kIsRunning, also sets kWasStarted. Uses
179   // std::memory_order_relaxed semantics and therefore doesn't synchronize the
180   // state of any other memory with future readers. (See the comment in
181   // RecordFree() for why this is safe.)
182   static void SetProfilingStateFlag(ProfilingStateFlag flag);
183 
184   // Atomically removes `flag` from `profiling_state_`. DCHECK's if it was not
185   // already set. Uses std::memory_order_relaxed semantics and therefore doesn't
186   // synchronize the state of any other memory with future readers. (See the
187   // comment in RecordFree() for why this is safe.)
188   static void ResetProfilingStateFlag(ProfilingStateFlag flag);
189 
190   void DoRecordAllocation(const ProfilingStateFlagMask state,
191                           void* address,
192                           size_t size,
193                           base::allocator::dispatcher::AllocationSubsystem type,
194                           const char* context);
195   void DoRecordFree(void* address);
196 
197   void BalanceAddressesHashSet();
198 
199   Lock mutex_;
200 
201   // The |observers_| list is guarded by |mutex_|, however a copy of it
202   // is made before invoking the observers (to avoid performing expensive
203   // operations under the lock) as such the SamplesObservers themselves need
204   // to be thread-safe and support being invoked racily after
205   // RemoveSamplesObserver().
206   //
207   // This class handles allocation, so it must never use raw_ptr<T>. In
208   // particular, raw_ptr<T> with `enable_backup_ref_ptr_instance_tracer`
209   // developer option allocates memory, which would cause reentrancy issues:
210   // allocating memory while allocating memory.
211   // More details in https://crbug.com/340815319
212   RAW_PTR_EXCLUSION std::vector<SamplesObserver*> observers_ GUARDED_BY(mutex_);
213 
214   // Fast, thread-safe access to the current profiling state.
215   static std::atomic<ProfilingStateFlagMask> profiling_state_;
216 
217   friend class NoDestructor<PoissonAllocationSampler>;
218   friend class PoissonAllocationSamplerStateTest;
219   friend class SamplingHeapProfilerTest;
220   FRIEND_TEST_ALL_PREFIXES(PoissonAllocationSamplerTest, MuteHooksWithoutInit);
221   FRIEND_TEST_ALL_PREFIXES(SamplingHeapProfilerTest, HookedAllocatorMuted);
222 };
223 
OnAllocation(const base::allocator::dispatcher::AllocationNotificationData & allocation_data)224 ALWAYS_INLINE void PoissonAllocationSampler::OnAllocation(
225     const base::allocator::dispatcher::AllocationNotificationData&
226         allocation_data) {
227   // The allocation hooks may be installed before the sampler is started. Check
228   // if its ever been started first to avoid extra work on the fast path,
229   // because it's the most common case.
230   const ProfilingStateFlagMask state =
231       profiling_state_.load(std::memory_order_relaxed);
232   if (!(state & ProfilingStateFlag::kWasStarted)) [[likely]] {
233     return;
234   }
235 
236   const auto type = allocation_data.allocation_subsystem();
237 
238   // When sampling is muted for testing, only handle manual calls to
239   // RecordAlloc. (This doesn't need to be checked in RecordFree because muted
240   // allocations won't be added to sampled_addresses_set(), so RecordFree
241   // already skips them.)
242   if ((state & ProfilingStateFlag::kHookedSamplesMutedForTesting) &&
243       type !=
244           base::allocator::dispatcher::AllocationSubsystem::kManualForTesting)
245       [[unlikely]] {
246     return;
247   }
248 
249   // Note: ReentryGuard prevents from recursions introduced by malloc and
250   // initialization of thread local storage which happen in the allocation path
251   // only (please see docs of ReentryGuard for full details).
252   allocator::dispatcher::ReentryGuard reentry_guard;
253 
254   if (!reentry_guard) [[unlikely]] {
255     return;
256   }
257 
258   DoRecordAllocation(state, allocation_data.address(), allocation_data.size(),
259                      type, allocation_data.type_name());
260 }
261 
OnFree(const base::allocator::dispatcher::FreeNotificationData & free_data)262 ALWAYS_INLINE void PoissonAllocationSampler::OnFree(
263     const base::allocator::dispatcher::FreeNotificationData& free_data) {
264   // The allocation hooks may be installed before the sampler is started. Check
265   // if its ever been started first to avoid extra work on the fast path,
266   // because it's the most common case. Note that DoRecordFree still needs to be
267   // called if the sampler was started but is now stopped, to track allocations
268   // that were recorded while the sampler was still running.
269   //
270   // Relaxed ordering is safe here because there's only one case where
271   // RecordAlloc and RecordFree MUST see the same value of `profiling_state_`.
272   // Assume thread A updates `profiling_state_` from 0 to kWasStarted |
273   // kIsRunning, thread B calls RecordAlloc, and thread C calls RecordFree.
274   // (Something else could update `profiling_state_` to remove kIsRunning before
275   // RecordAlloc or RecordFree.)
276   //
277   // 1. If RecordAlloc(p) sees !kWasStarted or !kIsRunning it will return
278   //    immediately, so p won't be in sampled_address_set(). So no matter what
279   //    RecordFree(p) sees it will also return immediately.
280   //
281   // 2. If RecordFree() is called with a pointer that was never passed to
282   //    RecordAlloc(), again it will return immediately no matter what it sees.
283   //
284   // 3. If RecordAlloc(p) sees kIsRunning it will put p in
285   //    sampled_address_set(). In this case RecordFree(p) MUST see !kWasStarted
286   //    or it will return without removing p:
287   //
288   //    3a. If the program got p as the return value from malloc() and passed it
289   //        to free(), then RecordFree() happens-after RecordAlloc() and
290   //        therefore will see the same value of `profiling_state_` as
291   //        RecordAlloc() for all memory orders. (Proof: using the definitions
292   //        of sequenced-after, happens-after and inter-thread happens-after
293   //        from https://en.cppreference.com/w/cpp/atomic/memory_order, malloc()
294   //        calls RecordAlloc() so its return is sequenced-after RecordAlloc();
295   //        free() inter-thread happens-after malloc's return because it
296   //        consumes the result; RecordFree() is sequenced-after its caller,
297   //        free(); therefore RecordFree() interthread happens-after
298   //        RecordAlloc().)
299   //    3b. If the program is freeing a random pointer which coincidentally was
300   //        also returned from malloc(), such that free(p) does not happen-after
301   //        malloc(), then there is already an unavoidable race condition. If
302   //        the profiler sees malloc() before free(p), then it will add p to
303   //        sampled_addresses_set() and then remove it; otherwise it will do
304   //        nothing in RecordFree() and add p to sampled_addresses_set() in
305   //        RecordAlloc(), recording a potential leak. Reading
306   //        `profiling_state_` with relaxed ordering adds another possibility:
307   //        if the profiler sees malloc() with kWasStarted and then free without
308   //        kWasStarted, it will add p to sampled_addresses_set() in
309   //        RecordAlloc() and then do nothing in RecordFree(). This has the same
310   //        outcome as the existing race.
311   const ProfilingStateFlagMask state =
312       profiling_state_.load(std::memory_order_relaxed);
313   if (!(state & ProfilingStateFlag::kWasStarted)) [[likely]] {
314     return;
315   }
316 
317   void* const address = free_data.address();
318 
319   if (address == nullptr) [[unlikely]] {
320     return;
321   }
322   if (!sampled_addresses_set().Contains(address)) [[likely]] {
323     return;
324   }
325   if (ScopedMuteThreadSamples::IsMuted()) [[unlikely]] {
326     return;
327   }
328 
329   // Note: ReentryGuard prevents from recursions introduced by malloc and
330   // initialization of thread local storage which happen in the allocation path
331   // only (please see docs of ReentryGuard for full details). Therefore, the
332   // DoNotifyFree doesn't need to be guarded.
333 
334   DoRecordFree(address);
335 }
336 
337 }  // namespace base
338 
339 #endif  // BASE_SAMPLING_HEAP_PROFILER_POISSON_ALLOCATION_SAMPLER_H_
340