@@ -7,6 +7,8 @@ use std::num::NonZeroUsize;
77use std:: path:: Path ;
88use std:: path:: PathBuf ;
99use std:: sync:: LazyLock ;
10+ #[ cfg( target_os = "macos" ) ]
11+ use std:: sync:: OnceLock ;
1012
1113use crate :: marker:: SamplyMarker ;
1214use crate :: marker:: SamplyTimestamp ;
@@ -15,7 +17,9 @@ use crate::provider::WriteMarkerProvider;
1517
1618use nix:: sys:: mman:: MapFlags ;
1719use nix:: sys:: mman:: ProtFlags ;
20+ #[ cfg( target_os = "linux" ) ]
1821use nix:: time:: ClockId ;
22+ #[ cfg( target_os = "linux" ) ]
1923use nix:: time:: clock_gettime;
2024
2125use smallstr:: SmallString ;
@@ -35,19 +39,47 @@ thread_local! {
3539 static MARKER_FILE : RefCell <Option <File >> = RefCell :: new( create_marker_file( ) ) ;
3640}
3741
38- /// A monotonic nanosecond timestamp backed by [`ClockId::CLOCK_MONOTONIC`].
42+ #[ cfg( target_os = "macos" ) ]
43+ static NANOS_PER_TICK : OnceLock < mach2:: mach_time:: mach_timebase_info > = OnceLock :: new ( ) ;
44+
45+ /// A monotonic nanosecond timestamp implementation.
3946pub struct TimestampNowImpl ;
4047
4148impl TimestampNowProvider for TimestampNowImpl {
42- /// Queries `clock_gettime` and converts the result to monotonic nanoseconds.
49+ /// Queries the monotonic clock and converts the result to monotonic nanoseconds.
4350 fn now ( ) -> SamplyTimestamp {
44- let now = clock_gettime ( ClockId :: CLOCK_MONOTONIC ) . unwrap ( ) ;
45-
46- // Monotonic nanoseconds should only ever be positive.
47- #[ allow( clippy:: cast_sign_loss) ]
48- SamplyTimestamp :: from_monotonic_nanos (
49- now. tv_sec ( ) as u64 * 1_000_000_000 + now. tv_nsec ( ) as u64 ,
50- )
51+ #[ cfg( target_os = "linux" ) ]
52+ {
53+ let now = clock_gettime ( ClockId :: CLOCK_MONOTONIC ) . unwrap ( ) ;
54+
55+ // Monotonic nanoseconds should only ever be positive.
56+ #[ allow( clippy:: cast_sign_loss) ]
57+ SamplyTimestamp :: from_monotonic_nanos (
58+ now. tv_sec ( ) as u64 * 1_000_000_000 + now. tv_nsec ( ) as u64 ,
59+ )
60+ }
61+
62+ // Use mach_absolute_time() to match samply's clock source on macOS.
63+ // See https://github.com/mstange/samply/blob/2041b956f650bb92d912990052967d03fef66b75/samply/src/mac/time.rs#L8-L20
64+ #[ cfg( target_os = "macos" ) ]
65+ {
66+ use mach2:: mach_time;
67+
68+ let nanos_per_tick = NANOS_PER_TICK . get_or_init ( || {
69+ let mut info = mach_time:: mach_timebase_info:: default ( ) ;
70+ let errno = unsafe { mach_time:: mach_timebase_info ( & raw mut info) } ;
71+ if errno != 0 || info. denom == 0 {
72+ info. numer = 1 ;
73+ info. denom = 1 ;
74+ }
75+ info
76+ } ) ;
77+
78+ let time = unsafe { mach_time:: mach_absolute_time ( ) } ;
79+ let nanos = time * u64:: from ( nanos_per_tick. numer ) / u64:: from ( nanos_per_tick. denom ) ;
80+
81+ SamplyTimestamp :: from_monotonic_nanos ( nanos)
82+ }
5183 }
5284}
5385
@@ -83,12 +115,46 @@ where
83115 MARKER_FILE . with_borrow_mut ( |file| file. as_mut ( ) . map ( f) )
84116}
85117
118+ /// Returns a unique thread identifier for the current thread.
119+ #[ cfg( target_os = "linux" ) ]
120+ fn get_thread_id ( ) -> u32 {
121+ // Thread IDs on Linux are always positive, so the cast from i32 to u32 is safe.
122+ #[ allow( clippy:: cast_sign_loss) ]
123+ let tid = nix:: unistd:: gettid ( ) . as_raw ( ) as u32 ;
124+
125+ tid
126+ }
127+
128+ /// Returns a unique thread identifier for the current thread.
129+ #[ cfg( target_os = "macos" ) ]
130+ fn get_thread_id ( ) -> u32 {
131+ // Use pthread_threadid_np() to get the current thread's system thread ID.
132+ //
133+ // This is the simplest way to get our own thread ID. Samply uses thread_info()
134+ // instead because it's extracting thread IDs from other processes via mach ports.
135+ //
136+ // Both approaches return the same underlying system thread ID value.
137+ //
138+ // See https://github.com/mstange/samply/blob/2041b956f650bb92d912990052967d03fef66b75/samply/src/mac/thread_profiler.rs#L209-L229
139+ let mut tid: u64 = 0 ;
140+
141+ unsafe {
142+ libc:: pthread_threadid_np ( libc:: pthread_self ( ) , & raw mut tid) ;
143+ }
144+
145+ // Truncate to u32 to match samply's behavior.
146+ #[ allow( clippy:: cast_possible_truncation) ]
147+ let tid = tid as u32 ;
148+
149+ tid
150+ }
151+
86152/// Creates a new mmapped marker file for the current thread.
87153fn create_marker_file ( ) -> Option < File > {
88154 let file_name = markers_dir ( ) ?. join ( format ! (
89155 "marker-{}-{}.txt" ,
90- nix:: unistd:: getpid( ) ,
91- nix :: unistd :: gettid ( )
156+ nix:: unistd:: getpid( ) . as_raw ( ) ,
157+ get_thread_id ( )
92158 ) ) ;
93159
94160 let file = File :: options ( )
0 commit comments