mod csv;
mod units;
pub use csv::{load_separate_files, load_silent_csv, load_two_column_csv};
pub use units::{to_nanoseconds, TimeUnit};
use std::fmt;
#[derive(Debug)]
pub enum DataError {
Io(std::io::Error),
Parse {
line: usize,
message: String,
},
MissingGroup {
expected: String,
found: Vec<String>,
},
InsufficientSamples {
group: String,
got: usize,
min: usize,
},
InvalidValue {
line: usize,
value: String,
},
}
impl fmt::Display for DataError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DataError::Io(e) => write!(f, "IO error: {}", e),
DataError::Parse { line, message } => {
write!(f, "Parse error at line {}: {}", line, message)
}
DataError::MissingGroup { expected, found } => {
write!(
f,
"Missing group '{}' in data. Found groups: {:?}",
expected, found
)
}
DataError::InsufficientSamples { group, got, min } => {
write!(
f,
"Insufficient samples for group '{}': got {}, need at least {}",
group, got, min
)
}
DataError::InvalidValue { line, value } => {
write!(f, "Invalid timing value at line {}: '{}'", line, value)
}
}
}
}
impl std::error::Error for DataError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
DataError::Io(e) => Some(e),
_ => None,
}
}
}
impl From<std::io::Error> for DataError {
fn from(e: std::io::Error) -> Self {
DataError::Io(e)
}
}
#[derive(Debug, Clone)]
pub struct TimingData {
pub baseline_samples: Vec<u64>,
pub test_samples: Vec<u64>,
pub unit: TimeUnit,
pub metadata: Option<DataMetadata>,
}
impl TimingData {
pub fn new(baseline: Vec<u64>, test: Vec<u64>, unit: TimeUnit) -> Self {
Self {
baseline_samples: baseline,
test_samples: test,
unit,
metadata: None,
}
}
pub fn with_metadata(
baseline: Vec<u64>,
test: Vec<u64>,
unit: TimeUnit,
metadata: DataMetadata,
) -> Self {
Self {
baseline_samples: baseline,
test_samples: test,
unit,
metadata: Some(metadata),
}
}
pub fn min_samples(&self) -> usize {
self.baseline_samples.len().min(self.test_samples.len())
}
pub fn total_samples(&self) -> usize {
self.baseline_samples.len() + self.test_samples.len()
}
pub fn validate(&self, min_samples: usize) -> Result<(), DataError> {
if self.baseline_samples.len() < min_samples {
return Err(DataError::InsufficientSamples {
group: "baseline".to_string(),
got: self.baseline_samples.len(),
min: min_samples,
});
}
if self.test_samples.len() < min_samples {
return Err(DataError::InsufficientSamples {
group: "test".to_string(),
got: self.test_samples.len(),
min: min_samples,
});
}
Ok(())
}
pub fn to_nanoseconds(&self, ns_per_unit: f64) -> (Vec<f64>, Vec<f64>) {
let baseline_ns: Vec<f64> = self
.baseline_samples
.iter()
.map(|&s| s as f64 * ns_per_unit)
.collect();
let test_ns: Vec<f64> = self
.test_samples
.iter()
.map(|&s| s as f64 * ns_per_unit)
.collect();
(baseline_ns, test_ns)
}
}
#[derive(Debug, Clone, Default)]
pub struct DataMetadata {
pub source: Option<String>,
pub group_labels: Option<(String, String)>,
pub context: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_timing_data_validation() {
let data = TimingData::new(vec![1, 2, 3], vec![4, 5], TimeUnit::Cycles);
assert!(data.validate(2).is_ok());
assert!(data.validate(3).is_err()); }
#[test]
fn test_timing_data_to_nanoseconds() {
let data = TimingData::new(vec![100, 200], vec![150, 250], TimeUnit::Cycles);
let (baseline_ns, test_ns) = data.to_nanoseconds(0.5);
assert_eq!(baseline_ns, vec![50.0, 100.0]);
assert_eq!(test_ns, vec![75.0, 125.0]);
}
}