use std::{fs, io, ops::Deref, path::Path};
use self::parse::parse_trace;
use crate::{record::Record, Error, Result};
#[derive(Debug)]
pub struct Trace {
records: Box<[Record]>,
cpu_count: u32,
}
impl Trace {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
fs::File::open(path)
.map_err(|e| Error::io_error("Failed to open trace file", e))
.map(io::BufReader::new)
.and_then(parse_trace)
}
pub fn from_bytes<B: AsRef<[u8]>>(bytes: B) -> Result<Self> {
let reader = io::Cursor::new(bytes);
parse_trace(reader)
}
pub fn from_reader<R: io::Read>(reader: R) -> Result<Self> {
parse_trace(reader)
}
pub fn record_count(&self) -> usize {
self.records.len()
}
pub fn cpu_count(&self) -> u32 {
self.cpu_count
}
}
impl Deref for Trace {
type Target = [Record];
fn deref(&self) -> &Self::Target {
&self.records
}
}
mod parse {
use std::collections::HashMap;
use fxhash::FxBuildHasher;
use super::*;
use crate::{
record::{Domain, Event, EventCode, EVENT_EXTRA_CAPACITY},
util::IoReadUtil,
};
const TRC_TRACE_CPU_CHANGE: u32 = 0x0001F003;
const TRC_SCHED_TO_RUN: u32 = 0x00021F0F;
struct ParserData {
domains: HashMap<u32, Domain, FxBuildHasher>,
last_cpu: u32,
records: Vec<Record>,
last_tsc: u64,
}
pub(super) fn parse_trace<R: io::Read>(mut rdr: R) -> Result<Trace> {
let mut data = ParserData {
domains: HashMap::with_capacity_and_hasher(
u16::BITS as usize,
FxBuildHasher::default(),
),
last_cpu: 0,
records: Vec::with_capacity((u16::MAX / 2) as usize),
last_tsc: 0,
};
while let Some(record) = next_record(&mut rdr, &mut data)? {
if record.event.code == TRC_TRACE_CPU_CHANGE {
data.last_cpu = record.event.extra[0].unwrap_or(0);
continue;
}
data.records.push(record);
}
let records = {
data.records.sort();
data.records.into_boxed_slice()
};
match data.domains.len().try_into() {
Ok(cpu_count) => Ok(Trace { records, cpu_count }),
Err(_) => Err(Error::new(format_args!(
"Failed to set host CPU count: {} > u32::MAX",
data.domains.len()
))),
}
}
fn next_record<R: io::Read>(rdr: &mut R, data: &mut ParserData) -> Result<Option<Record>> {
fn read_event<R: io::Read>(rdr: &mut R, last_tsc: &mut u64) -> Result<Option<Event>> {
let Some(header) = rdr.read_ne_u32().ok() else {
return Ok(None);
};
let code = EventCode::from(header & 0x0FFFFFF);
let tsc = {
if header & (1 << 31) > 0 {
*last_tsc = rdr
.read_ne_u64()
.map_err(|e| Error::io_error("Failed to read tsc value", e))?;
}
*last_tsc
};
let extra = {
let len = ((header >> 28) as usize) & EVENT_EXTRA_CAPACITY;
let mut extra = [None; EVENT_EXTRA_CAPACITY];
for entry in extra.iter_mut().take(len) {
*entry = rdr
.read_ne_u32()
.map(Some)
.map_err(|e| Error::io_error("Failed to read extra value", e))?;
}
extra
};
Ok(Some(Event { code, tsc, extra }))
}
let Some(event) = read_event(rdr, &mut data.last_tsc)? else {
return Ok(None);
};
let cpu = data.last_cpu;
let domain = if event.code == (event.code & TRC_SCHED_TO_RUN) {
let extra_0 = event.extra[0].unwrap_or(0);
let domain = Domain::from(extra_0);
data.domains.insert(cpu, domain);
domain
} else {
data.domains.get(&cpu).copied().unwrap_or_default()
};
Ok(Some(Record { cpu, domain, event }))
}
}