use crate::log::{LogLevel, LogService, LogSource}; #[derive(Debug, Clone, Copy, Default)] pub struct TelemetryFrame { pub frame_index: u64, pub vm_steps: u32, pub cycles_used: u64, pub cycles_budget: u64, pub syscalls: u32, pub host_cpu_time_us: u64, pub violations: u32, // GFX Banks pub gfx_used_bytes: usize, pub gfx_inflight_bytes: usize, pub gfx_slots_occupied: u32, // Audio Banks pub audio_used_bytes: usize, pub audio_inflight_bytes: usize, pub audio_slots_occupied: u32, } #[derive(Debug, Clone, Copy, Default)] pub struct CertificationConfig { pub enabled: bool, pub cycles_budget_per_frame: Option, pub max_syscalls_per_frame: Option, pub max_host_cpu_us_per_frame: Option, } pub struct Certifier { pub config: CertificationConfig, } impl Certifier { pub fn new(config: CertificationConfig) -> Self { Self { config } } pub fn evaluate( &self, telemetry: &TelemetryFrame, log_service: &mut LogService, ts_ms: u64, ) -> usize { if !self.config.enabled { return 0; } let mut violations = 0; if let Some(budget) = self.config.cycles_budget_per_frame { if telemetry.cycles_used > budget { log_service.log( ts_ms, telemetry.frame_index, LogLevel::Warn, LogSource::Pos, 0xCA01, format!( "Cert: cycles_used exceeded budget ({} > {})", telemetry.cycles_used, budget ), ); violations += 1; } } if let Some(limit) = self.config.max_syscalls_per_frame { if telemetry.syscalls > limit { log_service.log( ts_ms, telemetry.frame_index, LogLevel::Warn, LogSource::Pos, 0xCA02, format!( "Cert: syscalls per frame exceeded limit ({} > {})", telemetry.syscalls, limit ), ); violations += 1; } } if let Some(limit) = self.config.max_host_cpu_us_per_frame { if telemetry.host_cpu_time_us > limit { log_service.log( ts_ms, telemetry.frame_index, LogLevel::Warn, LogSource::Pos, 0xCA03, format!( "Cert: host_cpu_time_us exceeded limit ({} > {})", telemetry.host_cpu_time_us, limit ), ); violations += 1; } } violations } } #[cfg(test)] mod tests { use super::*; use crate::log::LogService; #[test] fn test_certifier_violations() { let config = CertificationConfig { enabled: true, cycles_budget_per_frame: Some(100), max_syscalls_per_frame: Some(5), max_host_cpu_us_per_frame: Some(1000), }; let cert = Certifier::new(config); let mut ls = LogService::new(10); let mut tel = TelemetryFrame::default(); tel.cycles_used = 150; tel.syscalls = 10; tel.host_cpu_time_us = 500; let violations = cert.evaluate(&tel, &mut ls, 1000); assert_eq!(violations, 2); let logs = ls.get_recent(10); assert_eq!(logs.len(), 2); assert!(logs[0].msg.contains("cycles_used")); assert!(logs[1].msg.contains("syscalls")); } }