use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Instant;
use super::{LogEntry, LogHandler, LogLevel};
#[derive(Debug)]
pub struct LogCrateBridge;
impl LogCrateBridge {
pub fn install() {
let handler = Arc::new(LogCrateBridge);
super::set_handler(handler);
}
fn map_level(level: LogLevel) -> log::Level {
match level {
LogLevel::Trace => log::Level::Trace,
LogLevel::Debug => log::Level::Debug,
LogLevel::Info => log::Level::Info,
LogLevel::Warn => log::Level::Warn,
LogLevel::Error | LogLevel::Critical => log::Level::Error,
}
}
}
impl LogHandler for LogCrateBridge {
fn handle(&self, entry: &LogEntry) {
let level = Self::map_level(entry.level);
log::log!(target: &entry.module, level, "{}", entry.message);
}
}
#[macro_export]
macro_rules! scirs2_time {
($label:expr, $body:expr) => {{
let _start = std::time::Instant::now();
let _result = $body;
let _elapsed = _start.elapsed();
log::info!(
target: "scirs2::timing",
"[TIMING] {}: {:.6}s ({:.3}ms)",
$label,
_elapsed.as_secs_f64(),
_elapsed.as_secs_f64() * 1000.0
);
_result
}};
($logger:expr, $label:expr, $body:expr) => {{
let _start = std::time::Instant::now();
let _result = $body;
let _elapsed = _start.elapsed();
$logger.info(&format!(
"[TIMING] {}: {:.6}s ({:.3}ms)",
$label,
_elapsed.as_secs_f64(),
_elapsed.as_secs_f64() * 1000.0
));
_result
}};
}
pub struct TimingGuard {
label: String,
start: Instant,
}
impl TimingGuard {
pub fn new(label: &str) -> Self {
Self {
label: label.to_string(),
start: Instant::now(),
}
}
pub fn elapsed(&self) -> std::time::Duration {
self.start.elapsed()
}
}
impl Drop for TimingGuard {
fn drop(&mut self) {
let elapsed = self.start.elapsed();
log::info!(
target: "scirs2::timing",
"[TIMING] {}: {:.6}s ({:.3}ms)",
self.label,
elapsed.as_secs_f64(),
elapsed.as_secs_f64() * 1000.0
);
}
}
#[derive(Debug, Clone)]
pub struct MemoryTracker {
inner: Arc<MemoryTrackerInner>,
}
#[derive(Debug)]
struct MemoryTrackerInner {
current_bytes: AtomicU64,
peak_bytes: AtomicU64,
total_allocated: AtomicU64,
total_freed: AtomicU64,
allocation_count: AtomicUsize,
deallocation_count: AtomicUsize,
}
#[derive(Debug, Clone)]
pub struct MemoryReport {
pub current_bytes: u64,
pub peak_bytes: u64,
pub total_allocated: u64,
pub total_freed: u64,
pub allocation_count: usize,
pub deallocation_count: usize,
}
impl std::fmt::Display for MemoryReport {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "MemoryReport {{")?;
writeln!(
f,
" current: {} bytes ({:.2} MiB)",
self.current_bytes,
self.current_bytes as f64 / (1024.0 * 1024.0)
)?;
writeln!(
f,
" peak: {} bytes ({:.2} MiB)",
self.peak_bytes,
self.peak_bytes as f64 / (1024.0 * 1024.0)
)?;
writeln!(
f,
" allocated: {} bytes ({}x)",
self.total_allocated, self.allocation_count
)?;
writeln!(
f,
" freed: {} bytes ({}x)",
self.total_freed, self.deallocation_count
)?;
write!(f, "}}")
}
}
impl MemoryTracker {
pub fn new() -> Self {
Self {
inner: Arc::new(MemoryTrackerInner {
current_bytes: AtomicU64::new(0),
peak_bytes: AtomicU64::new(0),
total_allocated: AtomicU64::new(0),
total_freed: AtomicU64::new(0),
allocation_count: AtomicUsize::new(0),
deallocation_count: AtomicUsize::new(0),
}),
}
}
pub fn record_allocation(&self, bytes: u64) {
self.inner
.total_allocated
.fetch_add(bytes, Ordering::Relaxed);
self.inner.allocation_count.fetch_add(1, Ordering::Relaxed);
let new_current = self.inner.current_bytes.fetch_add(bytes, Ordering::Relaxed) + bytes;
let mut peak = self.inner.peak_bytes.load(Ordering::Relaxed);
while new_current > peak {
match self.inner.peak_bytes.compare_exchange_weak(
peak,
new_current,
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(actual) => peak = actual,
}
}
}
pub fn record_deallocation(&self, bytes: u64) {
self.inner.total_freed.fetch_add(bytes, Ordering::Relaxed);
self.inner
.deallocation_count
.fetch_add(1, Ordering::Relaxed);
let _ = self.inner.current_bytes.fetch_update(
Ordering::Relaxed,
Ordering::Relaxed,
|current| Some(current.saturating_sub(bytes)),
);
}
pub fn current_bytes(&self) -> u64 {
self.inner.current_bytes.load(Ordering::Relaxed)
}
pub fn peak_bytes(&self) -> u64 {
self.inner.peak_bytes.load(Ordering::Relaxed)
}
pub fn report(&self) -> MemoryReport {
MemoryReport {
current_bytes: self.inner.current_bytes.load(Ordering::Relaxed),
peak_bytes: self.inner.peak_bytes.load(Ordering::Relaxed),
total_allocated: self.inner.total_allocated.load(Ordering::Relaxed),
total_freed: self.inner.total_freed.load(Ordering::Relaxed),
allocation_count: self.inner.allocation_count.load(Ordering::Relaxed),
deallocation_count: self.inner.deallocation_count.load(Ordering::Relaxed),
}
}
pub fn reset(&self) {
self.inner.current_bytes.store(0, Ordering::Relaxed);
self.inner.peak_bytes.store(0, Ordering::Relaxed);
self.inner.total_allocated.store(0, Ordering::Relaxed);
self.inner.total_freed.store(0, Ordering::Relaxed);
self.inner.allocation_count.store(0, Ordering::Relaxed);
self.inner.deallocation_count.store(0, Ordering::Relaxed);
}
}
impl Default for MemoryTracker {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_log_crate_bridge_level_mapping() {
assert_eq!(
LogCrateBridge::map_level(LogLevel::Trace),
log::Level::Trace
);
assert_eq!(
LogCrateBridge::map_level(LogLevel::Debug),
log::Level::Debug
);
assert_eq!(LogCrateBridge::map_level(LogLevel::Info), log::Level::Info);
assert_eq!(LogCrateBridge::map_level(LogLevel::Warn), log::Level::Warn);
assert_eq!(
LogCrateBridge::map_level(LogLevel::Error),
log::Level::Error
);
assert_eq!(
LogCrateBridge::map_level(LogLevel::Critical),
log::Level::Error
);
}
#[test]
fn test_memory_tracker_allocation() {
let tracker = MemoryTracker::new();
assert_eq!(tracker.current_bytes(), 0);
tracker.record_allocation(1024);
assert_eq!(tracker.current_bytes(), 1024);
assert_eq!(tracker.peak_bytes(), 1024);
tracker.record_allocation(2048);
assert_eq!(tracker.current_bytes(), 3072);
assert_eq!(tracker.peak_bytes(), 3072);
}
#[test]
fn test_memory_tracker_deallocation() {
let tracker = MemoryTracker::new();
tracker.record_allocation(1024);
tracker.record_allocation(2048);
tracker.record_deallocation(1024);
assert_eq!(tracker.current_bytes(), 2048);
assert_eq!(tracker.peak_bytes(), 3072);
}
#[test]
fn test_memory_tracker_deallocation_underflow_protection() {
let tracker = MemoryTracker::new();
tracker.record_allocation(100);
tracker.record_deallocation(200); assert_eq!(tracker.current_bytes(), 0); }
#[test]
fn test_memory_tracker_report() {
let tracker = MemoryTracker::new();
tracker.record_allocation(1000);
tracker.record_allocation(2000);
tracker.record_deallocation(500);
let report = tracker.report();
assert_eq!(report.current_bytes, 2500);
assert_eq!(report.peak_bytes, 3000);
assert_eq!(report.total_allocated, 3000);
assert_eq!(report.total_freed, 500);
assert_eq!(report.allocation_count, 2);
assert_eq!(report.deallocation_count, 1);
}
#[test]
fn test_memory_tracker_reset() {
let tracker = MemoryTracker::new();
tracker.record_allocation(5000);
tracker.reset();
let report = tracker.report();
assert_eq!(report.current_bytes, 0);
assert_eq!(report.peak_bytes, 0);
assert_eq!(report.total_allocated, 0);
assert_eq!(report.allocation_count, 0);
}
#[test]
fn test_memory_tracker_clone_shares_state() {
let tracker1 = MemoryTracker::new();
let tracker2 = tracker1.clone();
tracker1.record_allocation(1024);
assert_eq!(tracker2.current_bytes(), 1024);
}
#[test]
fn test_memory_report_display() {
let tracker = MemoryTracker::new();
tracker.record_allocation(1_048_576); let report = tracker.report();
let display = format!("{report}");
assert!(display.contains("1.00 MiB"));
assert!(display.contains("1048576"));
}
#[test]
fn test_timing_guard_elapsed() {
let guard = TimingGuard::new("test_op");
std::thread::sleep(std::time::Duration::from_millis(10));
let elapsed = guard.elapsed();
assert!(elapsed.as_millis() >= 5, "Should have elapsed at least 5ms");
}
#[test]
fn test_timing_guard_does_not_panic_on_drop() {
{
let _guard = TimingGuard::new("scope_test");
let _ = 1 + 1;
}
}
#[test]
fn test_log_bridge_install_does_not_panic() {
LogCrateBridge::install();
}
#[test]
fn test_memory_tracker_thread_safety() {
let tracker = MemoryTracker::new();
let handles: Vec<_> = (0..4)
.map(|_| {
let t = tracker.clone();
std::thread::spawn(move || {
for _ in 0..100 {
t.record_allocation(10);
}
for _ in 0..50 {
t.record_deallocation(10);
}
})
})
.collect();
for h in handles {
h.join().expect("Thread should not panic");
}
let report = tracker.report();
assert_eq!(report.total_allocated, 4 * 100 * 10);
assert_eq!(report.total_freed, 4 * 50 * 10);
assert_eq!(report.allocation_count, 400);
assert_eq!(report.deallocation_count, 200);
assert_eq!(report.current_bytes, 4 * 50 * 10); }
}