use crate::benchmarks::io_baselines::estimate_execution_step_heap_bytes_core_fields;
use crate::checkpoint::execution_history::{ExecutionStep, StepOutcome};
use crate::reducer::state::PipelineState;
use std::time::Instant;
fn create_test_step(iteration: u32) -> ExecutionStep {
ExecutionStep::new(
"Development",
iteration,
"agent_invoked",
StepOutcome::success(Some("output".to_string()), vec!["file.rs".to_string()]),
)
.with_agent("test-agent")
.with_duration(5)
}
#[test]
fn benchmark_execution_history_growth_10_iterations() {
let mut state = PipelineState::initial(100, 5);
let start_size = std::mem::size_of_val(state.execution_history());
for i in 0..10 {
state.add_execution_step(create_test_step(i), 10);
}
let end_size = std::mem::size_of_val(state.execution_history());
let actual_heap_size: usize = state
.execution_history()
.iter()
.map(estimate_execution_step_heap_bytes_core_fields)
.sum();
let growth_per_iteration = if state.execution_history().is_empty() {
0
} else {
actual_heap_size / state.execution_history_len()
};
println!("\n=== Execution History Growth (10 iterations) ===");
println!("Stack size start: {start_size} bytes");
println!("Stack size end: {end_size} bytes");
println!("Heap size (estimated): {actual_heap_size} bytes");
println!("Growth per iteration: ~{growth_per_iteration} bytes");
println!("Total entries: {}", state.execution_history_len());
assert_eq!(state.execution_history_len(), 10);
}
#[test]
fn benchmark_execution_history_growth_100_iterations() {
let mut state = PipelineState::initial(100, 5);
let start = Instant::now();
for i in 0..100 {
state.add_execution_step(create_test_step(i), 100);
}
let duration = start.elapsed();
let actual_heap_size: usize = state
.execution_history()
.iter()
.map(estimate_execution_step_heap_bytes_core_fields)
.sum();
let growth_per_iteration = if state.execution_history().is_empty() {
0
} else {
actual_heap_size / state.execution_history_len()
};
println!("\n=== Execution History Growth (100 iterations) ===");
println!("Heap size (estimated): {actual_heap_size} bytes");
println!("Growth per iteration: ~{growth_per_iteration} bytes");
println!("Total entries: {}", state.execution_history_len());
println!("Time to populate: {duration:?}");
assert_eq!(state.execution_history_len(), 100);
}
#[test]
fn benchmark_execution_history_growth_1000_iterations() {
let mut state = PipelineState::initial(1000, 5);
let start = Instant::now();
for i in 0..1000 {
state.add_execution_step(create_test_step(i), 1000);
}
let duration = start.elapsed();
let actual_heap_size: usize = state
.execution_history()
.iter()
.map(estimate_execution_step_heap_bytes_core_fields)
.sum();
let growth_per_iteration = if state.execution_history().is_empty() {
0
} else {
actual_heap_size / state.execution_history_len()
};
let heap_kb = actual_heap_size / 1024;
let heap_megabytes = heap_kb / 1024;
println!("\n=== Execution History Growth (1000 iterations) ===");
println!("Heap size (estimated): {actual_heap_size} bytes ({heap_kb} KB, {heap_megabytes} MB)");
println!("Growth per iteration: ~{growth_per_iteration} bytes");
println!("Total entries: {}", state.execution_history_len());
println!("Time to populate: {duration:?}");
assert_eq!(state.execution_history_len(), 1000);
}
#[test]
fn benchmark_pipeline_state_size_empty() {
let state = PipelineState::initial(100, 5);
let base_size = std::mem::size_of_val(&state);
let execution_history_size = std::mem::size_of_val(state.execution_history());
println!("\n=== Pipeline State Size (Empty) ===");
println!("Base state size: {base_size} bytes");
println!("Execution history Vec size: {execution_history_size} bytes");
println!(
"Total execution history entries: {}",
state.execution_history_len()
);
assert!(state.execution_history().is_empty());
}
#[test]
fn benchmark_pipeline_state_size_with_100_steps() {
let mut state = PipelineState::initial(100, 5);
for i in 0..100 {
state.add_execution_step(create_test_step(i), 100);
}
let base_size = std::mem::size_of_val(&state);
let execution_history_size = std::mem::size_of_val(state.execution_history());
let heap_size: usize = state
.execution_history()
.iter()
.map(estimate_execution_step_heap_bytes_core_fields)
.sum();
println!("\n=== Pipeline State Size (100 steps) ===");
println!("Base state size: {base_size} bytes");
println!("Execution history Vec size: {execution_history_size} bytes");
println!("Execution history heap size: ~{heap_size} bytes");
println!("Total size estimate: ~{} bytes", base_size + heap_size);
println!(
"Total execution history entries: {}",
state.execution_history_len()
);
assert_eq!(state.execution_history_len(), 100);
}
#[test]
fn benchmark_memory_growth_rate() {
let mut state = PipelineState::initial(1000, 5);
let mut sizes = Vec::new();
for i in 0..1000 {
state.add_execution_step(create_test_step(i), 1000);
if (i + 1) % 100 == 0 {
let heap_size: usize = state
.execution_history()
.iter()
.map(|step| {
step.phase.len()
+ step.step_type.len()
+ step.timestamp.len()
+ step.agent.as_ref().map_or(0, |s| s.len())
})
.sum();
sizes.push((i + 1, heap_size));
}
}
println!("\n=== Memory Growth Rate ===");
println!("Iterations | Heap Size (KB) | Growth from prev");
println!("-----------|----------------|------------------");
for (idx, (iter, size)) in sizes.iter().enumerate() {
let size_kb = size / 1024;
let growth = if idx > 0 {
let prev_size = sizes[idx - 1].1;
let growth_kb = (size - prev_size) / 1024;
format!("+{growth_kb} KB")
} else {
String::from("baseline")
};
println!("{iter:10} | {size_kb:14} | {growth}");
}
assert_eq!(sizes.len(), 10);
}
#[test]
fn benchmark_checkpoint_cycle_memory_stability() {
let mut states = Vec::new();
for cycle in 0..50 {
let state = create_test_pipeline_state(100, 5, 1000);
let json = serde_json::to_string(&state).unwrap();
let _restored: PipelineState = serde_json::from_str(&json).unwrap();
if cycle % 10 == 0 {
states.push((cycle, json.len()));
}
}
println!("\n=== Checkpoint Cycle Memory Stability ===");
for (cycle, size) in &states {
println!("Cycle {:2}: {} KB", cycle, size / 1024);
}
let first_size = states[0].1;
for (cycle, size) in &states {
let diff_abs = size.abs_diff(first_size);
let diff_pct_100x = (diff_abs * 10000) / first_size; assert!(
diff_pct_100x < 500, "Cycle {cycle} size should be within 5% of initial: {} KB vs {} KB ({}.{:02}% diff)",
size / 1024,
first_size / 1024,
diff_pct_100x / 100,
diff_pct_100x % 100,
);
}
}
fn create_test_pipeline_state(
iterations: u32,
review_passes: u32,
history_size: usize,
) -> PipelineState {
let mut state = PipelineState::initial(iterations, review_passes);
for i in 0..history_size {
state.add_execution_step(
create_test_step(u32::try_from(i).expect("index fits in u32")),
history_size,
);
}
state
}
#[test]
fn benchmark_peak_memory_usage_during_large_state_serialization() {
let state = create_test_pipeline_state(100, 20, 2000);
let heap_before: usize = state
.execution_history()
.iter()
.map(|step| {
step.phase.len()
+ step.step_type.len()
+ step.timestamp.len()
+ step.agent.as_ref().map_or(0, |s| s.len())
})
.sum();
println!("\n=== Peak Memory During Serialization ===");
println!("Heap size before serialization: {} KB", heap_before / 1024);
let start = Instant::now();
let json = serde_json::to_string(&state).unwrap();
let duration = start.elapsed();
let json_size = json.len();
println!("Serialization time: {duration:?}");
println!("Serialized size: {} KB", json_size / 1024);
let ratio_100x = (json_size * 100) / heap_before;
println!(
"Memory overhead ratio: {}.{:02}x",
ratio_100x / 100,
ratio_100x % 100,
);
assert_eq!(state.execution_history_len(), 2000);
}
#[test]
fn benchmark_memory_usage_with_different_history_limits() {
let limits = vec![100, 500, 1000, 2000];
println!("\n=== Memory Usage by History Limit ===");
println!("Limit | Heap Size | Checkpoint Size | Per Entry");
println!("------|-----------|-----------------|----------");
for limit in limits {
let state = create_test_pipeline_state(100, 20, limit);
let heap_size: usize = state
.execution_history()
.iter()
.map(|step| {
step.phase.len()
+ step.step_type.len()
+ step.timestamp.len()
+ step.agent.as_ref().map_or(0, |s| s.len())
})
.sum();
let json = serde_json::to_string(&state).unwrap();
let checkpoint_size = json.len();
let per_entry_heap = heap_size / limit;
let per_entry_checkpoint = checkpoint_size / limit;
println!(
"{:5} | {:9} | {:15} | heap:{:4} ckpt:{:4}",
limit,
format!("{} KB", heap_size / 1024),
format!("{} KB", checkpoint_size / 1024),
per_entry_heap,
per_entry_checkpoint
);
}
}