1use crate::core::error::MemScopeError;
6use crate::snapshot::ActiveAllocation;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct AnalysisReport {
13 pub stats: MemoryStatsReport,
15 pub leaks: LeakReport,
17 pub cycles: CycleReport,
19 pub metrics: MetricsReport,
21}
22
23impl AnalysisReport {
24 pub fn has_issues(&self) -> bool {
26 self.leaks.has_leaks() || self.cycles.has_cycles()
27 }
28
29 pub fn summary(&self) -> String {
31 format!(
32 "Analysis Report:\n Allocations: {}\n Total Bytes: {}\n Leaks: {}\n Cycles: {}",
33 self.stats.allocation_count,
34 self.stats.total_bytes,
35 self.leaks.leak_count,
36 self.cycles.cycle_count
37 )
38 }
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct MemoryStatsReport {
44 pub allocation_count: usize,
46 pub total_bytes: usize,
48 pub peak_bytes: usize,
50 pub thread_count: usize,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct LeakReport {
57 pub leak_count: usize,
59 pub total_leaked_bytes: usize,
61 pub leaked_allocations: Vec<LeakInfo>,
63}
64
65impl LeakReport {
66 pub fn has_leaks(&self) -> bool {
68 self.leak_count > 0
69 }
70
71 pub fn empty() -> Self {
73 Self {
74 leak_count: 0,
75 total_leaked_bytes: 0,
76 leaked_allocations: vec![],
77 }
78 }
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct LeakInfo {
84 pub ptr: usize,
86 pub size: usize,
88 pub var_name: Option<String>,
90 pub type_name: Option<String>,
92 pub thread_id: u64,
94}
95
96impl From<&ActiveAllocation> for LeakInfo {
97 fn from(alloc: &ActiveAllocation) -> Self {
98 Self {
99 ptr: alloc.ptr.unwrap_or(0),
100 size: alloc.size,
101 var_name: alloc.var_name.clone(),
102 type_name: alloc.type_name.clone(),
103 thread_id: alloc.thread_id,
104 }
105 }
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct CycleReport {
111 pub cycle_count: usize,
113 pub cycles: Vec<CycleInfo>,
115}
116
117impl CycleReport {
118 pub fn has_cycles(&self) -> bool {
120 self.cycle_count > 0
121 }
122
123 pub fn empty() -> Self {
125 Self {
126 cycle_count: 0,
127 cycles: vec![],
128 }
129 }
130
131 pub fn from_graph<T>(_graph: &T) -> Self {
133 Self::empty()
134 }
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct CycleInfo {
140 pub nodes: Vec<u64>,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize, Default)]
146pub struct MetricsReport {
147 pub allocation_count: usize,
149 pub total_bytes: usize,
151 pub peak_bytes: usize,
153 pub thread_count: usize,
155 pub by_type: HashMap<String, TypeMetric>,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize, Default)]
161pub struct TypeMetric {
162 pub count: usize,
164 pub total_bytes: usize,
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct UafReport {
171 pub uaf_count: usize,
173 pub issues: Vec<UafInfo>,
175}
176
177impl UafReport {
178 pub fn empty() -> Self {
180 Self {
181 uaf_count: 0,
182 issues: vec![],
183 }
184 }
185
186 pub fn from_events(_events: &[crate::event_store::MemoryEvent]) -> Self {
188 Self::empty()
189 }
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct UafInfo {
195 pub ptr: usize,
197 pub deallocated_at: u64,
199 pub accessed_at: u64,
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct SafetyReport {
206 pub score: f64,
208 pub issue_count: usize,
210 pub issues: Vec<SafetyIssue>,
212}
213
214impl SafetyReport {
215 pub fn from_allocations(_allocations: &[&ActiveAllocation]) -> Self {
217 Self {
218 score: 100.0,
219 issue_count: 0,
220 issues: vec![],
221 }
222 }
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct SafetyIssue {
228 pub severity: IssueSeverity,
230 pub description: String,
232 pub ptr: Option<usize>,
234}
235
236#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
238pub enum IssueSeverity {
239 Low,
241 Medium,
243 High,
245 Critical,
247}
248
249#[allow(dead_code)]
251pub type ExportError = MemScopeError;
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 #[test]
258 fn test_empty_leak_report() {
259 let report = LeakReport::empty();
260 assert!(!report.has_leaks());
261 assert_eq!(report.leak_count, 0);
262 }
263
264 #[test]
265 fn test_empty_cycle_report() {
266 let report = CycleReport::empty();
267 assert!(!report.has_cycles());
268 assert_eq!(report.cycle_count, 0);
269 }
270
271 #[test]
272 fn test_analysis_report_summary() {
273 let report = AnalysisReport {
274 stats: MemoryStatsReport {
275 allocation_count: 10,
276 total_bytes: 1000,
277 peak_bytes: 500,
278 thread_count: 2,
279 },
280 leaks: LeakReport::empty(),
281 cycles: CycleReport::empty(),
282 metrics: MetricsReport::default(),
283 };
284 let summary = report.summary();
285 assert!(summary.contains("Allocations: 10"));
286 assert!(summary.contains("Total Bytes: 1000"));
287 }
288}