use crate::cook::execution::mapreduce::pure::dependency_analysis::{
analyze_dependencies, Command as DepCommand,
};
use std::collections::HashSet;
#[derive(Debug, Clone)]
pub struct PhaseCommand {
pub id: String,
pub command: String,
pub reads: HashSet<String>,
pub writes: HashSet<String>,
}
#[derive(Debug, Clone)]
pub struct ParallelBatch {
pub batch_id: usize,
pub commands: Vec<PhaseCommand>,
}
#[derive(Debug, Clone)]
pub struct ParallelizationPlan {
pub batches: Vec<ParallelBatch>,
pub total_commands: usize,
pub max_parallelism: usize,
}
pub fn create_parallelization_plan(commands: &[PhaseCommand]) -> ParallelizationPlan {
if commands.is_empty() {
return ParallelizationPlan {
batches: vec![],
total_commands: 0,
max_parallelism: 0,
};
}
let dep_commands: Vec<DepCommand> = commands
.iter()
.map(|cmd| DepCommand {
reads: cmd.reads.clone(),
writes: cmd.writes.clone(),
})
.collect();
let graph = analyze_dependencies(&dep_commands);
let batch_indices = graph.parallel_batches();
let mut batches = Vec::new();
let mut max_parallelism = 0;
for (batch_id, indices) in batch_indices.iter().enumerate() {
let batch_commands: Vec<PhaseCommand> =
indices.iter().map(|&i| commands[i].clone()).collect();
max_parallelism = max_parallelism.max(batch_commands.len());
batches.push(ParallelBatch {
batch_id,
commands: batch_commands,
});
}
ParallelizationPlan {
batches,
total_commands: commands.len(),
max_parallelism,
}
}
pub fn calculate_expected_speedup(plan: &ParallelizationPlan) -> f64 {
if plan.total_commands == 0 {
return 1.0;
}
let sequential_time = plan.total_commands as f64;
let parallel_time = plan.batches.len() as f64;
sequential_time / parallel_time
}
pub fn analyze_command_dependencies(command: &str) -> (HashSet<String>, HashSet<String>) {
let mut reads = HashSet::new();
let mut writes = HashSet::new();
if command.contains('>') || command.contains("write") {
writes.insert("output".to_string());
}
if command.contains("cat") || command.contains("read") || command.contains('<') {
reads.insert("input".to_string());
}
(reads, writes)
}
pub fn create_phase_commands_from_shell(shell_commands: &[String]) -> Vec<PhaseCommand> {
shell_commands
.iter()
.enumerate()
.map(|(i, cmd)| {
let (reads, writes) = analyze_command_dependencies(cmd);
PhaseCommand {
id: format!("cmd-{}", i),
command: cmd.clone(),
reads,
writes,
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn set<T>(items: &[T]) -> HashSet<String>
where
T: ToString,
{
items.iter().map(|s| s.to_string()).collect()
}
#[test]
fn test_create_parallelization_plan_independent_commands() {
let commands = vec![
PhaseCommand {
id: "cmd1".to_string(),
command: "echo 'hello'".to_string(),
reads: HashSet::new(),
writes: set(&["file1.txt"]),
},
PhaseCommand {
id: "cmd2".to_string(),
command: "echo 'world'".to_string(),
reads: HashSet::new(),
writes: set(&["file2.txt"]),
},
PhaseCommand {
id: "cmd3".to_string(),
command: "echo 'parallel'".to_string(),
reads: HashSet::new(),
writes: set(&["file3.txt"]),
},
];
let plan = create_parallelization_plan(&commands);
assert_eq!(plan.batches.len(), 1);
assert_eq!(plan.batches[0].commands.len(), 3);
assert_eq!(plan.max_parallelism, 3);
assert_eq!(plan.total_commands, 3);
}
#[test]
fn test_create_parallelization_plan_dependent_commands() {
let commands = vec![
PhaseCommand {
id: "cmd1".to_string(),
command: "echo 'data' > file.txt".to_string(),
reads: HashSet::new(),
writes: set(&["file.txt"]),
},
PhaseCommand {
id: "cmd2".to_string(),
command: "cat file.txt".to_string(),
reads: set(&["file.txt"]),
writes: HashSet::new(),
},
PhaseCommand {
id: "cmd3".to_string(),
command: "echo 'more data' > file2.txt".to_string(),
reads: HashSet::new(),
writes: set(&["file2.txt"]),
},
];
let plan = create_parallelization_plan(&commands);
assert_eq!(plan.batches.len(), 2);
assert_eq!(plan.batches[0].commands.len(), 2); assert_eq!(plan.batches[1].commands.len(), 1); assert_eq!(plan.max_parallelism, 2);
}
#[test]
fn test_calculate_expected_speedup() {
let plan = ParallelizationPlan {
batches: vec![ParallelBatch {
batch_id: 0,
commands: vec![],
}],
total_commands: 10,
max_parallelism: 10,
};
let speedup = calculate_expected_speedup(&plan);
assert_eq!(speedup, 10.0);
let plan = ParallelizationPlan {
batches: vec![
ParallelBatch {
batch_id: 0,
commands: vec![],
},
ParallelBatch {
batch_id: 1,
commands: vec![],
},
ParallelBatch {
batch_id: 2,
commands: vec![],
},
],
total_commands: 3,
max_parallelism: 1,
};
let speedup = calculate_expected_speedup(&plan);
assert_eq!(speedup, 1.0);
}
#[test]
fn test_analyze_command_dependencies() {
let (_reads, writes) = analyze_command_dependencies("echo 'test' > output.txt");
assert!(writes.contains("output"));
let (reads, _writes) = analyze_command_dependencies("cat input.txt");
assert!(reads.contains("input"));
let (reads, writes) = analyze_command_dependencies("cat input.txt > output.txt");
assert!(reads.contains("input"));
assert!(writes.contains("output"));
}
#[test]
fn test_create_phase_commands_from_shell() {
let shell_commands = vec![
"echo 'hello' > file1.txt".to_string(),
"cat file1.txt".to_string(),
"echo 'world' > file2.txt".to_string(),
];
let commands = create_phase_commands_from_shell(&shell_commands);
assert_eq!(commands.len(), 3);
assert_eq!(commands[0].id, "cmd-0");
assert_eq!(commands[1].id, "cmd-1");
assert_eq!(commands[2].id, "cmd-2");
}
#[test]
fn test_empty_plan() {
let plan = create_parallelization_plan(&[]);
assert_eq!(plan.batches.len(), 0);
assert_eq!(plan.total_commands, 0);
assert_eq!(plan.max_parallelism, 0);
assert_eq!(calculate_expected_speedup(&plan), 1.0);
}
}