11mod backtrace;
22mod interrupts;
3+ mod live_heap;
34mod sample_type_filter;
45pub mod stack_walking;
56mod thread_utils;
@@ -30,20 +31,18 @@ use core::mem::forget;
3031use core:: { ptr, str} ;
3132use cpu_time:: ThreadTime ;
3233use crossbeam_channel:: { Receiver , Sender , TrySendError } ;
33- use dashmap:: DashMap ;
3434use libdd_common:: tag:: Tag ;
3535use libdd_profiling:: api:: {
3636 Function , Label as ApiLabel , Location , Period , Sample , SampleType as ApiSampleType ,
3737 UpscalingInfo , ValueType as ApiValueType ,
3838} ;
3939use libdd_profiling:: internal:: Profile as InternalProfile ;
4040use log:: { debug, info, trace, warn} ;
41- use rustc_hash:: FxBuildHasher ;
4241use std:: borrow:: Cow ;
4342use std:: collections:: HashMap ;
4443use std:: hash:: Hash ;
4544use std:: num:: NonZeroI64 ;
46- use std:: sync:: atomic:: { AtomicBool , AtomicPtr , AtomicU64 , AtomicUsize , Ordering } ;
45+ use std:: sync:: atomic:: { AtomicBool , AtomicPtr , AtomicU64 , Ordering } ;
4746use std:: sync:: { Arc , Barrier , OnceLock } ;
4847use std:: thread:: JoinHandle ;
4948use std:: time:: { Duration , Instant , SystemTime , UNIX_EPOCH } ;
@@ -67,13 +66,6 @@ pub const NO_TIMESTAMP: i64 = 0;
6766// magnitude for the capacity.
6867const UPLOAD_CHANNEL_CAPACITY : usize = 8 ;
6968
70- /// HeapTracker uses FxHasher (rustc-hash) instead of the default SipHash.
71- /// FxHasher's multiply-rotate mix fully avalanches bits, spreading sequential
72- /// ZendMM bump-allocator addresses evenly across DashMap's 16 shards and
73- /// avoiding lock hot-spots under concurrent ZTS workloads.
74- /// FxBuildHasher satisfies Clone, which DashMap requires.
75- type HeapTracker = DashMap < usize , LiveHeapSample , FxBuildHasher > ;
76-
7769/// The global profiler. Profiler gets made during the first rinit after an
7870/// minit, and is destroyed on mshutdown.
7971static mut PROFILER : OnceLock < Profiler > = OnceLock :: new ( ) ;
@@ -273,9 +265,7 @@ pub(crate) struct LiveHeapSample {
273265 pub allocation_size : i64 ,
274266}
275267
276- /// Maximum number of allocations to track for live heap profiling.
277- /// This bounds memory usage. When full, new allocations are not tracked.
278- pub ( crate ) const LIVE_HEAP_TRACKER_MAX_SIZE : usize = 4096 ;
268+ use live_heap:: LiveHeapTracker ;
279269
280270pub struct Profiler {
281271 fork_barrier : Arc < Barrier > ,
@@ -292,14 +282,8 @@ pub struct Profiler {
292282 /// through this pointer.
293283 system_settings : AtomicPtr < SystemSettings > ,
294284
295- /// Tracks sampled allocations for live heap profiling.
296- /// Maps allocation pointer -> sample data for batched emission at export time.
297- /// Wrapped in Arc to share with TimeCollector for batched sample emission.
298- /// Uses a fast pointer hasher since addresses are already well-distributed.
299- live_heap_tracker : Arc < HeapTracker > ,
300- /// Cached entry count for live_heap_tracker. A single Relaxed load replaces
301- /// the 16 shard read-locks that DashMap::len() acquires per sampled allocation.
302- live_heap_tracker_count : Arc < AtomicUsize > ,
285+ /// Shared with TimeCollector for batched heap-live sample emission.
286+ live_heap_tracker : Arc < LiveHeapTracker < LiveHeapSample > > ,
303287}
304288
305289struct TimeCollector {
@@ -309,9 +293,7 @@ struct TimeCollector {
309293 upload_sender : Sender < UploadMessage > ,
310294 upload_period : Duration ,
311295 /// Shared tracker for batched heap-live sample emission at export time.
312- live_heap_tracker : Arc < HeapTracker > ,
313- /// See Profiler::live_heap_tracker_count.
314- live_heap_tracker_count : Arc < AtomicUsize > ,
296+ live_heap_tracker : Arc < LiveHeapTracker < LiveHeapSample > > ,
315297 /// Used to build correctly-positioned sample_values for heap-live samples
316298 /// without duplicating the type-string → index mapping.
317299 sample_types_filter : SampleTypeFilter ,
@@ -325,7 +307,7 @@ impl TimeCollector {
325307 profiles : & mut HashMap < Arc < ProfileIndex > , InternalProfile > ,
326308 started_at : & WallTime ,
327309 ) {
328- let tracker_len = self . live_heap_tracker_count . load ( Ordering :: Relaxed ) ;
310+ let tracker_len = self . live_heap_tracker . len ( ) ;
329311 if tracker_len == 0 {
330312 return ;
331313 }
@@ -336,11 +318,7 @@ impl TimeCollector {
336318 // for the duration of the Arc::clone calls, not for handle_sample_message.
337319 // This prevents concurrent efree calls on PHP threads from stalling on
338320 // shards that the TimeCollector is reading during a full export iteration.
339- let snapshot: Vec < LiveHeapSample > = self
340- . live_heap_tracker
341- . iter ( )
342- . map ( |entry| entry. value ( ) . clone ( ) )
343- . collect ( ) ;
321+ let snapshot = self . live_heap_tracker . snapshot ( ) ;
344322
345323 for tracked in snapshot {
346324 let sample_values = self . sample_types_filter . filter ( SampleValues {
@@ -837,8 +815,7 @@ impl Profiler {
837815 let interrupt_manager = Arc :: new ( InterruptManager :: new ( ) ) ;
838816 let ( message_sender, message_receiver) = crossbeam_channel:: bounded ( 100 ) ;
839817 let ( upload_sender, upload_receiver) = crossbeam_channel:: bounded ( UPLOAD_CHANNEL_CAPACITY ) ;
840- let live_heap_tracker = Arc :: new ( DashMap :: with_hasher ( FxBuildHasher ) ) ;
841- let live_heap_tracker_count = Arc :: new ( AtomicUsize :: new ( 0 ) ) ;
818+ let live_heap_tracker = Arc :: new ( LiveHeapTracker :: new ( ) ) ;
842819 let sample_types_filter = SampleTypeFilter :: new ( system_settings) ;
843820 let time_collector = TimeCollector {
844821 fork_barrier : fork_barrier. clone ( ) ,
@@ -847,7 +824,6 @@ impl Profiler {
847824 upload_sender : upload_sender. clone ( ) ,
848825 upload_period : UPLOAD_PERIOD ,
849826 live_heap_tracker : live_heap_tracker. clone ( ) ,
850- live_heap_tracker_count : live_heap_tracker_count. clone ( ) ,
851827 sample_types_filter : sample_types_filter. clone ( ) ,
852828 } ;
853829
@@ -888,7 +864,6 @@ impl Profiler {
888864 sample_types_filter,
889865 system_settings : AtomicPtr :: new ( system_settings as * const _ as * mut _ ) ,
890866 live_heap_tracker,
891- live_heap_tracker_count,
892867 }
893868 }
894869
@@ -945,31 +920,12 @@ impl Profiler {
945920 /// Track an allocation for live heap profiling.
946921 /// Returns true if tracked, false if tracking is disabled or limit reached.
947922 pub ( crate ) fn track_allocation ( & self , ptr : usize , sample : LiveHeapSample ) -> bool {
948- // Best-effort cap: in ZTS the count check and insert still race, so
949- // the map can briefly exceed LIVE_HEAP_TRACKER_MAX_SIZE. A single
950- // Relaxed load is equivalent correctness-wise to the former
951- // DashMap::len() but avoids 16 shard read-locks per sampled alloc.
952- if self . live_heap_tracker_count . load ( Ordering :: Relaxed ) >= LIVE_HEAP_TRACKER_MAX_SIZE {
953- return false ;
954- }
955-
956- let old = self . live_heap_tracker . insert ( ptr, sample) ;
957- if old. is_none ( ) {
958- self . live_heap_tracker_count . fetch_add ( 1 , Ordering :: Relaxed ) ;
959- }
960- true
923+ self . live_heap_tracker . track ( ptr, sample)
961924 }
962925
963926 /// Untrack an allocation. Returns the sample if it was tracked.
964927 pub ( crate ) fn untrack_allocation ( & self , ptr : usize ) -> Option < LiveHeapSample > {
965- let result = self
966- . live_heap_tracker
967- . remove ( & ptr)
968- . map ( |( _, sample) | sample) ;
969- if result. is_some ( ) {
970- self . live_heap_tracker_count . fetch_sub ( 1 , Ordering :: Relaxed ) ;
971- }
972- result
928+ self . live_heap_tracker . untrack ( ptr)
973929 }
974930
975931 pub fn send_local_root_span_resource (
@@ -1087,7 +1043,6 @@ impl Profiler {
10871043
10881044 // Clear live heap tracker to avoid stale entries from parent process
10891045 profiler. live_heap_tracker . clear ( ) ;
1090- profiler. live_heap_tracker_count . store ( 0 , Ordering :: Relaxed ) ;
10911046
10921047 // But we're not 100% sure everything is safe to drop, notably the
10931048 // join handles, so we leak the rest.
0 commit comments