diff --git a/runtime_tracing/Cargo.toml b/runtime_tracing/Cargo.toml index 665bd2e..1be984b 100644 --- a/runtime_tracing/Cargo.toml +++ b/runtime_tracing/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "runtime_tracing" version = "0.13.0" -edition = "2021" +edition = "2024" authors = ["Metacraft Labs Ltd"] description = "A library for the schema and tracing helpers for the CodeTracer db trace format" readme = "README.md" @@ -20,6 +20,9 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_repr = "0.1" capnp = "0.21.1" +zeekstd = "0.5.0" +cbor4ii = { version = "1.0.0", features = ["serde1", "use_std"] } +fscommon = "0.1.1" [build-dependencies] capnpc = "0.21.0" diff --git a/runtime_tracing/src/abstract_trace_writer.rs b/runtime_tracing/src/abstract_trace_writer.rs new file mode 100644 index 0000000..4f350bf --- /dev/null +++ b/runtime_tracing/src/abstract_trace_writer.rs @@ -0,0 +1,311 @@ +use std::{ + collections::HashMap, + env, + error::Error, + fs, + path::{Path, PathBuf}, +}; + +use crate::{ + AssignCellRecord, AssignCompoundItemRecord, AssignmentRecord, CallRecord, CellValueRecord, CompoundValueRecord, FullValueRecord, FunctionId, + FunctionRecord, Line, NONE_TYPE_ID, PathId, RValue, RecordEvent, ReturnRecord, StepRecord, TraceLowLevelEvent, TraceMetadata, TypeId, TypeKind, + TypeRecord, TypeSpecificInfo, VariableCellRecord, VariableId, tracer::TOP_LEVEL_FUNCTION_ID, +}; + +pub struct AbstractTraceWriterData { + // trace metadata: + pub workdir: PathBuf, + pub program: String, + pub args: Vec, + // internal tracer state: + pub path_list: Vec, + pub function_list: Vec<(String, PathId, Line)>, + + pub paths: HashMap, + pub functions: HashMap, + pub variables: HashMap, + pub types: HashMap, + + pub trace_metadata_path: Option, + pub trace_paths_path: Option, +} + +impl AbstractTraceWriterData { + pub fn new(program: &str, args: &[String]) -> Self { + AbstractTraceWriterData { + workdir: env::current_dir().expect("can access the current dir"), + program: program.to_string(), + args: args.to_vec(), + + path_list: vec![], + function_list: vec![], + paths: HashMap::new(), + functions: HashMap::new(), + variables: HashMap::new(), + types: HashMap::new(), + + trace_metadata_path: None, + trace_paths_path: None, + } + } +} + +pub trait AbstractTraceWriter { + fn get_data(&self) -> &AbstractTraceWriterData; + fn get_mut_data(&mut self) -> &mut AbstractTraceWriterData; + + fn add_event(&mut self, event: TraceLowLevelEvent); + fn append_events(&mut self, events: &mut Vec); + + fn begin_writing_trace_metadata(&mut self, path: &Path) -> Result<(), Box> { + self.get_mut_data().trace_metadata_path = Some(path.to_path_buf()); + Ok(()) + } + + fn begin_writing_trace_paths(&mut self, path: &Path) -> Result<(), Box> { + self.get_mut_data().trace_paths_path = Some(path.to_path_buf()); + Ok(()) + } + + fn start(&mut self, path: &std::path::Path, line: Line) { + let function_id = self.ensure_function_id("", path, line); + self.register_call(function_id, vec![]); + assert!(function_id == TOP_LEVEL_FUNCTION_ID); + + // probably we let the user choose, as different languages have + // different base types/names + // assert!(EXAMPLE_INT_TYPE_ID == self.load_type_id(TypeKind::Int, "Int")); + // assert!(EXAMPLE_FLOAT_TYPE_ID == self.load_type_id(TypeKind::Float, "Float")); + // assert!(EXAMPLE_BOOL_TYPE_ID == self.load_type_id(TypeKind::Bool, "Bool")); + // assert!(EXAMPLE_STRING_TYPE_ID == self.load_type_id(TypeKind::Bool, "String")); + assert!(NONE_TYPE_ID == self.ensure_type_id(TypeKind::None, "None")); + } + + fn ensure_path_id(&mut self, path: &std::path::Path) -> PathId { + if !self.get_data().paths.contains_key(path) { + let mut_data = self.get_mut_data(); + mut_data.paths.insert(path.to_path_buf(), PathId(mut_data.paths.len())); + self.register_path(path); + } + *self.get_data().paths.get(path).unwrap() + } + + fn ensure_function_id(&mut self, function_name: &str, path: &std::path::Path, line: Line) -> FunctionId { + if !self.get_data().functions.contains_key(function_name) { + // same function names for different path line? TODO + let mut_data = self.get_mut_data(); + mut_data.functions.insert(function_name.to_string(), FunctionId(mut_data.functions.len())); + self.register_function(function_name, path, line); + } + *self.get_data().functions.get(function_name).unwrap() + } + + fn ensure_type_id(&mut self, kind: crate::TypeKind, lang_type: &str) -> TypeId { + let typ = self.to_raw_type(kind, lang_type); + self.ensure_raw_type_id(typ) + } + + fn ensure_raw_type_id(&mut self, typ: crate::TypeRecord) -> TypeId { + if !self.get_data().types.contains_key(&typ.lang_type) { + let mut_data = self.get_mut_data(); + mut_data.types.insert(typ.lang_type.clone(), TypeId(mut_data.types.len())); + self.register_raw_type(typ.clone()); + } + *self.get_data().types.get(&typ.lang_type).unwrap() + } + + fn ensure_variable_id(&mut self, variable_name: &str) -> VariableId { + if !self.get_data().variables.contains_key(variable_name) { + let mut_data = self.get_mut_data(); + mut_data.variables.insert(variable_name.to_string(), VariableId(mut_data.variables.len())); + self.register_variable_name(variable_name); + } + *self.get_data().variables.get(variable_name).unwrap() + } + + fn register_path(&mut self, path: &std::path::Path) { + self.get_mut_data().path_list.push(path.to_path_buf()); + self.add_event(TraceLowLevelEvent::Path(path.to_path_buf())); + } + + fn register_function(&mut self, name: &str, path: &std::path::Path, line: Line) { + let path_id = self.ensure_path_id(path); + self.get_mut_data().function_list.push((name.to_string(), path_id, line)); + self.add_event(TraceLowLevelEvent::Function(FunctionRecord { + name: name.to_string(), + path_id, + line, + })); + } + + fn register_step(&mut self, path: &std::path::Path, line: Line) { + let path_id = self.ensure_path_id(path); + self.add_event(TraceLowLevelEvent::Step(StepRecord { path_id, line })); + } + + fn register_call(&mut self, function_id: FunctionId, args: Vec) { + // register a step for each call, the backend expects this for + // non-toplevel calls, so + // we ensure it directly from register_call + if function_id != TOP_LEVEL_FUNCTION_ID { + for arg in &args { + self.register_full_value(arg.variable_id, arg.value.clone()); + } + let function = &self.get_data().function_list[function_id.0]; + self.add_event(TraceLowLevelEvent::Step(StepRecord { + path_id: function.1, + line: function.2, + })); + } + // the actual call event: + self.add_event(TraceLowLevelEvent::Call(CallRecord { function_id, args })); + } + + fn arg(&mut self, name: &str, value: crate::ValueRecord) -> FullValueRecord { + let variable_id = self.ensure_variable_id(name); + FullValueRecord { variable_id, value } + } + + fn register_return(&mut self, return_value: crate::ValueRecord) { + self.add_event(TraceLowLevelEvent::Return(ReturnRecord { return_value })); + } + + fn register_special_event(&mut self, kind: crate::EventLogKind, content: &str) { + self.add_event(TraceLowLevelEvent::Event(RecordEvent { + kind, + metadata: "".to_string(), + content: content.to_string(), + })); + } + + fn to_raw_type(&self, kind: crate::TypeKind, lang_type: &str) -> crate::TypeRecord { + TypeRecord { + kind, + lang_type: lang_type.to_string(), + specific_info: TypeSpecificInfo::None, + } + } + + fn register_type(&mut self, kind: crate::TypeKind, lang_type: &str) { + let typ = self.to_raw_type(kind, lang_type); + self.add_event(TraceLowLevelEvent::Type(typ)); + } + + fn register_raw_type(&mut self, typ: crate::TypeRecord) { + self.add_event(TraceLowLevelEvent::Type(typ)); + } + + fn register_asm(&mut self, instructions: &[String]) { + self.add_event(TraceLowLevelEvent::Asm(instructions.to_vec())); + } + + fn register_variable_with_full_value(&mut self, name: &str, value: crate::ValueRecord) { + let variable_id = self.ensure_variable_id(name); + self.register_full_value(variable_id, value); + } + + fn register_variable_name(&mut self, variable_name: &str) { + self.add_event(TraceLowLevelEvent::VariableName(variable_name.to_string())); + } + + fn register_full_value(&mut self, variable_id: VariableId, value: crate::ValueRecord) { + self.add_event(TraceLowLevelEvent::Value(FullValueRecord { variable_id, value })); + } + + fn register_compound_value(&mut self, place: crate::Place, value: crate::ValueRecord) { + self.add_event(TraceLowLevelEvent::CompoundValue(CompoundValueRecord { place, value })); + } + + fn register_cell_value(&mut self, place: crate::Place, value: crate::ValueRecord) { + self.add_event(TraceLowLevelEvent::CellValue(CellValueRecord { place, value })); + } + + fn assign_compound_item(&mut self, place: crate::Place, index: usize, item_place: crate::Place) { + self.add_event(TraceLowLevelEvent::AssignCompoundItem(AssignCompoundItemRecord { + place, + index, + item_place, + })); + } + + fn assign_cell(&mut self, place: crate::Place, new_value: crate::ValueRecord) { + self.add_event(TraceLowLevelEvent::AssignCell(AssignCellRecord { place, new_value })); + } + + fn register_variable(&mut self, variable_name: &str, place: crate::Place) { + let variable_id = self.ensure_variable_id(variable_name); + self.add_event(TraceLowLevelEvent::VariableCell(VariableCellRecord { variable_id, place })); + } + + fn drop_variable(&mut self, variable_name: &str) { + let variable_id = self.ensure_variable_id(variable_name); + self.add_event(TraceLowLevelEvent::DropVariable(variable_id)); + } + + // history event helpers + fn assign(&mut self, variable_name: &str, rvalue: crate::RValue, pass_by: crate::PassBy) { + let variable_id = self.ensure_variable_id(variable_name); + self.add_event(TraceLowLevelEvent::Assignment(AssignmentRecord { + to: variable_id, + from: rvalue, + pass_by, + })); + } + + fn bind_variable(&mut self, variable_name: &str, place: crate::Place) { + let variable_id = self.ensure_variable_id(variable_name); + self.add_event(TraceLowLevelEvent::BindVariable(crate::BindVariableRecord { variable_id, place })); + } + + fn drop_variables(&mut self, variable_names: &[String]) { + let variable_ids: Vec = variable_names + .to_vec() + .iter() + .map(|variable_name| self.ensure_variable_id(variable_name)) + .collect(); + self.add_event(TraceLowLevelEvent::DropVariables(variable_ids)) + } + + fn simple_rvalue(&mut self, variable_name: &str) -> crate::RValue { + let variable_id = self.ensure_variable_id(variable_name); + RValue::Simple(variable_id) + } + + fn compound_rvalue(&mut self, variable_dependencies: &[String]) -> crate::RValue { + let variable_ids: Vec = variable_dependencies + .to_vec() + .iter() + .map(|variable_dependency| self.ensure_variable_id(variable_dependency)) + .collect(); + RValue::Compound(variable_ids) + } + + fn drop_last_step(&mut self) { + self.add_event(TraceLowLevelEvent::DropLastStep); + } + + fn finish_writing_trace_metadata(&mut self) -> Result<(), Box> { + if let Some(path) = &self.get_data().trace_metadata_path { + let trace_metadata = TraceMetadata { + program: self.get_data().program.clone(), + args: self.get_data().args.clone(), + workdir: self.get_data().workdir.clone(), + }; + let json = serde_json::to_string(&trace_metadata)?; + fs::write(path, json)?; + Ok(()) + } else { + panic!("finish_writing_trace_metadata() called without previous call to begin_writing_trace_metadata()"); + } + } + + fn finish_writing_trace_paths(&mut self) -> Result<(), Box> { + if let Some(path) = &self.get_data().trace_paths_path { + let json = serde_json::to_string(&self.get_data().path_list)?; + fs::write(path, json)?; + Ok(()) + } else { + panic!("finish_writing_trace_paths() called without previous call to begin_writing_trace_paths()"); + } + } +} diff --git a/runtime_tracing/src/capnptrace.rs b/runtime_tracing/src/capnptrace.rs index 7b3a4aa..5005f77 100644 --- a/runtime_tracing/src/capnptrace.rs +++ b/runtime_tracing/src/capnptrace.rs @@ -7,7 +7,7 @@ use std::str::FromStr; /// The next 3 bytes are reserved/version info. In the initial version, they are zero. Non-zero values might /// indicate incompatible future versions. /// The header is 8 bytes in size, ensuring 64-bit alignment for the rest of the file. -const HEADER: &[u8] = &[0xC0, 0xDE, 0x72, 0xAC, 0xE2, 0x00, 0x00, 0x00]; +pub const HEADER: &[u8] = &[0xC0, 0xDE, 0x72, 0xAC, 0xE2, 0x00, 0x00, 0x00]; impl From for trace::TypeKind { fn from(item: crate::TypeKind) -> Self { diff --git a/runtime_tracing/src/cbor_zstd_reader.rs b/runtime_tracing/src/cbor_zstd_reader.rs new file mode 100644 index 0000000..18f2d9e --- /dev/null +++ b/runtime_tracing/src/cbor_zstd_reader.rs @@ -0,0 +1,38 @@ +use std::io::{self, BufRead, BufReader, Read, Seek, Write}; + +use fscommon::StreamSlice; +use zeekstd::Decoder; + +use crate::{TraceLowLevelEvent, cbor_zstd_writer::HEADERV1}; + +fn is_at_eof(reader: &mut R) -> io::Result { + let buffer = reader.fill_buf()?; + Ok(buffer.is_empty()) +} + +pub fn read_trace(input: &mut (impl Read + Write + Seek)) -> Result, Box> { + let end_pos = input.seek(io::SeekFrom::End(0))?; + input.seek(io::SeekFrom::Start(0))?; + + let mut header_buf = [0; 8]; + let mut buf_reader = BufReader::new(&mut *input); + buf_reader.read_exact(&mut header_buf)?; + if header_buf != HEADERV1 { + panic!("Invalid file header (wrong file format or incompatible version)"); + } + + input.seek(io::SeekFrom::Start(0))?; + let input2 = StreamSlice::new(&mut *input, 8, end_pos)?; + + let decoder = Decoder::new(input2)?; + let mut buf_reader = BufReader::new(decoder); + + let mut result: Vec = vec![]; + + while !is_at_eof(&mut buf_reader)? { + let obj = cbor4ii::serde::from_reader::(&mut buf_reader)?; + result.push(obj); + } + + Ok(result) +} diff --git a/runtime_tracing/src/cbor_zstd_writer.rs b/runtime_tracing/src/cbor_zstd_writer.rs new file mode 100644 index 0000000..0bd10d8 --- /dev/null +++ b/runtime_tracing/src/cbor_zstd_writer.rs @@ -0,0 +1,81 @@ +use std::{fs::File, io::Write, path::PathBuf}; +use zeekstd::Encoder; + +use crate::{ + TraceLowLevelEvent, + abstract_trace_writer::{AbstractTraceWriter, AbstractTraceWriterData}, + trace_writer::TraceWriter, +}; + +/// The next 3 bytes are reserved/version info. +/// The header is 8 bytes in size, ensuring 64-bit alignment for the rest of the file. +pub const HEADERV1: &[u8] = &[ + 0xC0, 0xDE, 0x72, 0xAC, 0xE2, // The first 5 bytes identify the file as a CodeTracer file (hex l33tsp33k - C0DE72ACE2 for "CodeTracer"). + 0x01, // Indicates version 1 of the file format + 0x00, 0x00, +]; // Reserved, must be zero in this version. + +pub struct CborZstdTraceWriter<'a> { + base: AbstractTraceWriterData, + + trace_events_path: Option, + trace_events_file_zstd_encoder: Option>, +} + +impl CborZstdTraceWriter<'_> { + /// Create a new tracer instance for the given program and arguments. + pub fn new(program: &str, args: &[String]) -> Self { + CborZstdTraceWriter { + base: AbstractTraceWriterData::new(program, args), + + trace_events_path: None, + trace_events_file_zstd_encoder: None, + } + } +} + +impl AbstractTraceWriter for CborZstdTraceWriter<'_> { + fn get_data(&self) -> &AbstractTraceWriterData { + &self.base + } + + fn get_mut_data(&mut self) -> &mut AbstractTraceWriterData { + &mut self.base + } + + fn add_event(&mut self, event: TraceLowLevelEvent) { + let buf: Vec = Vec::new(); + let q = cbor4ii::serde::to_vec(buf, &event).unwrap(); + if let Some(enc) = &mut self.trace_events_file_zstd_encoder { + enc.write_all(&q).unwrap(); + } + } + + fn append_events(&mut self, events: &mut Vec) { + for e in events { + AbstractTraceWriter::add_event(self, e.clone()); + } + } +} + +impl TraceWriter for CborZstdTraceWriter<'_> { + fn begin_writing_trace_events(&mut self, path: &std::path::Path) -> Result<(), Box> { + let pb = path.to_path_buf(); + self.trace_events_path = Some(pb.clone()); + let mut file_output = std::fs::File::create(pb)?; + file_output.write_all(HEADERV1)?; + self.trace_events_file_zstd_encoder = Some(Encoder::new(file_output)?); + + Ok(()) + } + + fn finish_writing_trace_events(&mut self) -> Result<(), Box> { + if let Some(enc) = self.trace_events_file_zstd_encoder.take() { + enc.finish()?; + + Ok(()) + } else { + panic!("finish_writing_trace_events() called without previous call to begin_writing_trace_events()"); + } + } +} diff --git a/runtime_tracing/src/lib.rs b/runtime_tracing/src/lib.rs index b9393de..3afd539 100644 --- a/runtime_tracing/src/lib.rs +++ b/runtime_tracing/src/lib.rs @@ -8,13 +8,21 @@ //! This crate provides the [`Tracer`] type for emitting trace events and a //! collection of serializable structures describing the trace format. //! The format is documented in `docs/` and the README. +mod abstract_trace_writer; mod base64; mod capnptrace; +mod cbor_zstd_reader; +mod cbor_zstd_writer; +mod non_streaming_trace_writer; +mod trace_readers; +mod trace_writer; mod tracer; mod types; -pub use crate::tracer::{ - create_trace_reader, create_trace_writer, NonStreamingTraceWriter, TraceEventsFileFormat, TraceReader, TraceWriter, NONE_TYPE_ID, NONE_VALUE, -}; + +pub use crate::non_streaming_trace_writer::NonStreamingTraceWriter; +pub use crate::trace_readers::TraceReader; +pub use crate::trace_writer::TraceWriter; +pub use crate::tracer::{NONE_TYPE_ID, NONE_VALUE, TraceEventsFileFormat, create_trace_reader, create_trace_writer}; pub use crate::types::*; pub mod trace_capnp { diff --git a/runtime_tracing/src/non_streaming_trace_writer.rs b/runtime_tracing/src/non_streaming_trace_writer.rs new file mode 100644 index 0000000..5a00c77 --- /dev/null +++ b/runtime_tracing/src/non_streaming_trace_writer.rs @@ -0,0 +1,89 @@ +use std::{ + error::Error, + fs, + path::{Path, PathBuf}, +}; + +use crate::{ + TraceEventsFileFormat, TraceLowLevelEvent, TraceWriter, + abstract_trace_writer::{AbstractTraceWriter, AbstractTraceWriterData}, +}; + +/// State machine used to record [`TraceLowLevelEvent`]s. +/// +/// A `NonStreamingTraceWriter` instance accumulates events in memory and stores them on +/// disk when the `finish_writing_trace_*` methods are called. The in-memory event list +/// is exposed publicly. +pub struct NonStreamingTraceWriter { + base: AbstractTraceWriterData, + + // trace events + pub events: Vec, + + format: TraceEventsFileFormat, + trace_events_path: Option, +} + +impl NonStreamingTraceWriter { + /// Create a new tracer instance for the given program and arguments. + pub fn new(program: &str, args: &[String]) -> Self { + NonStreamingTraceWriter { + base: AbstractTraceWriterData::new(program, args), + + events: vec![], + + format: TraceEventsFileFormat::Binary, + trace_events_path: None, + } + } + + pub fn set_format(&mut self, format: TraceEventsFileFormat) { + self.format = format; + } +} + +impl AbstractTraceWriter for NonStreamingTraceWriter { + fn get_data(&self) -> &AbstractTraceWriterData { + &self.base + } + + fn get_mut_data(&mut self) -> &mut AbstractTraceWriterData { + &mut self.base + } + + fn add_event(&mut self, event: TraceLowLevelEvent) { + self.events.push(event) + } + + fn append_events(&mut self, events: &mut Vec) { + self.events.append(events) + } +} + +impl TraceWriter for NonStreamingTraceWriter { + fn begin_writing_trace_events(&mut self, path: &Path) -> Result<(), Box> { + self.trace_events_path = Some(path.to_path_buf()); + Ok(()) + } + + fn finish_writing_trace_events(&mut self) -> Result<(), Box> { + if let Some(path) = &self.trace_events_path { + match self.format { + TraceEventsFileFormat::Json => { + let json = serde_json::to_string(&self.events)?; + fs::write(path, json)?; + } + TraceEventsFileFormat::BinaryV0 => { + let mut file = fs::File::create(path)?; + crate::capnptrace::write_trace(&self.events, &mut file)?; + } + TraceEventsFileFormat::Binary => { + unreachable!() + } + } + Ok(()) + } else { + panic!("finish_writing_trace_events() called without previous call to begin_writing_trace_events()"); + } + } +} diff --git a/runtime_tracing/src/trace_readers.rs b/runtime_tracing/src/trace_readers.rs new file mode 100644 index 0000000..40d4821 --- /dev/null +++ b/runtime_tracing/src/trace_readers.rs @@ -0,0 +1,58 @@ +use std::{ + error::Error, + fs::{self, File}, + io::{BufReader, Read, Seek, SeekFrom}, + path::Path, +}; + +use crate::{TraceEventsFileFormat, TraceLowLevelEvent, capnptrace::HEADER, cbor_zstd_writer::HEADERV1}; + +pub trait TraceReader { + fn load_trace_events(&mut self, path: &Path) -> Result, Box>; +} + +pub struct JsonTraceReader {} + +impl TraceReader for JsonTraceReader { + fn load_trace_events(&mut self, path: &Path) -> Result, Box> { + let json = std::fs::read_to_string(path)?; + Ok(serde_json::from_str(&json)?) + } +} + +pub struct BinaryTraceReader {} + +fn detect_bin_file_version(input: &mut File) -> Result, Box> { + input.seek(SeekFrom::Start(0))?; + let mut header_buf = [0; 8]; + input.read_exact(&mut header_buf)?; + input.seek(SeekFrom::Start(0))?; + + if header_buf == HEADER { + Ok(Some(TraceEventsFileFormat::BinaryV0)) + } else if header_buf == HEADERV1 { + Ok(Some(TraceEventsFileFormat::Binary)) + } else { + Ok(None) + } +} + +impl TraceReader for BinaryTraceReader { + fn load_trace_events(&mut self, path: &Path) -> Result, Box> { + let mut file = fs::File::open(path)?; + let ver = detect_bin_file_version(&mut file)?; + match ver { + Some(TraceEventsFileFormat::BinaryV0) => { + let mut buf_reader = BufReader::new(file); + Ok(crate::capnptrace::read_trace(&mut buf_reader)?) + } + Some(TraceEventsFileFormat::Binary) => Ok(crate::cbor_zstd_reader::read_trace(&mut file)?), + Some(TraceEventsFileFormat::Json) => { + unreachable!() + } + None => { + panic!("Invalid file header (wrong file format or incompatible version)"); + } + } + } +} diff --git a/runtime_tracing/src/trace_writer.rs b/runtime_tracing/src/trace_writer.rs new file mode 100644 index 0000000..748f92b --- /dev/null +++ b/runtime_tracing/src/trace_writer.rs @@ -0,0 +1,130 @@ +use std::{error::Error, path::Path}; + +use crate::{ + EventLogKind, FullValueRecord, FunctionId, Line, PassBy, PathId, Place, RValue, TraceLowLevelEvent, TypeId, TypeKind, TypeRecord, ValueRecord, + VariableId, abstract_trace_writer::AbstractTraceWriter, +}; + +pub trait TraceWriter: AbstractTraceWriter { + fn begin_writing_trace_metadata(&mut self, path: &Path) -> Result<(), Box> { + AbstractTraceWriter::begin_writing_trace_metadata(self, path) + } + fn begin_writing_trace_events(&mut self, path: &Path) -> Result<(), Box>; + fn begin_writing_trace_paths(&mut self, path: &Path) -> Result<(), Box> { + AbstractTraceWriter::begin_writing_trace_paths(self, path) + } + + fn start(&mut self, path: &Path, line: Line) { + AbstractTraceWriter::start(self, path, line) + } + fn ensure_path_id(&mut self, path: &Path) -> PathId { + AbstractTraceWriter::ensure_path_id(self, path) + } + fn ensure_function_id(&mut self, function_name: &str, path: &Path, line: Line) -> FunctionId { + AbstractTraceWriter::ensure_function_id(self, function_name, path, line) + } + fn ensure_type_id(&mut self, kind: TypeKind, lang_type: &str) -> TypeId { + AbstractTraceWriter::ensure_type_id(self, kind, lang_type) + } + fn ensure_raw_type_id(&mut self, typ: TypeRecord) -> TypeId { + AbstractTraceWriter::ensure_raw_type_id(self, typ) + } + fn ensure_variable_id(&mut self, variable_name: &str) -> VariableId { + AbstractTraceWriter::ensure_variable_id(self, variable_name) + } + fn register_path(&mut self, path: &Path) { + AbstractTraceWriter::register_path(self, path) + } + fn register_function(&mut self, name: &str, path: &Path, line: Line) { + AbstractTraceWriter::register_function(self, name, path, line) + } + fn register_step(&mut self, path: &Path, line: Line) { + AbstractTraceWriter::register_step(self, path, line) + } + fn register_call(&mut self, function_id: FunctionId, args: Vec) { + AbstractTraceWriter::register_call(self, function_id, args) + } + fn arg(&mut self, name: &str, value: ValueRecord) -> FullValueRecord { + AbstractTraceWriter::arg(self, name, value) + } + fn register_return(&mut self, return_value: ValueRecord) { + AbstractTraceWriter::register_return(self, return_value) + } + // TODO: add metadata arg + fn register_special_event(&mut self, kind: EventLogKind, content: &str) { + AbstractTraceWriter::register_special_event(self, kind, content) + } + fn to_raw_type(&self, kind: TypeKind, lang_type: &str) -> TypeRecord { + AbstractTraceWriter::to_raw_type(self, kind, lang_type) + } + fn register_type(&mut self, kind: TypeKind, lang_type: &str) { + AbstractTraceWriter::register_type(self, kind, lang_type) + } + fn register_raw_type(&mut self, typ: TypeRecord) { + AbstractTraceWriter::register_raw_type(self, typ) + } + fn register_asm(&mut self, instructions: &[String]) { + AbstractTraceWriter::register_asm(self, instructions) + } + fn register_variable_with_full_value(&mut self, name: &str, value: ValueRecord) { + AbstractTraceWriter::register_variable_with_full_value(self, name, value) + } + fn register_variable_name(&mut self, variable_name: &str) { + AbstractTraceWriter::register_variable_name(self, variable_name) + } + fn register_full_value(&mut self, variable_id: VariableId, value: ValueRecord) { + AbstractTraceWriter::register_full_value(self, variable_id, value) + } + fn register_compound_value(&mut self, place: Place, value: ValueRecord) { + AbstractTraceWriter::register_compound_value(self, place, value) + } + fn register_cell_value(&mut self, place: Place, value: ValueRecord) { + AbstractTraceWriter::register_cell_value(self, place, value) + } + fn assign_compound_item(&mut self, place: Place, index: usize, item_place: Place) { + AbstractTraceWriter::assign_compound_item(self, place, index, item_place) + } + fn assign_cell(&mut self, place: Place, new_value: ValueRecord) { + AbstractTraceWriter::assign_cell(self, place, new_value) + } + fn register_variable(&mut self, variable_name: &str, place: Place) { + AbstractTraceWriter::register_variable(self, variable_name, place) + } + fn drop_variable(&mut self, variable_name: &str) { + AbstractTraceWriter::drop_variable(self, variable_name) + } + // history event helpers + fn assign(&mut self, variable_name: &str, rvalue: RValue, pass_by: PassBy) { + AbstractTraceWriter::assign(self, variable_name, rvalue, pass_by) + } + fn bind_variable(&mut self, variable_name: &str, place: Place) { + AbstractTraceWriter::bind_variable(self, variable_name, place) + } + fn drop_variables(&mut self, variable_names: &[String]) { + AbstractTraceWriter::drop_variables(self, variable_names) + } + fn simple_rvalue(&mut self, variable_name: &str) -> RValue { + AbstractTraceWriter::simple_rvalue(self, variable_name) + } + fn compound_rvalue(&mut self, variable_dependencies: &[String]) -> RValue { + AbstractTraceWriter::compound_rvalue(self, variable_dependencies) + } + fn drop_last_step(&mut self) { + AbstractTraceWriter::drop_last_step(self) + } + + fn add_event(&mut self, event: TraceLowLevelEvent) { + AbstractTraceWriter::add_event(self, event) + } + fn append_events(&mut self, events: &mut Vec) { + AbstractTraceWriter::append_events(self, events) + } + + fn finish_writing_trace_metadata(&mut self) -> Result<(), Box> { + AbstractTraceWriter::finish_writing_trace_metadata(self) + } + fn finish_writing_trace_events(&mut self) -> Result<(), Box>; + fn finish_writing_trace_paths(&mut self) -> Result<(), Box> { + AbstractTraceWriter::finish_writing_trace_paths(self) + } +} diff --git a/runtime_tracing/src/tracer.rs b/runtime_tracing/src/tracer.rs index e384947..49c4233 100644 --- a/runtime_tracing/src/tracer.rs +++ b/runtime_tracing/src/tracer.rs @@ -1,120 +1,14 @@ //! Helper for generating trace events from a running program or interpreter. -use std::collections::HashMap; -use std::env; -use std::error::Error; -use std::fs; -use std::io::BufReader; -use std::path::{Path, PathBuf}; - -use crate::types::{ - AssignCellRecord, AssignCompoundItemRecord, AssignmentRecord, CallRecord, CellValueRecord, CompoundValueRecord, EventLogKind, FullValueRecord, - FunctionId, FunctionRecord, Line, PassBy, PathId, Place, RecordEvent, ReturnRecord, StepRecord, TraceLowLevelEvent, TraceMetadata, TypeId, - TypeKind, TypeRecord, TypeSpecificInfo, ValueRecord, VariableCellRecord, VariableId, -}; -use crate::RValue; - -pub trait TraceReader { - fn load_trace_events(&mut self, path: &Path) -> Result, Box>; -} - -pub struct JsonTraceReader {} - -impl TraceReader for JsonTraceReader { - fn load_trace_events(&mut self, path: &Path) -> Result, Box> { - let json = std::fs::read_to_string(path)?; - Ok(serde_json::from_str(&json)?) - } -} - -pub struct BinaryTraceReader {} - -impl TraceReader for BinaryTraceReader { - fn load_trace_events(&mut self, path: &Path) -> Result, Box> { - let file = fs::File::open(path)?; - let mut buf_reader = BufReader::new(file); - Ok(crate::capnptrace::read_trace(&mut buf_reader)?) - } -} - -pub trait TraceWriter { - fn begin_writing_trace_metadata(&mut self, path: &Path) -> Result<(), Box>; - fn begin_writing_trace_events(&mut self, path: &Path) -> Result<(), Box>; - fn begin_writing_trace_paths(&mut self, path: &Path) -> Result<(), Box>; - - fn start(&mut self, path: &Path, line: Line); - fn ensure_path_id(&mut self, path: &Path) -> PathId; - fn ensure_function_id(&mut self, function_name: &str, path: &Path, line: Line) -> FunctionId; - fn ensure_type_id(&mut self, kind: TypeKind, lang_type: &str) -> TypeId; - fn ensure_raw_type_id(&mut self, typ: TypeRecord) -> TypeId; - fn ensure_variable_id(&mut self, variable_name: &str) -> VariableId; - fn register_path(&mut self, path: &Path); - fn register_function(&mut self, name: &str, path: &Path, line: Line); - fn register_step(&mut self, path: &Path, line: Line); - fn register_call(&mut self, function_id: FunctionId, args: Vec); - fn arg(&mut self, name: &str, value: ValueRecord) -> FullValueRecord; - fn register_return(&mut self, return_value: ValueRecord); - // TODO: add metadata arg - fn register_special_event(&mut self, kind: EventLogKind, content: &str); - fn to_raw_type(&self, kind: TypeKind, lang_type: &str) -> TypeRecord; - fn register_type(&mut self, kind: TypeKind, lang_type: &str); - fn register_raw_type(&mut self, typ: TypeRecord); - fn register_asm(&mut self, instructions: &[String]); - fn register_variable_with_full_value(&mut self, name: &str, value: ValueRecord); - fn register_variable_name(&mut self, variable_name: &str); - fn register_full_value(&mut self, variable_id: VariableId, value: ValueRecord); - fn register_compound_value(&mut self, place: Place, value: ValueRecord); - fn register_cell_value(&mut self, place: Place, value: ValueRecord); - fn assign_compound_item(&mut self, place: Place, index: usize, item_place: Place); - fn assign_cell(&mut self, place: Place, new_value: ValueRecord); - fn register_variable(&mut self, variable_name: &str, place: Place); - fn drop_variable(&mut self, variable_name: &str); - // history event helpers - fn assign(&mut self, variable_name: &str, rvalue: RValue, pass_by: PassBy); - fn bind_variable(&mut self, variable_name: &str, place: Place); - fn drop_variables(&mut self, variable_names: &[String]); - fn simple_rvalue(&mut self, variable_name: &str) -> RValue; - fn compound_rvalue(&mut self, variable_dependencies: &[String]) -> RValue; - fn drop_last_step(&mut self); - - fn add_event(&mut self, event: TraceLowLevelEvent); - fn append_events(&mut self, events: &mut Vec); - - fn finish_writing_trace_metadata(&mut self) -> Result<(), Box>; - fn finish_writing_trace_events(&mut self) -> Result<(), Box>; - fn finish_writing_trace_paths(&mut self) -> Result<(), Box>; -} - -/// State machine used to record [`TraceLowLevelEvent`]s. -/// -/// A `NonStreamingTraceWriter` instance accumulates events in memory and stores them on -/// disk when the `finish_writing_trace_*` methods are called. The in-memory event list -/// is exposed publicly. -pub struct NonStreamingTraceWriter { - // trace metadata: - workdir: PathBuf, - program: String, - args: Vec, - // trace events - pub events: Vec, - // internal tracer state: - path_list: Vec, - function_list: Vec<(String, PathId, Line)>, - - paths: HashMap, - functions: HashMap, - variables: HashMap, - types: HashMap, - - format: TraceEventsFileFormat, - trace_metadata_path: Option, - trace_events_path: Option, - trace_paths_path: Option, -} +use crate::TraceWriter; +use crate::non_streaming_trace_writer::NonStreamingTraceWriter; +use crate::trace_readers::{BinaryTraceReader, JsonTraceReader, TraceReader}; +use crate::types::{FunctionId, TypeId, ValueRecord}; #[derive(Debug, Clone, Copy)] pub enum TraceEventsFileFormat { Json, + BinaryV0, Binary, } @@ -129,341 +23,20 @@ pub const NONE_VALUE: ValueRecord = ValueRecord::None { type_id: NONE_TYPE_ID }; pub const TOP_LEVEL_FUNCTION_ID: FunctionId = FunctionId(0); -impl NonStreamingTraceWriter { - /// Create a new tracer instance for the given program and arguments. - pub fn new(program: &str, args: &[String]) -> Self { - NonStreamingTraceWriter { - workdir: env::current_dir().expect("can access the current dir"), - program: program.to_string(), - args: args.to_vec(), - events: vec![], - - path_list: vec![], - function_list: vec![], - paths: HashMap::new(), - functions: HashMap::new(), - variables: HashMap::new(), - types: HashMap::new(), - - format: TraceEventsFileFormat::Binary, - trace_metadata_path: None, - trace_events_path: None, - trace_paths_path: None, - } - } - - pub fn set_format(&mut self, format: TraceEventsFileFormat) { - self.format = format; - } - - pub fn load_trace_events(&mut self, path: &Path, format: TraceEventsFileFormat) -> Result<(), Box> { - match format { - TraceEventsFileFormat::Json => { - let json = std::fs::read_to_string(path)?; - self.events = serde_json::from_str(&json)?; - } - TraceEventsFileFormat::Binary => { - let file = fs::File::open(path)?; - let mut buf_reader = BufReader::new(file); - self.events = crate::capnptrace::read_trace(&mut buf_reader)?; - } - } - Ok(()) - } -} - -impl TraceWriter for NonStreamingTraceWriter { - fn begin_writing_trace_metadata(&mut self, path: &Path) -> Result<(), Box> { - self.trace_metadata_path = Some(path.to_path_buf()); - Ok(()) - } - - fn begin_writing_trace_events(&mut self, path: &Path) -> Result<(), Box> { - self.trace_events_path = Some(path.to_path_buf()); - Ok(()) - } - - fn begin_writing_trace_paths(&mut self, path: &Path) -> Result<(), Box> { - self.trace_paths_path = Some(path.to_path_buf()); - Ok(()) - } - - /// Begin tracing of a program starting at the given source location. - fn start(&mut self, path: &Path, line: Line) { - let function_id = self.ensure_function_id("", path, line); - self.register_call(function_id, vec![]); - assert!(function_id == TOP_LEVEL_FUNCTION_ID); - - // probably we let the user choose, as different languages have - // different base types/names - // assert!(EXAMPLE_INT_TYPE_ID == self.load_type_id(TypeKind::Int, "Int")); - // assert!(EXAMPLE_FLOAT_TYPE_ID == self.load_type_id(TypeKind::Float, "Float")); - // assert!(EXAMPLE_BOOL_TYPE_ID == self.load_type_id(TypeKind::Bool, "Bool")); - // assert!(EXAMPLE_STRING_TYPE_ID == self.load_type_id(TypeKind::Bool, "String")); - assert!(NONE_TYPE_ID == self.ensure_type_id(TypeKind::None, "None")); - } - - fn ensure_path_id(&mut self, path: &Path) -> PathId { - if !self.paths.contains_key(path) { - self.paths.insert(path.to_path_buf(), PathId(self.paths.len())); - self.register_path(path); - } - *self.paths.get(path).unwrap() - } - - fn ensure_function_id(&mut self, function_name: &str, path: &Path, line: Line) -> FunctionId { - if !self.functions.contains_key(function_name) { - // same function names for different path line? TODO - self.functions.insert(function_name.to_string(), FunctionId(self.functions.len())); - self.register_function(function_name, path, line); - } - *self.functions.get(function_name).unwrap() - } - - fn ensure_type_id(&mut self, kind: TypeKind, lang_type: &str) -> TypeId { - let typ = self.to_raw_type(kind, lang_type); - self.ensure_raw_type_id(typ) - } - - fn ensure_raw_type_id(&mut self, typ: TypeRecord) -> TypeId { - if !self.types.contains_key(&typ.lang_type) { - self.types.insert(typ.lang_type.clone(), TypeId(self.types.len())); - self.register_raw_type(typ.clone()); - } - *self.types.get(&typ.lang_type).unwrap() - } - - fn ensure_variable_id(&mut self, variable_name: &str) -> VariableId { - if !self.variables.contains_key(variable_name) { - self.variables.insert(variable_name.to_string(), VariableId(self.variables.len())); - self.register_variable_name(variable_name); - } - *self.variables.get(variable_name).unwrap() - } - - fn register_path(&mut self, path: &Path) { - self.path_list.push(path.to_path_buf()); - self.events.push(TraceLowLevelEvent::Path(path.to_path_buf())); - } - - fn register_function(&mut self, name: &str, path: &Path, line: Line) { - let path_id = self.ensure_path_id(path); - self.function_list.push((name.to_string(), path_id, line)); - self.events.push(TraceLowLevelEvent::Function(FunctionRecord { - name: name.to_string(), - path_id, - line, - })); - } - - fn register_step(&mut self, path: &Path, line: Line) { - let path_id = self.ensure_path_id(path); - self.events.push(TraceLowLevelEvent::Step(StepRecord { path_id, line })); - } - - fn register_call(&mut self, function_id: FunctionId, args: Vec) { - // register a step for each call, the backend expects this for - // non-toplevel calls, so - // we ensure it directly from register_call - if function_id != TOP_LEVEL_FUNCTION_ID { - for arg in &args { - self.register_full_value(arg.variable_id, arg.value.clone()); - } - let function = &self.function_list[function_id.0]; - self.events.push(TraceLowLevelEvent::Step(StepRecord { - path_id: function.1, - line: function.2, - })); - } - // the actual call event: - self.events.push(TraceLowLevelEvent::Call(CallRecord { function_id, args })); - } - - fn arg(&mut self, name: &str, value: ValueRecord) -> FullValueRecord { - let variable_id = self.ensure_variable_id(name); - FullValueRecord { variable_id, value } - } - - fn register_return(&mut self, return_value: ValueRecord) { - self.events.push(TraceLowLevelEvent::Return(ReturnRecord { return_value })); - } - - fn register_special_event(&mut self, kind: EventLogKind, content: &str) { - self.events.push(TraceLowLevelEvent::Event(RecordEvent { - kind, - metadata: "".to_string(), - content: content.to_string(), - })); - } - - fn to_raw_type(&self, kind: TypeKind, lang_type: &str) -> TypeRecord { - TypeRecord { - kind, - lang_type: lang_type.to_string(), - specific_info: TypeSpecificInfo::None, - } - } - - fn register_type(&mut self, kind: TypeKind, lang_type: &str) { - let typ = self.to_raw_type(kind, lang_type); - self.events.push(TraceLowLevelEvent::Type(typ)); - } - - fn register_raw_type(&mut self, typ: TypeRecord) { - self.events.push(TraceLowLevelEvent::Type(typ)); - } - - fn register_asm(&mut self, instructions: &[String]) { - self.events.push(TraceLowLevelEvent::Asm(instructions.to_vec())); - } - - fn register_variable_with_full_value(&mut self, name: &str, value: ValueRecord) { - let variable_id = self.ensure_variable_id(name); - self.register_full_value(variable_id, value); - } - - fn register_variable_name(&mut self, variable_name: &str) { - self.events.push(TraceLowLevelEvent::VariableName(variable_name.to_string())); - } - - fn register_full_value(&mut self, variable_id: VariableId, value: ValueRecord) { - self.events.push(TraceLowLevelEvent::Value(FullValueRecord { variable_id, value })); - } - - fn register_compound_value(&mut self, place: Place, value: ValueRecord) { - self.events.push(TraceLowLevelEvent::CompoundValue(CompoundValueRecord { place, value })); - } - - fn register_cell_value(&mut self, place: Place, value: ValueRecord) { - self.events.push(TraceLowLevelEvent::CellValue(CellValueRecord { place, value })); - } - - fn assign_compound_item(&mut self, place: Place, index: usize, item_place: Place) { - self.events.push(TraceLowLevelEvent::AssignCompoundItem(AssignCompoundItemRecord { - place, - index, - item_place, - })); - } - fn assign_cell(&mut self, place: Place, new_value: ValueRecord) { - self.events.push(TraceLowLevelEvent::AssignCell(AssignCellRecord { place, new_value })); - } - - fn register_variable(&mut self, variable_name: &str, place: Place) { - let variable_id = self.ensure_variable_id(variable_name); - self.events - .push(TraceLowLevelEvent::VariableCell(VariableCellRecord { variable_id, place })); - } - - fn drop_variable(&mut self, variable_name: &str) { - let variable_id = self.ensure_variable_id(variable_name); - self.events.push(TraceLowLevelEvent::DropVariable(variable_id)); - } - - // history event helpers - fn assign(&mut self, variable_name: &str, rvalue: RValue, pass_by: PassBy) { - let variable_id = self.ensure_variable_id(variable_name); - self.events.push(TraceLowLevelEvent::Assignment(AssignmentRecord { - to: variable_id, - from: rvalue, - pass_by, - })); - } - - fn bind_variable(&mut self, variable_name: &str, place: Place) { - let variable_id = self.ensure_variable_id(variable_name); - self.events - .push(TraceLowLevelEvent::BindVariable(crate::BindVariableRecord { variable_id, place })); - } - - fn drop_variables(&mut self, variable_names: &[String]) { - let variable_ids: Vec = variable_names - .to_vec() - .iter() - .map(|variable_name| self.ensure_variable_id(variable_name)) - .collect(); - self.events.push(TraceLowLevelEvent::DropVariables(variable_ids)) - } - - fn simple_rvalue(&mut self, variable_name: &str) -> RValue { - let variable_id = self.ensure_variable_id(variable_name); - RValue::Simple(variable_id) - } - - fn compound_rvalue(&mut self, variable_dependencies: &[String]) -> RValue { - let variable_ids: Vec = variable_dependencies - .to_vec() - .iter() - .map(|variable_dependency| self.ensure_variable_id(variable_dependency)) - .collect(); - RValue::Compound(variable_ids) - } - - fn drop_last_step(&mut self) { - self.events.push(TraceLowLevelEvent::DropLastStep); - } - - fn add_event(&mut self, event: TraceLowLevelEvent) { - self.events.push(event) - } - - fn append_events(&mut self, events: &mut Vec) { - self.events.append(events); - } - - fn finish_writing_trace_metadata(&mut self) -> Result<(), Box> { - if let Some(path) = &self.trace_metadata_path { - let trace_metadata = TraceMetadata { - program: self.program.clone(), - args: self.args.clone(), - workdir: self.workdir.clone(), - }; - let json = serde_json::to_string(&trace_metadata)?; - fs::write(path, json)?; - Ok(()) - } else { - panic!("finish_writing_trace_metadata() called without previous call to begin_writing_trace_metadata()"); - } - } - - fn finish_writing_trace_events(&mut self) -> Result<(), Box> { - if let Some(path) = &self.trace_events_path { - match self.format { - TraceEventsFileFormat::Json => { - let json = serde_json::to_string(&self.events)?; - fs::write(path, json)?; - } - TraceEventsFileFormat::Binary => { - let mut file = fs::File::create(path)?; - crate::capnptrace::write_trace(&self.events, &mut file)?; - } - } - Ok(()) - } else { - panic!("finish_writing_trace_events() called without previous call to begin_writing_trace_events()"); - } - } - - fn finish_writing_trace_paths(&mut self) -> Result<(), Box> { - if let Some(path) = &self.trace_paths_path { - let json = serde_json::to_string(&self.path_list)?; - fs::write(path, json)?; - Ok(()) - } else { - panic!("finish_writing_trace_paths() called without previous call to begin_writing_trace_paths()"); - } - } -} - pub fn create_trace_reader(format: TraceEventsFileFormat) -> Box { match format { TraceEventsFileFormat::Json => Box::new(JsonTraceReader {}), - TraceEventsFileFormat::Binary => Box::new(BinaryTraceReader {}), + TraceEventsFileFormat::BinaryV0 | TraceEventsFileFormat::Binary => Box::new(BinaryTraceReader {}), } } pub fn create_trace_writer(program: &str, args: &[String], format: TraceEventsFileFormat) -> Box { - let mut result = Box::new(NonStreamingTraceWriter::new(program, args)); - result.set_format(format); - result + match format { + TraceEventsFileFormat::Json | TraceEventsFileFormat::BinaryV0 => { + let mut result = Box::new(NonStreamingTraceWriter::new(program, args)); + result.set_format(format); + result + } + TraceEventsFileFormat::Binary => Box::new(crate::cbor_zstd_writer::CborZstdTraceWriter::new(program, args)), + } } diff --git a/runtime_tracing/tests/binary_format.rs b/runtime_tracing/tests/binary_format.rs index ae818f4..e4804f8 100644 --- a/runtime_tracing/tests/binary_format.rs +++ b/runtime_tracing/tests/binary_format.rs @@ -1,19 +1,19 @@ -use runtime_tracing::{create_trace_reader, create_trace_writer, TraceEventsFileFormat}; +use runtime_tracing::{TraceEventsFileFormat, TraceWriter, create_trace_reader, create_trace_writer}; use std::fs; use std::path::Path; -#[test] -fn test_binary_roundtrip() { +fn test_binary_roundtrip(ver: TraceEventsFileFormat, binfile: &str) { let json_path = Path::new("tests/data/trace.json"); let mut json_reader = create_trace_reader(TraceEventsFileFormat::Json); let original = json_reader.load_trace_events(json_path).unwrap(); - let bin_path = Path::new("tests/data/trace.bin"); + let bin_path_str = format!("tests/data/{}", binfile); + let bin_path = Path::new(&bin_path_str); - let mut bin_writer = create_trace_writer("", &[], TraceEventsFileFormat::Binary); + let mut bin_writer = create_trace_writer("", &[], ver); bin_writer.begin_writing_trace_events(bin_path).unwrap(); - bin_writer.append_events(&mut original.clone()); + TraceWriter::append_events(bin_writer.as_mut(), &mut original.clone()); bin_writer.finish_writing_trace_events().unwrap(); let mut bin_reader = create_trace_reader(TraceEventsFileFormat::Binary); @@ -26,3 +26,13 @@ fn test_binary_roundtrip() { assert_eq!(orig_json, new_json); } + +#[test] +fn test_binary_roundtrip_v0() { + test_binary_roundtrip(TraceEventsFileFormat::BinaryV0, "trace.v0.bin"); +} + +#[test] +fn test_binary_roundtrip_v1() { + test_binary_roundtrip(TraceEventsFileFormat::Binary, "trace.v1.bin"); +} diff --git a/runtime_tracing_cli/src/main.rs b/runtime_tracing_cli/src/main.rs index 0877c1d..f3ceb81 100644 --- a/runtime_tracing_cli/src/main.rs +++ b/runtime_tracing_cli/src/main.rs @@ -2,7 +2,7 @@ use std::path::Path; use crate::fmt_trace_cmd::FmtTraceCommand; use clap::{Args, Parser, Subcommand}; -use runtime_tracing::{create_trace_reader, create_trace_writer, TraceEventsFileFormat}; +use runtime_tracing::{create_trace_reader, create_trace_writer, TraceEventsFileFormat, TraceWriter}; mod fmt_trace_cmd; #[derive(Debug, Clone, Args)] @@ -48,7 +48,7 @@ fn main() { let mut trace_writer = create_trace_writer("", &[], output_file_format); let mut trace_events = trace_reader.load_trace_events(Path::new(&convert_command.input_file)).unwrap(); trace_writer.begin_writing_trace_events(Path::new(&convert_command.output_file)).unwrap(); - trace_writer.append_events(&mut trace_events); + TraceWriter::append_events(trace_writer.as_mut(), &mut trace_events); trace_writer.finish_writing_trace_events().unwrap(); } RuntimeTracingCliCommand::FormatTrace(fmt_trace_cmd) => {