#![allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IoStage {
Read,
Decompress,
Validate,
Decrypt,
Buffer,
Write,
Custom(String),
}
impl IoStage {
#[must_use]
pub fn stage_name(&self) -> &str {
match self {
IoStage::Read => "read",
IoStage::Decompress => "decompress",
IoStage::Validate => "validate",
IoStage::Decrypt => "decrypt",
IoStage::Buffer => "buffer",
IoStage::Write => "write",
IoStage::Custom(name) => name.as_str(),
}
}
}
#[derive(Debug, Clone)]
pub struct IoResult {
pub bytes_processed: u64,
pub elapsed_ms: u64,
pub stages_executed: Vec<String>,
pub success: bool,
}
impl IoResult {
#[allow(clippy::cast_precision_loss)]
#[must_use]
pub fn throughput_mbps(&self) -> f64 {
if self.elapsed_ms == 0 {
return 0.0;
}
let bytes_f = self.bytes_processed as f64;
let secs = self.elapsed_ms as f64 / 1000.0;
(bytes_f / (1024.0 * 1024.0)) / secs
}
}
#[derive(Debug, Default)]
pub struct IoPipeline {
stages: Vec<IoStage>,
}
impl IoPipeline {
#[must_use]
pub fn new() -> Self {
Self { stages: Vec::new() }
}
pub fn add_stage(&mut self, stage: IoStage) -> &mut Self {
self.stages.push(stage);
self
}
#[must_use]
pub fn stage_count(&self) -> usize {
self.stages.len()
}
pub fn execute(&self, data: &mut Vec<u8>, elapsed_ms: u64) -> IoResult {
let original_len = data.len() as u64;
let mut stages_executed = Vec::with_capacity(self.stages.len());
for stage in &self.stages {
match stage {
IoStage::Buffer => {
data.reserve(64);
}
IoStage::Validate
| IoStage::Read
| IoStage::Decompress
| IoStage::Decrypt
| IoStage::Write
| IoStage::Custom(_) => {
}
}
stages_executed.push(stage.stage_name().to_string());
}
IoResult {
bytes_processed: original_len,
elapsed_ms,
stages_executed,
success: true,
}
}
#[must_use]
pub fn stage_names(&self) -> Vec<&str> {
self.stages.iter().map(IoStage::stage_name).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stage_name_read() {
assert_eq!(IoStage::Read.stage_name(), "read");
}
#[test]
fn test_stage_name_decompress() {
assert_eq!(IoStage::Decompress.stage_name(), "decompress");
}
#[test]
fn test_stage_name_validate() {
assert_eq!(IoStage::Validate.stage_name(), "validate");
}
#[test]
fn test_stage_name_decrypt() {
assert_eq!(IoStage::Decrypt.stage_name(), "decrypt");
}
#[test]
fn test_stage_name_buffer() {
assert_eq!(IoStage::Buffer.stage_name(), "buffer");
}
#[test]
fn test_stage_name_write() {
assert_eq!(IoStage::Write.stage_name(), "write");
}
#[test]
fn test_stage_name_custom() {
let s = IoStage::Custom("my_stage".to_string());
assert_eq!(s.stage_name(), "my_stage");
}
#[test]
fn test_empty_pipeline() {
let p = IoPipeline::new();
assert_eq!(p.stage_count(), 0);
assert!(p.stage_names().is_empty());
}
#[test]
fn test_add_stages() {
let mut p = IoPipeline::new();
p.add_stage(IoStage::Read).add_stage(IoStage::Decompress);
assert_eq!(p.stage_count(), 2);
assert_eq!(p.stage_names(), vec!["read", "decompress"]);
}
#[test]
fn test_execute_records_stages() {
let mut p = IoPipeline::new();
p.add_stage(IoStage::Read)
.add_stage(IoStage::Validate)
.add_stage(IoStage::Write);
let mut data = vec![1u8, 2, 3, 4];
let result = p.execute(&mut data, 100);
assert!(result.success);
assert_eq!(result.stages_executed, vec!["read", "validate", "write"]);
assert_eq!(result.bytes_processed, 4);
assert_eq!(result.elapsed_ms, 100);
}
#[test]
fn test_throughput_mbps_zero_elapsed() {
let r = IoResult {
bytes_processed: 1024 * 1024,
elapsed_ms: 0,
stages_executed: vec![],
success: true,
};
assert_eq!(r.throughput_mbps(), 0.0);
}
#[test]
fn test_throughput_mbps_one_second() {
let r = IoResult {
bytes_processed: 1024 * 1024,
elapsed_ms: 1000,
stages_executed: vec![],
success: true,
};
let mbps = r.throughput_mbps();
assert!((mbps - 1.0).abs() < 1e-9);
}
#[test]
fn test_throughput_mbps_two_mib_half_second() {
let r = IoResult {
bytes_processed: 2 * 1024 * 1024,
elapsed_ms: 500,
stages_executed: vec![],
success: true,
};
let mbps = r.throughput_mbps();
assert!((mbps - 4.0).abs() < 1e-9);
}
#[test]
fn test_execute_buffer_stage() {
let mut p = IoPipeline::new();
p.add_stage(IoStage::Buffer);
let mut data = vec![0u8; 10];
let result = p.execute(&mut data, 50);
assert!(result.success);
assert_eq!(result.bytes_processed, 10);
}
#[test]
fn test_execute_custom_stage() {
let mut p = IoPipeline::new();
p.add_stage(IoStage::Custom("transcode".to_string()));
let mut data = vec![9u8; 5];
let result = p.execute(&mut data, 200);
assert_eq!(result.stages_executed, vec!["transcode"]);
}
}