1use serde::{Deserialize, Serialize};
36
37use super::node_id::NodeId;
38
39pub type ObjectId = NodeId;
46
47#[repr(u8)]
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
50pub enum OwnershipOp {
51 Create,
53 Drop,
55 RcClone,
57 ArcClone,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
63pub struct OwnershipEvent {
64 pub ts: u64,
66 pub op: OwnershipOp,
68 pub src: ObjectId,
70 pub dst: Option<ObjectId>,
72}
73
74impl OwnershipEvent {
75 pub fn new(ts: u64, op: OwnershipOp, src: ObjectId, dst: Option<ObjectId>) -> Self {
77 Self { ts, op, src, dst }
78 }
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct Node {
84 pub id: ObjectId,
86 pub type_name: String,
88 pub size: usize,
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
94pub enum EdgeKind {
95 Owns,
97 Contains,
99 Borrows,
101 RcClone,
103 ArcClone,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct Edge {
110 pub from: ObjectId,
112 pub to: ObjectId,
114 pub op: EdgeKind,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct OwnershipGraph {
121 pub nodes: Vec<Node>,
123 pub edges: Vec<Edge>,
125 pub cycles: Vec<Vec<ObjectId>>,
127 pub arc_clone_count: usize,
129}
130
131impl OwnershipGraph {
132 pub fn from_view(view: &crate::view::MemoryView) -> Self {
137 let allocations = view.allocations();
138 let passports: Vec<(ObjectId, String, usize, Vec<OwnershipEvent>)> = allocations
139 .iter()
140 .filter_map(|a| {
141 a.ptr.map(|ptr| {
142 let id = ObjectId::from_ptr(ptr);
143 let type_name = a.type_name.clone().unwrap_or_else(|| "unknown".to_string());
144 let event = OwnershipEvent::new(a.allocated_at, OwnershipOp::Create, id, None);
146 (id, type_name, a.size, vec![event])
147 })
148 })
149 .collect();
150
151 Self::build(&passports)
152 }
153
154 pub fn build<T: AsRef<[OwnershipEvent]>>(passports: &[(ObjectId, String, usize, T)]) -> Self {
156 let mut nodes = Vec::new();
157 let mut edges = Vec::new();
158 let mut arc_clone_count = 0;
159
160 for (id, type_name, size, events) in passports {
161 nodes.push(Node {
163 id: *id,
164 type_name: type_name.clone(),
165 size: *size,
166 });
167
168 for event in events.as_ref() {
170 match event.op {
171 OwnershipOp::RcClone => {
172 if let Some(dst) = event.dst {
173 edges.push(Edge {
174 from: event.src,
175 to: dst,
176 op: EdgeKind::RcClone,
177 });
178 }
179 }
180 OwnershipOp::ArcClone => {
181 arc_clone_count += 1;
182 if let Some(dst) = event.dst {
183 edges.push(Edge {
184 from: event.src,
185 to: dst,
186 op: EdgeKind::ArcClone,
187 });
188 }
189 }
190 OwnershipOp::Create | OwnershipOp::Drop => {
191 }
193 }
194 }
195 }
196
197 Self::compress_clone_chains(&mut edges);
199
200 let cycles = Self::detect_cycles(&edges);
202
203 OwnershipGraph {
204 nodes,
205 edges,
206 cycles,
207 arc_clone_count,
208 }
209 }
210
211 fn compress_clone_chains(edges: &mut Vec<Edge>) {
215 if edges.len() < 2 {
216 return;
217 }
218
219 let mut result: Vec<Edge> = Vec::with_capacity(edges.len());
220 let mut i = 0;
221
222 while i < edges.len() {
223 let mut current = edges[i].clone();
224
225 while i + 1 < edges.len()
227 && current.op == edges[i + 1].op
228 && current.to == edges[i + 1].from
229 {
230 current.to = edges[i + 1].to;
232 i += 1;
233 }
234
235 result.push(current);
236 i += 1;
237 }
238
239 *edges = result;
240 }
241
242 fn detect_cycles(edges: &[Edge]) -> Vec<Vec<ObjectId>> {
244 use crate::analysis::relationship_cycle_detector;
245
246 if edges.is_empty() {
247 return Vec::new();
248 }
249
250 let relationships: Vec<(String, String, String)> = edges
252 .iter()
253 .map(|e| {
254 (
255 format!("0x{:x}", e.from.0),
256 format!("0x{:x}", e.to.0),
257 format!("{:?}", e.op).to_lowercase(),
258 )
259 })
260 .collect();
261
262 let result = relationship_cycle_detector::detect_cycles_with_indices(&relationships);
263
264 let mut cycles = Vec::new();
266 let mut obj_id_map: std::collections::HashMap<String, ObjectId> =
267 std::collections::HashMap::new();
268
269 for edge in edges {
270 obj_id_map.insert(format!("0x{:x}", edge.from.0), edge.from);
271 obj_id_map.insert(format!("0x{:x}", edge.to.0), edge.to);
272 }
273
274 for (from_idx, to_idx) in result.cycle_edges {
275 if let (Some(from_label), Some(to_label)) = (
276 result.node_labels.get(from_idx),
277 result.node_labels.get(to_idx),
278 ) {
279 if let (Some(from_id), Some(to_id)) =
280 (obj_id_map.get(from_label), obj_id_map.get(to_label))
281 {
282 cycles.push(vec![*from_id, *to_id]);
284 }
285 }
286 }
287
288 cycles
289 }
290
291 pub fn has_arc_clone_storm(&self, threshold: usize) -> bool {
293 self.arc_clone_count > threshold
294 }
295
296 pub fn rc_clones(&self) -> Vec<&Edge> {
298 self.edges
299 .iter()
300 .filter(|e| e.op == EdgeKind::RcClone)
301 .collect()
302 }
303
304 pub fn arc_clones(&self) -> Vec<&Edge> {
306 self.edges
307 .iter()
308 .filter(|e| e.op == EdgeKind::ArcClone)
309 .collect()
310 }
311
312 pub fn diagnostics(&self, arc_storm_threshold: usize) -> OwnershipDiagnostics {
314 let mut issues = Vec::new();
315
316 for cycle in &self.cycles {
318 let cycle_type = self.detect_cycle_type(cycle);
319 issues.push(DiagnosticIssue::RcCycle {
320 nodes: cycle.clone(),
321 cycle_type,
322 });
323 }
324
325 if self.has_arc_clone_storm(arc_storm_threshold) {
327 issues.push(DiagnosticIssue::ArcCloneStorm {
328 clone_count: self.arc_clone_count,
329 threshold: arc_storm_threshold,
330 });
331 }
332
333 OwnershipDiagnostics {
334 issues,
335 total_nodes: self.nodes.len(),
336 total_edges: self.edges.len(),
337 rc_clone_count: self.rc_clones().len(),
338 arc_clone_count: self.arc_clone_count,
339 }
340 }
341
342 fn detect_cycle_type(&self, cycle: &[ObjectId]) -> CycleType {
344 for edge in &self.edges {
346 if cycle.contains(&edge.from)
347 && cycle.contains(&edge.to)
348 && edge.op == EdgeKind::RcClone
349 {
350 return CycleType::Rc;
351 }
352 }
353 CycleType::Arc
354 }
355
356 pub fn find_root_cause(&self) -> Option<RootCauseChain> {
358 if self.arc_clone_count > 50 {
360 return Some(RootCauseChain {
361 root_cause: RootCause::ArcCloneStorm,
362 description: format!(
363 "Arc clone storm detected: {} clones causing memory proliferation",
364 self.arc_clone_count
365 ),
366 impact: format!(
367 "Potential memory spike from {} Arc clone operations",
368 self.arc_clone_count
369 ),
370 });
371 }
372
373 if !self.cycles.is_empty() {
375 return Some(RootCauseChain {
376 root_cause: RootCause::RcCycle,
377 description: format!(
378 "Rc retain cycle detected: {} cycles found",
379 self.cycles.len()
380 ),
381 impact: "Memory leak due to reference count cycles".to_string(),
382 });
383 }
384
385 None
386 }
387}
388
389#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
391pub enum CycleType {
392 Rc,
394 Arc,
396}
397
398#[derive(Debug, Clone, Serialize, Deserialize)]
400pub enum DiagnosticIssue {
401 RcCycle {
403 nodes: Vec<ObjectId>,
404 cycle_type: CycleType,
405 },
406 ArcCloneStorm {
408 clone_count: usize,
409 threshold: usize,
410 },
411}
412
413#[derive(Debug, Clone, Serialize, Deserialize)]
415pub struct OwnershipDiagnostics {
416 pub issues: Vec<DiagnosticIssue>,
418 pub total_nodes: usize,
420 pub total_edges: usize,
422 pub rc_clone_count: usize,
424 pub arc_clone_count: usize,
426}
427
428impl OwnershipDiagnostics {
429 pub fn has_issues(&self) -> bool {
431 !self.issues.is_empty()
432 }
433
434 pub fn summary(&self) -> String {
436 let mut summary = format!(
437 "Ownership Graph: {} nodes, {} edges\n",
438 self.total_nodes, self.total_edges
439 );
440 for issue in &self.issues {
441 match issue {
442 DiagnosticIssue::RcCycle { nodes, cycle_type } => {
443 summary.push_str(&format!(
444 "🔴 {:?} Cycle detected: {} nodes\n",
445 cycle_type,
446 nodes.len()
447 ));
448 }
449 DiagnosticIssue::ArcCloneStorm {
450 clone_count,
451 threshold,
452 } => {
453 summary.push_str(&format!(
454 "⚠ Arc Clone Storm: {} clones (threshold: {})\n",
455 clone_count, threshold
456 ));
457 }
458 }
459 }
460 summary
461 }
462}
463
464#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
466pub enum RootCause {
467 ArcCloneStorm,
469 RcCycle,
471}
472
473#[derive(Debug, Clone, Serialize, Deserialize)]
475pub struct RootCauseChain {
476 pub root_cause: RootCause,
478 pub description: String,
480 pub impact: String,
482}
483
484#[cfg(test)]
485mod tests {
486 use super::*;
487
488 #[test]
489 fn test_object_id_from_ptr() {
490 let id = ObjectId::from_ptr(0x1000);
491 assert_eq!(id.0, 0x1000);
492 }
493
494 #[test]
495 fn test_ownership_event_creation() {
496 let id = NodeId(0x1000);
497 let event = OwnershipEvent::new(1000, OwnershipOp::Create, id, None);
498 assert_eq!(event.ts, 1000);
499 assert_eq!(event.op, OwnershipOp::Create);
500 }
501
502 #[test]
503 fn test_graph_build_empty() {
504 let passports: Vec<(NodeId, String, usize, Vec<OwnershipEvent>)> = vec![];
505 let graph = OwnershipGraph::build(&passports);
506 assert!(graph.nodes.is_empty());
507 assert!(graph.edges.is_empty());
508 assert!(graph.cycles.is_empty());
509 }
510
511 #[test]
512 fn test_graph_build_rc_clone() {
513 let id1 = NodeId(0x1000);
514 let id2 = NodeId(0x2000);
515 let events = vec![OwnershipEvent::new(
516 1000,
517 OwnershipOp::RcClone,
518 id1,
519 Some(id2),
520 )];
521 let passports = vec![(id1, "Rc<i32>".to_string(), 8, events)];
522
523 let graph = OwnershipGraph::build(&passports);
524 assert_eq!(graph.nodes.len(), 1);
525 assert_eq!(graph.edges.len(), 1);
526 assert_eq!(graph.edges[0].op, EdgeKind::RcClone);
527 }
528
529 #[test]
530 fn test_graph_build_arc_clone_storm() {
531 let id1 = NodeId(0x1000);
532 let mut events = Vec::new();
533 for i in 0..100 {
534 let dst = NodeId(0x2000 + i);
535 events.push(OwnershipEvent::new(
536 i,
537 OwnershipOp::ArcClone,
538 id1,
539 Some(dst),
540 ));
541 }
542 let passports = vec![(id1, "Arc<i32>".to_string(), 8, events)];
543
544 let graph = OwnershipGraph::build(&passports);
545 assert_eq!(graph.arc_clone_count, 100);
546 assert!(graph.has_arc_clone_storm(50));
547 }
548
549 #[test]
550 fn test_compress_clone_chains() {
551 let id1 = NodeId(0x1000);
552 let id2 = NodeId(0x2000);
553 let id3 = NodeId(0x3000);
554 let id4 = NodeId(0x4000);
555
556 let events = vec![
557 OwnershipEvent::new(1000, OwnershipOp::ArcClone, id1, Some(id2)),
558 OwnershipEvent::new(2000, OwnershipOp::ArcClone, id2, Some(id3)),
559 OwnershipEvent::new(3000, OwnershipOp::ArcClone, id3, Some(id4)),
560 ];
561 let passports = vec![(id1, "Arc<i32>".to_string(), 8, events)];
562
563 let graph = OwnershipGraph::build(&passports);
564
565 assert_eq!(graph.edges.len(), 1);
567 assert_eq!(graph.edges[0].from, id1);
568 assert_eq!(graph.edges[0].to, id4);
569 }
570
571 #[test]
572 fn test_diagnostics() {
573 let id1 = NodeId(0x1000);
574 let mut events = Vec::new();
575 for i in 0..100 {
576 let dst = NodeId(0x2000 + i);
577 events.push(OwnershipEvent::new(
578 i,
579 OwnershipOp::ArcClone,
580 id1,
581 Some(dst),
582 ));
583 }
584 let passports = vec![(id1, "Arc<i32>".to_string(), 8, events)];
585
586 let graph = OwnershipGraph::build(&passports);
587 let diagnostics = graph.diagnostics(50);
588
589 assert!(diagnostics.has_issues());
590 assert!(diagnostics.summary().contains("Arc Clone Storm"));
591 }
592
593 #[test]
594 fn test_root_cause_detection() {
595 let id1 = NodeId(0x1000);
596 let mut events = Vec::new();
597 for i in 0..100 {
598 let dst = NodeId(0x2000 + i);
599 events.push(OwnershipEvent::new(
600 i,
601 OwnershipOp::ArcClone,
602 id1,
603 Some(dst),
604 ));
605 }
606 let passports = vec![(id1, "Arc<i32>".to_string(), 8, events)];
607
608 let graph = OwnershipGraph::build(&passports);
609 let root_cause = graph.find_root_cause();
610
611 assert!(root_cause.is_some());
612 let chain = root_cause.unwrap();
613 assert_eq!(chain.root_cause, RootCause::ArcCloneStorm);
614 }
615}