#![cfg_attr(coverage_nightly, coverage(off))]
use super::CommandDispatcher;
use crate::cli::colors as c;
use crate::cli::OutputFormat;
impl CommandDispatcher {
pub(crate) async fn execute_show_metrics_command(
trend: bool,
days: usize,
metric: Option<String>,
format: OutputFormat,
failures_only: bool,
) -> anyhow::Result<()> {
use crate::services::metric_trends::{MetricTrendStore, TrendDirection};
let _ = trend;
let mut store = MetricTrendStore::new()?;
let metrics = if let Some(m) = metric {
vec![m]
} else {
store.metrics()?
};
for metric_name in &metrics {
let _ = store.trend(metric_name, days); }
store.update_hotness()?;
match format {
OutputFormat::Json => {
let mut results = serde_json::Map::new();
let hot_metrics = store.hot_metrics();
let mut hot_map = serde_json::Map::new();
for (name, score) in hot_metrics {
hot_map.insert(name, serde_json::json!(score));
}
results.insert(
"hot_metrics".to_string(),
serde_json::Value::Object(hot_map),
);
for metric_name in metrics {
if let Ok(trend_analysis) = store.trend(&metric_name, days) {
if failures_only && trend_analysis.direction != TrendDirection::Regressing {
continue;
}
results.insert(metric_name, serde_json::to_value(trend_analysis)?);
}
}
println!("{}", serde_json::to_string_pretty(&results)?);
}
_ => {
println!(
"\n{}Quality Metrics Trends ({} days){}\n",
c::BOLD_BLUE,
days,
c::RESET
);
let hot_metrics = store.hot_metrics();
if !hot_metrics.is_empty() {
println!("{}Hot Metrics (PageRank){}", c::BOLD_YELLOW, c::RESET);
for (idx, (name, score)) in hot_metrics.iter().enumerate().take(5) {
println!(" {}. {} (score: {:.4})", idx + 1, name, score);
}
println!();
}
let mut sorted_metrics: Vec<(String, f32)> = metrics
.iter()
.map(|m| {
let score = hot_metrics
.iter()
.find(|(name, _)| name == m)
.map(|(_, s)| *s)
.unwrap_or(0.0);
(m.clone(), score)
})
.collect();
sorted_metrics.sort_by(|a, b| b.1.total_cmp(&a.1));
for (metric_name, _hotness) in sorted_metrics {
if let Ok(trend_analysis) = store.trend(&metric_name, days) {
if failures_only && trend_analysis.direction != TrendDirection::Regressing {
continue;
}
let direction_symbol = match trend_analysis.direction {
TrendDirection::Improving => {
format!("{}Improving{}", c::GREEN, c::RESET)
}
TrendDirection::Stable => {
format!("{}Stable{}", c::YELLOW, c::RESET)
}
TrendDirection::Regressing => {
format!("{}Regressing{}", c::RED, c::RESET)
}
};
println!("{}{}{}", c::BOLD, metric_name, c::RESET);
println!(" Direction: {}", direction_symbol);
println!(" Mean: {:.2}", trend_analysis.mean);
println!(" Std Dev: {:.2}", trend_analysis.std_dev);
println!(
" Min/Max: {:.2} / {:.2}",
trend_analysis.min, trend_analysis.max
);
println!(" Slope: {:.2}/day", trend_analysis.slope);
println!(" Observations: {}", trend_analysis.count);
if trend_analysis.direction == TrendDirection::Regressing {
let recommendations = Self::generate_metric_recommendations(
&metric_name,
trend_analysis.slope,
);
if !recommendations.is_empty() {
println!(" {}Recommendations:{}", c::BOLD_YELLOW, c::RESET);
for rec in recommendations {
println!(" - {}", rec);
}
}
}
println!();
}
}
}
}
Ok(())
}
pub(crate) async fn execute_record_metric_command(
metric: String,
value: f64,
timestamp: Option<i64>,
) -> anyhow::Result<()> {
use crate::services::metric_trends::MetricTrendStore;
let mut store = MetricTrendStore::new()?;
let ts = timestamp.unwrap_or_else(|| chrono::Utc::now().timestamp());
store.record(&metric, value, ts)?;
println!("Recorded {} = {:.2} at timestamp {}", metric, value, ts);
if let Ok(trend_analysis) = store.trend(&metric, 30) {
println!(
" Last 30 days: mean={:.2}, slope={:.2}/day",
trend_analysis.mean, trend_analysis.slope
);
}
Ok(())
}
pub(crate) fn generate_metric_recommendations(metric: &str, slope_per_day: f64) -> Vec<String> {
let mut recommendations = Vec::new();
let days_to_critical = match metric {
"lint" => {
let threshold = 30_000.0;
let current_estimate = 26_500.0; ((threshold - current_estimate) / slope_per_day).max(0.0)
}
"test-fast" => {
let threshold = 300_000.0;
let current_estimate = 107_000.0;
((threshold - current_estimate) / slope_per_day).max(0.0)
}
"coverage" => {
let threshold = 600_000.0;
let current_estimate = 480_000.0;
((threshold - current_estimate) / slope_per_day).max(0.0)
}
"build-release" => {
let threshold = 900_000.0;
let current_estimate = 717_000.0;
((threshold - current_estimate) / slope_per_day).max(0.0)
}
_ => f64::MAX,
};
if days_to_critical < 30.0 {
recommendations.push(format!(
"WARNING: Approaching threshold in ~{:.0} days",
days_to_critical
));
}
match metric {
"lint" => {
recommendations.push("Remove unused dependencies (saves ~2-3s)".to_string());
recommendations.push("Enable incremental clippy analysis".to_string());
recommendations
.push("Review enabled lints (disable pedantic if not needed)".to_string());
}
"test-fast" => {
recommendations.push("Add #[ignore] to slow integration tests".to_string());
recommendations.push("Use proptest with reduced cases for CI".to_string());
recommendations.push("Parallelize test execution with nextest".to_string());
}
"coverage" => {
recommendations.push("Exclude slow tests from coverage run".to_string());
recommendations.push("Use cargo-llvm-cov with --skip-functions flag".to_string());
recommendations
.push("Consider sampling-based coverage for large projects".to_string());
}
"build-release" => {
recommendations.push(
"Enable sccache with CARGO_INCREMENTAL=0 (required for cache hits)".to_string(),
);
recommendations.push(
"Use per-project target dirs (avoid shared CARGO_TARGET_DIR lock contention)"
.to_string(),
);
recommendations
.push("Review feature flags (disable optional features)".to_string());
recommendations.push("Use mold/lld linker for faster linking".to_string());
}
_ => {}
}
recommendations
}
}