use std::fs::{File, OpenOptions};
use std::io::{BufWriter, Write};
use std::path::Path;
use katra_core::{KatraError, Result};
use serde::Serialize;
use crate::header::{FORMAT_VERSION, SCHEMA_HASH, TRACE_MAGIC, TraceHeader};
use crate::record::{TraceRecord, TraceSummary};
pub struct TraceWriter {
inner: BufWriter<File>,
count: u64,
}
impl TraceWriter {
pub fn create(path: &Path, header: &TraceHeader) -> Result<Self> {
let file = OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(path)
.map_err(KatraError::from)?;
let mut w = TraceWriter { inner: BufWriter::new(file), count: 0 };
w.write_header(header)?;
Ok(w)
}
fn write_raw<T: Serialize>(&mut self, value: &T) -> Result<()> {
let bytes =
bincode::serialize(value).map_err(|e| KatraError::Protocol(format!("bincode: {e}")))?;
let len = bytes.len() as u32;
self.inner
.write_all(&len.to_le_bytes())
.and_then(|_| self.inner.write_all(&bytes))
.map_err(KatraError::from)?;
Ok(())
}
fn write_header(&mut self, header: &TraceHeader) -> Result<()> {
debug_assert_eq!(header.format_version, FORMAT_VERSION);
debug_assert_eq!(header.schema_hash, SCHEMA_HASH);
let bytes = bincode::serialize(header)
.map_err(|e| KatraError::Protocol(format!("bincode header: {e}")))?;
self.inner
.write_all(&TRACE_MAGIC)
.and_then(|_| self.inner.write_all(&FORMAT_VERSION.to_le_bytes()))
.and_then(|_| self.inner.write_all(&(bytes.len() as u32).to_le_bytes()))
.and_then(|_| self.inner.write_all(&bytes))
.map_err(KatraError::from)?;
Ok(())
}
pub fn write(&mut self, record: &TraceRecord) -> Result<()> {
self.write_raw(record)?;
self.count += 1;
Ok(())
}
pub fn write_event(&mut self, event: &katra_core::TraceEvent) -> Result<()> {
self.write(&TraceRecord::Event(event.clone()))
}
pub fn count(&self) -> u64 {
self.count
}
pub fn flush_buffered(&mut self) -> Result<()> {
self.inner.flush().map_err(KatraError::from)
}
pub fn finish(&mut self, summary: &TraceSummary) -> Result<()> {
self.write(&TraceRecord::SessionSummary(summary.clone()))?;
self.inner.flush().map_err(KatraError::from)
}
pub fn close(self) -> Result<()> {
let mut w = self;
w.inner.flush().map_err(KatraError::from)?;
drop(w.inner);
Ok(())
}
}
pub fn default_header(
workload: &str,
capture: &katra_core::CaptureOptions,
notes: Vec<String>,
) -> TraceHeader {
let now_wall = katra_core::wall_now_ns();
let now_mono = katra_core::monotonic_now_ns();
TraceHeader {
format_version: FORMAT_VERSION,
schema_hash: SCHEMA_HASH,
tool_version: format!("katra3d {}", katra_core::KATRA_VERSION),
capture_options: capture.clone(),
start_wall_ns: now_wall,
start_mono_ns: now_mono,
process_id: std::process::id() as u64,
workload: workload.to_string(),
host: crate::reader::host_info(),
notes,
}
}