1use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
30use std::sync::Arc;
31use std::time::Instant;
32
33use super::{LogEntry, LogHandler, LogLevel};
34
35#[derive(Debug)]
45pub struct LogCrateBridge;
46
47impl LogCrateBridge {
48 pub fn install() {
53 let handler = Arc::new(LogCrateBridge);
54 super::set_handler(handler);
55 }
56
57 fn map_level(level: LogLevel) -> log::Level {
59 match level {
60 LogLevel::Trace => log::Level::Trace,
61 LogLevel::Debug => log::Level::Debug,
62 LogLevel::Info => log::Level::Info,
63 LogLevel::Warn => log::Level::Warn,
64 LogLevel::Error | LogLevel::Critical => log::Level::Error,
65 }
66 }
67}
68
69impl LogHandler for LogCrateBridge {
70 fn handle(&self, entry: &LogEntry) {
71 let level = Self::map_level(entry.level);
72 log::log!(target: &entry.module, level, "{}", entry.message);
75 }
76}
77
78#[macro_export]
109macro_rules! scirs2_time {
110 ($label:expr, $body:expr) => {{
111 let _start = std::time::Instant::now();
112 let _result = $body;
113 let _elapsed = _start.elapsed();
114 log::info!(
116 target: "scirs2::timing",
117 "[TIMING] {}: {:.6}s ({:.3}ms)",
118 $label,
119 _elapsed.as_secs_f64(),
120 _elapsed.as_secs_f64() * 1000.0
121 );
122 _result
123 }};
124 ($logger:expr, $label:expr, $body:expr) => {{
125 let _start = std::time::Instant::now();
126 let _result = $body;
127 let _elapsed = _start.elapsed();
128 $logger.info(&format!(
129 "[TIMING] {}: {:.6}s ({:.3}ms)",
130 $label,
131 _elapsed.as_secs_f64(),
132 _elapsed.as_secs_f64() * 1000.0
133 ));
134 _result
135 }};
136}
137
138pub struct TimingGuard {
155 label: String,
156 start: Instant,
157}
158
159impl TimingGuard {
160 pub fn new(label: &str) -> Self {
162 Self {
163 label: label.to_string(),
164 start: Instant::now(),
165 }
166 }
167
168 pub fn elapsed(&self) -> std::time::Duration {
170 self.start.elapsed()
171 }
172}
173
174impl Drop for TimingGuard {
175 fn drop(&mut self) {
176 let elapsed = self.start.elapsed();
177 log::info!(
178 target: "scirs2::timing",
179 "[TIMING] {}: {:.6}s ({:.3}ms)",
180 self.label,
181 elapsed.as_secs_f64(),
182 elapsed.as_secs_f64() * 1000.0
183 );
184 }
185}
186
187#[derive(Debug, Clone)]
196pub struct MemoryTracker {
197 inner: Arc<MemoryTrackerInner>,
198}
199
200#[derive(Debug)]
201struct MemoryTrackerInner {
202 current_bytes: AtomicU64,
204 peak_bytes: AtomicU64,
206 total_allocated: AtomicU64,
208 total_freed: AtomicU64,
210 allocation_count: AtomicUsize,
212 deallocation_count: AtomicUsize,
214}
215
216#[derive(Debug, Clone)]
218pub struct MemoryReport {
219 pub current_bytes: u64,
221 pub peak_bytes: u64,
223 pub total_allocated: u64,
225 pub total_freed: u64,
227 pub allocation_count: usize,
229 pub deallocation_count: usize,
231}
232
233impl std::fmt::Display for MemoryReport {
234 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235 writeln!(f, "MemoryReport {{")?;
236 writeln!(
237 f,
238 " current: {} bytes ({:.2} MiB)",
239 self.current_bytes,
240 self.current_bytes as f64 / (1024.0 * 1024.0)
241 )?;
242 writeln!(
243 f,
244 " peak: {} bytes ({:.2} MiB)",
245 self.peak_bytes,
246 self.peak_bytes as f64 / (1024.0 * 1024.0)
247 )?;
248 writeln!(
249 f,
250 " allocated: {} bytes ({}x)",
251 self.total_allocated, self.allocation_count
252 )?;
253 writeln!(
254 f,
255 " freed: {} bytes ({}x)",
256 self.total_freed, self.deallocation_count
257 )?;
258 write!(f, "}}")
259 }
260}
261
262impl MemoryTracker {
263 pub fn new() -> Self {
265 Self {
266 inner: Arc::new(MemoryTrackerInner {
267 current_bytes: AtomicU64::new(0),
268 peak_bytes: AtomicU64::new(0),
269 total_allocated: AtomicU64::new(0),
270 total_freed: AtomicU64::new(0),
271 allocation_count: AtomicUsize::new(0),
272 deallocation_count: AtomicUsize::new(0),
273 }),
274 }
275 }
276
277 pub fn record_allocation(&self, bytes: u64) {
279 self.inner
280 .total_allocated
281 .fetch_add(bytes, Ordering::Relaxed);
282 self.inner.allocation_count.fetch_add(1, Ordering::Relaxed);
283 let new_current = self.inner.current_bytes.fetch_add(bytes, Ordering::Relaxed) + bytes;
284 let mut peak = self.inner.peak_bytes.load(Ordering::Relaxed);
286 while new_current > peak {
287 match self.inner.peak_bytes.compare_exchange_weak(
288 peak,
289 new_current,
290 Ordering::Relaxed,
291 Ordering::Relaxed,
292 ) {
293 Ok(_) => break,
294 Err(actual) => peak = actual,
295 }
296 }
297 }
298
299 pub fn record_deallocation(&self, bytes: u64) {
301 self.inner.total_freed.fetch_add(bytes, Ordering::Relaxed);
302 self.inner
303 .deallocation_count
304 .fetch_add(1, Ordering::Relaxed);
305 let _ = self.inner.current_bytes.fetch_update(
307 Ordering::Relaxed,
308 Ordering::Relaxed,
309 |current| Some(current.saturating_sub(bytes)),
310 );
311 }
312
313 pub fn current_bytes(&self) -> u64 {
315 self.inner.current_bytes.load(Ordering::Relaxed)
316 }
317
318 pub fn peak_bytes(&self) -> u64 {
320 self.inner.peak_bytes.load(Ordering::Relaxed)
321 }
322
323 pub fn report(&self) -> MemoryReport {
325 MemoryReport {
326 current_bytes: self.inner.current_bytes.load(Ordering::Relaxed),
327 peak_bytes: self.inner.peak_bytes.load(Ordering::Relaxed),
328 total_allocated: self.inner.total_allocated.load(Ordering::Relaxed),
329 total_freed: self.inner.total_freed.load(Ordering::Relaxed),
330 allocation_count: self.inner.allocation_count.load(Ordering::Relaxed),
331 deallocation_count: self.inner.deallocation_count.load(Ordering::Relaxed),
332 }
333 }
334
335 pub fn reset(&self) {
337 self.inner.current_bytes.store(0, Ordering::Relaxed);
338 self.inner.peak_bytes.store(0, Ordering::Relaxed);
339 self.inner.total_allocated.store(0, Ordering::Relaxed);
340 self.inner.total_freed.store(0, Ordering::Relaxed);
341 self.inner.allocation_count.store(0, Ordering::Relaxed);
342 self.inner.deallocation_count.store(0, Ordering::Relaxed);
343 }
344}
345
346impl Default for MemoryTracker {
347 fn default() -> Self {
348 Self::new()
349 }
350}
351
352#[cfg(test)]
357mod tests {
358 use super::*;
359
360 #[test]
361 fn test_log_crate_bridge_level_mapping() {
362 assert_eq!(
363 LogCrateBridge::map_level(LogLevel::Trace),
364 log::Level::Trace
365 );
366 assert_eq!(
367 LogCrateBridge::map_level(LogLevel::Debug),
368 log::Level::Debug
369 );
370 assert_eq!(LogCrateBridge::map_level(LogLevel::Info), log::Level::Info);
371 assert_eq!(LogCrateBridge::map_level(LogLevel::Warn), log::Level::Warn);
372 assert_eq!(
373 LogCrateBridge::map_level(LogLevel::Error),
374 log::Level::Error
375 );
376 assert_eq!(
377 LogCrateBridge::map_level(LogLevel::Critical),
378 log::Level::Error
379 );
380 }
381
382 #[test]
383 fn test_memory_tracker_allocation() {
384 let tracker = MemoryTracker::new();
385 assert_eq!(tracker.current_bytes(), 0);
386
387 tracker.record_allocation(1024);
388 assert_eq!(tracker.current_bytes(), 1024);
389 assert_eq!(tracker.peak_bytes(), 1024);
390
391 tracker.record_allocation(2048);
392 assert_eq!(tracker.current_bytes(), 3072);
393 assert_eq!(tracker.peak_bytes(), 3072);
394 }
395
396 #[test]
397 fn test_memory_tracker_deallocation() {
398 let tracker = MemoryTracker::new();
399 tracker.record_allocation(1024);
400 tracker.record_allocation(2048);
401
402 tracker.record_deallocation(1024);
403 assert_eq!(tracker.current_bytes(), 2048);
404 assert_eq!(tracker.peak_bytes(), 3072);
406 }
407
408 #[test]
409 fn test_memory_tracker_deallocation_underflow_protection() {
410 let tracker = MemoryTracker::new();
411 tracker.record_allocation(100);
412 tracker.record_deallocation(200); assert_eq!(tracker.current_bytes(), 0); }
415
416 #[test]
417 fn test_memory_tracker_report() {
418 let tracker = MemoryTracker::new();
419 tracker.record_allocation(1000);
420 tracker.record_allocation(2000);
421 tracker.record_deallocation(500);
422
423 let report = tracker.report();
424 assert_eq!(report.current_bytes, 2500);
425 assert_eq!(report.peak_bytes, 3000);
426 assert_eq!(report.total_allocated, 3000);
427 assert_eq!(report.total_freed, 500);
428 assert_eq!(report.allocation_count, 2);
429 assert_eq!(report.deallocation_count, 1);
430 }
431
432 #[test]
433 fn test_memory_tracker_reset() {
434 let tracker = MemoryTracker::new();
435 tracker.record_allocation(5000);
436 tracker.reset();
437
438 let report = tracker.report();
439 assert_eq!(report.current_bytes, 0);
440 assert_eq!(report.peak_bytes, 0);
441 assert_eq!(report.total_allocated, 0);
442 assert_eq!(report.allocation_count, 0);
443 }
444
445 #[test]
446 fn test_memory_tracker_clone_shares_state() {
447 let tracker1 = MemoryTracker::new();
448 let tracker2 = tracker1.clone();
449
450 tracker1.record_allocation(1024);
451 assert_eq!(tracker2.current_bytes(), 1024);
452 }
453
454 #[test]
455 fn test_memory_report_display() {
456 let tracker = MemoryTracker::new();
457 tracker.record_allocation(1_048_576); let report = tracker.report();
459 let display = format!("{report}");
460 assert!(display.contains("1.00 MiB"));
461 assert!(display.contains("1048576"));
462 }
463
464 #[test]
465 fn test_timing_guard_elapsed() {
466 let guard = TimingGuard::new("test_op");
467 std::thread::sleep(std::time::Duration::from_millis(10));
468 let elapsed = guard.elapsed();
469 assert!(elapsed.as_millis() >= 5, "Should have elapsed at least 5ms");
470 }
472
473 #[test]
474 fn test_timing_guard_does_not_panic_on_drop() {
475 {
476 let _guard = TimingGuard::new("scope_test");
477 let _ = 1 + 1;
479 }
480 }
482
483 #[test]
484 fn test_log_bridge_install_does_not_panic() {
485 LogCrateBridge::install();
487 }
488
489 #[test]
490 fn test_memory_tracker_thread_safety() {
491 let tracker = MemoryTracker::new();
492 let handles: Vec<_> = (0..4)
493 .map(|_| {
494 let t = tracker.clone();
495 std::thread::spawn(move || {
496 for _ in 0..100 {
497 t.record_allocation(10);
498 }
499 for _ in 0..50 {
500 t.record_deallocation(10);
501 }
502 })
503 })
504 .collect();
505
506 for h in handles {
507 h.join().expect("Thread should not panic");
508 }
509
510 let report = tracker.report();
511 assert_eq!(report.total_allocated, 4 * 100 * 10);
512 assert_eq!(report.total_freed, 4 * 50 * 10);
513 assert_eq!(report.allocation_count, 400);
514 assert_eq!(report.deallocation_count, 200);
515 assert_eq!(report.current_bytes, 4 * 50 * 10); }
517}