use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use crate::{net, nvlink, stat, xgmi};
pub struct Sample<T> {
pub data: T,
pub taken_at: Instant,
}
fn sample<T>(read: impl FnOnce() -> std::io::Result<T>) -> Option<Sample<T>> {
let data = read().ok()?;
Some(Sample {
data,
taken_at: Instant::now(),
})
}
pub struct Snapshot {
pub stats: Option<Sample<Vec<stat::PortStat>>>,
pub ifstats: Option<Sample<Vec<net::IfStats>>>,
pub nvlink: Option<Sample<Vec<nvlink::NvLinkSnapshot>>>,
pub xgmi: Option<Sample<Vec<xgmi::XgmiSnapshot>>>,
pub processes: Option<Vec<stat::ProcessRdmaInfo>>,
pub taken_at: Instant,
}
fn collect() -> Snapshot {
let taken_at = Instant::now();
let processes = stat::read_all_qps()
.ok()
.map(|qps| stat::aggregate_by_process(&qps));
Snapshot {
stats: sample(stat::read_all_stats),
ifstats: sample(net::read_all_ifstats),
nvlink: sample(nvlink::read_all_nvlink_stats),
xgmi: sample(xgmi::read_all_xgmi_stats),
processes,
taken_at,
}
}
struct Shared {
slot: Mutex<Option<Snapshot>>,
interval_ms: AtomicU64,
stop: AtomicBool,
died: Mutex<Option<String>>,
}
pub struct Sampler {
shared: Arc<Shared>,
}
impl Sampler {
pub fn spawn(interval: Duration) -> Self {
let shared = Arc::new(Shared {
slot: Mutex::new(None),
interval_ms: AtomicU64::new(interval_to_ms(interval)),
stop: AtomicBool::new(false),
died: Mutex::new(None),
});
let thread_shared = shared.clone();
std::thread::spawn(move || run(&thread_shared));
Self { shared }
}
pub fn try_latest(&self) -> Option<Snapshot> {
self.shared
.slot
.lock()
.unwrap_or_else(|e| e.into_inner())
.take()
}
pub fn death_reason(&self) -> Option<String> {
self.shared
.died
.lock()
.unwrap_or_else(|e| e.into_inner())
.clone()
}
pub fn set_interval(&self, interval: Duration) {
self.shared
.interval_ms
.store(interval_to_ms(interval), Ordering::Relaxed);
}
pub fn stop(&self) {
self.shared.stop.store(true, Ordering::Relaxed);
}
}
impl Drop for Sampler {
fn drop(&mut self) {
self.stop();
}
}
fn interval_to_ms(interval: Duration) -> u64 {
(interval.as_millis() as u64).max(1)
}
fn panic_message(payload: Box<dyn std::any::Any + Send>) -> String {
if let Some(s) = payload.downcast_ref::<&str>() {
(*s).to_string()
} else if let Some(s) = payload.downcast_ref::<String>() {
s.clone()
} else {
"unknown panic".to_string()
}
}
fn run(shared: &Shared) {
loop {
if shared.stop.load(Ordering::Relaxed) {
return;
}
let snap = match std::panic::catch_unwind(collect) {
Ok(s) => s,
Err(payload) => {
let mut died = shared.died.lock().unwrap_or_else(|e| e.into_inner());
*died = Some(panic_message(payload));
return;
}
};
*shared.slot.lock().unwrap_or_else(|e| e.into_inner()) = Some(snap);
let started = Instant::now();
loop {
if shared.stop.load(Ordering::Relaxed) {
return;
}
let interval = Duration::from_millis(shared.interval_ms.load(Ordering::Relaxed));
if started.elapsed() >= interval {
break;
}
std::thread::sleep(Duration::from_millis(50));
}
}
}