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