use std::sync::Arc;
use std::time::Duration;
use tokio::time::timeout;
use crate::cli::SnapshotIncludes;
use crate::device::{
ChassisInfo, ChassisReader, CpuInfo, CpuReader, GpuInfo, GpuReader, MemoryInfo, MemoryReader,
ProcessInfo, create_chassis_reader, get_cpu_readers, get_gpu_readers, get_memory_readers,
};
use crate::snapshot::options::{Snapshot, SnapshotError};
use crate::storage::info::StorageInfo;
use crate::storage::reader::{LocalStorageReader, StorageReader};
pub trait SnapshotCollector: Send + Sync {
fn hostname(&self) -> String;
fn collect_gpus(&self) -> Vec<GpuInfo>;
fn collect_cpus(&self) -> Vec<CpuInfo>;
fn collect_memory(&self) -> Vec<MemoryInfo>;
fn collect_chassis(&self) -> Vec<ChassisInfo>;
fn collect_processes(&self) -> Vec<ProcessInfo>;
fn collect_storage(&self) -> Vec<StorageInfo>;
}
pub struct DefaultSnapshotCollector {
gpu_readers: Vec<Box<dyn GpuReader>>,
cpu_readers: Vec<Box<dyn CpuReader>>,
memory_readers: Vec<Box<dyn MemoryReader>>,
chassis_reader: Box<dyn ChassisReader>,
storage_reader: Box<dyn StorageReader>,
hostname: String,
}
impl DefaultSnapshotCollector {
pub fn new() -> Self {
Self {
gpu_readers: get_gpu_readers(),
cpu_readers: get_cpu_readers(),
memory_readers: get_memory_readers(),
chassis_reader: create_chassis_reader(),
storage_reader: Box::new(LocalStorageReader::new()),
hostname: crate::utils::get_hostname(),
}
}
}
impl Default for DefaultSnapshotCollector {
fn default() -> Self {
Self::new()
}
}
impl SnapshotCollector for DefaultSnapshotCollector {
fn hostname(&self) -> String {
self.hostname.clone()
}
fn collect_gpus(&self) -> Vec<GpuInfo> {
self.gpu_readers
.iter()
.flat_map(|r| r.get_gpu_info())
.collect()
}
fn collect_cpus(&self) -> Vec<CpuInfo> {
self.cpu_readers
.iter()
.flat_map(|r| r.get_cpu_info())
.collect()
}
fn collect_memory(&self) -> Vec<MemoryInfo> {
self.memory_readers
.iter()
.flat_map(|r| r.get_memory_info())
.collect()
}
fn collect_chassis(&self) -> Vec<ChassisInfo> {
self.chassis_reader.get_chassis_info().into_iter().collect()
}
fn collect_processes(&self) -> Vec<ProcessInfo> {
self.gpu_readers
.iter()
.flat_map(|r| r.get_process_info())
.collect()
}
fn collect_storage(&self) -> Vec<StorageInfo> {
self.storage_reader.get_storage_info()
}
}
pub async fn collect_once<C: SnapshotCollector + 'static>(
collector: Arc<C>,
includes: &SnapshotIncludes,
per_reader_timeout: Duration,
) -> Snapshot {
let mut snap = Snapshot::new(collector.hostname());
let sections: &[(&str, bool)] = &[
("gpu", includes.gpu),
("cpu", includes.cpu),
("memory", includes.memory),
("chassis", includes.chassis),
("process", includes.process),
("storage", includes.storage),
];
for (name, enabled) in sections {
if !*enabled {
continue;
}
let c = collector.clone();
let section = name.to_string();
let join_handle = tokio::task::spawn_blocking(move || match section.as_str() {
"gpu" => SectionResult::Gpus(c.collect_gpus()),
"cpu" => SectionResult::Cpus(c.collect_cpus()),
"memory" => SectionResult::Memory(c.collect_memory()),
"chassis" => SectionResult::Chassis(c.collect_chassis()),
"process" => SectionResult::Processes(c.collect_processes()),
"storage" => SectionResult::Storage(c.collect_storage()),
_ => SectionResult::Empty,
});
match timeout(per_reader_timeout, join_handle).await {
Ok(Ok(result)) => match result {
SectionResult::Gpus(v) => snap.gpus = Some(v),
SectionResult::Cpus(v) => snap.cpus = Some(v),
SectionResult::Memory(v) => snap.memory = Some(v),
SectionResult::Chassis(v) => snap.chassis = Some(v),
SectionResult::Processes(v) => snap.processes = Some(v),
SectionResult::Storage(v) => snap.storage = Some(v),
SectionResult::Empty => {}
},
Ok(Err(join_err)) => {
snap.errors.push(SnapshotError {
section: (*name).to_string(),
kind: if join_err.is_panic() {
"panic".to_string()
} else {
"error".to_string()
},
message: join_err.to_string(),
});
}
Err(_elapsed) => {
tracing::warn!(
section = *name,
timeout_ms = per_reader_timeout.as_millis() as u64,
"snapshot reader timed out; the blocking worker cannot be cancelled \
and will continue until the reader returns"
);
snap.errors.push(SnapshotError {
section: (*name).to_string(),
kind: "timeout".to_string(),
message: format!(
"reader exceeded timeout of {} ms",
per_reader_timeout.as_millis()
),
});
}
}
}
if let (Some(gpus), Some(chassis)) = (snap.gpus.as_ref(), snap.chassis.as_mut()) {
let total_gpu_power = crate::metrics::gpu_readings::total_power_watts(gpus);
if total_gpu_power > 0.0 {
for ci in chassis.iter_mut() {
if ci.total_power_watts.is_none() {
ci.total_power_watts = Some(total_gpu_power);
}
}
}
}
snap
}
enum SectionResult {
Gpus(Vec<GpuInfo>),
Cpus(Vec<CpuInfo>),
Memory(Vec<MemoryInfo>),
Chassis(Vec<ChassisInfo>),
Processes(Vec<ProcessInfo>),
Storage(Vec<StorageInfo>),
Empty,
}