1#[cfg(feature = "fd-track")]
2use std::sync::atomic::{AtomicUsize, Ordering};
3
4#[cfg(feature = "fd-track")]
5use tracing::debug;
6
7#[cfg(feature = "fd-track")]
8static BASELINE_FD_COUNT: AtomicUsize = AtomicUsize::new(0);
9#[cfg(feature = "fd-track")]
10static PEAK_FD_COUNT: AtomicUsize = AtomicUsize::new(0);
11
12#[cfg(any(target_os = "macos", target_os = "linux"))]
13const PROC_FD_PATH: &str = if cfg!(target_os = "macos") {
14 "/dev/fd"
15} else {
16 "/proc/self/fd"
17};
18
19#[cfg(any(target_os = "macos", target_os = "linux"))]
21pub fn count_open_fds() -> usize {
22 std::fs::read_dir(PROC_FD_PATH).map(|d| d.count()).unwrap_or(0)
23}
24
25#[cfg(not(any(target_os = "macos", target_os = "linux")))]
27pub fn count_open_fds() -> usize {
28 0
29}
30
31#[cfg(feature = "fd-track")]
32fn baseline_or_init() -> usize {
33 let baseline = BASELINE_FD_COUNT.load(Ordering::Relaxed);
34 if baseline == 0 {
35 set_fd_baseline();
36 BASELINE_FD_COUNT.load(Ordering::Relaxed)
37 } else {
38 baseline
39 }
40}
41
42#[cfg(feature = "fd-track")]
43fn update_peak(current: usize) {
44 let mut observed = PEAK_FD_COUNT.load(Ordering::Relaxed);
45 while current > observed
46 && PEAK_FD_COUNT
47 .compare_exchange_weak(observed, current, Ordering::Relaxed, Ordering::Relaxed)
48 .is_err()
49 {
50 observed = PEAK_FD_COUNT.load(Ordering::Relaxed);
51 }
52}
53
54pub fn set_fd_baseline() {
57 #[cfg(feature = "fd-track")]
58 {
59 let count = count_open_fds();
60 BASELINE_FD_COUNT.store(count, Ordering::Relaxed);
61 PEAK_FD_COUNT.store(count, Ordering::Relaxed);
62 debug!(
63 target: "xet_runtime::fd_track",
64 fd_count = count,
65 "FD baseline initialized"
66 );
67 }
68}
69
70pub fn fds_since_baseline() -> usize {
73 #[cfg(feature = "fd-track")]
74 {
75 let baseline = baseline_or_init();
76 count_open_fds().saturating_sub(baseline)
77 }
78 #[cfg(not(feature = "fd-track"))]
79 {
80 0
81 }
82}
83
84pub fn report_fd_count(label: &str) {
86 #[cfg(feature = "fd-track")]
87 {
88 let count = count_open_fds();
89 let baseline = baseline_or_init();
90 update_peak(count);
91 debug!(
92 target: "xet_runtime::fd_track",
93 label,
94 fd_count = count,
95 baseline,
96 delta = count as isize - baseline as isize,
97 opened_since_baseline = fds_since_baseline(),
98 peak = PEAK_FD_COUNT.load(Ordering::Relaxed),
99 "FD snapshot"
100 );
101 }
102 #[cfg(not(feature = "fd-track"))]
103 {
104 let _ = label;
105 }
106}
107
108#[derive(Debug, Default)]
111pub struct FdTrackGuard {
112 #[cfg(feature = "fd-track")]
113 label: String,
114 #[cfg(feature = "fd-track")]
115 start_count: usize,
116 #[cfg(all(feature = "fd-track", unix))]
117 start_snapshot: Vec<(i32, String)>,
118}
119
120pub fn track_fd_scope(label: impl Into<String>) -> FdTrackGuard {
121 #[cfg(feature = "fd-track")]
122 {
123 let label = label.into();
124 let start_count = count_open_fds();
125 let baseline = baseline_or_init();
126 update_peak(start_count);
127 debug!(
128 target: "xet_runtime::fd_track",
129 label,
130 start_fd = start_count,
131 baseline,
132 delta = start_count as isize - baseline as isize,
133 "FD scope start"
134 );
135 FdTrackGuard {
136 label,
137 start_count,
138 #[cfg(unix)]
139 start_snapshot: list_open_fds(),
140 }
141 }
142 #[cfg(not(feature = "fd-track"))]
143 {
144 let _ = label;
145 FdTrackGuard::default()
146 }
147}
148
149impl Drop for FdTrackGuard {
150 fn drop(&mut self) {
151 #[cfg(feature = "fd-track")]
152 {
153 let end_count = count_open_fds();
154 let baseline = baseline_or_init();
155 update_peak(end_count);
156 let change = end_count as isize - self.start_count as isize;
157 debug!(
158 target: "xet_runtime::fd_track",
159 label = self.label,
160 start_fd = self.start_count,
161 end_fd = end_count,
162 change,
163 baseline,
164 baseline_delta = end_count as isize - baseline as isize,
165 opened_since_baseline = fds_since_baseline(),
166 peak = PEAK_FD_COUNT.load(Ordering::Relaxed),
167 "FD scope end"
168 );
169
170 #[cfg(unix)]
171 if change > 0 {
172 let end_snapshot = list_open_fds();
173 print_new_fds(&self.label, &self.start_snapshot, &end_snapshot);
174 }
175 }
176 }
177}
178
179#[cfg(target_os = "macos")]
183pub fn list_open_fds() -> Vec<(i32, String)> {
184 #[cfg(feature = "fd-track")]
185 {
186 let Ok(entries) = std::fs::read_dir("/dev/fd") else {
187 return Vec::new();
188 };
189 let mut fds: Vec<(i32, String)> = entries
190 .filter_map(|e| {
191 let e = e.ok()?;
192 let fd: i32 = e.file_name().to_str()?.parse().ok()?;
193 let target = std::fs::read_link(e.path())
194 .map(|p| p.display().to_string())
195 .unwrap_or_else(|_| "<unknown>".into());
196 Some((fd, target))
197 })
198 .collect();
199 fds.sort_by_key(|(fd, _)| *fd);
200 fds
201 }
202 #[cfg(not(feature = "fd-track"))]
203 {
204 Vec::new()
205 }
206}
207
208#[cfg(target_os = "linux")]
211pub fn list_open_fds() -> Vec<(i32, String)> {
212 #[cfg(feature = "fd-track")]
213 {
214 let Ok(entries) = std::fs::read_dir("/proc/self/fd") else {
215 return Vec::new();
216 };
217 let mut fds: Vec<(i32, String)> = entries
218 .filter_map(|e| {
219 let e = e.ok()?;
220 let fd: i32 = e.file_name().to_str()?.parse().ok()?;
221 let target = std::fs::read_link(e.path())
222 .map(|p| p.display().to_string())
223 .unwrap_or_else(|_| "<unknown>".into());
224 Some((fd, target))
225 })
226 .collect();
227 fds.sort_by_key(|(fd, _)| *fd);
228 fds
229 }
230 #[cfg(not(feature = "fd-track"))]
231 {
232 Vec::new()
233 }
234}
235
236#[cfg(not(any(target_os = "macos", target_os = "linux")))]
237pub fn list_open_fds() -> Vec<(i32, String)> {
238 Vec::new()
239}
240
241#[cfg(unix)]
243pub fn print_new_fds(label: &str, before: &[(i32, String)], after: &[(i32, String)]) {
244 #[cfg(feature = "fd-track")]
245 {
246 let before_set: std::collections::HashSet<i32> = before.iter().map(|(fd, _)| *fd).collect();
247 let new_fds: Vec<_> = after.iter().filter(|(fd, _)| !before_set.contains(fd)).collect();
248
249 if new_fds.is_empty() {
250 debug!(target: "xet_runtime::fd_track", label, "FD diff: no new descriptors");
251 return;
252 }
253
254 debug!(
255 target: "xet_runtime::fd_track",
256 label,
257 new_fd_count = new_fds.len(),
258 "FD diff: detected new descriptors"
259 );
260 for (fd, target) in &new_fds {
261 debug!(target: "xet_runtime::fd_track", label, fd = *fd, target = %target, "FD diff entry");
262 }
263
264 let pid = std::process::id();
265 let fd_list: String = new_fds.iter().map(|(fd, _)| fd.to_string()).collect::<Vec<_>>().join(",");
266 if let Ok(output) = std::process::Command::new("lsof")
267 .args(["-p", &pid.to_string(), "-a", "-d", &fd_list])
268 .output()
269 && output.status.success()
270 {
271 let lsof = String::from_utf8_lossy(&output.stdout);
272 debug!(target: "xet_runtime::fd_track", label, lsof = %lsof, "FD diff lsof details");
273 }
274 }
275 #[cfg(not(feature = "fd-track"))]
276 {
277 let _ = (label, before, after);
278 }
279}
280
281#[cfg(not(unix))]
282pub fn print_new_fds(label: &str, before: &[(i32, String)], after: &[(i32, String)]) {
283 let _ = (label, before, after);
284}