use super::helpers::*;
#[allow(unused_imports)]
use super::helpers_state::*;
#[allow(unused_imports)]
use crate::core::{codegen, executor, migrate, parser, planner, resolver, secrets, state, types};
use std::path::Path;
pub(crate) fn cmd_status_machine_resource_drift_frequency(
sd: &Path,
machine: Option<&str>,
json: bool,
) -> Result<(), String> {
let machines = discover_machines(sd);
let targets: Vec<&String> = match machine {
Some(m) => machines.iter().filter(|n| n.as_str() == m).collect(),
None => machines.iter().collect(),
};
let frequencies = collect_drift_frequencies(sd, &targets);
if json {
let items: Vec<String> = frequencies
.iter()
.map(|(m, c)| format!("{{\"machine\":\"{m}\",\"drifted_resources\":{c}}}"))
.collect();
println!("{{\"drift_frequencies\":[{}]}}", items.join(","));
} else if frequencies.is_empty() {
println!("No drift frequency data available.");
} else {
println!("Drift frequency:");
for (m, c) in &frequencies {
println!(" {m} — {c} drifted resources");
}
}
Ok(())
}
pub(super) fn collect_drift_frequencies(sd: &Path, targets: &[&String]) -> Vec<(String, usize)> {
let mut frequencies = Vec::new();
for m in targets {
let path = sd.join(m).join("state.lock.yaml");
let content = match std::fs::read_to_string(&path) {
Ok(c) => c,
Err(_) => continue,
};
let lock: types::StateLock = match serde_yaml_ng::from_str(&content) {
Ok(l) => l,
Err(_) => continue,
};
let drifted = lock
.resources
.values()
.filter(|r| r.status == types::ResourceStatus::Drifted)
.count();
if drifted > 0 {
frequencies.push(((*m).clone(), drifted));
}
}
frequencies.sort_by(|a, b| a.0.cmp(&b.0));
frequencies
}
pub(crate) fn cmd_status_fleet_resource_drift_frequency(
sd: &Path,
machine: Option<&str>,
json: bool,
) -> Result<(), String> {
let machines = discover_machines(sd);
let targets: Vec<&String> = match machine {
Some(m) => machines.iter().filter(|n| n.as_str() == m).collect(),
None => machines.iter().collect(),
};
let frequencies = collect_drift_frequencies(sd, &targets);
let total: usize = frequencies.iter().map(|(_, c)| c).sum();
if json {
println!(
"{{\"fleet_drifted_resources\":{},\"machines\":{}}}",
total,
frequencies.len()
);
} else {
println!(
"Fleet drift frequency: {} drifted resources across {} machines",
total,
frequencies.len()
);
}
Ok(())
}
pub(crate) fn cmd_status_machine_resource_apply_duration_trend(
sd: &Path,
machine: Option<&str>,
json: bool,
) -> Result<(), String> {
let machines = discover_machines(sd);
let targets: Vec<&String> = match machine {
Some(m) => machines.iter().filter(|n| n.as_str() == m).collect(),
None => machines.iter().collect(),
};
let trends = collect_apply_duration_trends(sd, &targets);
if json {
let items: Vec<String> = trends
.iter()
.map(|(m, avg)| format!("{{\"machine\":\"{m}\",\"avg_duration_seconds\":{avg:.4}}}"))
.collect();
println!("{{\"apply_duration_trends\":[{}]}}", items.join(","));
} else if trends.is_empty() {
println!("No apply duration trend data available.");
} else {
println!("Apply duration trends:");
for (m, avg) in &trends {
println!(" {m} — avg {avg:.4}s");
}
}
Ok(())
}
pub(super) fn collect_apply_duration_trends(sd: &Path, targets: &[&String]) -> Vec<(String, f64)> {
let mut trends = Vec::new();
for m in targets {
let path = sd.join(m).join("state.lock.yaml");
let content = match std::fs::read_to_string(&path) {
Ok(c) => c,
Err(_) => continue,
};
let lock: types::StateLock = match serde_yaml_ng::from_str(&content) {
Ok(l) => l,
Err(_) => continue,
};
let durations: Vec<f64> = lock
.resources
.values()
.filter_map(|r| r.duration_seconds)
.collect();
if durations.is_empty() {
continue;
}
let avg = durations.iter().sum::<f64>() / durations.len() as f64;
trends.push(((*m).clone(), avg));
}
trends.sort_by(|a, b| a.0.cmp(&b.0));
trends
}
pub(crate) fn cmd_status_machine_resource_convergence_streak(
sd: &Path,
machine: Option<&str>,
json: bool,
) -> Result<(), String> {
let machines = discover_machines(sd);
let targets: Vec<&String> = match machine {
Some(m) => machines.iter().filter(|n| n.as_str() == m).collect(),
None => machines.iter().collect(),
};
let streaks = collect_convergence_streaks(sd, &targets);
if json {
let items: Vec<String> = streaks
.iter()
.map(|(m, s)| format!("{{\"machine\":\"{m}\",\"streak\":{s}}}"))
.collect();
println!("{{\"convergence_streaks\":[{}]}}", items.join(","));
} else if streaks.is_empty() {
println!("No convergence streak data available.");
} else {
println!("Convergence streaks:");
for (m, s) in &streaks {
println!(" {m} — {s} consecutive converged");
}
}
Ok(())
}
pub(super) fn collect_convergence_streaks(sd: &Path, targets: &[&String]) -> Vec<(String, usize)> {
let mut streaks = Vec::new();
for m in targets {
let path = sd.join(m).join("state.lock.yaml");
let content = match std::fs::read_to_string(&path) {
Ok(c) => c,
Err(_) => continue,
};
let lock: types::StateLock = match serde_yaml_ng::from_str(&content) {
Ok(l) => l,
Err(_) => continue,
};
let converged = lock
.resources
.values()
.filter(|r| r.status == types::ResourceStatus::Converged)
.count();
streaks.push(((*m).clone(), converged));
}
streaks.sort_by(|a, b| a.0.cmp(&b.0));
streaks
}
pub(crate) fn cmd_status_fleet_resource_convergence_streak(
sd: &Path,
machine: Option<&str>,
json: bool,
) -> Result<(), String> {
let machines = discover_machines(sd);
let targets: Vec<&String> = match machine {
Some(m) => machines.iter().filter(|n| n.as_str() == m).collect(),
None => machines.iter().collect(),
};
let streaks = collect_convergence_streaks(sd, &targets);
let total: usize = streaks.iter().map(|(_, s)| s).sum();
let avg = if !streaks.is_empty() {
total as f64 / streaks.len() as f64
} else {
0.0
};
if json {
println!(
"{{\"fleet_convergence_streak_avg\":{:.4},\"machines\":{}}}",
avg,
streaks.len()
);
} else {
println!(
"Fleet convergence streak avg: {:.4} ({} machines)",
avg,
streaks.len()
);
}
Ok(())
}
pub(crate) fn cmd_status_machine_resource_error_distribution(
sd: &Path,
machine: Option<&str>,
json: bool,
) -> Result<(), String> {
let machines = discover_machines(sd);
let targets: Vec<&String> = match machine {
Some(m) => machines.iter().filter(|n| n.as_str() == m).collect(),
None => machines.iter().collect(),
};
let distributions = collect_error_distributions(sd, &targets);
if json {
let items: Vec<String> = distributions
.iter()
.map(|(m, f, d)| format!("{{\"machine\":\"{m}\",\"failed\":{f},\"drifted\":{d}}}"))
.collect();
println!("{{\"error_distributions\":[{}]}}", items.join(","));
} else if distributions.is_empty() {
println!("No error distribution data available.");
} else {
println!("Error distribution:");
for (m, f, d) in &distributions {
println!(" {m} — {f} failed, {d} drifted");
}
}
Ok(())
}
pub(super) fn collect_error_distributions(
sd: &Path,
targets: &[&String],
) -> Vec<(String, usize, usize)> {
let mut dists = Vec::new();
for m in targets {
let path = sd.join(m).join("state.lock.yaml");
let content = match std::fs::read_to_string(&path) {
Ok(c) => c,
Err(_) => continue,
};
let lock: types::StateLock = match serde_yaml_ng::from_str(&content) {
Ok(l) => l,
Err(_) => continue,
};
let failed = lock
.resources
.values()
.filter(|r| r.status == types::ResourceStatus::Failed)
.count();
let drifted = lock
.resources
.values()
.filter(|r| r.status == types::ResourceStatus::Drifted)
.count();
if failed > 0 || drifted > 0 {
dists.push(((*m).clone(), failed, drifted));
}
}
dists.sort_by(|a, b| a.0.cmp(&b.0));
dists
}
pub(crate) fn cmd_status_machine_resource_drift_age(
sd: &Path,
machine: Option<&str>,
json: bool,
) -> Result<(), String> {
let machines = discover_machines(sd);
let targets: Vec<&String> = match machine {
Some(m) => machines.iter().filter(|n| n.as_str() == m).collect(),
None => machines.iter().collect(),
};
let ages = collect_drift_ages(sd, &targets);
if json {
let items: Vec<String> = ages
.iter()
.map(|(m, r, age)| {
format!("{{\"machine\":\"{m}\",\"resource\":\"{r}\",\"drift_age_hours\":{age:.2}}}")
})
.collect();
println!("{{\"drift_ages\":[{}]}}", items.join(","));
} else if ages.is_empty() {
println!("No drifted resources found.");
} else {
println!("Drift ages:");
for (m, r, age) in &ages {
println!(" {m}/{r} — {age:.2}h drifted");
}
}
Ok(())
}
pub(super) fn collect_drift_ages(sd: &Path, targets: &[&String]) -> Vec<(String, String, f64)> {
let mut ages = Vec::new();
for m in targets {
let path = sd.join(m).join("state.lock.yaml");
let content = match std::fs::read_to_string(&path) {
Ok(c) => c,
Err(_) => continue,
};
let lock: types::StateLock = match serde_yaml_ng::from_str(&content) {
Ok(l) => l,
Err(_) => continue,
};
for (rid, rl) in &lock.resources {
if rl.status != types::ResourceStatus::Drifted {
continue;
}
let hours = rl.duration_seconds.unwrap_or(0.0) / 3600.0;
ages.push(((*m).clone(), rid.clone(), hours));
}
}
ages.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
ages
}
pub(crate) fn cmd_status_fleet_resource_drift_age(
sd: &Path,
machine: Option<&str>,
json: bool,
) -> Result<(), String> {
let machines = discover_machines(sd);
let targets: Vec<&String> = match machine {
Some(m) => machines.iter().filter(|n| n.as_str() == m).collect(),
None => machines.iter().collect(),
};
let ages = collect_drift_ages(sd, &targets);
let total: f64 = ages.iter().map(|(_, _, h)| h).sum();
let avg = if !ages.is_empty() {
total / ages.len() as f64
} else {
0.0
};
if json {
println!(
"{{\"fleet_drift_age_avg_hours\":{:.2},\"drifted_resources\":{}}}",
avg,
ages.len()
);
} else {
println!(
"Fleet drift age: avg {:.2}h across {} drifted resources",
avg,
ages.len()
);
}
Ok(())
}
pub(super) use super::status_intelligence_ext_b::*;