use super::{Context, Result};
use std::collections::HashMap;
use std::time::{Duration, Instant};
use tokio::task::JoinSet;
#[derive(Debug, Clone)]
pub struct PerformanceTargets {
pub template_selection: Duration,
pub code_generation: Duration,
pub cleanroom_setup: Duration,
pub testing: Duration,
pub validation: Duration,
pub reporting: Duration,
pub total: Duration,
}
impl Default for PerformanceTargets {
fn default() -> Self {
Self {
template_selection: Duration::from_secs(3),
code_generation: Duration::from_secs(8),
cleanroom_setup: Duration::from_secs(7),
testing: Duration::from_secs(15),
validation: Duration::from_secs(7),
reporting: Duration::from_secs(3),
total: Duration::from_secs(45), }
}
}
impl PerformanceTargets {
pub fn required() -> Self {
Self {
total: Duration::from_secs(60),
..Default::default()
}
}
pub fn stretch() -> Self {
Self::default()
}
}
#[derive(Debug, Clone)]
pub struct StageMetrics {
pub name: String,
pub duration: Duration,
pub target: Duration,
pub met_target: bool,
}
impl StageMetrics {
pub fn new(name: impl Into<String>, duration: Duration, target: Duration) -> Self {
let met_target = duration <= target;
Self {
name: name.into(),
duration,
target,
met_target,
}
}
pub fn improvement_percent(&self) -> f64 {
if self.duration <= self.target {
let saved = (self.target - self.duration).as_secs_f64();
(saved / self.target.as_secs_f64()) * 100.0
} else {
let exceeded = (self.duration - self.target).as_secs_f64();
-((exceeded / self.target.as_secs_f64()) * 100.0)
}
}
}
pub struct PipelineProfiler {
stage_timings: HashMap<String, Duration>,
targets: PerformanceTargets,
total_start: Option<Instant>,
}
impl PipelineProfiler {
pub fn new(targets: PerformanceTargets) -> Self {
Self {
stage_timings: HashMap::new(),
targets,
total_start: None,
}
}
pub fn start_pipeline(&mut self) {
self.total_start = Some(Instant::now());
}
pub async fn profile_stage<F, R>(&mut self, name: &str, f: F) -> R
where
F: std::future::Future<Output = R>,
{
let start = Instant::now();
let result = f.await;
let duration = start.elapsed();
self.stage_timings.insert(name.to_string(), duration);
let target = match name {
"template_selection" => self.targets.template_selection,
"code_generation" => self.targets.code_generation,
"cleanroom_setup" => self.targets.cleanroom_setup,
"testing" => self.targets.testing,
"validation" => self.targets.validation,
"reporting" => self.targets.reporting,
_ => Duration::from_secs(10), };
if duration > target {
tracing::warn!(
stage = %name,
duration_ms = duration.as_millis(),
target_ms = target.as_millis(),
"⚠️ Stage exceeded target"
);
} else {
tracing::info!(
stage = %name,
duration_ms = duration.as_millis(),
target_ms = target.as_millis(),
"✅ Stage met target"
);
}
result
}
pub fn get_metrics(&self) -> Vec<StageMetrics> {
let mut metrics = Vec::new();
for (name, duration) in &self.stage_timings {
let target = match name.as_str() {
"template_selection" => self.targets.template_selection,
"code_generation" => self.targets.code_generation,
"cleanroom_setup" => self.targets.cleanroom_setup,
"testing" => self.targets.testing,
"validation" => self.targets.validation,
"reporting" => self.targets.reporting,
_ => Duration::from_secs(10),
};
metrics.push(StageMetrics::new(name, *duration, target));
}
metrics.sort_by(|a, b| b.duration.cmp(&a.duration));
metrics
}
pub fn total_duration(&self) -> Option<Duration> {
self.total_start.map(|start| start.elapsed())
}
pub fn report(&self) {
log::info!("\n📊 Pipeline Performance Report");
log::info!("═══════════════════════════════════════════════");
let metrics = self.get_metrics();
for metric in &metrics {
let status = if metric.met_target { "✅" } else { "❌" };
let improvement = metric.improvement_percent();
let sign = if improvement >= 0.0 { "↓" } else { "↑" };
log::info!(
"{} {:<20} {:>6.2}s / {:>6.2}s ({}{:>5.1}%)",
status,
metric.name,
metric.duration.as_secs_f64(),
metric.target.as_secs_f64(),
sign,
improvement.abs()
);
}
if let Some(total) = self.total_duration() {
let total_met = total <= self.targets.total;
let status = if total_met { "✅" } else { "❌" };
log::info!("───────────────────────────────────────────────");
log::info!(
"{} {:<20} {:>6.2}s / {:>6.2}s",
status,
"TOTAL PIPELINE",
total.as_secs_f64(),
self.targets.total.as_secs_f64()
);
if total_met {
log::info!("\n🎉 Performance target achieved!");
} else {
let exceeded = (total - self.targets.total).as_secs_f64();
log::warn!("\n⚠️ Performance target missed by {:.2}s", exceeded);
}
}
log::info!("═══════════════════════════════════════════════\n");
}
}
pub struct ParallelOrchestrator {
#[allow(dead_code)]
max_parallelism: usize,
}
impl ParallelOrchestrator {
pub fn new(max_parallelism: usize) -> Self {
Self { max_parallelism }
}
#[allow(clippy::type_complexity)]
pub async fn run_parallel<R>(
&self,
stages: Vec<(
&str,
Box<dyn std::future::Future<Output = Result<R>> + Send + Unpin>,
)>,
) -> Result<Vec<R>>
where
R: Send + 'static,
{
let mut set = JoinSet::new();
for (name, stage) in stages {
let name = name.to_string();
set.spawn(async move {
tracing::info!(stage = %name, "Starting parallel stage");
let result = stage.await;
tracing::info!(stage = %name, "Completed parallel stage");
result
});
}
let mut results = Vec::new();
while let Some(result) = set.join_next().await {
results.push(result.map_err(|e| {
super::LifecycleError::Other(format!("Parallel stage failed: {}", e))
})??);
}
Ok(results)
}
}
pub async fn run_optimized_pipeline(ctx: &Context, phases: &[String]) -> Result<()> {
let mut profiler = PipelineProfiler::new(PerformanceTargets::stretch());
profiler.start_pipeline();
tracing::info!("Stage 1: Parallel preparation");
profiler
.profile_stage("preparation", async {
tokio::join!(
async {
tracing::debug!("Fetching dependencies...");
tokio::time::sleep(Duration::from_millis(100)).await;
},
async {
tracing::debug!("Pre-warming containers...");
tokio::time::sleep(Duration::from_millis(200)).await;
},
async {
tracing::debug!("Validating configuration...");
tokio::time::sleep(Duration::from_millis(50)).await;
}
);
Ok::<_, super::LifecycleError>(())
})
.await?;
tracing::info!("Stage 2: Phase execution");
for phase in phases {
profiler
.profile_stage(phase, async {
super::run_phase(ctx, phase)?;
Ok::<_, super::LifecycleError>(())
})
.await?;
}
tracing::info!("Stage 3: Reporting");
profiler
.profile_stage("reporting", async {
tokio::time::sleep(Duration::from_millis(100)).await;
Ok::<_, super::LifecycleError>(())
})
.await?;
profiler.report();
Ok(())
}
pub async fn run_fast_validation(ctx: &Context) -> Result<()> {
tracing::info!("Running fast validation (cargo check)");
let check_result = tokio::process::Command::new("cargo")
.args(["check", "--all-targets", "--all-features"])
.current_dir(&ctx.root)
.output()
.await
.map_err(|e| super::LifecycleError::Other(format!("cargo check failed: {}", e)))?;
if !check_result.status.success() {
let stderr = String::from_utf8_lossy(&check_result.stderr);
return Err(super::LifecycleError::Other(format!(
"Validation failed: {}",
stderr
)));
}
tracing::info!("Fast validation passed");
Ok(())
}
pub struct ContainerPool {
pool_size: usize,
available_count: std::sync::atomic::AtomicUsize,
}
impl ContainerPool {
pub async fn new(pool_size: usize) -> Result<Self> {
tracing::info!(pool_size = %pool_size, "Initializing container pool");
let mut tasks = Vec::new();
for i in 0..pool_size {
tasks.push(tokio::spawn(async move {
tracing::debug!(container = i, "Pre-warming container");
tokio::time::sleep(Duration::from_millis(100)).await;
Ok::<_, super::LifecycleError>(())
}));
}
for task in tasks {
task.await
.map_err(|e| super::LifecycleError::Other(format!("Pool init failed: {}", e)))??;
}
tracing::info!("Container pool ready");
Ok(Self {
pool_size,
available_count: std::sync::atomic::AtomicUsize::new(pool_size),
})
}
pub fn available(&self) -> usize {
self.available_count
.load(std::sync::atomic::Ordering::Relaxed)
}
pub fn total(&self) -> usize {
self.pool_size
}
}
pub struct DependencyCache {
cache_dir: std::path::PathBuf,
}
impl DependencyCache {
pub fn new(cache_dir: std::path::PathBuf) -> Self {
Self { cache_dir }
}
pub async fn prefetch(&self) -> Result<()> {
tracing::info!("Pre-fetching dependencies");
let fetch_result = tokio::process::Command::new("cargo")
.arg("fetch")
.current_dir(self.cache_dir.parent().unwrap_or(&self.cache_dir))
.output()
.await
.map_err(|e| super::LifecycleError::Other(format!("cargo fetch failed: {}", e)))?;
if !fetch_result.status.success() {
let stderr = String::from_utf8_lossy(&fetch_result.stderr);
return Err(super::LifecycleError::Other(format!(
"Dependency prefetch failed: {}",
stderr
)));
}
tracing::info!("Dependencies prefetched successfully");
Ok(())
}
pub fn is_cached(&self, package: &str) -> bool {
let package_path = self.cache_dir.join(package);
package_path.exists()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_performance_targets() {
let targets = PerformanceTargets::default();
assert_eq!(targets.total, Duration::from_secs(45));
let required = PerformanceTargets::required();
assert_eq!(required.total, Duration::from_secs(60));
let stretch = PerformanceTargets::stretch();
assert_eq!(stretch.total, Duration::from_secs(45));
}
#[test]
fn test_stage_metrics() {
let metric = StageMetrics::new("test", Duration::from_secs(5), Duration::from_secs(10));
assert!(metric.met_target);
assert!(metric.improvement_percent() > 0.0);
let metric_exceeded =
StageMetrics::new("test", Duration::from_secs(15), Duration::from_secs(10));
assert!(!metric_exceeded.met_target);
assert!(metric_exceeded.improvement_percent() < 0.0);
}
#[tokio::test]
async fn test_pipeline_profiler() {
let mut profiler = PipelineProfiler::new(PerformanceTargets::default());
profiler.start_pipeline();
profiler
.profile_stage("test_stage", async {
tokio::time::sleep(Duration::from_millis(100)).await;
})
.await;
let metrics = profiler.get_metrics();
assert_eq!(metrics.len(), 1);
assert_eq!(metrics[0].name, "test_stage");
}
#[tokio::test]
async fn test_parallel_orchestrator() {
let _orchestrator = ParallelOrchestrator::new(4);
let (r1, r2, r3) = tokio::join!(
async { Ok::<i32, super::super::LifecycleError>(1) },
async { Ok::<i32, super::super::LifecycleError>(2) },
async { Ok::<i32, super::super::LifecycleError>(3) },
);
assert!(r1.is_ok());
assert!(r2.is_ok());
assert!(r3.is_ok());
}
#[tokio::test]
async fn test_container_pool() {
let pool = ContainerPool::new(3).await.unwrap();
assert_eq!(pool.total(), 3);
assert_eq!(pool.available(), 3);
}
#[test]
fn test_improvement_calculation() {
let metric = StageMetrics::new("test", Duration::from_secs(3), Duration::from_secs(5));
assert!((metric.improvement_percent() - 40.0).abs() < 0.01);
let metric_over = StageMetrics::new("test", Duration::from_secs(7), Duration::from_secs(5));
assert!((metric_over.improvement_percent() + 40.0).abs() < 0.01);
}
#[test]
fn test_performance_targets_defaults() {
let targets = PerformanceTargets::default();
assert!(targets.template_selection <= Duration::from_secs(3));
assert!(targets.code_generation <= Duration::from_secs(8));
assert!(targets.cleanroom_setup <= Duration::from_secs(7));
assert!(targets.testing <= Duration::from_secs(15));
assert!(targets.validation <= Duration::from_secs(7));
assert!(targets.reporting <= Duration::from_secs(3));
assert_eq!(targets.total, Duration::from_secs(45)); }
#[test]
fn test_stage_metrics_boundary_conditions() {
let metric = StageMetrics::new("exact", Duration::from_secs(10), Duration::from_secs(10));
assert!(metric.met_target);
assert_eq!(metric.improvement_percent(), 0.0);
let metric_under = StageMetrics::new(
"under",
Duration::from_millis(9999),
Duration::from_secs(10),
);
assert!(metric_under.met_target);
assert!(metric_under.improvement_percent() > 0.0);
let metric_over = StageMetrics::new(
"over",
Duration::from_millis(10001),
Duration::from_secs(10),
);
assert!(!metric_over.met_target);
assert!(metric_over.improvement_percent() < 0.0);
}
#[tokio::test]
async fn test_profiler_multiple_stages() {
let mut profiler = PipelineProfiler::new(PerformanceTargets::default());
profiler.start_pipeline();
profiler
.profile_stage("stage1", async {
tokio::time::sleep(Duration::from_millis(50)).await
})
.await;
profiler
.profile_stage("stage2", async {
tokio::time::sleep(Duration::from_millis(30)).await
})
.await;
profiler
.profile_stage("stage3", async {
tokio::time::sleep(Duration::from_millis(20)).await
})
.await;
let metrics = profiler.get_metrics();
assert_eq!(metrics.len(), 3);
assert!(metrics[0].duration >= metrics[1].duration);
assert!(metrics[1].duration >= metrics[2].duration);
}
#[tokio::test]
async fn test_profiler_total_duration() {
let mut profiler = PipelineProfiler::new(PerformanceTargets::default());
profiler.start_pipeline();
tokio::time::sleep(Duration::from_millis(100)).await;
let total = profiler.total_duration();
assert!(total.is_some());
assert!(total.unwrap() >= Duration::from_millis(100));
}
#[test]
fn test_profiler_no_start() {
let profiler = PipelineProfiler::new(PerformanceTargets::default());
assert!(profiler.total_duration().is_none());
}
#[tokio::test]
async fn test_parallel_orchestrator_empty() {
let orchestrator = ParallelOrchestrator::new(4);
let results: Result<Vec<i32>> = orchestrator.run_parallel(vec![]).await;
assert!(results.is_ok());
assert_eq!(results.unwrap().len(), 0);
}
#[tokio::test]
async fn test_container_pool_size() {
let pool = ContainerPool::new(5).await.unwrap();
assert_eq!(pool.total(), 5);
assert_eq!(pool.available(), 5);
}
#[tokio::test]
async fn test_container_pool_zero_size() {
let pool = ContainerPool::new(0).await.unwrap();
assert_eq!(pool.total(), 0);
assert_eq!(pool.available(), 0);
}
#[tokio::test]
async fn test_dependency_cache_is_cached() {
let temp_dir = std::env::temp_dir().join("ggen_test_cache");
std::fs::create_dir_all(&temp_dir).ok();
let cache = DependencyCache::new(temp_dir.clone());
assert!(!cache.is_cached("nonexistent_package_12345"));
std::fs::remove_dir_all(&temp_dir).ok();
}
#[test]
fn test_stage_metrics_sorting() {
let metric1 = StageMetrics::new("fast", Duration::from_secs(1), Duration::from_secs(5));
let metric2 = StageMetrics::new("slow", Duration::from_secs(10), Duration::from_secs(5));
let metric3 = StageMetrics::new("medium", Duration::from_secs(5), Duration::from_secs(5));
let mut metrics = [metric1.clone(), metric2.clone(), metric3.clone()];
metrics.sort_by(|a, b| b.duration.cmp(&a.duration));
assert_eq!(metrics[0].name, "slow");
assert_eq!(metrics[1].name, "medium");
assert_eq!(metrics[2].name, "fast");
}
#[tokio::test]
async fn test_profiler_stage_target_matching() {
let mut profiler = PipelineProfiler::new(PerformanceTargets::default());
profiler
.profile_stage("template_selection", async {
tokio::time::sleep(Duration::from_millis(100)).await
})
.await;
let metrics = profiler.get_metrics();
assert_eq!(metrics.len(), 1);
assert_eq!(metrics[0].target, profiler.targets.template_selection);
}
#[test]
fn test_performance_targets_required_vs_stretch() {
let required = PerformanceTargets::required();
let stretch = PerformanceTargets::stretch();
assert_eq!(required.total, Duration::from_secs(60));
assert_eq!(stretch.total, Duration::from_secs(45));
assert!(required.total > stretch.total);
}
#[tokio::test]
async fn test_profiler_concurrent_stages() {
let mut profiler = PipelineProfiler::new(PerformanceTargets::default());
profiler.start_pipeline();
tokio::join!(
profiler.profile_stage("concurrent1", async {
tokio::time::sleep(Duration::from_millis(50)).await
}),
async {
tokio::time::sleep(Duration::from_millis(25)).await;
}
);
let metrics = profiler.get_metrics();
assert!(!metrics.is_empty());
}
#[test]
fn test_stage_metrics_percentage_precision() {
let metric = StageMetrics::new("micro", Duration::from_micros(1), Duration::from_micros(2));
assert!(metric.met_target);
let pct = metric.improvement_percent();
assert!((pct - 50.0).abs() < 1.0);
}
#[tokio::test]
async fn test_container_pool_concurrent_init() {
let (pool1, pool2, pool3) = tokio::join!(
ContainerPool::new(2),
ContainerPool::new(3),
ContainerPool::new(1)
);
assert!(pool1.is_ok());
assert!(pool2.is_ok());
assert!(pool3.is_ok());
assert_eq!(pool1.unwrap().total(), 2);
assert_eq!(pool2.unwrap().total(), 3);
assert_eq!(pool3.unwrap().total(), 1);
}
#[test]
fn test_parallel_orchestrator_max_parallelism() {
let orch = ParallelOrchestrator::new(10);
assert_eq!(orch.max_parallelism, 10);
}
#[tokio::test]
async fn test_profiler_report_output() {
let mut profiler = PipelineProfiler::new(PerformanceTargets::default());
profiler.start_pipeline();
profiler
.profile_stage("test", async {
tokio::time::sleep(Duration::from_millis(10)).await;
})
.await;
profiler.report();
}
#[test]
fn test_stage_metrics_zero_duration() {
let metric = StageMetrics::new("instant", Duration::from_secs(0), Duration::from_secs(5));
assert!(metric.met_target);
assert_eq!(metric.improvement_percent(), 100.0);
}
}