use crate::constants::BLUR_VARIANCE_BLURRY;
use crate::quality::blur::BlurDetector;
use crate::types::CameraFrame;
use crate::types::CameraPerformanceMetrics;
use std::time::Instant;
pub struct PerfTracker {
pub capture_latency_ms: f32,
pub processing_time_ms: f32,
pub fps_actual: f32,
pub frames_captured: u64,
pub dropped_frames: u32,
pub buffer_overruns: u32,
last_frame: Option<(Vec<u8>, u32, u32, String)>,
last_capture: Option<Instant>,
}
impl Default for PerfTracker {
fn default() -> Self {
Self::new()
}
}
impl PerfTracker {
pub fn new() -> Self {
Self {
capture_latency_ms: 0.0,
processing_time_ms: 0.0,
fps_actual: 0.0,
frames_captured: 0,
dropped_frames: 0,
buffer_overruns: 0,
last_frame: None,
last_capture: None,
}
}
pub fn record_capture(
&mut self,
latency_ms: f32,
processing_ms: f32,
frame: Option<(Vec<u8>, u32, u32, String)>,
) {
self.capture_latency_ms = latency_ms;
self.processing_time_ms = processing_ms;
self.frames_captured += 1;
if let Some(f) = frame {
self.last_frame = Some(f);
}
let now = Instant::now();
if let Some(prev) = self.last_capture {
let elapsed = prev.elapsed().as_secs_f32();
if elapsed > 0.0 {
self.fps_actual = 1.0 / elapsed;
} else {
self.buffer_overruns += 1;
}
}
self.last_capture = Some(now);
}
pub fn record_drop(&mut self) {
self.dropped_frames += 1;
}
pub fn last_frame(&self) -> Option<&(Vec<u8>, u32, u32, String)> {
self.last_frame.as_ref()
}
pub fn memory_usage_mb(&self) -> f32 {
current_process_memory_mb()
}
}
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_precision_loss,
clippy::borrow_as_ptr,
clippy::items_after_statements
)]
pub fn current_process_memory_mb() -> f32 {
#[cfg(target_os = "linux")]
{
std::fs::read_to_string("/proc/self/statm")
.ok()
.and_then(|contents| {
let rss_pages: u64 = contents.split_whitespace().nth(1)?.parse().ok()?;
#[allow(clippy::cast_precision_loss)]
let mb = (rss_pages * 4096) as f32 / (1024.0 * 1024.0);
Some(mb)
})
.unwrap_or(0.0)
}
#[cfg(target_os = "macos")]
{
const MACH_TASK_BASIC_INFO: i32 = 20;
let mut info = [0i32; 12]; let mut count: u32 = info.len() as u32;
extern "C" {
fn mach_task_self() -> u32;
fn task_info(
task: u32,
flavor: i32,
task_info_out: *mut i32,
task_info_count: *mut u32,
) -> i32;
}
let ret = unsafe {
task_info(
mach_task_self(),
MACH_TASK_BASIC_INFO,
info.as_mut_ptr(),
&mut count,
)
};
if ret == 0 {
let resident = u64::from(info[2] as u32) | (u64::from(info[3] as u32) << 32);
resident as f32 / (1024.0 * 1024.0)
} else {
0.0
}
}
#[cfg(target_os = "windows")]
{
use windows::Win32::System::ProcessStatus::GetProcessMemoryInfo;
use windows::Win32::System::ProcessStatus::PROCESS_MEMORY_COUNTERS;
use windows::Win32::System::Threading::GetCurrentProcess;
unsafe {
let handle = GetCurrentProcess();
let mut counters = PROCESS_MEMORY_COUNTERS {
cb: u32::try_from(std::mem::size_of::<PROCESS_MEMORY_COUNTERS>())
.unwrap_or(u32::MAX),
..Default::default()
};
if GetProcessMemoryInfo(handle, &raw mut counters, counters.cb).is_ok() {
#[allow(clippy::cast_precision_loss)]
let ws = counters.WorkingSetSize as f32;
ws / (1024.0 * 1024.0)
} else {
0.0
}
}
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
{
0.0
}
}
pub fn build_metrics(tracker: &PerfTracker, device_id: &str) -> CameraPerformanceMetrics {
let quality_score = match tracker.last_frame() {
Some((buffer, width, height, format)) => {
let frame = CameraFrame::new(buffer.clone(), *width, *height, device_id.to_string())
.with_format(format.clone());
let detector = BlurDetector::new(BLUR_VARIANCE_BLURRY, 100.0);
detector.analyze_frame(&frame).quality_score
}
None => 0.0,
};
CameraPerformanceMetrics {
capture_latency_ms: tracker.capture_latency_ms,
processing_time_ms: tracker.processing_time_ms,
memory_usage_mb: tracker.memory_usage_mb(),
fps_actual: tracker.fps_actual,
dropped_frames: tracker.dropped_frames,
buffer_overruns: tracker.buffer_overruns,
quality_score,
}
}