blob: a5b4e4dee563df2b8de110f340e9109029577953 [file] [log] [blame]
Avi Drissmane4622aa2022-09-08 20:36:061// Copyright 2018 The Chromium Authors
Alexei Filippovd6363e472018-08-27 19:31:392// 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
Joe Mason0119d492023-01-19 06:31:498#include <atomic>
Joe Mason74ce1242025-03-24 16:48:329#include <optional>
Alexei Filippovd6363e472018-08-27 19:31:3910#include <vector>
11
André Kempe69aec282024-01-03 19:28:4512#include "base/allocator/dispatcher/notification_data.h"
André Kempe1f9ea582023-01-25 14:35:0313#include "base/allocator/dispatcher/reentry_guard.h"
André Kempeb99648e2023-01-06 17:21:2014#include "base/allocator/dispatcher/subsystem.h"
Alexei Filippovd6363e472018-08-27 19:31:3915#include "base/base_export.h"
Alexei Filippov02435742019-06-05 04:43:0116#include "base/compiler_specific.h"
Joe Masond9f877d2021-11-30 20:27:2817#include "base/gtest_prod_util.h"
Arthur Sonzognib8e46e92024-05-31 08:01:1918#include "base/memory/raw_ptr_exclusion.h"
Avi Drissmanded77172021-07-02 18:23:0019#include "base/no_destructor.h"
Alexei Filippov02435742019-06-05 04:43:0120#include "base/sampling_heap_profiler/lock_free_address_hash_set.h"
Alexei Filippovd6363e472018-08-27 19:31:3921#include "base/synchronization/lock.h"
Alexei Filippovf071fd42019-09-11 21:37:3422#include "base/thread_annotations.h"
Alexei Filippovd6363e472018-08-27 19:31:3923
24namespace base {
25
Joe Masond9f877d2021-11-30 20:27:2826class SamplingHeapProfilerTest;
27
Joe Masonbc586dc2024-12-11 18:28:5228// Stats about the allocation sampler.
29struct BASE_EXPORT PoissonAllocationSamplerStats {
Joe Masoncf0e2e12024-12-11 22:09:4730 PoissonAllocationSamplerStats(
31 size_t address_cache_hits,
32 size_t address_cache_misses,
33 size_t address_cache_max_size,
34 float address_cache_max_load_factor,
35 std::vector<size_t> address_cache_bucket_lengths);
36 ~PoissonAllocationSamplerStats();
37
38 PoissonAllocationSamplerStats(const PoissonAllocationSamplerStats&);
39 PoissonAllocationSamplerStats& operator=(
40 const PoissonAllocationSamplerStats&);
41
42 size_t address_cache_hits;
43 size_t address_cache_misses;
44 size_t address_cache_max_size;
45 float address_cache_max_load_factor;
46 std::vector<size_t> address_cache_bucket_lengths;
Joe Masonbc586dc2024-12-11 18:28:5247};
48
Alexei Filippovd6363e472018-08-27 19:31:3949// This singleton class implements Poisson sampling of the incoming allocations
50// stream. It hooks onto base::allocator and base::PartitionAlloc.
Alexei Filippovd6363e472018-08-27 19:31:3951// The only control parameter is sampling interval that controls average value
52// of the sampling intervals. The actual intervals between samples are
53// randomized using Poisson distribution to mitigate patterns in the allocation
54// stream.
55// Once accumulated allocation sizes fill up the current sample interval,
56// a sample is generated and sent to the observers via |SampleAdded| call.
57// When the corresponding memory that triggered the sample is freed observers
58// get notified with |SampleRemoved| call.
59//
60class BASE_EXPORT PoissonAllocationSampler {
61 public:
Alexei Filippovd6363e472018-08-27 19:31:3962 class SamplesObserver {
63 public:
64 virtual ~SamplesObserver() = default;
André Kempeb99648e2023-01-06 17:21:2065 virtual void SampleAdded(
66 void* address,
67 size_t size,
68 size_t total,
69 base::allocator::dispatcher::AllocationSubsystem type,
70 const char* context) = 0;
Alexei Filippovd6363e472018-08-27 19:31:3971 virtual void SampleRemoved(void* address) = 0;
72 };
73
Joe Masond9f877d2021-11-30 20:27:2874 // An instance of this class makes the sampler not report samples generated
Alexei Filippov28cc68d2018-09-13 07:58:3675 // within the object scope for the current thread.
76 // It allows observers to allocate/deallocate memory while holding a lock
77 // without a chance to get into reentrancy problems.
Alexei Filippovbc379632018-09-14 22:29:0678 class BASE_EXPORT ScopedMuteThreadSamples {
Alexei Filippov28cc68d2018-09-13 07:58:3679 public:
Alexei Filippovbc379632018-09-14 22:29:0680 ScopedMuteThreadSamples();
81 ~ScopedMuteThreadSamples();
Alexei Filippove48985e2019-02-01 00:27:4182
Joe Masond9f877d2021-11-30 20:27:2883 ScopedMuteThreadSamples(const ScopedMuteThreadSamples&) = delete;
84 ScopedMuteThreadSamples& operator=(const ScopedMuteThreadSamples&) = delete;
85
Alexei Filippove48985e2019-02-01 00:27:4186 static bool IsMuted();
Joe Masonbb9554192025-01-09 19:21:0087
88 private:
89 bool was_muted_ = false;
Alexei Filippov28cc68d2018-09-13 07:58:3690 };
91
Joe Masonf18dd4a2022-05-02 18:41:5192 // An instance of this class makes the sampler behave deterministically to
93 // ensure test results are repeatable. Does not support nesting.
94 class BASE_EXPORT ScopedSuppressRandomnessForTesting {
95 public:
96 ScopedSuppressRandomnessForTesting();
97 ~ScopedSuppressRandomnessForTesting();
98
99 ScopedSuppressRandomnessForTesting(
100 const ScopedSuppressRandomnessForTesting&) = delete;
101 ScopedSuppressRandomnessForTesting& operator=(
102 const ScopedSuppressRandomnessForTesting&) = delete;
103
104 static bool IsSuppressed();
105 };
106
Joe Mason5fb082b42024-05-14 18:34:35107 // An instance of this class makes the sampler only report samples with
108 // AllocatorType kManualForTesting, not those from hooked allocators. This
109 // allows unit tests to set test expectations based on only explicit calls to
110 // RecordAlloc and RecordFree.
111 //
112 // The accumulated bytes on the thread that creates a
113 // ScopedMuteHookedSamplesForTesting will also be reset to 0, and restored
114 // when the object leaves scope. This gives tests a known state to start
115 // recording samples on one thread: a full interval must pass to record a
116 // sample. Other threads will still have a random number of accumulated bytes.
117 //
118 // Only one instance may exist at a time.
119 class BASE_EXPORT ScopedMuteHookedSamplesForTesting {
120 public:
121 ScopedMuteHookedSamplesForTesting();
122 ~ScopedMuteHookedSamplesForTesting();
123
124 // Move-only.
125 ScopedMuteHookedSamplesForTesting(
126 const ScopedMuteHookedSamplesForTesting&) = delete;
127 ScopedMuteHookedSamplesForTesting& operator=(
128 const ScopedMuteHookedSamplesForTesting&) = delete;
129 ScopedMuteHookedSamplesForTesting(ScopedMuteHookedSamplesForTesting&&);
130 ScopedMuteHookedSamplesForTesting& operator=(
131 ScopedMuteHookedSamplesForTesting&&);
132
133 private:
134 intptr_t accumulated_bytes_snapshot_;
135 };
136
Alexei Filippovd6363e472018-08-27 19:31:39137 // Must be called early during the process initialization. It creates and
138 // reserves a TLS slot.
139 static void Init();
140
Alexei Filippovd6363e472018-08-27 19:31:39141 void AddSamplesObserver(SamplesObserver*);
Alexei Filippovf071fd42019-09-11 21:37:34142
143 // Note: After an observer is removed it is still possible to receive
144 // a notification to that observer. This is not a problem currently as
145 // the only client of this interface is the base::SamplingHeapProfiler,
146 // which is a singleton.
147 // If there's a need for this functionality in the future, one might
148 // want to put observers notification loop under a reader-writer lock.
Alexei Filippovd6363e472018-08-27 19:31:39149 void RemoveSamplesObserver(SamplesObserver*);
150
Joe Masonaf0833ca2022-01-28 21:49:48151 // Sets the mean number of bytes that will be allocated before taking a
152 // sample.
153 void SetSamplingInterval(size_t sampling_interval_bytes);
154
155 // Returns the current mean sampling interval, in bytes.
156 size_t SamplingInterval() const;
157
Joe Mason74ce1242025-03-24 16:48:32158 // Sets the max load factor before rebalancing the LockFreeAddressHashSet, or
159 // resets it to the default if `load_factor` is nulloptr.
160 void SetTargetHashSetLoadFactor(std::optional<float> load_factor);
161
Joe Masonbc586dc2024-12-11 18:28:52162 // Returns statistics about the allocation sampler, and resets the running
163 // counts so that each call to this returns only stats about the period
164 // between calls.
165 PoissonAllocationSamplerStats GetAndResetStats();
166
André Kempe543fe172023-04-05 19:40:05167 ALWAYS_INLINE void OnAllocation(
André Kempe69aec282024-01-03 19:28:45168 const base::allocator::dispatcher::AllocationNotificationData&
169 allocation_data);
170 ALWAYS_INLINE void OnFree(
171 const base::allocator::dispatcher::FreeNotificationData& free_data);
André Kempef3959c072023-01-19 19:49:29172
Alexei Filippovd6363e472018-08-27 19:31:39173 static PoissonAllocationSampler* Get();
174
Peter Boström75cd3c02021-09-28 15:23:18175 PoissonAllocationSampler(const PoissonAllocationSampler&) = delete;
176 PoissonAllocationSampler& operator=(const PoissonAllocationSampler&) = delete;
177
Joe Mason5fb082b42024-05-14 18:34:35178 // Returns true if a ScopedMuteHookedSamplesForTesting exists. This can be
179 // read from any thread.
Joe Mason0119d492023-01-19 06:31:49180 static bool AreHookedSamplesMuted() {
181 return profiling_state_.load(std::memory_order_relaxed) &
182 ProfilingStateFlag::kHookedSamplesMutedForTesting;
183 }
Joe Masond9f877d2021-11-30 20:27:28184
Joe Masonbb9554192025-01-09 19:21:00185 // Returns the number of allocated bytes that have been observed.
186 static intptr_t GetAccumulatedBytesForTesting();
187
Alexei Filippovd6363e472018-08-27 19:31:39188 private:
Joe Mason0119d492023-01-19 06:31:49189 // Flags recording the state of the profiler. This does not use enum class so
190 // flags can be used in a bitmask.
191 enum ProfilingStateFlag {
192 // Set if profiling has ever been started in this session of Chrome. Once
193 // this is set, it is never reset. This is used to optimize the common case
194 // where profiling is never used.
195 kWasStarted = 1 << 0,
196 // Set if profiling is currently running. This flag is toggled on and off
197 // as sample observers are added and removed.
198 kIsRunning = 1 << 1,
199 // Set if a ScopedMuteHookedSamplesForTesting object exists.
200 kHookedSamplesMutedForTesting = 1 << 2,
201 };
202 using ProfilingStateFlagMask = int;
203
Alexei Filippovd6363e472018-08-27 19:31:39204 PoissonAllocationSampler();
205 ~PoissonAllocationSampler() = delete;
206
Alexei Filippovd6363e472018-08-27 19:31:39207 static size_t GetNextSampleInterval(size_t base_interval);
Joe Masonea96b4e2022-04-26 16:07:42208
209 // Return the set of sampled addresses. This is only valid to call after
210 // Init().
Alexei Filippovd6363e472018-08-27 19:31:39211 static LockFreeAddressHashSet& sampled_addresses_set();
212
Joe Mason0119d492023-01-19 06:31:49213 // Atomically adds `flag` to `profiling_state_`. DCHECK's if it was already
214 // set. If `flag` is kIsRunning, also sets kWasStarted. Uses
215 // std::memory_order_relaxed semantics and therefore doesn't synchronize the
216 // state of any other memory with future readers. (See the comment in
217 // RecordFree() for why this is safe.)
218 static void SetProfilingStateFlag(ProfilingStateFlag flag);
219
220 // Atomically removes `flag` from `profiling_state_`. DCHECK's if it was not
221 // already set. Uses std::memory_order_relaxed semantics and therefore doesn't
222 // synchronize the state of any other memory with future readers. (See the
223 // comment in RecordFree() for why this is safe.)
224 static void ResetProfilingStateFlag(ProfilingStateFlag flag);
225
André Kempef3959c072023-01-19 19:49:29226 void DoRecordAllocation(const ProfilingStateFlagMask state,
227 void* address,
228 size_t size,
229 base::allocator::dispatcher::AllocationSubsystem type,
230 const char* context);
Alexei Filippovd6363e472018-08-27 19:31:39231 void DoRecordFree(void* address);
232
Joe Mason74ce1242025-03-24 16:48:32233 void BalanceAddressesHashSet() EXCLUSIVE_LOCKS_REQUIRED(mutex_);
Alexei Filippovd6363e472018-08-27 19:31:39234
Alexei Filippovd6363e472018-08-27 19:31:39235 Lock mutex_;
Joe Mason0119d492023-01-19 06:31:49236
Alexei Filippovf071fd42019-09-11 21:37:34237 // The |observers_| list is guarded by |mutex_|, however a copy of it
238 // is made before invoking the observers (to avoid performing expensive
239 // operations under the lock) as such the SamplesObservers themselves need
240 // to be thread-safe and support being invoked racily after
241 // RemoveSamplesObserver().
Arthur Sonzognib8e46e92024-05-31 08:01:19242 //
243 // This class handles allocation, so it must never use raw_ptr<T>. In
244 // particular, raw_ptr<T> with `enable_backup_ref_ptr_instance_tracer`
245 // developer option allocates memory, which would cause reentrancy issues:
246 // allocating memory while allocating memory.
247 // More details in https://crbug.com/340815319
248 RAW_PTR_EXCLUSION std::vector<SamplesObserver*> observers_ GUARDED_BY(mutex_);
Alexei Filippovd6363e472018-08-27 19:31:39249
Joe Mason0119d492023-01-19 06:31:49250 // Fast, thread-safe access to the current profiling state.
251 static std::atomic<ProfilingStateFlagMask> profiling_state_;
252
Joe Masonbc586dc2024-12-11 18:28:52253 // Running counts for PoissonAllocationSamplerStats. These are all atomic or
254 // mutex-guarded because they're updated from multiple threads. The atomics
255 // can always be accessed using std::memory_order_relaxed since each value is
256 // separately recorded in UMA and no other memory accesses depend on it. Some
257 // values are correlated (eg. `address_cache_hits_` and
258 // `address_cache_misses_`), and this might see a write to one but not the
259 // other, but this shouldn't cause enough errors in the aggregated UMA metrics
260 // to be worth adding overhead to avoid it.
261 std::atomic<size_t> address_cache_hits_;
262 std::atomic<size_t> address_cache_misses_;
263 size_t address_cache_max_size_ GUARDED_BY(mutex_) = 0;
Joe Mason74ce1242025-03-24 16:48:32264 // The max load factor that's observed in sampled_addresses_set().
Joe Masonbc586dc2024-12-11 18:28:52265 float address_cache_max_load_factor_ GUARDED_BY(mutex_) = 0;
266
Joe Mason74ce1242025-03-24 16:48:32267 // The load factor that will trigger rebalancing in sampled_addresses_set().
268 // By definition `address_cache_max_load_factor_` will never exceed this.
269 float address_cache_target_load_factor_ GUARDED_BY(mutex_) = 1.0;
270
Alexei Filippovd6363e472018-08-27 19:31:39271 friend class NoDestructor<PoissonAllocationSampler>;
Joe Mason0119d492023-01-19 06:31:49272 friend class PoissonAllocationSamplerStateTest;
Alexei Filippov678cb252018-09-18 01:11:15273 friend class SamplingHeapProfilerTest;
Joe Masonea96b4e2022-04-26 16:07:42274 FRIEND_TEST_ALL_PREFIXES(PoissonAllocationSamplerTest, MuteHooksWithoutInit);
Joe Mason74ce1242025-03-24 16:48:32275 FRIEND_TEST_ALL_PREFIXES(PoissonAllocationSamplerLoadFactorTest,
276 BalanceSampledAddressesSet);
Joe Masond9f877d2021-11-30 20:27:28277 FRIEND_TEST_ALL_PREFIXES(SamplingHeapProfilerTest, HookedAllocatorMuted);
Alexei Filippovd6363e472018-08-27 19:31:39278};
279
André Kempe543fe172023-04-05 19:40:05280ALWAYS_INLINE void PoissonAllocationSampler::OnAllocation(
André Kempe69aec282024-01-03 19:28:45281 const base::allocator::dispatcher::AllocationNotificationData&
282 allocation_data) {
André Kempef3959c072023-01-19 19:49:29283 // The allocation hooks may be installed before the sampler is started. Check
284 // if its ever been started first to avoid extra work on the fast path,
285 // because it's the most common case.
286 const ProfilingStateFlagMask state =
287 profiling_state_.load(std::memory_order_relaxed);
Peter Kastingfa488992024-08-06 07:48:14288 if (!(state & ProfilingStateFlag::kWasStarted)) [[likely]] {
André Kempef3959c072023-01-19 19:49:29289 return;
290 }
291
André Kempe69aec282024-01-03 19:28:45292 const auto type = allocation_data.allocation_subsystem();
293
André Kempef3959c072023-01-19 19:49:29294 // When sampling is muted for testing, only handle manual calls to
295 // RecordAlloc. (This doesn't need to be checked in RecordFree because muted
296 // allocations won't be added to sampled_addresses_set(), so RecordFree
297 // already skips them.)
Peter Kastingfa488992024-08-06 07:48:14298 if ((state & ProfilingStateFlag::kHookedSamplesMutedForTesting) &&
299 type !=
300 base::allocator::dispatcher::AllocationSubsystem::kManualForTesting)
301 [[unlikely]] {
André Kempef3959c072023-01-19 19:49:29302 return;
303 }
304
André Kempe1f9ea582023-01-25 14:35:03305 // Note: ReentryGuard prevents from recursions introduced by malloc and
306 // initialization of thread local storage which happen in the allocation path
307 // only (please see docs of ReentryGuard for full details).
308 allocator::dispatcher::ReentryGuard reentry_guard;
309
Peter Kastingfa488992024-08-06 07:48:14310 if (!reentry_guard) [[unlikely]] {
André Kempe1f9ea582023-01-25 14:35:03311 return;
312 }
313
André Kempe69aec282024-01-03 19:28:45314 DoRecordAllocation(state, allocation_data.address(), allocation_data.size(),
315 type, allocation_data.type_name());
André Kempef3959c072023-01-19 19:49:29316}
317
André Kempe69aec282024-01-03 19:28:45318ALWAYS_INLINE void PoissonAllocationSampler::OnFree(
319 const base::allocator::dispatcher::FreeNotificationData& free_data) {
Joe Mason0119d492023-01-19 06:31:49320 // The allocation hooks may be installed before the sampler is started. Check
321 // if its ever been started first to avoid extra work on the fast path,
322 // because it's the most common case. Note that DoRecordFree still needs to be
323 // called if the sampler was started but is now stopped, to track allocations
324 // that were recorded while the sampler was still running.
325 //
326 // Relaxed ordering is safe here because there's only one case where
327 // RecordAlloc and RecordFree MUST see the same value of `profiling_state_`.
328 // Assume thread A updates `profiling_state_` from 0 to kWasStarted |
329 // kIsRunning, thread B calls RecordAlloc, and thread C calls RecordFree.
330 // (Something else could update `profiling_state_` to remove kIsRunning before
331 // RecordAlloc or RecordFree.)
332 //
333 // 1. If RecordAlloc(p) sees !kWasStarted or !kIsRunning it will return
334 // immediately, so p won't be in sampled_address_set(). So no matter what
335 // RecordFree(p) sees it will also return immediately.
336 //
337 // 2. If RecordFree() is called with a pointer that was never passed to
338 // RecordAlloc(), again it will return immediately no matter what it sees.
339 //
340 // 3. If RecordAlloc(p) sees kIsRunning it will put p in
341 // sampled_address_set(). In this case RecordFree(p) MUST see !kWasStarted
342 // or it will return without removing p:
343 //
344 // 3a. If the program got p as the return value from malloc() and passed it
345 // to free(), then RecordFree() happens-after RecordAlloc() and
346 // therefore will see the same value of `profiling_state_` as
347 // RecordAlloc() for all memory orders. (Proof: using the definitions
348 // of sequenced-after, happens-after and inter-thread happens-after
349 // from https://en.cppreference.com/w/cpp/atomic/memory_order, malloc()
350 // calls RecordAlloc() so its return is sequenced-after RecordAlloc();
351 // free() inter-thread happens-after malloc's return because it
352 // consumes the result; RecordFree() is sequenced-after its caller,
353 // free(); therefore RecordFree() interthread happens-after
354 // RecordAlloc().)
355 // 3b. If the program is freeing a random pointer which coincidentally was
356 // also returned from malloc(), such that free(p) does not happen-after
357 // malloc(), then there is already an unavoidable race condition. If
358 // the profiler sees malloc() before free(p), then it will add p to
359 // sampled_addresses_set() and then remove it; otherwise it will do
360 // nothing in RecordFree() and add p to sampled_addresses_set() in
361 // RecordAlloc(), recording a potential leak. Reading
362 // `profiling_state_` with relaxed ordering adds another possibility:
363 // if the profiler sees malloc() with kWasStarted and then free without
364 // kWasStarted, it will add p to sampled_addresses_set() in
365 // RecordAlloc() and then do nothing in RecordFree(). This has the same
366 // outcome as the existing race.
367 const ProfilingStateFlagMask state =
368 profiling_state_.load(std::memory_order_relaxed);
Peter Kastingfa488992024-08-06 07:48:14369 if (!(state & ProfilingStateFlag::kWasStarted)) [[likely]] {
Alexei Filippov02435742019-06-05 04:43:01370 return;
Joe Mason0119d492023-01-19 06:31:49371 }
André Kempe69aec282024-01-03 19:28:45372
373 void* const address = free_data.address();
374
Peter Kastingfa488992024-08-06 07:48:14375 if (address == nullptr) [[unlikely]] {
Joe Mason0119d492023-01-19 06:31:49376 return;
377 }
Peter Kastingfa488992024-08-06 07:48:14378 if (!sampled_addresses_set().Contains(address)) [[likely]] {
Joe Masonbc586dc2024-12-11 18:28:52379 address_cache_misses_.fetch_add(1, std::memory_order_relaxed);
André Kempef3959c072023-01-19 19:49:29380 return;
Joe Mason0119d492023-01-19 06:31:49381 }
Joe Masonbc586dc2024-12-11 18:28:52382 address_cache_hits_.fetch_add(1, std::memory_order_relaxed);
Peter Kastingfa488992024-08-06 07:48:14383 if (ScopedMuteThreadSamples::IsMuted()) [[unlikely]] {
André Kempef3959c072023-01-19 19:49:29384 return;
385 }
386
André Kempe1f9ea582023-01-25 14:35:03387 // Note: ReentryGuard prevents from recursions introduced by malloc and
388 // initialization of thread local storage which happen in the allocation path
389 // only (please see docs of ReentryGuard for full details). Therefore, the
390 // DoNotifyFree doesn't need to be guarded.
391
André Kempef3959c072023-01-19 19:49:29392 DoRecordFree(address);
Alexei Filippov02435742019-06-05 04:43:01393}
394
Alexei Filippovd6363e472018-08-27 19:31:39395} // namespace base
396
397#endif // BASE_SAMPLING_HEAP_PROFILER_POISSON_ALLOCATION_SAMPLER_H_