Skip to content

Commit 8264095

Browse files
committed
Support samply-markers on macOS
This commit ensures that the `unix.rs` file correctly handles emitting markers on macOS. I hadn't realized in previous commits that the functionality was only working on Linux. It should work on both systems now.
1 parent 838ebbe commit 8264095

File tree

4 files changed

+122
-21
lines changed

4 files changed

+122
-21
lines changed

Cargo.lock

Lines changed: 12 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

samply-markers/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ nix = { version = "0.30.1", features = ["mman", "process", "time"], optional = t
2424
smallstr = { version = "0.3.1", optional = true }
2525
tempfile = { version = "3.23.0", optional = true }
2626

27+
[target.'cfg(target_os = "macos")'.dependencies]
28+
libc = "0.2"
29+
mach2 = "0.4"
30+
2731
[target.'cfg(unix)'.dev-dependencies]
2832
nix = { version = "0.30.1", features = ["process"] }
2933
regex = "1.12.2"

samply-markers/src/provider/unix.rs

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use std::num::NonZeroUsize;
77
use std::path::Path;
88
use std::path::PathBuf;
99
use std::sync::LazyLock;
10+
#[cfg(target_os = "macos")]
11+
use std::sync::OnceLock;
1012

1113
use crate::marker::SamplyMarker;
1214
use crate::marker::SamplyTimestamp;
@@ -15,7 +17,9 @@ use crate::provider::WriteMarkerProvider;
1517

1618
use nix::sys::mman::MapFlags;
1719
use nix::sys::mman::ProtFlags;
20+
#[cfg(target_os = "linux")]
1821
use nix::time::ClockId;
22+
#[cfg(target_os = "linux")]
1923
use nix::time::clock_gettime;
2024

2125
use 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.
3946
pub struct TimestampNowImpl;
4047

4148
impl 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.
87153
fn 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()

samply-markers/tests/unix.rs

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,26 @@ use std::thread;
99
use regex::Regex;
1010
use samply_markers::prelude::*;
1111

12+
/// Returns a unique thread identifier for the current thread.
13+
#[cfg(target_os = "linux")]
14+
fn get_thread_id() -> u32 {
15+
nix::unistd::gettid().as_raw() as u32
16+
}
17+
18+
/// Returns a unique thread identifier for the current thread.
19+
#[cfg(target_os = "macos")]
20+
fn get_thread_id() -> u32 {
21+
// Use pthread_threadid_np() to get the thread ID, same as samply does.
22+
// This returns a u64 but we cast to u32 to match samply's behavior and
23+
// ensure the thread ID fits in the marker filename format.
24+
// See samply/src/mac/thread_profiler.rs:228
25+
unsafe {
26+
let mut tid: u64 = 0;
27+
libc::pthread_threadid_np(libc::pthread_self(), &mut tid);
28+
tid as u32
29+
}
30+
}
31+
1232
/// Asserts that the marker file for the given PID/TID matches the expected regex pattern(s).
1333
macro_rules! assert_marker_file {
1434
// Multiple regex patterns.
@@ -43,7 +63,7 @@ macro_rules! assert_marker_file {
4363
}
4464

4565
/// Helper function to find marker files for a given PID/TID in the system temp directory
46-
fn find_marker_file(pid: nix::unistd::Pid, tid: nix::unistd::Pid) -> Option<std::path::PathBuf> {
66+
fn find_marker_file(pid: u32, tid: u32) -> Option<std::path::PathBuf> {
4767
let temp_dir = env::temp_dir();
4868
let pattern = format!("marker-{pid}-{tid}.txt");
4969

@@ -73,8 +93,8 @@ fn instant_writes_marker_to_file() {
7393
samply_marker!({ name: "InstantMarker2" });
7494
samply_marker!({ name: "InstantMarker3" });
7595

76-
let pid = nix::unistd::getpid();
77-
let tid = nix::unistd::gettid();
96+
let pid = nix::unistd::getpid().as_raw() as u32;
97+
let tid = get_thread_id();
7898

7999
let contents = assert_marker_file!(
80100
pid,
@@ -112,8 +132,8 @@ fn interval_writes_marker_to_file() {
112132
thread::sleep(std::time::Duration::from_millis(4));
113133
}
114134

115-
let pid = nix::unistd::getpid();
116-
let tid = nix::unistd::gettid();
135+
let pid = nix::unistd::getpid().as_raw() as u32;
136+
let tid = get_thread_id();
117137

118138
let contents = assert_marker_file!(
119139
pid,
@@ -170,8 +190,8 @@ fn measure_writes_marker_to_file() {
170190
"Expected measure to preserve return value."
171191
);
172192

173-
let pid = nix::unistd::getpid();
174-
let tid = nix::unistd::gettid();
193+
let pid = nix::unistd::getpid().as_raw() as u32;
194+
let tid = get_thread_id();
175195

176196
let contents = assert_marker_file!(
177197
pid,
@@ -204,7 +224,7 @@ fn multiple_threads_create_separate_files() {
204224
samply_marker!({ name: format!("thread {i} marker C") });
205225
thread::sleep(std::time::Duration::from_millis(10));
206226

207-
nix::unistd::gettid()
227+
get_thread_id()
208228
})
209229
})
210230
.collect::<Vec<_>>();
@@ -214,7 +234,7 @@ fn multiple_threads_create_separate_files() {
214234
.map(|h| h.join().expect("thread panicked"))
215235
.collect::<Vec<_>>();
216236

217-
let pid = nix::unistd::getpid();
237+
let pid = nix::unistd::getpid().as_raw() as u32;
218238

219239
for tid in tids {
220240
assert_marker_file!(

0 commit comments

Comments
 (0)