1use serde::{Deserialize, Serialize};
36
37#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
39pub struct ObjectId(pub u64);
40
41impl ObjectId {
42 pub fn from_ptr(ptr: usize) -> Self {
44 ObjectId(ptr as u64)
45 }
46}
47
48#[repr(u8)]
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
51pub enum OwnershipOp {
52 Create,
54 Drop,
56 RcClone,
58 ArcClone,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
64pub struct OwnershipEvent {
65 pub ts: u64,
67 pub op: OwnershipOp,
69 pub src: ObjectId,
71 pub dst: Option<ObjectId>,
73}
74
75impl OwnershipEvent {
76 pub fn new(ts: u64, op: OwnershipOp, src: ObjectId, dst: Option<ObjectId>) -> Self {
78 Self { ts, op, src, dst }
79 }
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct Node {
85 pub id: ObjectId,
87 pub type_name: String,
89 pub size: usize,
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
95pub enum EdgeKind {
96 Owns,
98 Borrows,
100 RcClone,
102 ArcClone,
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct Edge {
109 pub from: ObjectId,
111 pub to: ObjectId,
113 pub op: EdgeKind,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct OwnershipGraph {
120 pub nodes: Vec<Node>,
122 pub edges: Vec<Edge>,
124 pub cycles: Vec<Vec<ObjectId>>,
126 pub arc_clone_count: usize,
128}
129
130impl OwnershipGraph {
131 pub fn build<T: AsRef<[OwnershipEvent]>>(passports: &[(ObjectId, String, usize, T)]) -> Self {
133 let mut nodes = Vec::new();
134 let mut edges = Vec::new();
135 let mut arc_clone_count = 0;
136
137 for (id, type_name, size, events) in passports {
138 nodes.push(Node {
140 id: *id,
141 type_name: type_name.clone(),
142 size: *size,
143 });
144
145 for event in events.as_ref() {
147 match event.op {
148 OwnershipOp::RcClone => {
149 if let Some(dst) = event.dst {
150 edges.push(Edge {
151 from: event.src,
152 to: dst,
153 op: EdgeKind::RcClone,
154 });
155 }
156 }
157 OwnershipOp::ArcClone => {
158 arc_clone_count += 1;
159 if let Some(dst) = event.dst {
160 edges.push(Edge {
161 from: event.src,
162 to: dst,
163 op: EdgeKind::ArcClone,
164 });
165 }
166 }
167 OwnershipOp::Create | OwnershipOp::Drop => {
168 }
170 }
171 }
172 }
173
174 Self::compress_clone_chains(&mut edges);
176
177 let cycles = Self::detect_cycles(&edges);
179
180 OwnershipGraph {
181 nodes,
182 edges,
183 cycles,
184 arc_clone_count,
185 }
186 }
187
188 fn compress_clone_chains(edges: &mut Vec<Edge>) {
190 let mut i = 0;
191 while i + 1 < edges.len() {
192 if edges[i].op == edges[i + 1].op && edges[i].to == edges[i + 1].from {
193 edges[i].to = edges[i + 1].to;
194 edges.remove(i + 1);
195 } else {
196 i += 1;
197 }
198 }
199 }
200
201 fn detect_cycles(edges: &[Edge]) -> Vec<Vec<ObjectId>> {
203 use crate::analysis::relationship_cycle_detector;
204
205 if edges.is_empty() {
206 return Vec::new();
207 }
208
209 let relationships: Vec<(String, String, String)> = edges
211 .iter()
212 .map(|e| {
213 (
214 format!("0x{:x}", e.from.0),
215 format!("0x{:x}", e.to.0),
216 format!("{:?}", e.op).to_lowercase(),
217 )
218 })
219 .collect();
220
221 let result = relationship_cycle_detector::detect_cycles_with_indices(&relationships);
222
223 let mut cycles = Vec::new();
225 let mut obj_id_map: std::collections::HashMap<String, ObjectId> =
226 std::collections::HashMap::new();
227
228 for edge in edges {
229 obj_id_map.insert(format!("0x{:x}", edge.from.0), edge.from);
230 obj_id_map.insert(format!("0x{:x}", edge.to.0), edge.to);
231 }
232
233 for (from_idx, to_idx) in result.cycle_edges {
234 if let (Some(from_label), Some(to_label)) = (
235 result.node_labels.get(from_idx),
236 result.node_labels.get(to_idx),
237 ) {
238 if let (Some(from_id), Some(to_id)) =
239 (obj_id_map.get(from_label), obj_id_map.get(to_label))
240 {
241 cycles.push(vec![*from_id, *to_id]);
243 }
244 }
245 }
246
247 cycles
248 }
249
250 pub fn has_arc_clone_storm(&self, threshold: usize) -> bool {
252 self.arc_clone_count > threshold
253 }
254
255 pub fn rc_clones(&self) -> Vec<&Edge> {
257 self.edges
258 .iter()
259 .filter(|e| e.op == EdgeKind::RcClone)
260 .collect()
261 }
262
263 pub fn arc_clones(&self) -> Vec<&Edge> {
265 self.edges
266 .iter()
267 .filter(|e| e.op == EdgeKind::ArcClone)
268 .collect()
269 }
270
271 pub fn diagnostics(&self, arc_storm_threshold: usize) -> OwnershipDiagnostics {
273 let mut issues = Vec::new();
274
275 for cycle in &self.cycles {
277 let cycle_type = self.detect_cycle_type(cycle);
278 issues.push(DiagnosticIssue::RcCycle {
279 nodes: cycle.clone(),
280 cycle_type,
281 });
282 }
283
284 if self.has_arc_clone_storm(arc_storm_threshold) {
286 issues.push(DiagnosticIssue::ArcCloneStorm {
287 clone_count: self.arc_clone_count,
288 threshold: arc_storm_threshold,
289 });
290 }
291
292 OwnershipDiagnostics {
293 issues,
294 total_nodes: self.nodes.len(),
295 total_edges: self.edges.len(),
296 rc_clone_count: self.rc_clones().len(),
297 arc_clone_count: self.arc_clone_count,
298 }
299 }
300
301 fn detect_cycle_type(&self, cycle: &[ObjectId]) -> CycleType {
303 for edge in &self.edges {
305 if cycle.contains(&edge.from)
306 && cycle.contains(&edge.to)
307 && edge.op == EdgeKind::RcClone
308 {
309 return CycleType::Rc;
310 }
311 }
312 CycleType::Arc
313 }
314
315 pub fn find_root_cause(&self) -> Option<RootCauseChain> {
317 if self.arc_clone_count > 50 {
319 return Some(RootCauseChain {
320 root_cause: RootCause::ArcCloneStorm,
321 description: format!(
322 "Arc clone storm detected: {} clones causing memory proliferation",
323 self.arc_clone_count
324 ),
325 impact: format!(
326 "Potential memory spike from {} Arc clone operations",
327 self.arc_clone_count
328 ),
329 });
330 }
331
332 if !self.cycles.is_empty() {
334 return Some(RootCauseChain {
335 root_cause: RootCause::RcCycle,
336 description: format!(
337 "Rc retain cycle detected: {} cycles found",
338 self.cycles.len()
339 ),
340 impact: "Memory leak due to reference count cycles".to_string(),
341 });
342 }
343
344 None
345 }
346}
347
348#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
350pub enum CycleType {
351 Rc,
353 Arc,
355}
356
357#[derive(Debug, Clone, Serialize, Deserialize)]
359pub enum DiagnosticIssue {
360 RcCycle {
362 nodes: Vec<ObjectId>,
363 cycle_type: CycleType,
364 },
365 ArcCloneStorm {
367 clone_count: usize,
368 threshold: usize,
369 },
370}
371
372#[derive(Debug, Clone, Serialize, Deserialize)]
374pub struct OwnershipDiagnostics {
375 pub issues: Vec<DiagnosticIssue>,
377 pub total_nodes: usize,
379 pub total_edges: usize,
381 pub rc_clone_count: usize,
383 pub arc_clone_count: usize,
385}
386
387impl OwnershipDiagnostics {
388 pub fn has_issues(&self) -> bool {
390 !self.issues.is_empty()
391 }
392
393 pub fn summary(&self) -> String {
395 let mut summary = format!(
396 "Ownership Graph: {} nodes, {} edges\n",
397 self.total_nodes, self.total_edges
398 );
399 for issue in &self.issues {
400 match issue {
401 DiagnosticIssue::RcCycle { nodes, cycle_type } => {
402 summary.push_str(&format!(
403 "🔴 {:?} Cycle detected: {} nodes\n",
404 cycle_type,
405 nodes.len()
406 ));
407 }
408 DiagnosticIssue::ArcCloneStorm {
409 clone_count,
410 threshold,
411 } => {
412 summary.push_str(&format!(
413 "⚠ Arc Clone Storm: {} clones (threshold: {})\n",
414 clone_count, threshold
415 ));
416 }
417 }
418 }
419 summary
420 }
421}
422
423#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
425pub enum RootCause {
426 ArcCloneStorm,
428 RcCycle,
430}
431
432#[derive(Debug, Clone, Serialize, Deserialize)]
434pub struct RootCauseChain {
435 pub root_cause: RootCause,
437 pub description: String,
439 pub impact: String,
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446
447 #[test]
448 fn test_object_id_from_ptr() {
449 let id = ObjectId::from_ptr(0x1000);
450 assert_eq!(id.0, 0x1000);
451 }
452
453 #[test]
454 fn test_ownership_event_creation() {
455 let id = ObjectId(0x1000);
456 let event = OwnershipEvent::new(1000, OwnershipOp::Create, id, None);
457 assert_eq!(event.ts, 1000);
458 assert_eq!(event.op, OwnershipOp::Create);
459 }
460
461 #[test]
462 fn test_graph_build_empty() {
463 let passports: Vec<(ObjectId, String, usize, Vec<OwnershipEvent>)> = vec![];
464 let graph = OwnershipGraph::build(&passports);
465 assert!(graph.nodes.is_empty());
466 assert!(graph.edges.is_empty());
467 assert!(graph.cycles.is_empty());
468 }
469
470 #[test]
471 fn test_graph_build_rc_clone() {
472 let id1 = ObjectId(0x1000);
473 let id2 = ObjectId(0x2000);
474 let events = vec![OwnershipEvent::new(
475 1000,
476 OwnershipOp::RcClone,
477 id1,
478 Some(id2),
479 )];
480 let passports = vec![(id1, "Rc<i32>".to_string(), 8, events)];
481
482 let graph = OwnershipGraph::build(&passports);
483 assert_eq!(graph.nodes.len(), 1);
484 assert_eq!(graph.edges.len(), 1);
485 assert_eq!(graph.edges[0].op, EdgeKind::RcClone);
486 }
487
488 #[test]
489 fn test_graph_build_arc_clone_storm() {
490 let id1 = ObjectId(0x1000);
491 let mut events = Vec::new();
492 for i in 0..100 {
493 let dst = ObjectId(0x2000 + i);
494 events.push(OwnershipEvent::new(
495 i,
496 OwnershipOp::ArcClone,
497 id1,
498 Some(dst),
499 ));
500 }
501 let passports = vec![(id1, "Arc<i32>".to_string(), 8, events)];
502
503 let graph = OwnershipGraph::build(&passports);
504 assert_eq!(graph.arc_clone_count, 100);
505 assert!(graph.has_arc_clone_storm(50));
506 }
507
508 #[test]
509 fn test_compress_clone_chains() {
510 let id1 = ObjectId(0x1000);
511 let id2 = ObjectId(0x2000);
512 let id3 = ObjectId(0x3000);
513 let id4 = ObjectId(0x4000);
514
515 let events = vec![
516 OwnershipEvent::new(1000, OwnershipOp::ArcClone, id1, Some(id2)),
517 OwnershipEvent::new(2000, OwnershipOp::ArcClone, id2, Some(id3)),
518 OwnershipEvent::new(3000, OwnershipOp::ArcClone, id3, Some(id4)),
519 ];
520 let passports = vec![(id1, "Arc<i32>".to_string(), 8, events)];
521
522 let graph = OwnershipGraph::build(&passports);
523
524 assert_eq!(graph.edges.len(), 1);
526 assert_eq!(graph.edges[0].from, id1);
527 assert_eq!(graph.edges[0].to, id4);
528 }
529
530 #[test]
531 fn test_diagnostics() {
532 let id1 = ObjectId(0x1000);
533 let mut events = Vec::new();
534 for i in 0..100 {
535 let dst = ObjectId(0x2000 + i);
536 events.push(OwnershipEvent::new(
537 i,
538 OwnershipOp::ArcClone,
539 id1,
540 Some(dst),
541 ));
542 }
543 let passports = vec![(id1, "Arc<i32>".to_string(), 8, events)];
544
545 let graph = OwnershipGraph::build(&passports);
546 let diagnostics = graph.diagnostics(50);
547
548 assert!(diagnostics.has_issues());
549 assert!(diagnostics.summary().contains("Arc Clone Storm"));
550 }
551
552 #[test]
553 fn test_root_cause_detection() {
554 let id1 = ObjectId(0x1000);
555 let mut events = Vec::new();
556 for i in 0..100 {
557 let dst = ObjectId(0x2000 + i);
558 events.push(OwnershipEvent::new(
559 i,
560 OwnershipOp::ArcClone,
561 id1,
562 Some(dst),
563 ));
564 }
565 let passports = vec![(id1, "Arc<i32>".to_string(), 8, events)];
566
567 let graph = OwnershipGraph::build(&passports);
568 let root_cause = graph.find_root_cause();
569
570 assert!(root_cause.is_some());
571 let chain = root_cause.unwrap();
572 assert_eq!(chain.root_cause, RootCause::ArcCloneStorm);
573 }
574}