1use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
4use std::time::{Duration, Instant};
5
6#[derive(Debug, Default)]
8pub struct MemoryMetrics {
9 total_allocs: AtomicU64,
11 total_deallocs: AtomicU64,
13 total_bytes: AtomicUsize,
15 peak_bytes: AtomicUsize,
17 current_bytes: AtomicUsize,
19 context_allocs: AtomicU64,
21 graph_allocs: AtomicU64,
23}
24
25impl MemoryMetrics {
26 pub fn new() -> Self {
28 Self::default()
29 }
30
31 pub fn record_alloc(&self, bytes: usize) {
33 self.total_allocs.fetch_add(1, Ordering::Relaxed);
34 self.total_bytes.fetch_add(bytes, Ordering::Relaxed);
35 let current = self.current_bytes.fetch_add(bytes, Ordering::Relaxed) + bytes;
36
37 let mut peak = self.peak_bytes.load(Ordering::Relaxed);
39 while current > peak {
40 match self.peak_bytes.compare_exchange_weak(
41 peak,
42 current,
43 Ordering::Relaxed,
44 Ordering::Relaxed,
45 ) {
46 Ok(_) => break,
47 Err(x) => peak = x,
48 }
49 }
50 }
51
52 pub fn record_dealloc(&self, bytes: usize) {
54 self.total_deallocs.fetch_add(1, Ordering::Relaxed);
55 self.current_bytes.fetch_sub(bytes, Ordering::Relaxed);
56 }
57
58 pub fn record_context_alloc(&self) {
60 self.context_allocs.fetch_add(1, Ordering::Relaxed);
61 }
62
63 pub fn record_graph_alloc(&self) {
65 self.graph_allocs.fetch_add(1, Ordering::Relaxed);
66 }
67
68 pub fn total_allocations(&self) -> u64 {
70 self.total_allocs.load(Ordering::Relaxed)
71 }
72
73 pub fn total_deallocations(&self) -> u64 {
75 self.total_deallocs.load(Ordering::Relaxed)
76 }
77
78 pub fn total_bytes(&self) -> usize {
80 self.total_bytes.load(Ordering::Relaxed)
81 }
82
83 pub fn peak_bytes(&self) -> usize {
85 self.peak_bytes.load(Ordering::Relaxed)
86 }
87
88 pub fn current_bytes(&self) -> usize {
90 self.current_bytes.load(Ordering::Relaxed)
91 }
92
93 pub fn context_allocations(&self) -> u64 {
95 self.context_allocs.load(Ordering::Relaxed)
96 }
97
98 pub fn graph_allocations(&self) -> u64 {
100 self.graph_allocs.load(Ordering::Relaxed)
101 }
102
103 pub fn active_allocations(&self) -> u64 {
105 let allocs = self.total_allocs.load(Ordering::Relaxed);
106 let deallocs = self.total_deallocs.load(Ordering::Relaxed);
107 allocs.saturating_sub(deallocs)
108 }
109
110 pub fn reset(&self) {
112 self.total_allocs.store(0, Ordering::Relaxed);
113 self.total_deallocs.store(0, Ordering::Relaxed);
114 self.total_bytes.store(0, Ordering::Relaxed);
115 self.peak_bytes.store(0, Ordering::Relaxed);
116 self.current_bytes.store(0, Ordering::Relaxed);
117 self.context_allocs.store(0, Ordering::Relaxed);
118 self.graph_allocs.store(0, Ordering::Relaxed);
119 }
120
121 pub fn summary(&self) -> String {
123 format!(
124 "Memory Metrics:\n\
125 Total Allocations: {}\n\
126 Total Deallocations: {}\n\
127 Active Allocations: {}\n\
128 Total Bytes: {} ({:.2} MB)\n\
129 Current Bytes: {} ({:.2} MB)\n\
130 Peak Bytes: {} ({:.2} MB)\n\
131 Context Allocations: {}\n\
132 Graph Allocations: {}",
133 self.total_allocations(),
134 self.total_deallocations(),
135 self.active_allocations(),
136 self.total_bytes(),
137 self.total_bytes() as f64 / 1024.0 / 1024.0,
138 self.current_bytes(),
139 self.current_bytes() as f64 / 1024.0 / 1024.0,
140 self.peak_bytes(),
141 self.peak_bytes() as f64 / 1024.0 / 1024.0,
142 self.context_allocations(),
143 self.graph_allocations(),
144 )
145 }
146}
147
148pub struct AllocationTracker {
150 start_allocs: u64,
151 start_bytes: usize,
152 start_time: Instant,
153 name: String,
154}
155
156impl AllocationTracker {
157 pub fn new(name: impl Into<String>) -> Self {
159 let metrics = crate::memory::global_metrics();
160 let m = metrics.read();
161
162 Self {
163 start_allocs: m.total_allocations(),
164 start_bytes: m.total_bytes(),
165 start_time: Instant::now(),
166 name: name.into(),
167 }
168 }
169
170 pub fn stop(&self) {
172 let metrics = crate::memory::global_metrics();
173 let m = metrics.read();
174
175 let allocs = m.total_allocations() - self.start_allocs;
176 let bytes = m.total_bytes() - self.start_bytes;
177 let duration = self.start_time.elapsed();
178
179 println!(
180 "[{}] Allocations: {}, Bytes: {} ({:.2} MB), Duration: {:?}",
181 self.name,
182 allocs,
183 bytes,
184 bytes as f64 / 1024.0 / 1024.0,
185 duration
186 );
187 }
188}
189
190impl Drop for AllocationTracker {
191 fn drop(&mut self) {
192 }
194}
195
196pub struct MemoryProfiler;
198
199impl MemoryProfiler {
200 pub fn profile<F, R>(name: &str, f: F) -> R
202 where
203 F: FnOnce() -> R,
204 {
205 let tracker = AllocationTracker::new(name);
206 let result = f();
207 tracker.stop();
208 result
209 }
210
211 pub async fn profile_async<F, Fut, R>(name: &str, f: F) -> R
213 where
214 F: FnOnce() -> Fut,
215 Fut: std::future::Future<Output = R>,
216 {
217 let tracker = AllocationTracker::new(name);
218 let result = f().await;
219 tracker.stop();
220 result
221 }
222
223 pub fn snapshot() -> MemorySnapshot {
225 let metrics = crate::memory::global_metrics();
226 let m = metrics.read();
227
228 MemorySnapshot {
229 total_allocs: m.total_allocations(),
230 total_deallocs: m.total_deallocations(),
231 current_bytes: m.current_bytes(),
232 peak_bytes: m.peak_bytes(),
233 timestamp: Instant::now(),
234 }
235 }
236}
237
238#[derive(Debug, Clone)]
240pub struct MemorySnapshot {
241 pub total_allocs: u64,
242 pub total_deallocs: u64,
243 pub current_bytes: usize,
244 pub peak_bytes: usize,
245 pub timestamp: Instant,
246}
247
248impl MemorySnapshot {
249 pub fn diff(&self, other: &MemorySnapshot) -> MemoryDiff {
251 MemoryDiff {
252 allocs_delta: self.total_allocs as i64 - other.total_allocs as i64,
253 bytes_delta: self.current_bytes as i64 - other.current_bytes as i64,
254 duration: self.timestamp.duration_since(other.timestamp),
255 }
256 }
257}
258
259#[derive(Debug, Clone)]
261pub struct MemoryDiff {
262 pub allocs_delta: i64,
263 pub bytes_delta: i64,
264 pub duration: Duration,
265}
266
267impl MemoryDiff {
268 pub fn format(&self) -> String {
270 format!(
271 "Δ Allocations: {:+}, Δ Bytes: {:+} ({:+.2} MB), Duration: {:?}",
272 self.allocs_delta,
273 self.bytes_delta,
274 self.bytes_delta as f64 / 1024.0 / 1024.0,
275 self.duration
276 )
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn test_memory_metrics() {
286 let metrics = MemoryMetrics::new();
287
288 metrics.record_alloc(1024);
289 assert_eq!(metrics.total_allocations(), 1);
290 assert_eq!(metrics.current_bytes(), 1024);
291 assert_eq!(metrics.peak_bytes(), 1024);
292
293 metrics.record_alloc(2048);
294 assert_eq!(metrics.total_allocations(), 2);
295 assert_eq!(metrics.current_bytes(), 3072);
296 assert_eq!(metrics.peak_bytes(), 3072);
297
298 metrics.record_dealloc(1024);
299 assert_eq!(metrics.current_bytes(), 2048);
300 assert_eq!(metrics.peak_bytes(), 3072); }
302
303 #[test]
304 fn test_active_allocations() {
305 let metrics = MemoryMetrics::new();
306
307 metrics.record_alloc(100);
308 metrics.record_alloc(200);
309 assert_eq!(metrics.active_allocations(), 2);
310
311 metrics.record_dealloc(100);
312 assert_eq!(metrics.active_allocations(), 1);
313 }
314
315 #[test]
316 fn test_memory_snapshot_diff() {
317 let snap1 = MemorySnapshot {
318 total_allocs: 10,
319 total_deallocs: 5,
320 current_bytes: 1024,
321 peak_bytes: 2048,
322 timestamp: Instant::now(),
323 };
324
325 std::thread::sleep(Duration::from_millis(10));
326
327 let snap2 = MemorySnapshot {
328 total_allocs: 15,
329 total_deallocs: 7,
330 current_bytes: 2048,
331 peak_bytes: 4096,
332 timestamp: Instant::now(),
333 };
334
335 let diff = snap2.diff(&snap1);
336 assert_eq!(diff.allocs_delta, 5);
337 assert_eq!(diff.bytes_delta, 1024);
338 assert!(diff.duration.as_millis() >= 10);
339 }
340
341 #[test]
342 fn test_metrics_reset() {
343 let metrics = MemoryMetrics::new();
344
345 metrics.record_alloc(1024);
346 metrics.record_context_alloc();
347 assert_eq!(metrics.total_allocations(), 1);
348 assert_eq!(metrics.context_allocations(), 1);
349
350 metrics.reset();
351 assert_eq!(metrics.total_allocations(), 0);
352 assert_eq!(metrics.context_allocations(), 0);
353 }
354}