fn urgency_recommendation(days: usize) -> String {
if days <= 7 {
"⚠️ URGENT: Threshold breach imminent - prioritize optimization".to_string()
} else if days <= 14 {
"⚠️ WARNING: Threshold breach in 2 weeks - schedule optimization".to_string()
} else {
format!("ℹ️ INFO: {days} days until breach - plan optimization")
}
}
fn metric_specific_recommendations(metric: &str) -> Vec<String> {
match metric {
"lint" => vec![
"Remove unused dependencies (saves ~2-3s)".into(),
"Enable incremental clippy analysis".into(),
"Review enabled clippy lints (disable pedantic if not needed)".into(),
"Use cargo-cache to clean old artifacts".into(),
],
"test-fast" => vec![
"Parallelize test execution (use --test-threads)".into(),
"Use #[ignore] for slow property tests".into(),
"Implement test fixtures to reduce setup time".into(),
"Profile tests to identify slowest ones".into(),
],
"coverage" => vec![
"Run coverage only in CI (skip locally)".into(),
"Use --exclude for non-critical modules".into(),
"Skip expensive property-based tests in coverage".into(),
"Consider sampling coverage (not 100% runs)".into(),
],
"build-release" => vec![
"Enable LTO only in final release builds".into(),
"Reduce codegen-units for faster linking".into(),
"Use sccache with CARGO_INCREMENTAL=0 (incremental builds cannot be cached)".into(),
"Use per-project target dirs (avoid shared CARGO_TARGET_DIR lock contention)".into(),
"Review dependency tree for bloat".into(),
],
_ => vec![
format!("Review {metric} history for optimization opportunities"),
"Profile to identify bottlenecks".into(),
],
}
}
impl MetricTrendStore {
fn generate_recommendations(
&self,
metric: &str,
breach_in_days: Option<usize>,
_threshold: f64,
) -> Vec<String> {
let days = match breach_in_days {
Some(d) => d,
None => return vec![
"No threshold breach predicted in forecast period".to_string(),
"Continue current practices".to_string(),
],
};
let mut recommendations = vec![urgency_recommendation(days)];
recommendations.extend(metric_specific_recommendations(metric));
recommendations
}
fn persist(&self, metric: &str) -> Result<()> {
if let Some(observations) = self.cache.get(metric) {
let path = self.storage_path.join(format!("{}.json", metric));
let json = serde_json::to_string_pretty(observations)?;
std::fs::write(&path, json).context("Failed to write metric observations")?;
}
Ok(())
}
fn load(&mut self, metric: &str) -> Result<()> {
let path = self.storage_path.join(format!("{}.json", metric));
if path.exists() {
let json = std::fs::read_to_string(&path)?;
let observations: Vec<MetricObservation> = serde_json::from_str(&json)?;
self.cache.insert(metric.to_string(), observations.clone());
for (idx, obs) in observations.iter().enumerate() {
if self.node_map.contains_key(&obs.timestamp) {
continue;
}
let node_id = NodeId(self.next_node_id);
self.next_node_id += 1;
self.node_map.insert(obs.timestamp, node_id);
self.reverse_node_map.insert(node_id, obs.timestamp);
if idx > 0 {
let prev_obs = &observations[idx - 1];
if let Some(prev_node_id) = self.node_map.get(&prev_obs.timestamp) {
let delta_t = (obs.timestamp - prev_obs.timestamp) as f32;
self.graph.add_edge(*prev_node_id, node_id, delta_t)?;
}
}
}
}
Ok(())
}
pub fn metrics(&mut self) -> Result<Vec<String>> {
let mut metrics = Vec::new();
for entry in std::fs::read_dir(&self.storage_path)? {
let entry = entry?;
if let Some(name) = entry.path().file_stem() {
metrics.push(name.to_string_lossy().to_string());
}
}
Ok(metrics)
}
}