1use anyhow::{Context, Result};
8use serde::Serialize;
9use std::fs::File;
10use std::io::Write;
11use std::os::fd::AsRawFd;
12use std::path::{Path, PathBuf};
13
14use crate::bench::{
15 BenchResult, RAND_IO_BYTES, RAND_ITERS, SEQ_IO_BYTES, SEQ_ITERS, bench_cufile, bench_io_uring,
16 bench_posix, open_test_file,
17};
18use crate::cuda_min::{CudaCtx, DeviceBuffer, PinnedBuffer};
19
20#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
21pub enum Backend {
22 CuFileDirect,
23 CuFileCompat,
24 IoUring,
25 PosixOnly,
26 None,
27}
28
29#[derive(Debug, Clone)]
30pub struct ProbeConfig {
31 pub dir: PathBuf,
32 pub test_file_bytes: u64,
33}
34
35#[derive(Debug, Clone, Serialize)]
36pub struct ProbeResult {
37 pub libcufile_loaded: bool,
38 pub libcufile_load_error: Option<String>,
39 pub cufile_version: Option<i32>,
40 pub nvidia_fs_kmod_loaded: bool,
41 pub cufile_driver_open_ok: bool,
42 pub cufile_driver_open_error: Option<String>,
43 pub cufile_seq_4mib: Option<f64>,
44 pub cufile_rand_64kib: Option<f64>,
45 pub io_uring_seq_4mib: Option<f64>,
46 pub io_uring_rand_64kib: Option<f64>,
47 pub posix_seq_4mib: Option<f64>,
48 pub posix_rand_64kib: Option<f64>,
49 pub recommended: Backend,
50 pub recommendation_reason: String,
51}
52
53fn fill_test_file(path: &Path, bytes: u64) -> Result<()> {
54 if path.exists() && std::fs::metadata(path)?.len() == bytes {
55 return Ok(());
56 }
57 let mut f = File::create(path).with_context(|| format!("create {}", path.display()))?;
58 let chunk = vec![0xA5u8; 1 << 20];
59 let mut written = 0u64;
60 while written < bytes {
61 let n = ((bytes - written) as usize).min(chunk.len());
62 f.write_all(&chunk[..n])?;
63 written += n as u64;
64 }
65 f.sync_all()?;
66 Ok(())
67}
68
69fn run_one_bench<F>(label: &str, f: F) -> Option<f64>
70where
71 F: FnOnce() -> Result<BenchResult>,
72{
73 match f() {
74 Ok(r) => {
75 tracing::info!(
76 "{label}: {:.1} MiB/s ({} iters @ {} bytes)",
77 r.mib_per_sec,
78 r.iters,
79 r.bytes_per_io
80 );
81 Some(r.mib_per_sec)
82 }
83 Err(e) => {
84 tracing::warn!("{label}: SKIPPED ({e:#})");
85 None
86 }
87 }
88}
89
90pub fn run_probe(cfg: &ProbeConfig) -> Result<ProbeResult> {
91 std::fs::create_dir_all(&cfg.dir).with_context(|| format!("mkdir {}", cfg.dir.display()))?;
92 let test_path = cfg.dir.join("probe-test.bin");
93 fill_test_file(&test_path, cfg.test_file_bytes)?;
94
95 let nvidia_fs = cufile_sys::nvidia_fs_loaded();
96 let cufile_load = cufile_sys::CuFile::load();
97 let (libcufile_loaded, libcufile_err, cufile) = match cufile_load {
98 Ok(c) => (true, None, Some(c)),
99 Err(e) => (false, Some(e), None),
100 };
101 let mut version: Option<i32> = None;
102 let mut driver_open_ok = false;
103 let mut driver_open_err: Option<String> = None;
104 if let Some(cufile) = cufile.as_ref() {
105 let mut v = 0i32;
106 let r = unsafe { (cufile.get_version)(&mut v) };
107 if r.err == cufile_sys::CU_FILE_SUCCESS {
108 version = Some(v);
109 }
110 let r = unsafe { (cufile.driver_open)() };
111 if r.err == cufile_sys::CU_FILE_SUCCESS {
112 driver_open_ok = true;
113 } else {
114 driver_open_err = Some(format!("{} ({})", r.err, cufile_sys::err_to_str(r.err)));
115 }
116 }
117
118 let cuda = CudaCtx::new(0).context("init CUDA context for probe")?;
119 let dev = DeviceBuffer::new(SEQ_IO_BYTES)?;
120 let pinned = PinnedBuffer::new(SEQ_IO_BYTES)?;
121 let (file, file_bytes) = open_test_file(&test_path)?;
122 let fd = file.as_raw_fd();
123
124 let cufile_seq = cufile.as_ref().filter(|_| driver_open_ok).and_then(|c| {
125 run_one_bench("cuFile seq 4MiB", || {
126 bench_cufile(c, fd, file_bytes, &dev, SEQ_IO_BYTES, SEQ_ITERS, true)
127 })
128 });
129 let cufile_rand = cufile.as_ref().filter(|_| driver_open_ok).and_then(|c| {
130 run_one_bench("cuFile rand 64KiB", || {
131 bench_cufile(c, fd, file_bytes, &dev, RAND_IO_BYTES, RAND_ITERS, false)
132 })
133 });
134 let iou_seq = run_one_bench("io_uring seq 4MiB", || {
135 bench_io_uring(
136 &cuda,
137 fd,
138 file_bytes,
139 &pinned,
140 &dev,
141 SEQ_IO_BYTES,
142 SEQ_ITERS,
143 true,
144 )
145 });
146 let iou_rand = run_one_bench("io_uring rand 64KiB", || {
147 bench_io_uring(
148 &cuda,
149 fd,
150 file_bytes,
151 &pinned,
152 &dev,
153 RAND_IO_BYTES,
154 RAND_ITERS,
155 false,
156 )
157 });
158 let posix_seq = run_one_bench("posix seq 4MiB", || {
159 bench_posix(
160 &cuda,
161 fd,
162 file_bytes,
163 &pinned,
164 &dev,
165 SEQ_IO_BYTES,
166 SEQ_ITERS,
167 true,
168 )
169 });
170 let posix_rand = run_one_bench("posix rand 64KiB", || {
171 bench_posix(
172 &cuda,
173 fd,
174 file_bytes,
175 &pinned,
176 &dev,
177 RAND_IO_BYTES,
178 RAND_ITERS,
179 false,
180 )
181 });
182 let _ = (file, dev, pinned, cuda);
183
184 let (recommended, reason) = decide(nvidia_fs, driver_open_ok, cufile_seq, iou_seq, posix_seq);
185
186 Ok(ProbeResult {
187 libcufile_loaded,
188 libcufile_load_error: libcufile_err,
189 cufile_version: version,
190 nvidia_fs_kmod_loaded: nvidia_fs,
191 cufile_driver_open_ok: driver_open_ok,
192 cufile_driver_open_error: driver_open_err,
193 cufile_seq_4mib: cufile_seq,
194 cufile_rand_64kib: cufile_rand,
195 io_uring_seq_4mib: iou_seq,
196 io_uring_rand_64kib: iou_rand,
197 posix_seq_4mib: posix_seq,
198 posix_rand_64kib: posix_rand,
199 recommended,
200 recommendation_reason: reason,
201 })
202}
203
204fn decide(
205 nvfs: bool,
206 cufile_open: bool,
207 cufile: Option<f64>,
208 iou: Option<f64>,
209 posix: Option<f64>,
210) -> (Backend, String) {
211 let cf = cufile.unwrap_or(0.0);
212 let iu = iou.unwrap_or(0.0);
213 let px = posix.unwrap_or(0.0);
214 if nvfs && cufile_open && cf >= iu * 1.05 {
215 return (
216 Backend::CuFileDirect,
217 format!("nvidia-fs loaded; cuFile {cf:.0} MiB/s ≥ io_uring {iu:.0} MiB/s"),
218 );
219 }
220 if cufile_open && cf >= iu.max(px) * 1.05 {
221 return (
222 Backend::CuFileCompat,
223 format!("cuFile compat-mode {cf:.0} MiB/s beats io_uring {iu:.0} / posix {px:.0}"),
224 );
225 }
226 if iu >= px && iu > 0.0 {
227 return (
228 Backend::IoUring,
229 format!("io_uring {iu:.0} MiB/s ≥ posix {px:.0} MiB/s"),
230 );
231 }
232 if px > 0.0 {
233 return (
234 Backend::PosixOnly,
235 format!("posix-only fallback {px:.0} MiB/s"),
236 );
237 }
238 (Backend::None, "no backend produced bandwidth".into())
239}