codemem_engine/enrichment/
performance.rs1use super::EnrichResult;
4use crate::CodememEngine;
5use codemem_core::{CodememError, GraphBackend, NodeKind};
6use serde_json::json;
7use std::collections::HashMap;
8
9impl CodememEngine {
10 pub fn enrich_performance(
12 &self,
13 top: usize,
14 namespace: Option<&str>,
15 ) -> Result<EnrichResult, CodememError> {
16 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 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 layers = graph.topological_layers();
37
38 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 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 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 {
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 let mut insights_stored = 0;
111
112 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 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 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 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}