memscope_rs/analyzer/
core.rs1use crate::analyzer::report::{
4 AnalysisReport, CycleReport, LeakReport, MemoryStatsReport, MetricsReport,
5};
6use crate::view::MemoryView;
7use tracing::{debug, info};
8
9use super::{DetectionAnalysis, ExportEngine, GraphAnalysis, MetricsAnalysis, TimelineAnalysis};
10
11pub struct Analyzer {
16 view: MemoryView,
17 graph: Option<GraphAnalysis>,
18 detect: Option<DetectionAnalysis>,
19 metrics: Option<MetricsAnalysis>,
20 timeline: Option<TimelineAnalysis>,
21}
22
23impl Analyzer {
24 pub fn from_tracker(
26 tracker: &crate::capture::backends::global_tracking::GlobalTracker,
27 ) -> Self {
28 info!("Creating Analyzer from GlobalTracker");
29 Self::from_view(MemoryView::from_tracker(tracker))
30 }
31
32 pub fn from_view(view: MemoryView) -> Self {
34 let alloc_count = view.len();
35 info!("Creating Analyzer with {} allocations", alloc_count);
36 Self {
37 view,
38 graph: None,
39 detect: None,
40 metrics: None,
41 timeline: None,
42 }
43 }
44
45 pub fn graph(&mut self) -> &mut GraphAnalysis {
47 if self.graph.is_none() {
48 debug!("Initializing GraphAnalysis (lazy)");
49 self.graph = Some(GraphAnalysis::from_view(&self.view));
50 }
51 self.graph
52 .as_mut()
53 .expect("GraphAnalysis should be initialized after lazy initialization")
54 }
55
56 pub fn detect(&mut self) -> &DetectionAnalysis {
58 if self.detect.is_none() {
59 debug!("Initializing DetectionAnalysis (lazy)");
60 self.detect = Some(DetectionAnalysis::from_view(&self.view));
61 }
62 self.detect
63 .as_ref()
64 .expect("DetectionAnalysis should be initialized after lazy initialization")
65 }
66
67 pub fn metrics(&mut self) -> &MetricsAnalysis {
69 if self.metrics.is_none() {
70 debug!("Initializing MetricsAnalysis (lazy)");
71 self.metrics = Some(MetricsAnalysis::from_view(&self.view));
72 }
73 self.metrics
74 .as_ref()
75 .expect("MetricsAnalysis should be initialized after lazy initialization")
76 }
77
78 pub fn timeline(&mut self) -> &TimelineAnalysis {
80 if self.timeline.is_none() {
81 debug!("Initializing TimelineAnalysis (lazy)");
82 self.timeline = Some(TimelineAnalysis::from_view(&self.view));
83 }
84 self.timeline
85 .as_ref()
86 .expect("TimelineAnalysis should be initialized after lazy initialization")
87 }
88
89 pub fn export(&self) -> ExportEngine<'_> {
91 ExportEngine::new(&self.view)
92 }
93
94 pub fn view(&self) -> &MemoryView {
96 &self.view
97 }
98
99 pub fn analyze(&mut self) -> AnalysisReport {
152 info!("Starting full analysis");
153 let stats = MemoryStatsReport {
154 allocation_count: self.view.len(),
155 total_bytes: self.view.total_memory(),
156 peak_bytes: self.view.snapshot().stats.peak_memory,
157 thread_count: self.view.snapshot().thread_stats.len(),
158 };
159 debug!(
160 "Stats: {} allocations, {} bytes, {} threads",
161 stats.allocation_count, stats.total_bytes, stats.thread_count
162 );
163
164 let leaks = self.detect().leaks();
165 if leaks.leak_count > 0 {
166 info!(
167 "Leak detection found {} leaks ({} bytes)",
168 leaks.leak_count, leaks.total_leaked_bytes
169 );
170 }
171
172 let cycles = self.graph().cycles();
173 if cycles.cycle_count > 0 {
174 info!("Cycle detection found {} cycles", cycles.cycle_count);
175 }
176
177 let metrics = self.metrics().summary();
178 info!("Analysis complete");
179
180 AnalysisReport {
181 stats,
182 leaks,
183 cycles,
184 metrics,
185 }
186 }
187
188 pub fn quick_leak_check(&mut self) -> LeakReport {
190 self.detect().leaks()
191 }
192
193 pub fn quick_cycle_check(&mut self) -> CycleReport {
195 self.graph().cycles()
196 }
197
198 pub fn quick_metrics(&mut self) -> MetricsReport {
200 self.metrics().summary()
201 }
202}
203
204impl Clone for Analyzer {
205 fn clone(&self) -> Self {
206 Self {
207 view: self.view.clone(),
208 graph: None,
209 detect: None,
210 metrics: None,
211 timeline: None,
212 }
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219 use crate::event_store::MemoryEvent;
220
221 #[test]
225 fn test_analyzer_from_events() {
226 let events = vec![
227 MemoryEvent::allocate(0x1000, 64, 1),
228 MemoryEvent::allocate(0x2000, 128, 2),
229 ];
230 let view = MemoryView::from_events(events);
231 let mut analyzer = Analyzer::from_view(view);
232
233 let metrics = analyzer.quick_metrics();
234 assert_eq!(
235 metrics.allocation_count, 2,
236 "both allocations must be visible"
237 );
238
239 let leaks = analyzer.quick_leak_check();
240 assert_eq!(
241 leaks.leak_count, 0,
242 "an allocation that is still live at the cut is not evidence of a leak"
243 );
244 assert_eq!(
245 leaks.total_leaked_bytes, 0,
246 "no leaked bytes may be reported without an assessment rule"
247 );
248 }
249
250 #[test]
253 fn test_quick_leak_check() {
254 let events = vec![MemoryEvent::allocate(0x1000, 64, 1)];
255 let view = MemoryView::from_events(events);
256 let mut analyzer = Analyzer::from_view(view);
257 let leaks = analyzer.quick_leak_check();
258 assert_eq!(
259 leaks.leak_count, 0,
260 "being alive at the cut is not evidence of a leak"
261 );
262 assert_eq!(
263 leaks.total_leaked_bytes, 0,
264 "no bytes may be reported leaked"
265 );
266 }
267}