use crate::cook::execution::mapreduce::types::{MapPhase, ReducePhase, SetupPhase};
use anyhow::{anyhow, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExecutionPlan {
pub phases: Vec<PhaseSpec>,
pub parallelism: usize,
pub resource_requirements: ResourceEstimate,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PhaseSpec {
Setup,
Map,
Reduce,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResourceEstimate {
pub max_agents: usize,
pub memory_per_agent_mb: usize,
pub total_memory_mb: usize,
}
pub fn plan_phases(has_setup: bool, has_reduce: bool) -> ExecutionPlan {
let phases = build_phase_sequence(has_setup, has_reduce);
let parallelism = calculate_default_parallelism();
let resource_requirements = estimate_resources(parallelism);
ExecutionPlan {
phases,
parallelism,
resource_requirements,
}
}
fn build_phase_sequence(has_setup: bool, has_reduce: bool) -> Vec<PhaseSpec> {
let mut phases = Vec::with_capacity(3);
if has_setup {
phases.push(PhaseSpec::Setup);
}
phases.push(PhaseSpec::Map);
if has_reduce {
phases.push(PhaseSpec::Reduce);
}
phases
}
fn calculate_default_parallelism() -> usize {
std::thread::available_parallelism()
.map(|p| p.get())
.unwrap_or(4)
}
fn estimate_resources(parallelism: usize) -> ResourceEstimate {
const MEMORY_PER_AGENT_MB: usize = 512;
ResourceEstimate {
max_agents: parallelism,
memory_per_agent_mb: MEMORY_PER_AGENT_MB,
total_memory_mb: parallelism * MEMORY_PER_AGENT_MB,
}
}
pub fn validate_phase_config(
setup: Option<&SetupPhase>,
map: &MapPhase,
reduce: Option<&ReducePhase>,
) -> Result<()> {
if map.agent_template.is_empty() {
return Err(anyhow!("Map phase requires at least one command"));
}
if map.config.input.trim().is_empty() {
return Err(anyhow!("Map phase requires an input source"));
}
if let Some(setup_phase) = setup {
if setup_phase.commands.is_empty() {
return Err(anyhow!("Setup phase requires at least one command"));
}
}
if let Some(reduce_phase) = reduce {
if reduce_phase.commands.is_empty() {
return Err(anyhow!("Reduce phase requires at least one command"));
}
}
if map.config.max_parallel == 0 {
return Err(anyhow!("Map phase parallelism must be greater than 0"));
}
Ok(())
}
pub fn calculate_optimal_parallelism(
requested_parallelism: usize,
work_item_count: usize,
) -> usize {
requested_parallelism.min(work_item_count).max(1)
}
pub fn should_skip_phase(
phase: PhaseSpec,
has_setup_commands: bool,
has_reduce_commands: bool,
has_map_results: bool,
) -> bool {
match phase {
PhaseSpec::Setup => !has_setup_commands,
PhaseSpec::Map => false, PhaseSpec::Reduce => !has_reduce_commands || !has_map_results,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_phase_sequence_all_phases() {
let phases = build_phase_sequence(true, true);
assert_eq!(
phases,
vec![PhaseSpec::Setup, PhaseSpec::Map, PhaseSpec::Reduce]
);
}
#[test]
fn test_build_phase_sequence_map_only() {
let phases = build_phase_sequence(false, false);
assert_eq!(phases, vec![PhaseSpec::Map]);
}
#[test]
fn test_build_phase_sequence_setup_and_map() {
let phases = build_phase_sequence(true, false);
assert_eq!(phases, vec![PhaseSpec::Setup, PhaseSpec::Map]);
}
#[test]
fn test_build_phase_sequence_map_and_reduce() {
let phases = build_phase_sequence(false, true);
assert_eq!(phases, vec![PhaseSpec::Map, PhaseSpec::Reduce]);
}
#[test]
fn test_plan_phases_returns_expected_structure() {
let plan = plan_phases(true, true);
assert_eq!(plan.phases.len(), 3);
assert!(plan.parallelism > 0);
assert!(plan.resource_requirements.max_agents > 0);
}
#[test]
fn test_calculate_optimal_parallelism_respects_item_count() {
assert_eq!(calculate_optimal_parallelism(10, 5), 5);
assert_eq!(calculate_optimal_parallelism(10, 20), 10);
}
#[test]
fn test_calculate_optimal_parallelism_minimum_one() {
assert_eq!(calculate_optimal_parallelism(0, 10), 1);
assert_eq!(calculate_optimal_parallelism(10, 0), 1);
}
#[test]
fn test_should_skip_phase_setup() {
assert!(should_skip_phase(PhaseSpec::Setup, false, true, true));
assert!(!should_skip_phase(PhaseSpec::Setup, true, true, true));
}
#[test]
fn test_should_skip_phase_map_never() {
assert!(!should_skip_phase(PhaseSpec::Map, false, false, false));
assert!(!should_skip_phase(PhaseSpec::Map, true, true, true));
}
#[test]
fn test_should_skip_phase_reduce() {
assert!(should_skip_phase(PhaseSpec::Reduce, true, false, true));
assert!(should_skip_phase(PhaseSpec::Reduce, true, true, false));
assert!(!should_skip_phase(PhaseSpec::Reduce, true, true, true));
}
#[test]
fn test_estimate_resources() {
let estimate = estimate_resources(4);
assert_eq!(estimate.max_agents, 4);
assert_eq!(estimate.memory_per_agent_mb, 512);
assert_eq!(estimate.total_memory_mb, 2048);
}
}