use super::{GpuBackend, GpuProcess, GpuSnapshot};
use anyhow::{Context, Result};
use std::io::BufRead;
use std::path::Path;
#[derive(serde::Deserialize)]
struct LogRecord {
#[serde(default)]
gpus: Vec<GpuSnapshot>,
#[serde(default)]
processes: Vec<GpuProcess>,
}
pub struct ReplayBackend {
lines: std::io::Lines<std::io::BufReader<std::fs::File>>,
last: LogRecord,
finished: bool,
}
pub fn load(path: &Path) -> Result<Box<dyn GpuBackend>> {
let file = std::fs::File::open(path)
.with_context(|| format!("opening replay log {}", path.display()))?;
let mut lines = std::io::BufReader::new(file).lines();
let first = next_record(&mut lines)
.with_context(|| format!("{}: no valid JSONL records", path.display()))?;
Ok(Box::new(ReplayBackend {
lines,
last: first,
finished: false,
}))
}
fn next_record(lines: &mut std::io::Lines<std::io::BufReader<std::fs::File>>) -> Option<LogRecord> {
for line in lines.by_ref() {
let Ok(line) = line else { return None };
if let Ok(rec) = serde_json::from_str::<LogRecord>(&line) {
return Some(rec);
}
}
None
}
impl GpuBackend for ReplayBackend {
fn name(&self) -> &'static str {
"replay"
}
fn poll(&mut self) -> Result<Vec<GpuSnapshot>> {
if !self.finished {
match next_record(&mut self.lines) {
Some(rec) => self.last = rec,
None => self.finished = true, }
}
Ok(self.last.gpus.clone())
}
fn processes(&mut self) -> Vec<GpuProcess> {
self.last.processes.clone()
}
}