1use std::time::Instant;
35
36#[derive(Debug, Clone)]
42pub struct BandwidthMeasurement {
43 pub from_node: usize,
45 pub to_node: usize,
47 pub bandwidth_gb_s: f64,
49 pub latency_ns: f64,
51 pub transfer_size_bytes: usize,
53}
54
55#[derive(Debug, Clone)]
61pub struct NumaBandwidthMatrix {
62 pub n_nodes: usize,
64 pub bandwidth: Vec<Vec<f64>>,
66 pub latency: Vec<Vec<f64>>,
68}
69
70impl NumaBandwidthMatrix {
71 pub fn uniform(n_nodes: usize, bandwidth_gb_s: f64, latency_ns: f64) -> Self {
74 let row = vec![bandwidth_gb_s; n_nodes.max(1)];
75 let lat_row = vec![latency_ns; n_nodes.max(1)];
76 let n = n_nodes.max(1);
77 Self {
78 n_nodes: n,
79 bandwidth: vec![row; n],
80 latency: vec![lat_row; n],
81 }
82 }
83
84 pub fn get_bandwidth(&self, from: usize, to: usize) -> f64 {
88 self.bandwidth
89 .get(from)
90 .and_then(|row| row.get(to))
91 .copied()
92 .unwrap_or(0.0)
93 }
94
95 pub fn get_latency(&self, from: usize, to: usize) -> f64 {
99 self.latency
100 .get(from)
101 .and_then(|row| row.get(to))
102 .copied()
103 .unwrap_or(f64::MAX)
104 }
105
106 pub fn best_route(&self, src: usize, dst: usize) -> f64 {
114 self.get_bandwidth(src, dst)
115 }
116
117 pub fn highest_bandwidth_node(&self) -> usize {
121 let mut best_node = 0usize;
122 let mut best_avg = f64::NEG_INFINITY;
123
124 for from in 0..self.n_nodes {
125 let row = &self.bandwidth[from];
126 let avg = if row.is_empty() {
127 0.0
128 } else {
129 row.iter().sum::<f64>() / row.len() as f64
130 };
131 if avg > best_avg {
132 best_avg = avg;
133 best_node = from;
134 }
135 }
136 best_node
137 }
138
139 pub fn display(&self) -> String {
141 let mut out = String::from("NUMA Bandwidth Matrix (GB/s):\n");
142 out.push_str(" ");
143 for j in 0..self.n_nodes {
144 out.push_str(&format!(" {:>6}", format!("N{j}")));
145 }
146 out.push('\n');
147
148 for (i, row) in self.bandwidth.iter().enumerate() {
149 out.push_str(&format!("N{i:<4}"));
150 for &bw in row {
151 out.push_str(&format!(" {:>6.2}", bw));
152 }
153 out.push('\n');
154 }
155 out
156 }
157
158 pub fn node_count(&self) -> usize {
160 self.n_nodes
161 }
162}
163
164pub fn measure_copy_bandwidth(transfer_size_bytes: usize) -> BandwidthMeasurement {
174 let src: Vec<u8> = vec![0xABu8; transfer_size_bytes];
176 let mut dst = vec![0u8; transfer_size_bytes];
177
178 dst.copy_from_slice(&src);
180
181 let _ = dst[transfer_size_bytes / 2];
183
184 let repetitions: u64 = 3;
186 let start = Instant::now();
187 for _ in 0..repetitions {
188 dst.copy_from_slice(&src);
189 }
190 let elapsed = start.elapsed();
191
192 let _ = dst[0];
194
195 let bytes_transferred = transfer_size_bytes as u64 * repetitions;
196 let elapsed_secs = elapsed.as_secs_f64();
197
198 let bandwidth_gb_s = if elapsed_secs > 0.0 {
200 bytes_transferred as f64 / elapsed_secs / 1e9
201 } else {
202 f64::MAX
203 };
204
205 let latency_ns = if repetitions > 0 {
206 elapsed.as_nanos() as f64 / repetitions as f64
207 } else {
208 0.0
209 };
210
211 BandwidthMeasurement {
212 from_node: 0,
213 to_node: 0,
214 bandwidth_gb_s,
215 latency_ns,
216 transfer_size_bytes,
217 }
218}
219
220pub fn probe_bandwidth_matrix() -> NumaBandwidthMatrix {
232 let n_nodes = detect_numa_node_count();
235 let probe_size = 4 * 1024 * 1024; let measurement = measure_copy_bandwidth(probe_size);
237
238 NumaBandwidthMatrix::uniform(n_nodes, measurement.bandwidth_gb_s, measurement.latency_ns)
239}
240
241fn detect_numa_node_count() -> usize {
245 #[cfg(target_os = "linux")]
247 {
248 if let Some(count) = try_read_linux_numa_count() {
249 return count;
250 }
251 }
252 1
253}
254
255#[cfg(target_os = "linux")]
256fn try_read_linux_numa_count() -> Option<usize> {
257 use std::fs;
258 let contents = fs::read_to_string("/sys/devices/system/node/online").ok()?;
259 parse_node_count_from_range(contents.trim())
262}
263
264fn parse_node_count_from_range(s: &str) -> Option<usize> {
266 let mut count = 0usize;
267 for part in s.split(',') {
268 let part = part.trim();
269 if part.is_empty() {
270 continue;
271 }
272 if let Some((lo_str, hi_str)) = part.split_once('-') {
273 let lo: usize = lo_str.trim().parse().ok()?;
274 let hi: usize = hi_str.trim().parse().ok()?;
275 if hi >= lo {
276 count += hi - lo + 1;
277 }
278 } else {
279 let _id: usize = part.parse().ok()?;
281 count += 1;
282 }
283 }
284 if count > 0 {
285 Some(count)
286 } else {
287 None
288 }
289}
290
291pub fn optimal_placement_node(
303 matrix: &NumaBandwidthMatrix,
304 src_node: usize,
305 data_size: usize,
306) -> usize {
307 let _ = data_size; if src_node >= matrix.n_nodes {
310 return 0;
311 }
312
313 let bandwidth_row = &matrix.bandwidth[src_node];
315 let mut best_node = src_node;
316 let mut best_bw = bandwidth_row.get(src_node).copied().unwrap_or(0.0);
317
318 for (to, &bw) in bandwidth_row.iter().enumerate() {
319 if bw > best_bw {
320 best_bw = bw;
321 best_node = to;
322 }
323 }
324 best_node
325}
326
327#[cfg(test)]
332mod tests {
333 use super::*;
334
335 #[test]
336 fn test_bandwidth_matrix_uniform() {
337 let matrix = NumaBandwidthMatrix::uniform(2, 50.0, 100.0);
338 assert_eq!(matrix.n_nodes, 2);
339 assert_eq!(matrix.get_bandwidth(0, 0), 50.0);
340 assert_eq!(matrix.get_bandwidth(0, 1), 50.0);
341 assert_eq!(matrix.get_bandwidth(1, 0), 50.0);
342 assert_eq!(matrix.get_bandwidth(1, 1), 50.0);
343 assert_eq!(matrix.get_latency(0, 1), 100.0);
344 assert_eq!(matrix.get_bandwidth(5, 0), 0.0);
346 assert_eq!(matrix.get_latency(5, 0), f64::MAX);
347 }
348
349 #[test]
350 fn test_bandwidth_matrix_single_node_fallback() {
351 let matrix = NumaBandwidthMatrix::uniform(0, 10.0, 200.0);
353 assert_eq!(matrix.n_nodes, 1);
354 assert_eq!(matrix.get_bandwidth(0, 0), 10.0);
355 }
356
357 #[test]
358 fn test_bandwidth_measure() {
359 let m = measure_copy_bandwidth(1024 * 1024);
361 assert!(m.bandwidth_gb_s > 0.0, "bandwidth must be positive");
362 assert!(m.latency_ns > 0.0, "latency must be positive");
363 assert_eq!(m.transfer_size_bytes, 1024 * 1024);
364 assert_eq!(m.from_node, 0);
365 assert_eq!(m.to_node, 0);
366 }
367
368 #[test]
369 fn test_bandwidth_matrix_display() {
370 let matrix = NumaBandwidthMatrix::uniform(2, 42.0, 80.0);
371 let s = matrix.display();
372 assert!(!s.is_empty(), "display string should not be empty");
373 assert!(s.contains("42.00"), "should contain bandwidth value");
374 }
375
376 #[test]
377 fn test_optimal_placement_single_node() {
378 let matrix = NumaBandwidthMatrix::uniform(1, 50.0, 100.0);
379 let node = optimal_placement_node(&matrix, 0, 4 * 1024 * 1024);
380 assert_eq!(node, 0, "single-node system => always node 0");
381 }
382
383 #[test]
384 fn test_optimal_placement_out_of_range() {
385 let matrix = NumaBandwidthMatrix::uniform(2, 50.0, 100.0);
386 let node = optimal_placement_node(&matrix, 99, 1024);
388 assert_eq!(node, 0, "out-of-range src should return 0");
389 }
390
391 #[test]
392 fn test_optimal_placement_multi_node_prefers_high_bw() {
393 let n = 3;
394 let mut matrix = NumaBandwidthMatrix::uniform(n, 10.0, 100.0);
395 matrix.bandwidth[0][2] = 100.0;
397 let node = optimal_placement_node(&matrix, 0, 1024);
398 assert_eq!(node, 2, "should prefer node 2 with highest bandwidth");
399 }
400
401 #[test]
402 fn test_highest_bandwidth_node() {
403 let n = 3;
404 let mut matrix = NumaBandwidthMatrix::uniform(n, 10.0, 100.0);
405 for j in 0..n {
407 matrix.bandwidth[1][j] = 50.0;
408 }
409 assert_eq!(
410 matrix.highest_bandwidth_node(),
411 1,
412 "node 1 should have the highest average outgoing BW"
413 );
414 }
415
416 #[test]
417 fn test_best_route() {
418 let matrix = NumaBandwidthMatrix::uniform(2, 30.0, 50.0);
419 assert_eq!(matrix.best_route(0, 1), 30.0);
420 assert_eq!(matrix.best_route(1, 0), 30.0);
421 }
422
423 #[test]
424 fn test_probe_bandwidth_matrix_returns_valid() {
425 let matrix = probe_bandwidth_matrix();
426 assert!(matrix.n_nodes >= 1, "must have at least one node");
427 assert!(
428 matrix.get_bandwidth(0, 0) > 0.0,
429 "local bandwidth must be positive"
430 );
431 }
432
433 #[test]
434 fn test_parse_node_count_from_range() {
435 assert_eq!(parse_node_count_from_range("0"), Some(1));
436 assert_eq!(parse_node_count_from_range("0-3"), Some(4));
437 assert_eq!(parse_node_count_from_range("0,2"), Some(2));
438 assert_eq!(parse_node_count_from_range("0-1,4-7"), Some(6));
439 assert_eq!(parse_node_count_from_range(""), None);
440 }
441}