use std::time::Duration;
use crate::color;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProductTiming {
pub display: String,
pub processor: String,
pub duration: Duration,
pub start_offset: Option<Duration>,
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct ProcessStats {
pub processed: usize,
pub failed: usize,
pub flaky: usize,
pub skipped: usize,
pub restored: usize,
pub files_created: usize,
pub files_restored: usize,
pub duration: Duration,
pub product_timings: Vec<ProductTiming>,
}
impl ProcessStats {
pub const fn total(&self) -> usize {
self.processed + self.failed + self.skipped + self.restored
}
}
#[derive(Default)]
pub struct BuildStats {
pub categories: Vec<ProcessStats>,
pub total_duration: Duration,
pub failed_count: usize,
pub failed_messages: Vec<String>,
pub phase_timings: Vec<(String, Duration)>,
}
impl BuildStats {
pub fn add(&mut self, stats: ProcessStats) {
if stats.total() > 0 {
self.categories.push(stats);
}
}
pub fn total_processed(&self) -> usize {
self.categories.iter().map(|s| s.processed).sum()
}
pub fn total_skipped(&self) -> usize {
self.categories.iter().map(|s| s.skipped).sum()
}
pub fn total_restored(&self) -> usize {
self.categories.iter().map(|s| s.restored).sum()
}
pub fn total_files_created(&self) -> usize {
self.categories.iter().map(|s| s.files_created).sum()
}
pub fn total_files_restored(&self) -> usize {
self.categories.iter().map(|s| s.files_restored).sum()
}
pub fn total_flaky(&self) -> usize {
self.categories.iter().map(|s| s.flaky).sum()
}
pub fn print_summary(&self, summary: bool, timings: bool) {
if crate::json_output::is_json_mode() || crate::runtime_flags::quiet() {
return;
}
if !summary && !timings {
return;
}
if self.categories.is_empty() && self.failed_count == 0 {
if summary {
println!("{}", color::dim("Nothing to build."));
}
return;
}
if summary {
let total_processed = self.total_processed();
let total_restored = self.total_restored();
let total_failed = self.failed_count;
let total_skipped = self.total_skipped();
let total_files_created = self.total_files_created();
let total_files_restored = self.total_files_restored();
let total_flaky = self.total_flaky();
let built_part = if total_files_created > 0 {
format!("{total_processed} built ({total_files_created} files created)")
} else {
format!("{total_processed} built")
};
let restored_part = if total_files_restored > 0 {
format!("{total_restored} restored ({total_files_restored} files)")
} else {
format!("{total_restored} restored")
};
let lead = format!("{built_part}, {restored_part}, {total_failed} failed");
let aside = format!("{total_skipped} unchanged, {total_flaky} flaky");
println!("[build] summary: {lead} ({aside})");
}
if self.failed_count > 0 {
println!(
"{}",
color::red(&format!(
"Build finished with {} error(s):",
self.failed_count
))
);
for msg in &self.failed_messages {
println!("{} {}", color::red("*"), msg);
}
}
if timings {
println!();
println!("{}", color::bold("Timing:"));
if !self.phase_timings.is_empty() {
let rows: Vec<Vec<String>> = self
.phase_timings
.iter()
.map(|(name, dur)| vec![name.clone(), format!("{:.3}s", dur.as_secs_f64())])
.collect();
crate::tables::print_table(&["Phase", "Duration"], &rows);
}
for cat in &self.categories {
for pt in &cat.product_timings {
println!(
"[{}] {} {}",
pt.processor,
pt.display,
color::dim(&format!("({:.3}s)", pt.duration.as_secs_f64()))
);
}
}
let total: f64 = self
.phase_timings
.iter()
.map(|(_, d)| d.as_secs_f64())
.sum();
println!("{}", color::bold(&format!("Total: {total:.3}s")));
}
}
}