1use crate::capture::system_monitor;
41use crate::core::tracker::MemoryTracker;
42use crate::event_store::{EventStore, MemoryEvent};
43use crate::render_engine::export::{export_snapshot_to_json, ExportJsonOptions};
44use crate::snapshot::MemorySnapshot;
45
46use std::collections::HashMap;
47use std::sync::{Arc, Mutex};
48use std::time::{Duration, Instant};
49
50#[derive(Debug, Clone)]
51pub struct SamplingConfig {
52 pub sample_rate: f64,
53 pub capture_call_stack: bool,
54 pub max_stack_depth: usize,
55}
56
57impl Default for SamplingConfig {
58 fn default() -> Self {
59 Self {
60 sample_rate: 1.0,
61 capture_call_stack: false,
62 max_stack_depth: 10,
63 }
64 }
65}
66
67impl SamplingConfig {
68 pub fn demo() -> Self {
69 Self {
70 sample_rate: 0.1,
71 capture_call_stack: false,
72 max_stack_depth: 5,
73 }
74 }
75
76 pub fn full() -> Self {
77 Self {
78 sample_rate: 1.0,
79 capture_call_stack: true,
80 max_stack_depth: 20,
81 }
82 }
83
84 pub fn high_performance() -> Self {
85 Self {
86 sample_rate: 0.01,
87 capture_call_stack: false,
88 max_stack_depth: 0,
89 }
90 }
91}
92
93#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
94pub struct SystemSnapshot {
95 pub timestamp: u64,
96 pub cpu_usage_percent: f64,
97 pub memory_usage_bytes: u64,
98 pub memory_usage_percent: f64,
99 pub thread_count: usize,
100 pub disk_read_bps: u64,
101 pub disk_write_bps: u64,
102 pub network_rx_bps: u64,
103 pub network_tx_bps: u64,
104 pub gpu_usage_percent: f64,
105 pub gpu_memory_used: u64,
106 pub gpu_memory_total: u64,
107}
108
109#[derive(Debug, Clone)]
110pub struct AnalysisReport {
111 pub total_allocations: usize,
112 pub total_deallocations: usize,
113 pub active_allocations: usize,
114 pub peak_memory_bytes: u64,
115 pub current_memory_bytes: u64,
116 pub allocation_rate_per_sec: f64,
117 pub deallocation_rate_per_sec: f64,
118 pub hotspots: Vec<AllocationHotspot>,
119 pub system_snapshots: Vec<SystemSnapshot>,
120}
121
122#[derive(Debug, Clone)]
123pub struct AllocationHotspot {
124 pub var_name: String,
125 pub type_name: String,
126 pub total_size: usize,
127 pub allocation_count: usize,
128 pub location: Option<String>,
129}
130
131pub struct Tracker {
132 inner: Arc<MemoryTracker>,
133 event_store: Arc<EventStore>,
134 config: Arc<Mutex<TrackerConfig>>,
135 start_time: Instant,
136 system_snapshots: Arc<Mutex<Vec<SystemSnapshot>>>,
137}
138
139impl Clone for Tracker {
140 fn clone(&self) -> Self {
141 Tracker {
142 inner: self.inner.clone(),
143 event_store: self.event_store.clone(),
144 config: self.config.clone(),
145 start_time: Instant::now(), system_snapshots: self.system_snapshots.clone(),
147 }
148 }
149}
150
151#[derive(Debug, Clone)]
152struct TrackerConfig {
153 sampling: SamplingConfig,
154 auto_export_on_drop: bool,
155 export_path: Option<String>,
156}
157
158impl Tracker {
159 pub fn new() -> Self {
160 Self {
161 inner: Arc::new(MemoryTracker::new()),
162 event_store: Arc::new(EventStore::new()),
163 config: Arc::new(Mutex::new(TrackerConfig {
164 sampling: SamplingConfig::default(),
165 auto_export_on_drop: false,
166 export_path: None,
167 })),
168 start_time: Instant::now(),
169 system_snapshots: Arc::new(Mutex::new(Vec::new())),
170 }
171 }
172
173 pub fn global() -> Self {
174 use crate::core::tracker::get_tracker;
175 static GLOBAL_EVENT_STORE: std::sync::OnceLock<Arc<EventStore>> =
176 std::sync::OnceLock::new();
177 static GLOBAL_CONFIG: std::sync::OnceLock<Arc<Mutex<TrackerConfig>>> =
178 std::sync::OnceLock::new();
179 static GLOBAL_SYSTEM_SNAPSHOTS: std::sync::OnceLock<Arc<Mutex<Vec<SystemSnapshot>>>> =
180 std::sync::OnceLock::new();
181
182 Self {
183 inner: get_tracker(),
184 event_store: GLOBAL_EVENT_STORE
185 .get_or_init(|| Arc::new(EventStore::new()))
186 .clone(),
187 config: GLOBAL_CONFIG
188 .get_or_init(|| {
189 Arc::new(Mutex::new(TrackerConfig {
190 sampling: SamplingConfig::default(),
191 auto_export_on_drop: false,
192 export_path: None,
193 }))
194 })
195 .clone(),
196 start_time: Instant::now(),
197 system_snapshots: GLOBAL_SYSTEM_SNAPSHOTS
198 .get_or_init(|| Arc::new(Mutex::new(Vec::new())))
199 .clone(),
200 }
201 }
202
203 pub fn with_system_monitoring(self) -> Self {
204 self.capture_system_snapshot();
205 self
206 }
207
208 pub fn with_sampling(self, config: SamplingConfig) -> Self {
209 if let Ok(mut cfg) = self.config.lock() {
210 cfg.sampling = config;
211 }
212 self
213 }
214
215 pub fn with_auto_export(self, path: &str) -> Self {
216 if let Ok(mut cfg) = self.config.lock() {
217 cfg.auto_export_on_drop = true;
218 cfg.export_path = Some(path.to_string());
219 }
220 self
221 }
222
223 pub fn track_as<T: crate::Trackable>(&self, var: &T, name: &str, file: &str, line: u32) {
224 if let Ok(cfg) = self.config.lock() {
225 if cfg.sampling.sample_rate < 1.0 {
226 use std::collections::hash_map::DefaultHasher;
227 use std::hash::{Hash, Hasher};
228 let mut hasher = DefaultHasher::new();
229 let timestamp = std::time::SystemTime::now()
232 .duration_since(std::time::UNIX_EPOCH)
233 .unwrap_or_default()
234 .as_nanos();
235 timestamp.hash(&mut hasher);
236 std::thread::current().id().hash(&mut hasher);
237 name.hash(&mut hasher);
238 file.hash(&mut hasher);
239 line.hash(&mut hasher);
240 let hash = hasher.finish();
241 let threshold = (cfg.sampling.sample_rate * 1000.0) as u64;
242 if (hash % 1000) > threshold {
243 return;
244 }
245 }
246 }
247
248 self.track_inner(var, name, file, line);
249 }
250
251 fn track_inner<T: crate::Trackable>(&self, var: &T, name: &str, file: &str, line: u32) {
252 let type_name = var.get_type_name().to_string();
253 let size = var.get_size_estimate();
254
255 let ptr = var.get_heap_ptr().unwrap_or_else(|| {
256 use std::cell::Cell;
257 thread_local! {
258 static COUNTER: Cell<u64> = const { Cell::new(0x8000_0000) };
259 }
260 COUNTER.with(|counter| {
261 let val = counter.get();
262 counter.set(val.wrapping_add(1));
263 val as usize
264 })
265 });
266
267 if let Err(e) = self.inner.track_allocation(ptr, size) {
268 tracing::error!("Failed to track allocation at ptr {:x}: {}", ptr, e);
269 return;
270 }
271
272 let thread_id_u64 = crate::utils::current_thread_id_u64();
273
274 let mut event = MemoryEvent::allocate(ptr, size, thread_id_u64);
275 event.var_name = Some(name.to_string());
276 event.type_name = Some(type_name.clone());
277 self.event_store.record(event);
278
279 if let Err(e) =
280 self.inner
281 .associate_var(ptr, name.to_string(), type_name, Some(file), Some(line))
282 {
283 tracing::error!("Failed to associate var '{}' at ptr {:x}: {}", name, ptr, e);
284 }
285 }
286
287 pub fn track_deallocation(&self, ptr: usize) -> crate::TrackingResult<bool> {
288 let size = self.inner.get_allocation_size(ptr).unwrap_or(0);
289
290 let result = self.inner.track_deallocation(ptr)?;
291
292 if result {
294 let thread_id_u64 = crate::utils::current_thread_id_u64();
295
296 let event = MemoryEvent::deallocate(ptr, size, thread_id_u64);
297 self.event_store.record(event);
298 }
299
300 Ok(result)
301 }
302
303 pub fn events(&self) -> Vec<MemoryEvent> {
304 self.event_store.snapshot()
305 }
306
307 pub fn event_store(&self) -> &Arc<EventStore> {
308 &self.event_store
309 }
310
311 fn capture_system_snapshot(&self) {
312 let snapshot = SystemSnapshot {
313 timestamp: std::time::SystemTime::now()
314 .duration_since(std::time::UNIX_EPOCH)
315 .unwrap_or_default()
316 .as_millis() as u64,
317 cpu_usage_percent: system_monitor::cpu_usage(),
318 memory_usage_bytes: system_monitor::memory_used(),
319 memory_usage_percent: system_monitor::memory_usage_percent(),
320 thread_count: system_monitor::thread_count(),
321 disk_read_bps: system_monitor::disk_read_bps(),
322 disk_write_bps: system_monitor::disk_write_bps(),
323 network_rx_bps: system_monitor::network_rx_bps(),
324 network_tx_bps: system_monitor::network_tx_bps(),
325 gpu_usage_percent: system_monitor::gpu_memory_usage_percent(),
326 gpu_memory_used: system_monitor::gpu_memory_used(),
327 gpu_memory_total: system_monitor::gpu_memory_total(),
328 };
329
330 if let Ok(mut snapshots) = self.system_snapshots.lock() {
331 snapshots.push(snapshot);
332 }
333 }
334
335 pub fn stats(&self) -> crate::core::types::MemoryStats {
336 let stats = self.inner.get_stats().unwrap_or_default();
337 crate::core::types::MemoryStats {
338 total_allocations: stats.total_allocations as usize,
339 total_allocated: stats.total_allocated as usize,
340 active_allocations: stats.active_allocations,
341 active_memory: stats.active_memory as usize,
342 peak_allocations: stats.peak_allocations,
343 peak_memory: stats.peak_memory as usize,
344 total_deallocations: stats.total_deallocations as usize,
345 total_deallocated: stats.total_deallocated as usize,
346 leaked_allocations: stats.leaked_allocations,
347 leaked_memory: stats.leaked_memory as usize,
348 ..Default::default()
349 }
350 }
351
352 pub fn analyze(&self) -> AnalysisReport {
353 let stats = self.stats();
354 let allocations = self.inner.get_active_allocations().unwrap_or_default();
355 let elapsed = self.start_time.elapsed().as_secs_f64();
356
357 let current_memory: usize = allocations.iter().map(|a| a.size).sum();
358 let peak_memory = stats.peak_memory.max(current_memory);
359
360 let mut hotspot_map: HashMap<String, (String, usize, usize)> = HashMap::new();
361 for alloc in &allocations {
362 if let Some(ref var_name) = alloc.var_name {
363 let key = var_name.clone();
364 let entry = hotspot_map.entry(key).or_insert((
365 alloc.type_name.clone().unwrap_or_default(),
366 0,
367 0,
368 ));
369 entry.1 += alloc.size;
370 entry.2 += 1;
371 }
372 }
373
374 let hotspots: Vec<AllocationHotspot> = hotspot_map
375 .into_iter()
376 .map(
377 |(var_name, (type_name, total_size, count))| AllocationHotspot {
378 var_name,
379 type_name,
380 total_size,
381 allocation_count: count,
382 location: None,
383 },
384 )
385 .collect();
386
387 let system_snapshots = self
388 .system_snapshots
389 .lock()
390 .unwrap_or_else(|e| e.into_inner())
391 .clone();
392
393 AnalysisReport {
394 total_allocations: stats.total_allocations,
395 total_deallocations: stats.total_deallocations,
396 active_allocations: allocations.len(),
397 peak_memory_bytes: peak_memory as u64,
398 current_memory_bytes: current_memory as u64,
399 allocation_rate_per_sec: if elapsed > 0.0 {
400 stats.total_allocations as f64 / elapsed
401 } else {
402 0.0
403 },
404 deallocation_rate_per_sec: if elapsed > 0.0 {
405 stats.total_deallocations as f64 / elapsed
406 } else {
407 0.0
408 },
409 hotspots,
410 system_snapshots,
411 }
412 }
413
414 pub fn inner(&self) -> &Arc<MemoryTracker> {
415 &self.inner
416 }
417
418 pub fn elapsed(&self) -> Duration {
419 self.start_time.elapsed()
420 }
421
422 pub fn system_snapshots(&self) -> Vec<SystemSnapshot> {
423 self.system_snapshots
424 .lock()
425 .unwrap_or_else(|e| e.into_inner())
426 .clone()
427 }
428
429 pub fn current_system_snapshot(&self) -> SystemSnapshot {
430 SystemSnapshot {
431 timestamp: std::time::SystemTime::now()
432 .duration_since(std::time::UNIX_EPOCH)
433 .unwrap_or_default()
434 .as_millis() as u64,
435 cpu_usage_percent: system_monitor::cpu_usage(),
436 memory_usage_bytes: system_monitor::memory_used(),
437 memory_usage_percent: system_monitor::memory_usage_percent(),
438 thread_count: system_monitor::thread_count(),
439 disk_read_bps: system_monitor::disk_read_bps(),
440 disk_write_bps: system_monitor::disk_write_bps(),
441 network_rx_bps: system_monitor::network_rx_bps(),
442 network_tx_bps: system_monitor::network_tx_bps(),
443 gpu_usage_percent: system_monitor::gpu_memory_usage_percent(),
444 gpu_memory_used: system_monitor::gpu_memory_used(),
445 gpu_memory_total: system_monitor::gpu_memory_total(),
446 }
447 }
448}
449
450impl Default for Tracker {
451 fn default() -> Self {
452 Self::new()
453 }
454}
455
456impl Drop for Tracker {
457 fn drop(&mut self) {
458 if let Ok(cfg) = self.config.lock() {
459 if cfg.auto_export_on_drop {
460 if let Some(ref path) = cfg.export_path {
461 let allocations = self.inner.get_active_allocations().unwrap_or_default();
462 let snapshot = MemorySnapshot::from_allocation_infos(allocations);
463 let options = ExportJsonOptions::default();
464 if let Err(e) =
465 export_snapshot_to_json(&snapshot, std::path::Path::new(path), &options)
466 {
467 tracing::error!("Failed to auto-export on drop: {}", e);
468 }
469 }
470 }
471 }
472 }
473}
474
475impl serde::Serialize for AnalysisReport {
476 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
477 where
478 S: serde::Serializer,
479 {
480 use serde::ser::SerializeStruct;
481 let mut state = serializer.serialize_struct("AnalysisReport", 9)?;
482 state.serialize_field("total_allocations", &self.total_allocations)?;
483 state.serialize_field("total_deallocations", &self.total_deallocations)?;
484 state.serialize_field("active_allocations", &self.active_allocations)?;
485 state.serialize_field("peak_memory_bytes", &self.peak_memory_bytes)?;
486 state.serialize_field("current_memory_bytes", &self.current_memory_bytes)?;
487 state.serialize_field("allocation_rate_per_sec", &self.allocation_rate_per_sec)?;
488 state.serialize_field("deallocation_rate_per_sec", &self.deallocation_rate_per_sec)?;
489 state.serialize_field("hotspots", &self.hotspots)?;
490 state.serialize_field("system_snapshots", &self.system_snapshots)?;
491 state.end()
492 }
493}
494
495impl serde::Serialize for AllocationHotspot {
496 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
497 where
498 S: serde::Serializer,
499 {
500 use serde::ser::SerializeStruct;
501 let mut state = serializer.serialize_struct("AllocationHotspot", 5)?;
502 state.serialize_field("var_name", &self.var_name)?;
503 state.serialize_field("type_name", &self.type_name)?;
504 state.serialize_field("total_size", &self.total_size)?;
505 state.serialize_field("allocation_count", &self.allocation_count)?;
506 state.serialize_field("location", &self.location)?;
507 state.end()
508 }
509}
510
511#[macro_export]
512macro_rules! tracker {
513 () => {
514 $crate::tracker::Tracker::new()
515 };
516}
517
518#[macro_export]
519macro_rules! track {
520 ($tracker:expr, $var:expr) => {{
521 let var_name = stringify!($var);
522 $tracker.track_as(&$var, var_name, file!(), line!());
523 }};
524}
525
526#[cfg(test)]
527mod tests {
528 use super::*;
529
530 #[test]
531 fn test_tracker_creation() {
532 let tracker = Tracker::new();
533 let _ = tracker;
534 }
535
536 #[test]
537 fn test_tracker_with_config() {
538 let tracker = Tracker::new()
539 .with_sampling(SamplingConfig::demo())
540 .with_system_monitoring();
541 let _ = tracker;
542 }
543
544 #[test]
545 fn test_track_macro() {
546 let tracker = tracker!();
547 let my_vec = vec![1, 2, 3];
548 track!(tracker, my_vec);
549 }
550
551 #[test]
552 fn test_analyze() {
553 let tracker = tracker!();
554 let data = vec![1, 2, 3];
555 track!(tracker, data);
556 let report = tracker.analyze();
557 assert!(report.total_allocations > 0);
558 }
559
560 #[test]
561 #[cfg(target_os = "macos")]
562 fn test_system_monitoring() {
563 std::thread::sleep(std::time::Duration::from_millis(200));
564
565 let cpu = system_monitor::cpu_usage();
566 let mem = system_monitor::memory_used();
567 let total = system_monitor::memory_total();
568
569 println!("CPU: {:.2}%", cpu);
570 println!("Memory: {} / {} bytes", mem, total);
571
572 assert!((0.0..=100.0).contains(&cpu));
573 assert!(total > 0);
574 }
575
576 #[test]
577 fn test_current_system_snapshot() {
578 std::thread::sleep(std::time::Duration::from_millis(150));
579
580 let tracker = tracker!();
581 let snapshot = tracker.current_system_snapshot();
582
583 println!(
584 "Snapshot: CPU={:.2}%, Mem={:.2}%",
585 snapshot.cpu_usage_percent, snapshot.memory_usage_percent
586 );
587
588 assert!(snapshot.cpu_usage_percent >= 0.0 && snapshot.cpu_usage_percent <= 100.0);
589 }
590}