Skip to main content

codemem_engine/enrichment/
performance.rs

1//! Performance analysis: coupling, dependency depth, PageRank, complexity.
2
3use super::EnrichResult;
4use crate::CodememEngine;
5use codemem_core::{CodememError, GraphBackend, NodeKind};
6use serde_json::json;
7use std::collections::HashMap;
8
9impl CodememEngine {
10    /// Enrich the graph with performance analysis: coupling, dependency depth, PageRank, complexity.
11    pub fn enrich_performance(
12        &self,
13        top: usize,
14        namespace: Option<&str>,
15    ) -> Result<EnrichResult, CodememError> {
16        // Collect data from graph into local variables, then drop lock
17        let all_nodes;
18        let mut coupling_data: Vec<(String, String, usize)> = Vec::new();
19        let mut high_coupling_count = 0;
20        let layers: Vec<Vec<String>>;
21        let mut file_pagerank: Vec<(String, String, f64)> = Vec::new();
22        {
23            let graph = self.lock_graph()?;
24            all_nodes = graph.get_all_nodes();
25
26            // 1. Compute coupling (in-degree + out-degree) for each node
27            for node in &all_nodes {
28                let degree = graph.get_edges(&node.id).map(|e| e.len()).unwrap_or(0);
29                coupling_data.push((node.id.clone(), node.label.clone(), degree));
30                if degree > self.config.enrichment.perf_min_coupling_degree {
31                    high_coupling_count += 1;
32                }
33            }
34
35            // 2. Compute dependency depth via topological layers
36            layers = graph.topological_layers();
37
38            // 3. PageRank for critical path (File nodes only)
39            for node in &all_nodes {
40                if node.kind == NodeKind::File {
41                    let pr = graph.get_pagerank(&node.id);
42                    if pr > 0.0 {
43                        file_pagerank.push((node.id.clone(), node.label.clone(), pr));
44                    }
45                }
46            }
47        }
48        // Graph lock released here
49
50        let max_depth = layers.len();
51        file_pagerank.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap_or(std::cmp::Ordering::Equal));
52
53        // 4. File complexity from symbol counts (computed from local all_nodes)
54        let mut file_symbol_counts: HashMap<String, usize> = HashMap::new();
55        for node in &all_nodes {
56            match node.kind {
57                NodeKind::Function
58                | NodeKind::Method
59                | NodeKind::Class
60                | NodeKind::Interface
61                | NodeKind::Type => {
62                    if let Some(file_path) = node.payload.get("file_path").and_then(|v| v.as_str())
63                    {
64                        *file_symbol_counts.entry(file_path.to_string()).or_default() += 1;
65                    }
66                }
67                _ => {}
68            }
69        }
70
71        // Annotate graph nodes (short lock scope for writes only)
72        {
73            let mut graph = self.lock_graph()?;
74
75            for (node_id, _label, degree) in &coupling_data {
76                if let Ok(Some(mut node)) = graph.get_node(node_id) {
77                    node.payload.insert("coupling_score".into(), json!(degree));
78                    let _ = graph.add_node(node);
79                }
80            }
81
82            for (layer_idx, layer) in layers.iter().enumerate() {
83                for node_id in layer {
84                    if let Ok(Some(mut node)) = graph.get_node(node_id) {
85                        node.payload
86                            .insert("dependency_layer".into(), json!(layer_idx));
87                        let _ = graph.add_node(node);
88                    }
89                }
90            }
91
92            for (node_id, _label, rank) in file_pagerank.iter().take(top) {
93                if let Ok(Some(mut node)) = graph.get_node(node_id) {
94                    node.payload
95                        .insert("critical_path_rank".into(), json!(rank));
96                    let _ = graph.add_node(node);
97                }
98            }
99
100            for (file_path, sym_count) in &file_symbol_counts {
101                let node_id = format!("file:{file_path}");
102                if let Ok(Some(mut node)) = graph.get_node(&node_id) {
103                    node.payload.insert("symbol_count".into(), json!(sym_count));
104                    let _ = graph.add_node(node);
105                }
106            }
107        }
108
109        // Store insights
110        let mut insights_stored = 0;
111
112        // High-coupling nodes
113        coupling_data.sort_by(|a, b| b.2.cmp(&a.2));
114        for (node_id, label, degree) in coupling_data.iter().take(top) {
115            if *degree > self.config.enrichment.perf_min_coupling_degree {
116                let content = format!(
117                    "High coupling: {} has {} dependencies — refactoring risk",
118                    label, degree
119                );
120                if self
121                    .store_insight(
122                        &content,
123                        "performance",
124                        &["coupling"],
125                        0.7,
126                        namespace,
127                        std::slice::from_ref(node_id),
128                    )
129                    .is_some()
130                {
131                    insights_stored += 1;
132                }
133            }
134        }
135
136        // Deep dependency chain
137        if max_depth > 5 {
138            let content = format!(
139                "Deep dependency chain: {} layers — impacts build and test times",
140                max_depth
141            );
142            if self
143                .store_insight(
144                    &content,
145                    "performance",
146                    &["dependency-depth"],
147                    0.6,
148                    namespace,
149                    &[],
150                )
151                .is_some()
152            {
153                insights_stored += 1;
154            }
155        }
156
157        // Critical bottleneck (top PageRank file)
158        if let Some((node_id, label, _)) = file_pagerank.first() {
159            let content = format!(
160                "Critical bottleneck: {} — highest centrality file, changes cascade widely",
161                label
162            );
163            if self
164                .store_insight(
165                    &content,
166                    "performance",
167                    &["critical-path"],
168                    0.8,
169                    namespace,
170                    std::slice::from_ref(node_id),
171                )
172                .is_some()
173            {
174                insights_stored += 1;
175            }
176        }
177
178        // Complex files (high symbol count)
179        let mut complex_files: Vec<_> = file_symbol_counts.iter().collect();
180        complex_files.sort_by(|a, b| b.1.cmp(a.1));
181        for (file_path, sym_count) in complex_files.iter().take(top) {
182            if **sym_count > self.config.enrichment.perf_min_symbol_count {
183                let content = format!("Complex file: {} — {} symbols", file_path, sym_count);
184                if self
185                    .store_insight(
186                        &content,
187                        "performance",
188                        &["complexity"],
189                        0.5,
190                        namespace,
191                        &[format!("file:{file_path}")],
192                    )
193                    .is_some()
194                {
195                    insights_stored += 1;
196                }
197            }
198        }
199
200        self.save_index();
201
202        let critical_files: Vec<_> = file_pagerank
203            .iter()
204            .take(top)
205            .map(|(_, label, score)| json!({"file": label, "pagerank": score}))
206            .collect();
207
208        Ok(EnrichResult {
209            insights_stored,
210            details: json!({
211                "high_coupling_count": high_coupling_count,
212                "max_depth": max_depth,
213                "critical_files": critical_files,
214                "insights_stored": insights_stored,
215            }),
216        })
217    }
218}