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