ipfrs_tensorlogic/
memory_profiler.rs1use parking_lot::RwLock;
28use std::collections::HashMap;
29use std::sync::Arc;
30use std::time::{Duration, Instant};
31
32#[derive(Debug, Clone)]
34pub struct MemoryStats {
35 pub track_count: usize,
37 pub total_bytes: usize,
39 pub peak_bytes: usize,
41 pub avg_bytes: usize,
43 pub total_duration: Duration,
45 pub avg_duration: Duration,
47}
48
49impl MemoryStats {
50 fn new() -> Self {
51 Self {
52 track_count: 0,
53 total_bytes: 0,
54 peak_bytes: 0,
55 avg_bytes: 0,
56 total_duration: Duration::ZERO,
57 avg_duration: Duration::ZERO,
58 }
59 }
60
61 fn update(&mut self, bytes: usize, duration: Duration) {
62 self.track_count += 1;
63 self.total_bytes += bytes;
64 self.peak_bytes = self.peak_bytes.max(bytes);
65 self.total_duration += duration;
66
67 self.avg_bytes = self.total_bytes.checked_div(self.track_count).unwrap_or(0);
68 self.avg_duration = self
69 .total_duration
70 .checked_div(self.track_count as u32)
71 .unwrap_or(Duration::ZERO);
72 }
73}
74
75pub struct MemoryTrackingGuard {
77 profiler: Arc<MemoryProfiler>,
78 operation: String,
79 start_time: Instant,
80 initial_memory: usize,
81}
82
83impl Drop for MemoryTrackingGuard {
84 fn drop(&mut self) {
85 let duration = self.start_time.elapsed();
86 let current_memory = self.profiler.get_current_memory_usage();
87 let bytes_used = current_memory.saturating_sub(self.initial_memory);
88
89 let mut stats = self.profiler.stats.write();
90 let entry = stats
91 .entry(self.operation.clone())
92 .or_insert_with(MemoryStats::new);
93 entry.update(bytes_used, duration);
94 }
95}
96
97pub struct MemoryProfiler {
99 stats: Arc<RwLock<HashMap<String, MemoryStats>>>,
100}
101
102impl MemoryProfiler {
103 pub fn new() -> Arc<Self> {
105 Arc::new(Self {
106 stats: Arc::new(RwLock::new(HashMap::new())),
107 })
108 }
109
110 pub fn start_tracking(self: &Arc<Self>, operation: &str) -> MemoryTrackingGuard {
114 MemoryTrackingGuard {
115 profiler: Arc::clone(self),
116 operation: operation.to_string(),
117 start_time: Instant::now(),
118 initial_memory: self.get_current_memory_usage(),
119 }
120 }
121
122 pub fn get_stats(&self, operation: &str) -> Option<MemoryStats> {
124 self.stats.read().get(operation).cloned()
125 }
126
127 pub fn get_all_stats(&self) -> HashMap<String, MemoryStats> {
129 self.stats.read().clone()
130 }
131
132 pub fn clear(&self) {
134 self.stats.write().clear();
135 }
136
137 #[cfg(target_os = "linux")]
141 fn get_current_memory_usage(&self) -> usize {
142 if let Ok(contents) = std::fs::read_to_string("/proc/self/statm") {
144 if let Some(first) = contents.split_whitespace().next() {
145 if let Ok(pages) = first.parse::<usize>() {
146 return pages * 4096;
148 }
149 }
150 }
151 0
152 }
153
154 #[cfg(not(target_os = "linux"))]
155 fn get_current_memory_usage(&self) -> usize {
156 0
159 }
160
161 pub fn generate_report(&self) -> MemoryProfilingReport {
163 let stats = self.get_all_stats();
164 let total_operations = stats.len();
165 let total_tracked = stats.values().map(|s| s.track_count).sum();
166 let total_bytes: usize = stats.values().map(|s| s.total_bytes).sum();
167 let max_peak = stats.values().map(|s| s.peak_bytes).max().unwrap_or(0);
168
169 let mut operations: Vec<_> = stats.into_iter().collect();
170 operations.sort_by_key(|a| std::cmp::Reverse(a.1.peak_bytes));
171
172 MemoryProfilingReport {
173 total_operations,
174 total_tracked,
175 total_bytes,
176 max_peak_bytes: max_peak,
177 operations,
178 }
179 }
180}
181
182impl Default for MemoryProfiler {
183 fn default() -> Self {
184 Self {
185 stats: Arc::new(RwLock::new(HashMap::new())),
186 }
187 }
188}
189
190#[derive(Debug)]
192pub struct MemoryProfilingReport {
193 pub total_operations: usize,
195 pub total_tracked: usize,
197 pub total_bytes: usize,
199 pub max_peak_bytes: usize,
201 pub operations: Vec<(String, MemoryStats)>,
203}
204
205impl MemoryProfilingReport {
206 pub fn print(&self) {
208 println!("=== Memory Profiling Report ===");
209 println!("Total operations: {}", self.total_operations);
210 println!("Total tracks: {}", self.total_tracked);
211 println!(
212 "Total bytes: {} ({:.2} MB)",
213 self.total_bytes,
214 self.total_bytes as f64 / 1024.0 / 1024.0
215 );
216 println!(
217 "Max peak: {} ({:.2} MB)",
218 self.max_peak_bytes,
219 self.max_peak_bytes as f64 / 1024.0 / 1024.0
220 );
221 println!("\nTop memory-consuming operations:");
222 println!(
223 "{:<40} {:>12} {:>12} {:>12} {:>10}",
224 "Operation", "Tracks", "Peak", "Avg", "Avg Time"
225 );
226 println!(
227 "{:-<40} {:-<12} {:-<12} {:-<12} {:-<10}",
228 "", "", "", "", ""
229 );
230
231 for (i, (name, stats)) in self.operations.iter().enumerate().take(10) {
232 println!(
233 "{:<40} {:>12} {:>12} {:>12} {:>10?}",
234 if name.len() > 40 {
235 format!("{}...", &name[..37])
236 } else {
237 name.clone()
238 },
239 stats.track_count,
240 format_bytes(stats.peak_bytes),
241 format_bytes(stats.avg_bytes),
242 stats.avg_duration
243 );
244 if i >= 9 {
245 break;
246 }
247 }
248 }
249}
250
251fn format_bytes(bytes: usize) -> String {
253 if bytes < 1024 {
254 format!("{} B", bytes)
255 } else if bytes < 1024 * 1024 {
256 format!("{:.1} KB", bytes as f64 / 1024.0)
257 } else if bytes < 1024 * 1024 * 1024 {
258 format!("{:.1} MB", bytes as f64 / 1024.0 / 1024.0)
259 } else {
260 format!("{:.1} GB", bytes as f64 / 1024.0 / 1024.0 / 1024.0)
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267
268 #[test]
269 fn test_memory_profiler_basic() {
270 let profiler = MemoryProfiler::new();
271
272 {
273 let _guard = profiler.start_tracking("test_operation");
274 std::thread::sleep(Duration::from_millis(10));
276 }
277
278 let stats = profiler.get_stats("test_operation");
279 assert!(stats.is_some());
280
281 let stats = stats.expect("test: should succeed");
282 assert_eq!(stats.track_count, 1);
283 assert!(stats.total_duration >= Duration::from_millis(10));
284 }
285
286 #[test]
287 fn test_memory_profiler_multiple_tracks() {
288 let profiler = MemoryProfiler::new();
289
290 for _ in 0..5 {
291 let _guard = profiler.start_tracking("repeated_op");
292 std::thread::sleep(Duration::from_millis(5));
293 }
294
295 let stats = profiler
296 .get_stats("repeated_op")
297 .expect("test: should succeed");
298 assert_eq!(stats.track_count, 5);
299 assert!(stats.avg_duration >= Duration::from_millis(5));
300 }
301
302 #[test]
303 fn test_memory_profiler_multiple_operations() {
304 let profiler = MemoryProfiler::new();
305
306 {
307 let _guard1 = profiler.start_tracking("op1");
308 std::thread::sleep(Duration::from_millis(5));
309 }
310
311 {
312 let _guard2 = profiler.start_tracking("op2");
313 std::thread::sleep(Duration::from_millis(10));
314 }
315
316 let all_stats = profiler.get_all_stats();
317 assert_eq!(all_stats.len(), 2);
318 assert!(all_stats.contains_key("op1"));
319 assert!(all_stats.contains_key("op2"));
320 }
321
322 #[test]
323 fn test_memory_profiler_clear() {
324 let profiler = MemoryProfiler::new();
325
326 {
327 let _guard = profiler.start_tracking("test");
328 }
329
330 assert_eq!(profiler.get_all_stats().len(), 1);
331
332 profiler.clear();
333 assert_eq!(profiler.get_all_stats().len(), 0);
334 }
335
336 #[test]
337 fn test_memory_profiler_report() {
338 let profiler = MemoryProfiler::new();
339
340 {
341 let _guard = profiler.start_tracking("op1");
342 }
343
344 {
345 let _guard = profiler.start_tracking("op2");
346 }
347
348 let report = profiler.generate_report();
349 assert_eq!(report.total_operations, 2);
350 assert_eq!(report.total_tracked, 2);
351 }
352
353 #[test]
354 fn test_format_bytes() {
355 assert_eq!(format_bytes(512), "512 B");
356 assert_eq!(format_bytes(1024), "1.0 KB");
357 assert_eq!(format_bytes(1024 * 1024), "1.0 MB");
358 assert_eq!(format_bytes(1024 * 1024 * 1024), "1.0 GB");
359 }
360}