rust_logic_graph/memory/
metrics.rs

1//! Memory metrics and tracking
2
3use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
4use std::time::{Duration, Instant};
5
6/// Memory allocation metrics
7#[derive(Debug, Default)]
8pub struct MemoryMetrics {
9    /// Total number of allocations
10    total_allocs: AtomicU64,
11    /// Total number of deallocations
12    total_deallocs: AtomicU64,
13    /// Total bytes allocated
14    total_bytes: AtomicUsize,
15    /// Peak memory usage
16    peak_bytes: AtomicUsize,
17    /// Current memory usage
18    current_bytes: AtomicUsize,
19    /// Number of context allocations
20    context_allocs: AtomicU64,
21    /// Number of graph allocations
22    graph_allocs: AtomicU64,
23}
24
25impl MemoryMetrics {
26    /// Create new memory metrics
27    pub fn new() -> Self {
28        Self::default()
29    }
30
31    /// Record an allocation
32    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        // Update peak if necessary
38        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    /// Record a deallocation
53    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    /// Record a context allocation
59    pub fn record_context_alloc(&self) {
60        self.context_allocs.fetch_add(1, Ordering::Relaxed);
61    }
62
63    /// Record a graph allocation
64    pub fn record_graph_alloc(&self) {
65        self.graph_allocs.fetch_add(1, Ordering::Relaxed);
66    }
67
68    /// Get total allocations
69    pub fn total_allocations(&self) -> u64 {
70        self.total_allocs.load(Ordering::Relaxed)
71    }
72
73    /// Get total deallocations
74    pub fn total_deallocations(&self) -> u64 {
75        self.total_deallocs.load(Ordering::Relaxed)
76    }
77
78    /// Get total bytes allocated
79    pub fn total_bytes(&self) -> usize {
80        self.total_bytes.load(Ordering::Relaxed)
81    }
82
83    /// Get peak memory usage
84    pub fn peak_bytes(&self) -> usize {
85        self.peak_bytes.load(Ordering::Relaxed)
86    }
87
88    /// Get current memory usage
89    pub fn current_bytes(&self) -> usize {
90        self.current_bytes.load(Ordering::Relaxed)
91    }
92
93    /// Get context allocations
94    pub fn context_allocations(&self) -> u64 {
95        self.context_allocs.load(Ordering::Relaxed)
96    }
97
98    /// Get graph allocations
99    pub fn graph_allocations(&self) -> u64 {
100        self.graph_allocs.load(Ordering::Relaxed)
101    }
102
103    /// Get active allocations
104    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    /// Reset all metrics
111    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    /// Print memory metrics summary
122    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
148/// Allocation tracker for scoped measurements
149pub struct AllocationTracker {
150    start_allocs: u64,
151    start_bytes: usize,
152    start_time: Instant,
153    name: String,
154}
155
156impl AllocationTracker {
157    /// Create a new allocation tracker
158    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    /// Stop tracking and print results
171    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        // Auto-print on drop if not explicitly stopped
193    }
194}
195
196/// Memory profiling utilities
197pub struct MemoryProfiler;
198
199impl MemoryProfiler {
200    /// Profile a function's memory usage
201    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    /// Profile an async function's memory usage
212    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    /// Get current memory snapshot
224    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/// Memory snapshot at a point in time
239#[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    /// Calculate difference from another snapshot
250    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/// Difference between two memory snapshots
260#[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    /// Format as human-readable string
269    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); // Peak doesn't decrease
301    }
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}