PR-07 telemetry + certs
This commit is contained in:
parent
c37b7aca46
commit
b9c278c427
4
.gitignore
vendored
4
.gitignore
vendored
@ -46,6 +46,6 @@ ehthumbs.db
|
|||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
|
|
||||||
mnt
|
sdcard
|
||||||
mnt/**
|
sdcard/**
|
||||||
|
|
||||||
|
|||||||
@ -5,23 +5,81 @@ mod log_sink;
|
|||||||
|
|
||||||
use crate::prometeu_runner::PrometeuRunner;
|
use crate::prometeu_runner::PrometeuRunner;
|
||||||
use winit::event_loop::EventLoop;
|
use winit::event_loop::EventLoop;
|
||||||
|
use prometeu_core::telemetry::CertificationConfig;
|
||||||
|
|
||||||
|
fn load_cap_config(path: &str) -> Option<CertificationConfig> {
|
||||||
|
let content = std::fs::read_to_string(path).ok()?;
|
||||||
|
let mut config = CertificationConfig {
|
||||||
|
enabled: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
for line in content.lines() {
|
||||||
|
let line = line.trim();
|
||||||
|
if line.is_empty() || line.starts_with('#') { continue; }
|
||||||
|
let parts: Vec<&str> = line.split('=').collect();
|
||||||
|
if parts.len() != 2 { continue; }
|
||||||
|
let key = parts[0].trim();
|
||||||
|
let val = parts[1].trim();
|
||||||
|
|
||||||
|
match key {
|
||||||
|
"cycles_budget" => config.cycles_budget_per_frame = val.parse().ok(),
|
||||||
|
"max_syscalls" => config.max_syscalls_per_frame = val.parse().ok(),
|
||||||
|
"max_host_cpu_us" => config.max_host_cpu_us_per_frame = val.parse().ok(),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(config)
|
||||||
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let args: Vec<String> = std::env::args().collect();
|
let args: Vec<String> = std::env::args().collect();
|
||||||
let mut fs_root = None;
|
let mut fs_root = None;
|
||||||
|
let mut cap_config = None;
|
||||||
|
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while i < args.len() {
|
while i < args.len() {
|
||||||
if args[i] == "--fs-root" && i + 1 < args.len() {
|
if args[i] == "--fs-root" && i + 1 < args.len() {
|
||||||
fs_root = Some(args[i + 1].clone());
|
fs_root = Some(args[i + 1].clone());
|
||||||
i += 1;
|
i += 1;
|
||||||
|
} else if args[i] == "--cap" && i + 1 < args.len() {
|
||||||
|
cap_config = load_cap_config(&args[i + 1]);
|
||||||
|
i += 1;
|
||||||
}
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let event_loop = EventLoop::new()?;
|
let event_loop = EventLoop::new()?;
|
||||||
|
|
||||||
let mut runner = PrometeuRunner::new(fs_root);
|
let mut runner = PrometeuRunner::new(fs_root, cap_config);
|
||||||
event_loop.run_app(&mut runner)?;
|
event_loop.run_app(&mut runner)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_load_cap_config() {
|
||||||
|
let content = "cycles_budget=500\nmax_syscalls=10\n# comentário\nmax_host_cpu_us=2000";
|
||||||
|
let path = "test_cap.cfg";
|
||||||
|
std::fs::write(path, content).unwrap();
|
||||||
|
|
||||||
|
let config = load_cap_config(path).unwrap();
|
||||||
|
assert!(config.enabled);
|
||||||
|
assert_eq!(config.cycles_budget_per_frame, Some(500));
|
||||||
|
assert_eq!(config.max_syscalls_per_frame, Some(10));
|
||||||
|
assert_eq!(config.max_host_cpu_us_per_frame, Some(2000));
|
||||||
|
|
||||||
|
std::fs::remove_file(path).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_load_cap_config_not_found() {
|
||||||
|
let config = load_cap_config("non_existent.cfg");
|
||||||
|
assert!(config.is_none());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -17,6 +17,8 @@ use winit::event_loop::{ActiveEventLoop, ControlFlow};
|
|||||||
use winit::keyboard::{KeyCode, PhysicalKey};
|
use winit::keyboard::{KeyCode, PhysicalKey};
|
||||||
use winit::window::{Window, WindowAttributes, WindowId};
|
use winit::window::{Window, WindowAttributes, WindowId};
|
||||||
|
|
||||||
|
use prometeu_core::telemetry::CertificationConfig;
|
||||||
|
|
||||||
pub struct PrometeuRunner {
|
pub struct PrometeuRunner {
|
||||||
window: Option<&'static Window>,
|
window: Option<&'static Window>,
|
||||||
pixels: Option<Pixels<'static>>,
|
pixels: Option<Pixels<'static>>,
|
||||||
@ -35,6 +37,10 @@ pub struct PrometeuRunner {
|
|||||||
|
|
||||||
last_stats_update: Instant,
|
last_stats_update: Instant,
|
||||||
frames_since_last_update: u64,
|
frames_since_last_update: u64,
|
||||||
|
current_fps: f64,
|
||||||
|
|
||||||
|
overlay_enabled: bool,
|
||||||
|
|
||||||
audio_load_accum_us: u64,
|
audio_load_accum_us: u64,
|
||||||
audio_load_samples: u64,
|
audio_load_samples: u64,
|
||||||
audio_perf_consumer: Option<ringbuf::wrap::CachingCons<Arc<HeapRb<u64>>>>,
|
audio_perf_consumer: Option<ringbuf::wrap::CachingCons<Arc<HeapRb<u64>>>>,
|
||||||
@ -44,10 +50,10 @@ pub struct PrometeuRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PrometeuRunner {
|
impl PrometeuRunner {
|
||||||
pub(crate) fn new(fs_root: Option<String>) -> Self {
|
pub(crate) fn new(fs_root: Option<String>, cap_config: Option<CertificationConfig>) -> Self {
|
||||||
let target_fps = 60;
|
let target_fps = 60;
|
||||||
|
|
||||||
let mut firmware = Firmware::new();
|
let mut firmware = Firmware::new(cap_config);
|
||||||
if let Some(root) = &fs_root {
|
if let Some(root) = &fs_root {
|
||||||
let backend = HostDirBackend::new(root);
|
let backend = HostDirBackend::new(root);
|
||||||
firmware.os.mount_fs(Box::new(backend));
|
firmware.os.mount_fs(Box::new(backend));
|
||||||
@ -67,6 +73,8 @@ impl PrometeuRunner {
|
|||||||
|
|
||||||
last_stats_update: Instant::now(),
|
last_stats_update: Instant::now(),
|
||||||
frames_since_last_update: 0,
|
frames_since_last_update: 0,
|
||||||
|
current_fps: 0.0,
|
||||||
|
overlay_enabled: false,
|
||||||
audio_load_accum_us: 0,
|
audio_load_accum_us: 0,
|
||||||
audio_load_samples: 0,
|
audio_load_samples: 0,
|
||||||
audio_perf_consumer: None,
|
audio_perf_consumer: None,
|
||||||
@ -227,6 +235,12 @@ impl ApplicationHandler for PrometeuRunner {
|
|||||||
KeyCode::KeyZ => self.input_signals.start_signal = is_down,
|
KeyCode::KeyZ => self.input_signals.start_signal = is_down,
|
||||||
KeyCode::ShiftLeft | KeyCode::ShiftRight => self.input_signals.select_signal = is_down,
|
KeyCode::ShiftLeft | KeyCode::ShiftRight => self.input_signals.select_signal = is_down,
|
||||||
|
|
||||||
|
KeyCode::F1 => {
|
||||||
|
if is_down {
|
||||||
|
self.overlay_enabled = !self.overlay_enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -304,8 +318,8 @@ impl ApplicationHandler for PrometeuRunner {
|
|||||||
// Atualiza estatísticas a cada 1 segundo real
|
// Atualiza estatísticas a cada 1 segundo real
|
||||||
let stats_elapsed = now.duration_since(self.last_stats_update);
|
let stats_elapsed = now.duration_since(self.last_stats_update);
|
||||||
if stats_elapsed >= Duration::from_secs(1) {
|
if stats_elapsed >= Duration::from_secs(1) {
|
||||||
|
self.current_fps = self.frames_since_last_update as f64 / stats_elapsed.as_secs_f64();
|
||||||
if let Some(window) = self.window {
|
if let Some(window) = self.window {
|
||||||
let fps = self.frames_since_last_update as f64 / stats_elapsed.as_secs_f64();
|
|
||||||
let kb = self.hardware.gfx.memory_usage_bytes() as f64 / 1024.0;
|
let kb = self.hardware.gfx.memory_usage_bytes() as f64 / 1024.0;
|
||||||
|
|
||||||
// comparação fixa sempre contra 60Hz, manter mesmo quando fazer teste de stress na CPU
|
// comparação fixa sempre contra 60Hz, manter mesmo quando fazer teste de stress na CPU
|
||||||
@ -321,7 +335,7 @@ impl ApplicationHandler for PrometeuRunner {
|
|||||||
|
|
||||||
let title = format!(
|
let title = format!(
|
||||||
"PROMETEU | GFX: {:.1} KB | FPS: {:.1} | Load: {:.1}% (C) + {:.1}% (A) | Frame: tick {} logical {}",
|
"PROMETEU | GFX: {:.1} KB | FPS: {:.1} | Load: {:.1}% (C) + {:.1}% (A) | Frame: tick {} logical {}",
|
||||||
kb, fps, cpu_load_core, cpu_load_audio, self.firmware.os.tick_index, self.firmware.os.logical_frame_index
|
kb, self.current_fps, cpu_load_core, cpu_load_audio, self.firmware.os.tick_index, self.firmware.os.logical_frame_index
|
||||||
);
|
);
|
||||||
window.set_title(&title);
|
window.set_title(&title);
|
||||||
}
|
}
|
||||||
@ -341,6 +355,36 @@ impl ApplicationHandler for PrometeuRunner {
|
|||||||
};
|
};
|
||||||
self.log_sink.process_events(new_events);
|
self.log_sink.process_events(new_events);
|
||||||
|
|
||||||
|
// Overlay de Telemetria
|
||||||
|
if self.overlay_enabled {
|
||||||
|
self.hardware.gfx.present(); // Traz o front para o back para desenhar por cima
|
||||||
|
|
||||||
|
let tel = &self.firmware.os.telemetry_last;
|
||||||
|
let color_text = prometeu_core::model::Color::WHITE;
|
||||||
|
let color_bg = prometeu_core::model::Color::INDIGO; // Azul escuro para destacar
|
||||||
|
let color_warn = prometeu_core::model::Color::RED;
|
||||||
|
|
||||||
|
self.hardware.gfx.fill_rect(5, 5, 140, 65, color_bg);
|
||||||
|
self.hardware.gfx.draw_text(10, 10, &format!("FPS: {:.1}", self.current_fps), color_text);
|
||||||
|
self.hardware.gfx.draw_text(10, 18, &format!("HOST: {:.2}MS", tel.host_cpu_time_us as f64 / 1000.0), color_text);
|
||||||
|
self.hardware.gfx.draw_text(10, 26, &format!("STEPS: {}", tel.vm_steps), color_text);
|
||||||
|
self.hardware.gfx.draw_text(10, 34, &format!("SYSC: {}", tel.syscalls), color_text);
|
||||||
|
self.hardware.gfx.draw_text(10, 42, &format!("CYC: {}", tel.cycles_used), color_text);
|
||||||
|
|
||||||
|
let cert_color = if tel.violations > 0 { color_warn } else { color_text };
|
||||||
|
self.hardware.gfx.draw_text(10, 50, &format!("CERT LAST: {}", tel.violations), cert_color);
|
||||||
|
|
||||||
|
if tel.violations > 0 {
|
||||||
|
if let Some(event) = self.firmware.os.log_service.get_recent(10).into_iter().rev().find(|e| e.tag >= 0xCA01 && e.tag <= 0xCA03) {
|
||||||
|
let mut msg = event.msg.clone();
|
||||||
|
if msg.len() > 30 { msg.truncate(30); }
|
||||||
|
self.hardware.gfx.draw_text(10, 58, &msg, color_warn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.hardware.gfx.present(); // Devolve para o front com o overlay aplicado
|
||||||
|
}
|
||||||
|
|
||||||
self.request_redraw();
|
self.request_redraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,8 @@ use crate::prometeu_hub::PrometeuHub;
|
|||||||
use crate::prometeu_os::PrometeuOS;
|
use crate::prometeu_os::PrometeuOS;
|
||||||
use crate::virtual_machine::VirtualMachine;
|
use crate::virtual_machine::VirtualMachine;
|
||||||
|
|
||||||
|
use crate::telemetry::CertificationConfig;
|
||||||
|
|
||||||
pub struct Firmware {
|
pub struct Firmware {
|
||||||
pub vm: VirtualMachine,
|
pub vm: VirtualMachine,
|
||||||
pub os: PrometeuOS,
|
pub os: PrometeuOS,
|
||||||
@ -15,10 +17,10 @@ pub struct Firmware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Firmware {
|
impl Firmware {
|
||||||
pub fn new() -> Self {
|
pub fn new(cap_config: Option<CertificationConfig>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
vm: VirtualMachine::default(),
|
vm: VirtualMachine::default(),
|
||||||
os: PrometeuOS::new(),
|
os: PrometeuOS::new(cap_config),
|
||||||
hub: PrometeuHub::new(),
|
hub: PrometeuHub::new(),
|
||||||
state: FirmwareState::Reset(ResetStep),
|
state: FirmwareState::Reset(ResetStep),
|
||||||
state_initialized: false,
|
state_initialized: false,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
use crate::model::{Color, HudTileLayer, ScrollableTileLayer, Sprite, TileBank, TileMap, TileSize};
|
use crate::model::{Color, HudTileLayer, ScrollableTileLayer, Sprite, TileBank, TileMap, TileSize};
|
||||||
|
use std::mem::size_of;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
pub enum BlendMode {
|
pub enum BlendMode {
|
||||||
@ -380,6 +381,80 @@ impl Gfx {
|
|||||||
|
|
||||||
total
|
total
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn draw_text(&mut self, x: i32, y: i32, text: &str, color: Color) {
|
||||||
|
let mut cx = x;
|
||||||
|
for c in text.chars() {
|
||||||
|
self.draw_char(cx, y, c, color);
|
||||||
|
cx += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_char(&mut self, x: i32, y: i32, c: char, color: Color) {
|
||||||
|
let glyph: [u8; 5] = match c.to_ascii_uppercase() {
|
||||||
|
'0' => [0x7, 0x5, 0x5, 0x5, 0x7],
|
||||||
|
'1' => [0x2, 0x6, 0x2, 0x2, 0x7],
|
||||||
|
'2' => [0x7, 0x1, 0x7, 0x4, 0x7],
|
||||||
|
'3' => [0x7, 0x1, 0x7, 0x1, 0x7],
|
||||||
|
'4' => [0x5, 0x5, 0x7, 0x1, 0x1],
|
||||||
|
'5' => [0x7, 0x4, 0x7, 0x1, 0x7],
|
||||||
|
'6' => [0x7, 0x4, 0x7, 0x5, 0x7],
|
||||||
|
'7' => [0x7, 0x1, 0x1, 0x1, 0x1],
|
||||||
|
'8' => [0x7, 0x5, 0x7, 0x5, 0x7],
|
||||||
|
'9' => [0x7, 0x5, 0x7, 0x1, 0x7],
|
||||||
|
'A' => [0x7, 0x5, 0x7, 0x5, 0x5],
|
||||||
|
'B' => [0x6, 0x5, 0x6, 0x5, 0x6],
|
||||||
|
'C' => [0x7, 0x4, 0x4, 0x4, 0x7],
|
||||||
|
'D' => [0x6, 0x5, 0x5, 0x5, 0x6],
|
||||||
|
'E' => [0x7, 0x4, 0x6, 0x4, 0x7],
|
||||||
|
'F' => [0x7, 0x4, 0x6, 0x4, 0x4],
|
||||||
|
'G' => [0x7, 0x4, 0x5, 0x5, 0x7],
|
||||||
|
'H' => [0x5, 0x5, 0x7, 0x5, 0x5],
|
||||||
|
'I' => [0x7, 0x2, 0x2, 0x2, 0x7],
|
||||||
|
'J' => [0x1, 0x1, 0x1, 0x5, 0x2],
|
||||||
|
'K' => [0x5, 0x5, 0x6, 0x5, 0x5],
|
||||||
|
'L' => [0x4, 0x4, 0x4, 0x4, 0x7],
|
||||||
|
'M' => [0x5, 0x7, 0x5, 0x5, 0x5],
|
||||||
|
'N' => [0x5, 0x5, 0x5, 0x5, 0x5],
|
||||||
|
'O' => [0x7, 0x5, 0x5, 0x5, 0x7],
|
||||||
|
'P' => [0x7, 0x5, 0x7, 0x4, 0x4],
|
||||||
|
'Q' => [0x7, 0x5, 0x5, 0x7, 0x1],
|
||||||
|
'R' => [0x7, 0x5, 0x6, 0x5, 0x5],
|
||||||
|
'S' => [0x7, 0x4, 0x7, 0x1, 0x7],
|
||||||
|
'T' => [0x7, 0x2, 0x2, 0x2, 0x2],
|
||||||
|
'U' => [0x5, 0x5, 0x5, 0x5, 0x7],
|
||||||
|
'V' => [0x5, 0x5, 0x5, 0x5, 0x2],
|
||||||
|
'W' => [0x5, 0x5, 0x5, 0x7, 0x5],
|
||||||
|
'X' => [0x5, 0x5, 0x2, 0x5, 0x5],
|
||||||
|
'Y' => [0x5, 0x5, 0x2, 0x2, 0x2],
|
||||||
|
'Z' => [0x7, 0x1, 0x2, 0x4, 0x7],
|
||||||
|
':' => [0x0, 0x2, 0x0, 0x2, 0x0],
|
||||||
|
'.' => [0x0, 0x0, 0x0, 0x0, 0x2],
|
||||||
|
',' => [0x0, 0x0, 0x0, 0x2, 0x4],
|
||||||
|
'!' => [0x2, 0x2, 0x2, 0x0, 0x2],
|
||||||
|
'?' => [0x7, 0x1, 0x2, 0x0, 0x2],
|
||||||
|
' ' => [0x0, 0x0, 0x0, 0x0, 0x0],
|
||||||
|
'|' => [0x2, 0x2, 0x2, 0x2, 0x2],
|
||||||
|
'/' => [0x1, 0x1, 0x2, 0x4, 0x4],
|
||||||
|
'(' => [0x2, 0x4, 0x4, 0x4, 0x2],
|
||||||
|
')' => [0x2, 0x1, 0x1, 0x1, 0x2],
|
||||||
|
'>' => [0x4, 0x2, 0x1, 0x2, 0x4],
|
||||||
|
'<' => [0x1, 0x2, 0x4, 0x2, 0x1],
|
||||||
|
_ => [0x7, 0x7, 0x7, 0x7, 0x7],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (row_idx, row) in glyph.iter().enumerate() {
|
||||||
|
for col_idx in 0..3 {
|
||||||
|
if (row >> (2 - col_idx)) & 1 == 1 {
|
||||||
|
let px = x + col_idx as i32;
|
||||||
|
let py = y + row_idx as i32;
|
||||||
|
if px >= 0 && px < self.w as i32 && py >= 0 && py < self.h as i32 {
|
||||||
|
self.back[py as usize * self.w + px as usize] = color.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Faz blend em RGB565 por canal com saturação.
|
/// Faz blend em RGB565 por canal com saturação.
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
pub mod hardware;
|
pub mod hardware;
|
||||||
pub mod log;
|
pub mod log;
|
||||||
pub mod virtual_machine;
|
pub mod virtual_machine;
|
||||||
mod model;
|
pub mod model;
|
||||||
pub mod firmware;
|
pub mod firmware;
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
|
pub mod telemetry;
|
||||||
mod prometeu_os;
|
mod prometeu_os;
|
||||||
mod prometeu_hub;
|
mod prometeu_hub;
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ use crate::hardware::{HardwareBridge, InputSignals};
|
|||||||
use crate::log::{LogLevel, LogService, LogSource};
|
use crate::log::{LogLevel, LogService, LogSource};
|
||||||
use crate::model::{Cartridge, Color, Sample};
|
use crate::model::{Cartridge, Color, Sample};
|
||||||
use crate::prometeu_os::NativeInterface;
|
use crate::prometeu_os::NativeInterface;
|
||||||
|
use crate::telemetry::{CertificationConfig, Certifier, TelemetryFrame};
|
||||||
use crate::virtual_machine::{Value, VirtualMachine};
|
use crate::virtual_machine::{Value, VirtualMachine};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -32,6 +33,12 @@ pub struct PrometeuOS {
|
|||||||
pub log_service: LogService,
|
pub log_service: LogService,
|
||||||
pub current_app_id: u32,
|
pub current_app_id: u32,
|
||||||
pub logs_written_this_frame: HashMap<u32, u32>,
|
pub logs_written_this_frame: HashMap<u32, u32>,
|
||||||
|
|
||||||
|
// Telemetria e Certificação
|
||||||
|
pub telemetry_current: TelemetryFrame,
|
||||||
|
pub telemetry_last: TelemetryFrame,
|
||||||
|
pub certifier: Certifier,
|
||||||
|
|
||||||
boot_time: Instant,
|
boot_time: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,7 +49,7 @@ impl PrometeuOS {
|
|||||||
pub const MAX_LOG_LEN: usize = 256;
|
pub const MAX_LOG_LEN: usize = 256;
|
||||||
pub const MAX_LOGS_PER_FRAME: u32 = 10;
|
pub const MAX_LOGS_PER_FRAME: u32 = 10;
|
||||||
|
|
||||||
pub fn new() -> Self {
|
pub fn new(cap_config: Option<CertificationConfig>) -> Self {
|
||||||
let boot_time = Instant::now();
|
let boot_time = Instant::now();
|
||||||
let mut os = Self {
|
let mut os = Self {
|
||||||
tick_index: 0,
|
tick_index: 0,
|
||||||
@ -60,6 +67,9 @@ impl PrometeuOS {
|
|||||||
log_service: LogService::new(4096),
|
log_service: LogService::new(4096),
|
||||||
current_app_id: 0,
|
current_app_id: 0,
|
||||||
logs_written_this_frame: HashMap::new(),
|
logs_written_this_frame: HashMap::new(),
|
||||||
|
telemetry_current: TelemetryFrame::default(),
|
||||||
|
telemetry_last: TelemetryFrame::default(),
|
||||||
|
certifier: Certifier::new(cap_config.unwrap_or_default()),
|
||||||
boot_time,
|
boot_time,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -138,6 +148,12 @@ impl PrometeuOS {
|
|||||||
self.logical_frame_active = true;
|
self.logical_frame_active = true;
|
||||||
self.logical_frame_remaining_cycles = Self::CYCLES_PER_LOGICAL_FRAME;
|
self.logical_frame_remaining_cycles = Self::CYCLES_PER_LOGICAL_FRAME;
|
||||||
self.begin_logical_frame(signals, hw);
|
self.begin_logical_frame(signals, hw);
|
||||||
|
|
||||||
|
// Início do frame: resetar acumulador da telemetria (mas manter frame_index)
|
||||||
|
self.telemetry_current = TelemetryFrame {
|
||||||
|
frame_index: self.logical_frame_index,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Budget para este tick: o mínimo entre a fatia do tick e o que resta no frame lógico
|
// Budget para este tick: o mínimo entre a fatia do tick e o que resta no frame lógico
|
||||||
@ -151,8 +167,23 @@ impl PrometeuOS {
|
|||||||
Ok(run) => {
|
Ok(run) => {
|
||||||
self.logical_frame_remaining_cycles = self.logical_frame_remaining_cycles.saturating_sub(run.cycles_used);
|
self.logical_frame_remaining_cycles = self.logical_frame_remaining_cycles.saturating_sub(run.cycles_used);
|
||||||
|
|
||||||
|
// Acumula métricas
|
||||||
|
self.telemetry_current.cycles_used += run.cycles_used;
|
||||||
|
self.telemetry_current.vm_steps += run.steps_executed;
|
||||||
|
|
||||||
if run.reason == crate::virtual_machine::LogicalFrameEndingReason::FrameSync {
|
if run.reason == crate::virtual_machine::LogicalFrameEndingReason::FrameSync {
|
||||||
hw.gfx_mut().render_all();
|
hw.gfx_mut().render_all();
|
||||||
|
|
||||||
|
// Finaliza telemetria do frame (host_cpu_time será refinado no final do tick)
|
||||||
|
self.telemetry_current.host_cpu_time_us = start.elapsed().as_micros() as u64;
|
||||||
|
|
||||||
|
// Certificação (CAP)
|
||||||
|
let ts_ms = self.boot_time.elapsed().as_millis() as u64;
|
||||||
|
self.telemetry_current.violations = self.certifier.evaluate(&self.telemetry_current, &mut self.log_service, ts_ms) as u32;
|
||||||
|
|
||||||
|
// Latch: o que o overlay lê
|
||||||
|
self.telemetry_last = self.telemetry_current;
|
||||||
|
|
||||||
self.logical_frame_index += 1;
|
self.logical_frame_index += 1;
|
||||||
self.logical_frame_active = false;
|
self.logical_frame_active = false;
|
||||||
self.logical_frame_remaining_cycles = 0;
|
self.logical_frame_remaining_cycles = 0;
|
||||||
@ -167,6 +198,12 @@ impl PrometeuOS {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.last_frame_cpu_time_us = start.elapsed().as_micros() as u64;
|
self.last_frame_cpu_time_us = start.elapsed().as_micros() as u64;
|
||||||
|
|
||||||
|
// Se o frame acabou neste tick, atualizamos o tempo real final no latch
|
||||||
|
if !self.logical_frame_active && self.telemetry_last.frame_index == self.logical_frame_index.wrapping_sub(1) {
|
||||||
|
self.telemetry_last.host_cpu_time_us = self.last_frame_cpu_time_us;
|
||||||
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,7 +314,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_infinite_loop_budget_reset_bug() {
|
fn test_infinite_loop_budget_reset_bug() {
|
||||||
let mut os = PrometeuOS::new();
|
let mut os = PrometeuOS::new(None);
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hw = Hardware::new();
|
let mut hw = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -315,7 +352,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_budget_reset_on_frame_sync() {
|
fn test_budget_reset_on_frame_sync() {
|
||||||
let mut os = PrometeuOS::new();
|
let mut os = PrometeuOS::new(None);
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hw = Hardware::new();
|
let mut hw = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -360,7 +397,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_syscall_log_write_and_rate_limit() {
|
fn test_syscall_log_write_and_rate_limit() {
|
||||||
let mut os = PrometeuOS::new();
|
let mut os = PrometeuOS::new(None);
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hw = Hardware::new();
|
let mut hw = Hardware::new();
|
||||||
|
|
||||||
@ -430,6 +467,7 @@ mod tests {
|
|||||||
|
|
||||||
impl NativeInterface for PrometeuOS {
|
impl NativeInterface for PrometeuOS {
|
||||||
fn syscall(&mut self, id: u32, vm: &mut VirtualMachine, hw: &mut dyn HardwareBridge) -> Result<u64, String> {
|
fn syscall(&mut self, id: u32, vm: &mut VirtualMachine, hw: &mut dyn HardwareBridge) -> Result<u64, String> {
|
||||||
|
self.telemetry_current.syscalls += 1;
|
||||||
match id {
|
match id {
|
||||||
// system.has_cart() -> bool
|
// system.has_cart() -> bool
|
||||||
0x0001 => {
|
0x0001 => {
|
||||||
|
|||||||
112
crates/prometeu-core/src/telemetry.rs
Normal file
112
crates/prometeu-core/src/telemetry.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
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 syscalls: u32,
|
||||||
|
pub host_cpu_time_us: u64,
|
||||||
|
pub violations: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
pub struct CertificationConfig {
|
||||||
|
pub enabled: bool,
|
||||||
|
pub cycles_budget_per_frame: Option<u64>,
|
||||||
|
pub max_syscalls_per_frame: Option<u32>,
|
||||||
|
pub max_host_cpu_us_per_frame: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,6 +16,7 @@ pub enum LogicalFrameEndingReason {
|
|||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct BudgetReport {
|
pub struct BudgetReport {
|
||||||
pub cycles_used: u64,
|
pub cycles_used: u64,
|
||||||
|
pub steps_executed: u32,
|
||||||
pub reason: LogicalFrameEndingReason,
|
pub reason: LogicalFrameEndingReason,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,6 +71,7 @@ impl VirtualMachine {
|
|||||||
hw: &mut dyn HardwareBridge,
|
hw: &mut dyn HardwareBridge,
|
||||||
) -> Result<BudgetReport, String> {
|
) -> Result<BudgetReport, String> {
|
||||||
let start_cycles = self.cycles;
|
let start_cycles = self.cycles;
|
||||||
|
let mut steps_executed = 0;
|
||||||
let mut ending_reason: Option<LogicalFrameEndingReason> = None;
|
let mut ending_reason: Option<LogicalFrameEndingReason> = None;
|
||||||
|
|
||||||
while (self.cycles - start_cycles) < budget
|
while (self.cycles - start_cycles) < budget
|
||||||
@ -85,11 +87,13 @@ impl VirtualMachine {
|
|||||||
if opcode == OpCode::FrameSync {
|
if opcode == OpCode::FrameSync {
|
||||||
self.pc += 2;
|
self.pc += 2;
|
||||||
self.cycles += OpCode::FrameSync.cycles();
|
self.cycles += OpCode::FrameSync.cycles();
|
||||||
|
steps_executed += 1;
|
||||||
ending_reason = Some(LogicalFrameEndingReason::FrameSync);
|
ending_reason = Some(LogicalFrameEndingReason::FrameSync);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.step(native, hw)?;
|
self.step(native, hw)?;
|
||||||
|
steps_executed += 1;
|
||||||
|
|
||||||
// garante progresso real
|
// garante progresso real
|
||||||
if self.pc == pc_before && self.cycles == cycles_before && !self.halted {
|
if self.pc == pc_before && self.cycles == cycles_before && !self.halted {
|
||||||
@ -109,6 +113,7 @@ impl VirtualMachine {
|
|||||||
|
|
||||||
Ok(BudgetReport {
|
Ok(BudgetReport {
|
||||||
cycles_used: self.cycles - start_cycles,
|
cycles_used: self.cycles - start_cycles,
|
||||||
|
steps_executed,
|
||||||
reason: ending_reason.unwrap(),
|
reason: ending_reason.unwrap(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user