Skip to main content

ci_patterns/
ci_patterns.rs

1//! CI build time anomaly detection: find clustering and phase shifts in build durations.
2
3use crackle_runtime::{CrackleTask, Kiln, ThermalProfile, TaskOutput};
4
5struct CiBuild {
6    duration_secs: f64,
7    test_count: f64,
8    branch: String,
9}
10
11impl CrackleTask for CiBuild {
12    type Output = f64;
13
14    fn fire(&self) -> TaskOutput<Self::Output> {
15        TaskOutput::new(
16            self.duration_secs,
17            vec![
18                ("duration".into(), self.duration_secs),
19                ("test_count".into(), self.test_count),
20                ("duration_per_test".into(), self.duration_secs / self.test_count.max(1.0)),
21            ],
22        )
23    }
24
25    fn label(&self) -> String {
26        self.branch.clone()
27    }
28}
29
30fn main() {
31    let mut kiln = Kiln::new(ThermalProfile::default());
32
33    // Normal builds
34    kiln.fire_and_record(CiBuild { duration_secs: 45.0, test_count: 200.0, branch: "feature/auth".into() }).unwrap();
35    kiln.fire_and_record(CiBuild { duration_secs: 42.0, test_count: 195.0, branch: "fix/typo".into() }).unwrap();
36    kiln.fire_and_record(CiBuild { duration_secs: 48.0, test_count: 210.0, branch: "feature/ui".into() }).unwrap();
37
38    // Something changed — builds got slower
39    kiln.fire_and_record(CiBuild { duration_secs: 95.0, test_count: 200.0, branch: "feature/cache".into() }).unwrap();
40    kiln.fire_and_record(CiBuild { duration_secs: 102.0, test_count: 198.0, branch: "chore/deps".into() }).unwrap();
41
42    let patterns = kiln.cool();
43    println!("CI Build Pattern Analysis");
44    println!("=========================\n");
45
46    for p in &patterns {
47        println!("[{}] {}", p.kind().to_string().to_uppercase(), p.description());
48        println!("  confidence: {:.2}", p.confidence());
49        println!("  branches: {:?}\n", p.involved_tasks());
50    }
51
52    // Check for phase transition in build duration
53    let phase_shifts: Vec<_> = patterns.iter()
54        .filter(|p| format!("{}", p.kind()) == "phase transition")
55        .collect();
56
57    if !phase_shifts.is_empty() {
58        println!("⚠️  Build duration shifted significantly — investigate recent changes!");
59    }
60}