1use std::collections::{HashMap, VecDeque};
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::time::{Duration, Instant};
9
10use parking_lot::RwLock;
11use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Clone)]
15pub struct MemoryProfilerConfig {
16 pub sampling_interval: Duration,
18
19 pub max_snapshots: usize,
21
22 pub track_allocations: bool,
24
25 pub allocation_threshold: usize,
27}
28
29impl Default for MemoryProfilerConfig {
30 fn default() -> Self {
31 Self {
32 sampling_interval: Duration::from_secs(10),
33 max_snapshots: 1000,
34 track_allocations: true,
35 allocation_threshold: 1024,
36 }
37 }
38}
39
40pub struct MemoryProfiler {
42 config: MemoryProfilerConfig,
43
44 regions: RwLock<HashMap<String, MemoryRegion>>,
46
47 snapshots: RwLock<VecDeque<TimestampedSnapshot>>,
49
50 total_allocated: AtomicU64,
52 total_deallocated: AtomicU64,
53 current_bytes: AtomicU64,
54 peak_bytes: AtomicU64,
55 allocation_count: AtomicU64,
56 deallocation_count: AtomicU64,
57
58 started_at: RwLock<Option<Instant>>,
60 last_snapshot_at: RwLock<Option<Instant>>,
61 is_running: std::sync::atomic::AtomicBool,
62}
63
64impl MemoryProfiler {
65 pub fn new(config: MemoryProfilerConfig) -> Self {
67 Self {
68 config,
69 regions: RwLock::new(HashMap::new()),
70 snapshots: RwLock::new(VecDeque::new()),
71 total_allocated: AtomicU64::new(0),
72 total_deallocated: AtomicU64::new(0),
73 current_bytes: AtomicU64::new(0),
74 peak_bytes: AtomicU64::new(0),
75 allocation_count: AtomicU64::new(0),
76 deallocation_count: AtomicU64::new(0),
77 started_at: RwLock::new(None),
78 last_snapshot_at: RwLock::new(None),
79 is_running: std::sync::atomic::AtomicBool::new(false),
80 }
81 }
82
83 pub fn start(&mut self) {
85 let now = Instant::now();
86 *self.started_at.write() = Some(now);
87 *self.last_snapshot_at.write() = Some(now);
88 self.is_running.store(true, Ordering::SeqCst);
89 }
90
91 pub fn stop(&mut self) {
93 self.is_running.store(false, Ordering::SeqCst);
94 }
95
96 pub fn is_running(&self) -> bool {
98 self.is_running.load(Ordering::SeqCst)
99 }
100
101 pub fn record_allocation(&self, label: &str, size: usize) {
103 let size = size as u64;
104
105 self.total_allocated.fetch_add(size, Ordering::Relaxed);
107 self.allocation_count.fetch_add(1, Ordering::Relaxed);
108
109 let new_current = self.current_bytes.fetch_add(size, Ordering::Relaxed) + size;
110
111 let mut peak = self.peak_bytes.load(Ordering::Relaxed);
113 while new_current > peak {
114 match self.peak_bytes.compare_exchange_weak(
115 peak,
116 new_current,
117 Ordering::Relaxed,
118 Ordering::Relaxed,
119 ) {
120 Ok(_) => break,
121 Err(p) => peak = p,
122 }
123 }
124
125 let mut regions = self.regions.write();
127 regions
128 .entry(label.to_string())
129 .or_insert_with(|| MemoryRegion::new(label))
130 .record_allocation(size);
131
132 drop(regions);
134 self.maybe_take_snapshot();
135 }
136
137 pub fn record_deallocation(&self, label: &str, size: usize) {
139 let size = size as u64;
140
141 self.total_deallocated.fetch_add(size, Ordering::Relaxed);
143 self.deallocation_count.fetch_add(1, Ordering::Relaxed);
144 self.current_bytes.fetch_sub(size, Ordering::Relaxed);
145
146 let mut regions = self.regions.write();
148 if let Some(region) = regions.get_mut(label) {
149 region.record_deallocation(size);
150 }
151 }
152
153 fn maybe_take_snapshot(&self) {
155 let mut last_snapshot = self.last_snapshot_at.write();
156 if let Some(last) = *last_snapshot {
157 if last.elapsed() >= self.config.sampling_interval {
158 *last_snapshot = Some(Instant::now());
159 drop(last_snapshot);
160
161 let snapshot = TimestampedSnapshot {
162 timestamp: chrono::Utc::now(),
163 snapshot: self.snapshot(),
164 };
165
166 let mut snapshots = self.snapshots.write();
167 snapshots.push_back(snapshot);
168
169 while snapshots.len() > self.config.max_snapshots {
171 snapshots.pop_front();
172 }
173 }
174 }
175 }
176
177 pub fn snapshot(&self) -> MemorySnapshot {
179 let regions = self.regions.read();
180 let region_snapshots: HashMap<String, RegionSnapshot> = regions
181 .iter()
182 .map(|(name, region)| (name.clone(), region.snapshot()))
183 .collect();
184
185 MemorySnapshot {
186 current_bytes: self.current_bytes.load(Ordering::Relaxed),
187 peak_bytes: self.peak_bytes.load(Ordering::Relaxed),
188 total_allocated: self.total_allocated.load(Ordering::Relaxed),
189 total_deallocated: self.total_deallocated.load(Ordering::Relaxed),
190 allocation_count: self.allocation_count.load(Ordering::Relaxed),
191 deallocation_count: self.deallocation_count.load(Ordering::Relaxed),
192 regions: region_snapshots,
193 }
194 }
195
196 pub fn history(&self) -> Vec<TimestampedSnapshot> {
198 self.snapshots.read().iter().cloned().collect()
199 }
200
201 pub fn generate_report(&self) -> MemoryReport {
203 let snapshot = self.snapshot();
204 let history = self.history();
205
206 let growth_rate = if history.len() >= 2 {
208 let first = &history[0];
209 let last = &history[history.len() - 1];
210 let time_diff = (last.timestamp - first.timestamp).num_seconds() as f64;
211 if time_diff > 0.0 {
212 let byte_diff =
213 last.snapshot.current_bytes as f64 - first.snapshot.current_bytes as f64;
214 Some(byte_diff / time_diff) } else {
216 None
217 }
218 } else {
219 None
220 };
221
222 MemoryReport {
223 current_bytes: snapshot.current_bytes,
224 peak_bytes: snapshot.peak_bytes,
225 total_allocated: snapshot.total_allocated,
226 total_deallocated: snapshot.total_deallocated,
227 allocation_count: snapshot.allocation_count,
228 deallocation_count: snapshot.deallocation_count,
229 regions: snapshot.regions,
230 growth_rate_bytes_per_sec: growth_rate,
231 snapshot_count: history.len(),
232 }
233 }
234
235 pub fn reset(&mut self) {
237 self.regions.write().clear();
238 self.snapshots.write().clear();
239 self.total_allocated.store(0, Ordering::Relaxed);
240 self.total_deallocated.store(0, Ordering::Relaxed);
241 self.current_bytes.store(0, Ordering::Relaxed);
242 self.peak_bytes.store(0, Ordering::Relaxed);
243 self.allocation_count.store(0, Ordering::Relaxed);
244 self.deallocation_count.store(0, Ordering::Relaxed);
245 }
246
247 pub fn region_usage(&self, label: &str) -> Option<RegionSnapshot> {
249 self.regions.read().get(label).map(|r| r.snapshot())
250 }
251
252 pub fn regions(&self) -> Vec<String> {
254 self.regions.read().keys().cloned().collect()
255 }
256}
257
258#[derive(Debug)]
260pub struct MemoryRegion {
261 name: String,
262 current_bytes: AtomicU64,
263 peak_bytes: AtomicU64,
264 total_allocated: AtomicU64,
265 total_deallocated: AtomicU64,
266 allocation_count: AtomicU64,
267 deallocation_count: AtomicU64,
268}
269
270impl MemoryRegion {
271 pub fn new(name: &str) -> Self {
273 Self {
274 name: name.to_string(),
275 current_bytes: AtomicU64::new(0),
276 peak_bytes: AtomicU64::new(0),
277 total_allocated: AtomicU64::new(0),
278 total_deallocated: AtomicU64::new(0),
279 allocation_count: AtomicU64::new(0),
280 deallocation_count: AtomicU64::new(0),
281 }
282 }
283
284 pub fn record_allocation(&self, size: u64) {
286 self.total_allocated.fetch_add(size, Ordering::Relaxed);
287 self.allocation_count.fetch_add(1, Ordering::Relaxed);
288
289 let new_current = self.current_bytes.fetch_add(size, Ordering::Relaxed) + size;
290
291 let mut peak = self.peak_bytes.load(Ordering::Relaxed);
293 while new_current > peak {
294 match self.peak_bytes.compare_exchange_weak(
295 peak,
296 new_current,
297 Ordering::Relaxed,
298 Ordering::Relaxed,
299 ) {
300 Ok(_) => break,
301 Err(p) => peak = p,
302 }
303 }
304 }
305
306 pub fn record_deallocation(&self, size: u64) {
308 self.total_deallocated.fetch_add(size, Ordering::Relaxed);
309 self.deallocation_count.fetch_add(1, Ordering::Relaxed);
310 self.current_bytes.fetch_sub(size, Ordering::Relaxed);
311 }
312
313 pub fn snapshot(&self) -> RegionSnapshot {
315 RegionSnapshot {
316 name: self.name.clone(),
317 current_bytes: self.current_bytes.load(Ordering::Relaxed),
318 peak_bytes: self.peak_bytes.load(Ordering::Relaxed),
319 total_allocated: self.total_allocated.load(Ordering::Relaxed),
320 total_deallocated: self.total_deallocated.load(Ordering::Relaxed),
321 allocation_count: self.allocation_count.load(Ordering::Relaxed),
322 deallocation_count: self.deallocation_count.load(Ordering::Relaxed),
323 }
324 }
325}
326
327#[derive(Debug, Clone, Serialize, Deserialize)]
329pub struct MemorySnapshot {
330 pub current_bytes: u64,
332
333 pub peak_bytes: u64,
335
336 pub total_allocated: u64,
338
339 pub total_deallocated: u64,
341
342 pub allocation_count: u64,
344
345 pub deallocation_count: u64,
347
348 pub regions: HashMap<String, RegionSnapshot>,
350}
351
352#[derive(Debug, Clone, Serialize, Deserialize)]
354pub struct TimestampedSnapshot {
355 pub timestamp: chrono::DateTime<chrono::Utc>,
357
358 pub snapshot: MemorySnapshot,
360}
361
362#[derive(Debug, Clone, Serialize, Deserialize)]
364pub struct RegionSnapshot {
365 pub name: String,
367
368 pub current_bytes: u64,
370
371 pub peak_bytes: u64,
373
374 pub total_allocated: u64,
376
377 pub total_deallocated: u64,
379
380 pub allocation_count: u64,
382
383 pub deallocation_count: u64,
385}
386
387impl RegionSnapshot {
388 pub fn fragmentation_ratio(&self) -> f64 {
390 if self.deallocation_count == 0 {
391 return 0.0;
392 }
393 self.allocation_count as f64 / self.deallocation_count as f64
394 }
395
396 pub fn average_allocation_size(&self) -> u64 {
398 if self.allocation_count == 0 {
399 return 0;
400 }
401 self.total_allocated / self.allocation_count
402 }
403}
404
405#[derive(Debug, Clone, Serialize, Deserialize)]
407pub struct MemoryReport {
408 pub current_bytes: u64,
410
411 pub peak_bytes: u64,
413
414 pub total_allocated: u64,
416
417 pub total_deallocated: u64,
419
420 pub allocation_count: u64,
422
423 pub deallocation_count: u64,
425
426 pub regions: HashMap<String, RegionSnapshot>,
428
429 pub growth_rate_bytes_per_sec: Option<f64>,
431
432 pub snapshot_count: usize,
434}
435
436impl MemoryReport {
437 pub fn is_stable(&self, threshold_bytes_per_sec: f64) -> bool {
439 match self.growth_rate_bytes_per_sec {
440 Some(rate) => rate.abs() < threshold_bytes_per_sec,
441 None => true, }
443 }
444
445 pub fn largest_region(&self) -> Option<(&String, &RegionSnapshot)> {
447 self.regions
448 .iter()
449 .max_by_key(|(_, r)| r.current_bytes)
450 }
451
452 pub fn efficiency(&self) -> f64 {
454 if self.total_allocated == 0 {
455 return 1.0;
456 }
457 self.total_deallocated as f64 / self.total_allocated as f64
458 }
459}
460
461#[cfg(test)]
462mod tests {
463 use super::*;
464
465 #[test]
466 fn test_memory_profiler_basic() {
467 let mut profiler = MemoryProfiler::new(MemoryProfilerConfig::default());
468 profiler.start();
469
470 profiler.record_allocation("test", 1024);
471 assert_eq!(profiler.snapshot().current_bytes, 1024);
472
473 profiler.record_allocation("test", 2048);
474 assert_eq!(profiler.snapshot().current_bytes, 3072);
475
476 profiler.record_deallocation("test", 1024);
477 assert_eq!(profiler.snapshot().current_bytes, 2048);
478 }
479
480 #[test]
481 fn test_memory_profiler_peak() {
482 let mut profiler = MemoryProfiler::new(MemoryProfilerConfig::default());
483 profiler.start();
484
485 profiler.record_allocation("test", 1000);
486 profiler.record_allocation("test", 2000);
487 profiler.record_deallocation("test", 1500);
488
489 let snapshot = profiler.snapshot();
490 assert_eq!(snapshot.current_bytes, 1500);
491 assert_eq!(snapshot.peak_bytes, 3000);
492 }
493
494 #[test]
495 fn test_memory_profiler_regions() {
496 let mut profiler = MemoryProfiler::new(MemoryProfilerConfig::default());
497 profiler.start();
498
499 profiler.record_allocation("devices", 1000);
500 profiler.record_allocation("registers", 2000);
501 profiler.record_allocation("devices", 500);
502
503 let regions = profiler.regions();
504 assert!(regions.contains(&"devices".to_string()));
505 assert!(regions.contains(&"registers".to_string()));
506
507 let device_region = profiler.region_usage("devices").unwrap();
508 assert_eq!(device_region.current_bytes, 1500);
509 assert_eq!(device_region.allocation_count, 2);
510 }
511
512 #[test]
513 fn test_memory_profiler_reset() {
514 let mut profiler = MemoryProfiler::new(MemoryProfilerConfig::default());
515 profiler.start();
516
517 profiler.record_allocation("test", 1024);
518 profiler.reset();
519
520 let snapshot = profiler.snapshot();
521 assert_eq!(snapshot.current_bytes, 0);
522 assert_eq!(snapshot.allocation_count, 0);
523 }
524
525 #[test]
526 fn test_memory_report() {
527 let mut profiler = MemoryProfiler::new(MemoryProfilerConfig::default());
528 profiler.start();
529
530 profiler.record_allocation("a", 1000);
531 profiler.record_allocation("b", 2000);
532 profiler.record_deallocation("a", 500);
533
534 let report = profiler.generate_report();
535 assert_eq!(report.current_bytes, 2500);
536 assert_eq!(report.allocation_count, 2);
537 assert_eq!(report.deallocation_count, 1);
538
539 let largest = report.largest_region();
540 assert!(largest.is_some());
541 assert_eq!(largest.unwrap().0, "b");
542 }
543
544 #[test]
545 fn test_region_snapshot_metrics() {
546 let region = MemoryRegion::new("test");
547 region.record_allocation(100);
548 region.record_allocation(200);
549 region.record_deallocation(50);
550
551 let snapshot = region.snapshot();
552 assert_eq!(snapshot.average_allocation_size(), 150); assert_eq!(snapshot.fragmentation_ratio(), 2.0); }
555}