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